diff --git a/README.md b/README.md
index 2ca00b05..32e09d59 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ This project is based on ES5. Some ES2015 features are used but most of them are
1. Create the component file in `src/js/game/components/.js`
2. Create a component class (e.g. `MyFancyComponent`) which `extends Component`
-3. Create a `static getId()` method which should return the `CamelCaseName` without component (e.g. `MyFancy`)
+3. Create a `static getId()` method which should return the `PascalCaseName` without component (e.g. `MyFancy`)
4. If any data needs to be persisted, create a `static getSchema()` which should return the properties to be saved (See other components)
5. Add a constructor. **The constructor must be called with optional parameters only!** `new MyFancyComponent({})` should always work.
6. Add any props you need in the constructor.
diff --git a/res/ui/building_icons/virtual_processor.png b/res/ui/building_icons/virtual_processor.png
new file mode 100644
index 00000000..f5471999
Binary files /dev/null and b/res/ui/building_icons/virtual_processor.png differ
diff --git a/res/ui/icons/display_sorted.png b/res/ui/icons/display_sorted.png
new file mode 100644
index 00000000..2e3c2bad
Binary files /dev/null and b/res/ui/icons/display_sorted.png differ
diff --git a/res_built/atlas/atlas0_hq.json b/res_built/atlas/atlas0_hq.json
index 2aa01927..36bd58b0 100644
--- a/res_built/atlas/atlas0_hq.json
+++ b/res_built/atlas/atlas0_hq.json
@@ -1,1308 +1,1420 @@
-{"frames": {
-
-"sprites/belt/built/forward_0.png":
-{
- "frame": {"x":440,"y":742,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_1.png":
-{
- "frame": {"x":1925,"y":1008,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_2.png":
-{
- "frame": {"x":1540,"y":1139,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_3.png":
-{
- "frame": {"x":1803,"y":1118,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_4.png":
-{
- "frame": {"x":1923,"y":1156,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_5.png":
-{
- "frame": {"x":1801,"y":1266,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_6.png":
-{
- "frame": {"x":1921,"y":1304,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_7.png":
-{
- "frame": {"x":432,"y":1334,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_8.png":
-{
- "frame": {"x":430,"y":1482,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_9.png":
-{
- "frame": {"x":141,"y":1877,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_10.png":
-{
- "frame": {"x":438,"y":890,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_11.png":
-{
- "frame": {"x":438,"y":1038,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_12.png":
-{
- "frame": {"x":1542,"y":991,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/forward_13.png":
-{
- "frame": {"x":437,"y":1186,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_0.png":
-{
- "frame": {"x":145,"y":1475,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_1.png":
-{
- "frame": {"x":3,"y":1575,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_2.png":
-{
- "frame": {"x":279,"y":1493,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_3.png":
-{
- "frame": {"x":137,"y":1609,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_4.png":
-{
- "frame": {"x":3,"y":1709,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_5.png":
-{
- "frame": {"x":271,"y":1627,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_6.png":
-{
- "frame": {"x":137,"y":1743,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_7.png":
-{
- "frame": {"x":3,"y":1843,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_8.png":
-{
- "frame": {"x":1379,"y":1274,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_9.png":
-{
- "frame": {"x":1513,"y":1287,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_10.png":
-{
- "frame": {"x":1076,"y":1288,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_11.png":
-{
- "frame": {"x":927,"y":1295,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_12.png":
-{
- "frame": {"x":786,"y":1363,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/left_13.png":
-{
- "frame": {"x":552,"y":1416,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_0.png":
-{
- "frame": {"x":1647,"y":1294,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_1.png":
-{
- "frame": {"x":1781,"y":1414,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_2.png":
-{
- "frame": {"x":1344,"y":1408,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_3.png":
-{
- "frame": {"x":1195,"y":1520,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_4.png":
-{
- "frame": {"x":1054,"y":1556,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_5.png":
-{
- "frame": {"x":1478,"y":1421,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_6.png":
-{
- "frame": {"x":1329,"y":1542,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_7.png":
-{
- "frame": {"x":1188,"y":1654,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_8.png":
-{
- "frame": {"x":1612,"y":1428,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_9.png":
-{
- "frame": {"x":1463,"y":1555,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_10.png":
-{
- "frame": {"x":1915,"y":1452,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_11.png":
-{
- "frame": {"x":1210,"y":1386,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_12.png":
-{
- "frame": {"x":1061,"y":1422,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/belt/built/right_13.png":
-{
- "frame": {"x":920,"y":1429,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/belt_left.png":
-{
- "frame": {"x":1322,"y":1676,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/belt_right.png":
-{
- "frame": {"x":1746,"y":1548,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/belt_top.png":
-{
- "frame": {"x":261,"y":1877,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/constant_signal.png":
-{
- "frame": {"x":1937,"y":759,"w":105,"h":127},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":105,"h":127},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/cutter-quad.png":
-{
- "frame": {"x":3,"y":151,"w":548,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":548,"h":144},
- "sourceSize": {"w":576,"h":144}
-},
-"sprites/blueprints/cutter.png":
-{
- "frame": {"x":847,"y":298,"w":256,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":256,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/display.png":
-{
- "frame": {"x":1597,"y":1562,"w":128,"h":136},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":8,"y":8,"w":128,"h":136},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/filter.png":
-{
- "frame": {"x":1107,"y":556,"w":268,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":16,"y":0,"w":268,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/lever.png":
-{
- "frame": {"x":1823,"y":732,"w":110,"h":116},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":17,"w":110,"h":116},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/logic_gate-not.png":
-{
- "frame": {"x":1545,"y":843,"w":123,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":123,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/logic_gate-or.png":
-{
- "frame": {"x":560,"y":888,"w":144,"h":123},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":123},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/logic_gate-transistor.png":
-{
- "frame": {"x":1820,"y":970,"w":101,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":101,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/logic_gate-xor.png":
-{
- "frame": {"x":1101,"y":852,"w":144,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/logic_gate.png":
-{
- "frame": {"x":824,"y":885,"w":144,"h":133},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":133},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/miner-chainable.png":
-{
- "frame": {"x":150,"y":1035,"w":136,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/miner.png":
-{
- "frame": {"x":150,"y":1182,"w":136,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/mixer.png":
-{
- "frame": {"x":1676,"y":584,"w":261,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":13,"y":0,"w":261,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/painter-double.png":
-{
- "frame": {"x":1683,"y":3,"w":288,"h":287},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":287},
- "sourceSize": {"w":288,"h":288}
-},
-"sprites/blueprints/painter-mirrored.png":
-{
- "frame": {"x":555,"y":298,"w":288,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/painter-quad.png":
-{
- "frame": {"x":3,"y":3,"w":560,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":560,"h":144},
- "sourceSize": {"w":576,"h":144}
-},
-"sprites/blueprints/painter.png":
-{
- "frame": {"x":3,"y":299,"w":288,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/rotater-ccw.png":
-{
- "frame": {"x":3,"y":1035,"w":143,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/rotater-fl.png":
-{
- "frame": {"x":1396,"y":979,"w":142,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":142,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/rotater.png":
-{
- "frame": {"x":290,"y":1060,"w":143,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/splitter-compact-inverse.png":
-{
- "frame": {"x":1249,"y":988,"w":142,"h":138},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":2,"w":142,"h":138},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/splitter-compact.png":
-{
- "frame": {"x":1094,"y":1146,"w":139,"h":138},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":2,"w":139,"h":138},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/splitter.png":
-{
- "frame": {"x":295,"y":299,"w":256,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":256,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/stacker.png":
-{
- "frame": {"x":295,"y":594,"w":261,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":13,"y":0,"w":261,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/blueprints/trash-storage.png":
-{
- "frame": {"x":847,"y":593,"w":250,"h":288},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":21,"y":0,"w":250,"h":288},
- "sourceSize": {"w":288,"h":288}
-},
-"sprites/blueprints/trash.png":
-{
- "frame": {"x":292,"y":742,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/underground_belt_entry-tier2.png":
-{
- "frame": {"x":3,"y":1330,"w":138,"h":125},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":19,"w":138,"h":125},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/underground_belt_entry.png":
-{
- "frame": {"x":3,"y":1459,"w":138,"h":112},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":32,"w":138,"h":112},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/underground_belt_exit-tier2.png":
-{
- "frame": {"x":558,"y":1185,"w":139,"h":112},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":139,"h":112},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/underground_belt_exit.png":
-{
- "frame": {"x":1237,"y":1270,"w":138,"h":112},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":138,"h":112},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire-cross.png":
-{
- "frame": {"x":3,"y":887,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire-split.png":
-{
- "frame": {"x":801,"y":1022,"w":144,"h":82},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":62,"w":144,"h":82},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire-turn.png":
-{
- "frame": {"x":706,"y":1036,"w":82,"h":82},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":62,"y":62,"w":82,"h":82},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire.png":
-{
- "frame": {"x":1107,"y":151,"w":20,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":62,"y":0,"w":20,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire_tunnel-coating.png":
-{
- "frame": {"x":255,"y":677,"w":33,"h":135},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":55,"y":4,"w":33,"h":135},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/blueprints/wire_tunnel.png":
-{
- "frame": {"x":290,"y":1208,"w":138,"h":135},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":138,"h":135},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/belt_left.png":
-{
- "frame": {"x":145,"y":1475,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/belt_right.png":
-{
- "frame": {"x":1647,"y":1294,"w":130,"h":130},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/belt_top.png":
-{
- "frame": {"x":440,"y":742,"w":116,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/constant_signal.png":
-{
- "frame": {"x":1941,"y":628,"w":104,"h":127},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":104,"h":127},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/cutter-quad.png":
-{
- "frame": {"x":555,"y":151,"w":548,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":548,"h":143},
- "sourceSize": {"w":576,"h":144}
-},
-"sprites/buildings/cutter.png":
-{
- "frame": {"x":847,"y":446,"w":256,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":256,"h":143},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/display.png":
-{
- "frame": {"x":1545,"y":704,"w":126,"h":135},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":126,"h":135},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/filter.png":
-{
- "frame": {"x":1379,"y":556,"w":267,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":267,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/hub.png":
-{
- "frame": {"x":1131,"y":3,"w":548,"h":549},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":16,"w":548,"h":549},
- "sourceSize": {"w":576,"h":576}
-},
-"sprites/buildings/lever.png":
-{
- "frame": {"x":1823,"y":852,"w":109,"h":114},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":18,"y":18,"w":109,"h":114},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/logic_gate-not.png":
-{
- "frame": {"x":972,"y":885,"w":122,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":12,"y":0,"w":122,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/logic_gate-or.png":
-{
- "frame": {"x":1396,"y":852,"w":143,"h":123},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":143,"h":123},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/logic_gate-transistor.png":
-{
- "frame": {"x":151,"y":887,"w":100,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":100,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/logic_gate-xor.png":
-{
- "frame": {"x":3,"y":1183,"w":143,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":143,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/logic_gate.png":
-{
- "frame": {"x":1249,"y":852,"w":143,"h":132},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":143,"h":132},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/miner-chainable.png":
-{
- "frame": {"x":150,"y":1329,"w":136,"h":142},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/miner.png":
-{
- "frame": {"x":290,"y":1347,"w":136,"h":142},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/mixer.png":
-{
- "frame": {"x":560,"y":594,"w":260,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":260,"h":143},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/painter-double.png":
-{
- "frame": {"x":1683,"y":294,"w":288,"h":286},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":286},
- "sourceSize": {"w":288,"h":288}
-},
-"sprites/buildings/painter-mirrored.png":
-{
- "frame": {"x":555,"y":446,"w":288,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/painter-quad.png":
-{
- "frame": {"x":567,"y":3,"w":560,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":560,"h":144},
- "sourceSize": {"w":576,"h":144}
-},
-"sprites/buildings/painter.png":
-{
- "frame": {"x":3,"y":447,"w":288,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/rotater-ccw.png":
-{
- "frame": {"x":1098,"y":999,"w":141,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/rotater-fl.png":
-{
- "frame": {"x":949,"y":1033,"w":141,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":141,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/rotater.png":
-{
- "frame": {"x":1395,"y":1127,"w":141,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/splitter-compact-inverse.png":
-{
- "frame": {"x":1243,"y":1130,"w":141,"h":136},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":3,"w":141,"h":136},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/splitter-compact.png":
-{
- "frame": {"x":792,"y":1108,"w":139,"h":136},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":3,"w":139,"h":136},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/splitter.png":
-{
- "frame": {"x":295,"y":447,"w":256,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":256,"h":143},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/stacker.png":
-{
- "frame": {"x":560,"y":741,"w":260,"h":143},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":260,"h":143},
- "sourceSize": {"w":288,"h":144}
-},
-"sprites/buildings/trash-storage.png":
-{
- "frame": {"x":3,"y":595,"w":248,"h":288},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":22,"y":0,"w":248,"h":288},
- "sourceSize": {"w":288,"h":288}
-},
-"sprites/buildings/trash.png":
-{
- "frame": {"x":1101,"y":704,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/underground_belt_entry-tier2.png":
-{
- "frame": {"x":1660,"y":1166,"w":137,"h":124},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":20,"w":137,"h":124},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/underground_belt_entry.png":
-{
- "frame": {"x":935,"y":1180,"w":137,"h":111},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":33,"w":137,"h":111},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/underground_belt_exit-tier2.png":
-{
- "frame": {"x":786,"y":1248,"w":137,"h":111},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/underground_belt_exit.png":
-{
- "frame": {"x":557,"y":1301,"w":137,"h":111},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire-cross.png":
-{
- "frame": {"x":1249,"y":704,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire-split.png":
-{
- "frame": {"x":290,"y":890,"w":144,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire-turn.png":
-{
- "frame": {"x":706,"y":1122,"w":81,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire.png":
-{
- "frame": {"x":2027,"y":184,"w":18,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire_tunnel-coating.png":
-{
- "frame": {"x":255,"y":816,"w":31,"h":134},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":56,"y":5,"w":31,"h":134},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/buildings/wire_tunnel.png":
-{
- "frame": {"x":1662,"y":1028,"w":137,"h":134},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":5,"w":137,"h":134},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/debug/acceptor_slot.png":
-{
- "frame": {"x":1107,"y":447,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-"sprites/debug/ejector_slot.png":
-{
- "frame": {"x":1107,"y":463,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-"sprites/misc/hub_direction_indicator.png":
-{
- "frame": {"x":1975,"y":184,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/misc/slot_bad_arrow.png":
-{
- "frame": {"x":255,"y":638,"w":35,"h":35},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":2,"w":35,"h":35},
- "sourceSize": {"w":39,"h":39}
-},
-"sprites/misc/slot_good_arrow.png":
-{
- "frame": {"x":255,"y":595,"w":35,"h":39},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":0,"w":35,"h":39},
- "sourceSize": {"w":39,"h":39}
-},
-"sprites/misc/storage_overlay.png":
-{
- "frame": {"x":708,"y":988,"w":89,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":89,"h":44},
- "sourceSize": {"w":90,"h":45}
-},
-"sprites/misc/waypoint.png":
-{
- "frame": {"x":48,"y":1977,"w":38,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":38,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/boolean_false.png":
-{
- "frame": {"x":255,"y":954,"w":31,"h":41},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":5,"w":31,"h":41},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/boolean_true.png":
-{
- "frame": {"x":1650,"y":556,"w":22,"h":41},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":5,"w":22,"h":41},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/display/blue.png":
-{
- "frame": {"x":1975,"y":288,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/cyan.png":
-{
- "frame": {"x":1975,"y":339,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/green.png":
-{
- "frame": {"x":1975,"y":390,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/purple.png":
-{
- "frame": {"x":1975,"y":441,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/red.png":
-{
- "frame": {"x":1975,"y":492,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/white.png":
-{
- "frame": {"x":1975,"y":543,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/display/yellow.png":
-{
- "frame": {"x":90,"y":1977,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
- "sourceSize": {"w":49,"h":49}
-},
-"sprites/wires/lever_on.png":
-{
- "frame": {"x":1936,"y":890,"w":109,"h":114},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":18,"y":18,"w":109,"h":114},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/logical_acceptor.png":
-{
- "frame": {"x":1975,"y":3,"w":62,"h":106},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":43,"y":0,"w":62,"h":106},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/logical_ejector.png":
-{
- "frame": {"x":1975,"y":113,"w":60,"h":67},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":44,"y":0,"w":60,"h":67},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/network_conflict.png":
-{
- "frame": {"x":271,"y":1810,"w":47,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":2,"w":47,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/network_empty.png":
-{
- "frame": {"x":3,"y":1977,"w":41,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":41,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/overlay_tile.png":
-{
- "frame": {"x":708,"y":888,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/color_cross.png":
-{
- "frame": {"x":1397,"y":704,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/color_forward.png":
-{
- "frame": {"x":2026,"y":332,"w":18,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/color_split.png":
-{
- "frame": {"x":290,"y":975,"w":144,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/color_turn.png":
-{
- "frame": {"x":701,"y":1207,"w":81,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/conflict_cross.png":
-{
- "frame": {"x":1675,"y":732,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/conflict_forward.png":
-{
- "frame": {"x":2026,"y":480,"w":18,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/conflict_split.png":
-{
- "frame": {"x":558,"y":1015,"w":144,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/conflict_turn.png":
-{
- "frame": {"x":701,"y":1292,"w":81,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/regular_cross.png":
-{
- "frame": {"x":1249,"y":704,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/regular_forward.png":
-{
- "frame": {"x":2027,"y":184,"w":18,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/regular_split.png":
-{
- "frame": {"x":290,"y":890,"w":144,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/regular_turn.png":
-{
- "frame": {"x":706,"y":1122,"w":81,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/shape_cross.png":
-{
- "frame": {"x":1672,"y":880,"w":144,"h":144},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/shape_forward.png":
-{
- "frame": {"x":1107,"y":299,"w":18,"h":144},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/shape_split.png":
-{
- "frame": {"x":558,"y":1100,"w":144,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/sets/shape_turn.png":
-{
- "frame": {"x":698,"y":1377,"w":81,"h":81},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
- "sourceSize": {"w":144,"h":144}
-},
-"sprites/wires/wires_preview.png":
-{
- "frame": {"x":1975,"y":236,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-}},
-"meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "1.0",
- "image": "atlas0_hq.png",
- "format": "RGBA8888",
- "size": {"w":2048,"h":2048},
- "scale": "0.75",
- "smartupdate": "$TexturePacker:SmartUpdate:876f0711b44fa7bbab8d2539e9651766:ff01f850e086ef31c114b036c3a32e6d:908b89f5ca8ff73e331a35a3b14d0604$"
-}
-}
+{"frames": {
+
+"sprites/belt/built/forward_0.png":
+{
+ "frame": {"x":440,"y":742,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_1.png":
+{
+ "frame": {"x":1545,"y":991,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_2.png":
+{
+ "frame": {"x":428,"y":1186,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_3.png":
+{
+ "frame": {"x":427,"y":1334,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_4.png":
+{
+ "frame": {"x":971,"y":1317,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_5.png":
+{
+ "frame": {"x":421,"y":1482,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_6.png":
+{
+ "frame": {"x":414,"y":1630,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_7.png":
+{
+ "frame": {"x":969,"y":1465,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_8.png":
+{
+ "frame": {"x":412,"y":1778,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_9.png":
+{
+ "frame": {"x":961,"y":1613,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_10.png":
+{
+ "frame": {"x":1545,"y":1139,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_11.png":
+{
+ "frame": {"x":1534,"y":1287,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_12.png":
+{
+ "frame": {"x":1530,"y":1435,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/forward_13.png":
+{
+ "frame": {"x":433,"y":1038,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_0.png":
+{
+ "frame": {"x":151,"y":1035,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_1.png":
+{
+ "frame": {"x":151,"y":1169,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_2.png":
+{
+ "frame": {"x":3,"y":1696,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_3.png":
+{
+ "frame": {"x":541,"y":1580,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_4.png":
+{
+ "frame": {"x":278,"y":1687,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_5.png":
+{
+ "frame": {"x":137,"y":1705,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_6.png":
+{
+ "frame": {"x":3,"y":1830,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_7.png":
+{
+ "frame": {"x":1091,"y":1354,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_8.png":
+{
+ "frame": {"x":827,"y":1578,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_9.png":
+{
+ "frame": {"x":534,"y":1714,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_10.png":
+{
+ "frame": {"x":151,"y":1303,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_11.png":
+{
+ "frame": {"x":146,"y":1437,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_12.png":
+{
+ "frame": {"x":280,"y":1553,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/left_13.png":
+{
+ "frame": {"x":144,"y":1571,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_0.png":
+{
+ "frame": {"x":271,"y":1821,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_1.png":
+{
+ "frame": {"x":137,"y":1839,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_2.png":
+{
+ "frame": {"x":532,"y":1848,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_3.png":
+{
+ "frame": {"x":1359,"y":1447,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_4.png":
+{
+ "frame": {"x":1223,"y":1539,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_5.png":
+{
+ "frame": {"x":1081,"y":1622,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_6.png":
+{
+ "frame": {"x":1357,"y":1581,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_7.png":
+{
+ "frame": {"x":1215,"y":1673,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_8.png":
+{
+ "frame": {"x":1081,"y":1756,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_9.png":
+{
+ "frame": {"x":798,"y":1909,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_10.png":
+{
+ "frame": {"x":1225,"y":1405,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_11.png":
+{
+ "frame": {"x":1089,"y":1488,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_12.png":
+{
+ "frame": {"x":827,"y":1712,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/belt/built/right_13.png":
+{
+ "frame": {"x":668,"y":1717,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/belt_left.png":
+{
+ "frame": {"x":932,"y":1909,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/belt_right.png":
+{
+ "frame": {"x":1066,"y":1909,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/belt_top.png":
+{
+ "frame": {"x":961,"y":1761,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/constant_signal.png":
+{
+ "frame": {"x":1938,"y":759,"w":105,"h":127},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":105,"h":127},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/cutter-quad.png":
+{
+ "frame": {"x":3,"y":151,"w":548,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":548,"h":144},
+ "sourceSize": {"w":576,"h":144}
+},
+"sprites/blueprints/cutter.png":
+{
+ "frame": {"x":847,"y":298,"w":256,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":256,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/display.png":
+{
+ "frame": {"x":666,"y":1909,"w":128,"h":136},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":8,"y":8,"w":128,"h":136},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/filter.png":
+{
+ "frame": {"x":1107,"y":556,"w":268,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":16,"y":0,"w":268,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/lever.png":
+{
+ "frame": {"x":1823,"y":732,"w":111,"h":129},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":4,"w":111,"h":129},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/logic_gate-not.png":
+{
+ "frame": {"x":1545,"y":843,"w":123,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":123,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/logic_gate-or.png":
+{
+ "frame": {"x":1249,"y":989,"w":144,"h":123},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":123},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/logic_gate-transistor.png":
+{
+ "frame": {"x":1820,"y":996,"w":101,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":101,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/logic_gate-xor.png":
+{
+ "frame": {"x":290,"y":890,"w":144,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/logic_gate.png":
+{
+ "frame": {"x":1249,"y":852,"w":144,"h":133},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":133},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/miner-chainable.png":
+{
+ "frame": {"x":689,"y":1424,"w":136,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/miner.png":
+{
+ "frame": {"x":547,"y":1433,"w":136,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/mixer.png":
+{
+ "frame": {"x":1676,"y":584,"w":261,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":13,"y":0,"w":261,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/painter-double.png":
+{
+ "frame": {"x":1683,"y":3,"w":288,"h":287},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":287},
+ "sourceSize": {"w":288,"h":288}
+},
+"sprites/blueprints/painter-mirrored.png":
+{
+ "frame": {"x":555,"y":298,"w":288,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/painter-quad.png":
+{
+ "frame": {"x":3,"y":3,"w":560,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":560,"h":144},
+ "sourceSize": {"w":576,"h":144}
+},
+"sprites/blueprints/painter.png":
+{
+ "frame": {"x":3,"y":299,"w":288,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/rotater-ccw.png":
+{
+ "frame": {"x":1665,"y":1198,"w":143,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/rotater-fl.png":
+{
+ "frame": {"x":948,"y":1169,"w":142,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":142,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/rotater.png":
+{
+ "frame": {"x":1899,"y":1169,"w":143,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/splitter-compact-inverse.png":
+{
+ "frame": {"x":1242,"y":1116,"w":142,"h":138},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":142,"h":138},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":1094,"y":1212,"w":142,"h":138},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":142,"h":138},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/splitter-compact-merge.png":
+{
+ "frame": {"x":1650,"y":1493,"w":139,"h":138},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":2,"w":139,"h":138},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/splitter-compact.png":
+{
+ "frame": {"x":1844,"y":1604,"w":139,"h":138},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":2,"w":139,"h":138},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/splitter.png":
+{
+ "frame": {"x":295,"y":299,"w":256,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":256,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/stacker.png":
+{
+ "frame": {"x":295,"y":594,"w":261,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":13,"y":0,"w":261,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/blueprints/trash-storage.png":
+{
+ "frame": {"x":847,"y":593,"w":250,"h":288},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":21,"y":0,"w":250,"h":288},
+ "sourceSize": {"w":288,"h":288}
+},
+"sprites/blueprints/trash.png":
+{
+ "frame": {"x":292,"y":742,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":553,"y":1176,"w":138,"h":125},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":19,"w":138,"h":125},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/underground_belt_entry.png":
+{
+ "frame": {"x":285,"y":1322,"w":138,"h":112},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":32,"w":138,"h":112},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":3,"y":1326,"w":139,"h":112},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":139,"h":112},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/underground_belt_exit.png":
+{
+ "frame": {"x":3,"y":1442,"w":138,"h":112},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":138,"h":112},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/virtual_processor-analyzer.png":
+{
+ "frame": {"x":3,"y":887,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/virtual_processor-rotater.png":
+{
+ "frame": {"x":1925,"y":1021,"w":118,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":118,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":1397,"y":852,"w":144,"h":133},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":133},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/virtual_processor-unstacker.png":
+{
+ "frame": {"x":1101,"y":704,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/virtual_processor.png":
+{
+ "frame": {"x":285,"y":1037,"w":144,"h":141},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":3,"w":144,"h":141},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire-cross.png":
+{
+ "frame": {"x":1249,"y":704,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire-split.png":
+{
+ "frame": {"x":1397,"y":989,"w":144,"h":82},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":62,"w":144,"h":82},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire-turn.png":
+{
+ "frame": {"x":1813,"y":1144,"w":82,"h":82},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":62,"y":62,"w":82,"h":82},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire.png":
+{
+ "frame": {"x":1107,"y":151,"w":20,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":62,"y":0,"w":20,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire_tunnel-coating.png":
+{
+ "frame": {"x":255,"y":677,"w":33,"h":135},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":55,"y":4,"w":33,"h":135},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/blueprints/wire_tunnel.png":
+{
+ "frame": {"x":702,"y":1170,"w":138,"h":135},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":138,"h":135},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/belt_left.png":
+{
+ "frame": {"x":151,"y":1035,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/belt_right.png":
+{
+ "frame": {"x":271,"y":1821,"w":130,"h":130},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/belt_top.png":
+{
+ "frame": {"x":440,"y":742,"w":116,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/constant_signal.png":
+{
+ "frame": {"x":1941,"y":628,"w":104,"h":127},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":104,"h":127},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/cutter-quad.png":
+{
+ "frame": {"x":555,"y":151,"w":548,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":548,"h":143},
+ "sourceSize": {"w":576,"h":144}
+},
+"sprites/buildings/cutter.png":
+{
+ "frame": {"x":847,"y":446,"w":256,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":256,"h":143},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/display.png":
+{
+ "frame": {"x":1545,"y":704,"w":126,"h":135},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":126,"h":135},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/filter.png":
+{
+ "frame": {"x":1379,"y":556,"w":267,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":267,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/hub.png":
+{
+ "frame": {"x":1131,"y":3,"w":548,"h":549},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":16,"w":548,"h":549},
+ "sourceSize": {"w":576,"h":576}
+},
+"sprites/buildings/lever.png":
+{
+ "frame": {"x":1823,"y":865,"w":109,"h":127},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":18,"y":5,"w":109,"h":127},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/logic_gate-not.png":
+{
+ "frame": {"x":972,"y":885,"w":122,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":12,"y":0,"w":122,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/logic_gate-or.png":
+{
+ "frame": {"x":1095,"y":1085,"w":143,"h":123},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":123},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/logic_gate-transistor.png":
+{
+ "frame": {"x":151,"y":887,"w":100,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":100,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/logic_gate-xor.png":
+{
+ "frame": {"x":1897,"y":1317,"w":143,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/logic_gate.png":
+{
+ "frame": {"x":948,"y":1033,"w":143,"h":132},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":132},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/miner-chainable.png":
+{
+ "frame": {"x":829,"y":1432,"w":136,"h":142},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/miner.png":
+{
+ "frame": {"x":687,"y":1571,"w":136,"h":142},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/mixer.png":
+{
+ "frame": {"x":560,"y":594,"w":260,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":260,"h":143},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/painter-double.png":
+{
+ "frame": {"x":1683,"y":294,"w":288,"h":286},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":286},
+ "sourceSize": {"w":288,"h":288}
+},
+"sprites/buildings/painter-mirrored.png":
+{
+ "frame": {"x":555,"y":446,"w":288,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/painter-quad.png":
+{
+ "frame": {"x":567,"y":3,"w":560,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":560,"h":144},
+ "sourceSize": {"w":576,"h":144}
+},
+"sprites/buildings/painter.png":
+{
+ "frame": {"x":3,"y":447,"w":288,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":288,"h":144},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/rotater-ccw.png":
+{
+ "frame": {"x":1240,"y":1258,"w":141,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/rotater-fl.png":
+{
+ "frame": {"x":1385,"y":1300,"w":141,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":141,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/rotater.png":
+{
+ "frame": {"x":1654,"y":1346,"w":141,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/splitter-compact-inverse.png":
+{
+ "frame": {"x":1884,"y":1464,"w":141,"h":136},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":3,"w":141,"h":136},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":1388,"y":1160,"w":142,"h":136},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":3,"w":142,"h":136},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/splitter-compact-merge.png":
+{
+ "frame": {"x":559,"y":1036,"w":139,"h":136},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":3,"w":139,"h":136},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/splitter-compact.png":
+{
+ "frame": {"x":285,"y":1182,"w":139,"h":136},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":3,"w":139,"h":136},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/splitter.png":
+{
+ "frame": {"x":295,"y":447,"w":256,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":256,"h":143},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/stacker.png":
+{
+ "frame": {"x":560,"y":741,"w":260,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":260,"h":143},
+ "sourceSize": {"w":288,"h":144}
+},
+"sprites/buildings/trash-storage.png":
+{
+ "frame": {"x":3,"y":595,"w":248,"h":288},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":22,"y":0,"w":248,"h":288},
+ "sourceSize": {"w":288,"h":288}
+},
+"sprites/buildings/trash.png":
+{
+ "frame": {"x":1397,"y":704,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":548,"y":1305,"w":137,"h":124},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":20,"w":137,"h":124},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/underground_belt_entry.png":
+{
+ "frame": {"x":280,"y":1438,"w":137,"h":111},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":33,"w":137,"h":111},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":689,"y":1309,"w":137,"h":111},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/underground_belt_exit.png":
+{
+ "frame": {"x":830,"y":1317,"w":137,"h":111},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/virtual_processor-analyzer.png":
+{
+ "frame": {"x":1675,"y":732,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/virtual_processor-rotater.png":
+{
+ "frame": {"x":438,"y":890,"w":117,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":117,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":801,"y":1033,"w":143,"h":133},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":143,"h":133},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/virtual_processor-unstacker.png":
+{
+ "frame": {"x":3,"y":1035,"w":144,"h":143},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":144,"h":143},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/virtual_processor.png":
+{
+ "frame": {"x":3,"y":1182,"w":144,"h":140},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":144,"h":140},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire-cross.png":
+{
+ "frame": {"x":1672,"y":880,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire-split.png":
+{
+ "frame": {"x":1098,"y":1000,"w":144,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire-turn.png":
+{
+ "frame": {"x":1812,"y":1230,"w":81,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire.png":
+{
+ "frame": {"x":2027,"y":184,"w":18,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire_tunnel-coating.png":
+{
+ "frame": {"x":255,"y":816,"w":31,"h":134},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":56,"y":5,"w":31,"h":134},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/buildings/wire_tunnel.png":
+{
+ "frame": {"x":3,"y":1558,"w":137,"h":134},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":5,"w":137,"h":134},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/debug/acceptor_slot.png":
+{
+ "frame": {"x":1107,"y":447,"w":12,"h":12},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
+ "sourceSize": {"w":12,"h":12}
+},
+"sprites/debug/ejector_slot.png":
+{
+ "frame": {"x":1107,"y":463,"w":12,"h":12},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
+ "sourceSize": {"w":12,"h":12}
+},
+"sprites/misc/hub_direction_indicator.png":
+{
+ "frame": {"x":1975,"y":184,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/misc/slot_bad_arrow.png":
+{
+ "frame": {"x":255,"y":638,"w":35,"h":35},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":35,"h":35},
+ "sourceSize": {"w":39,"h":39}
+},
+"sprites/misc/slot_good_arrow.png":
+{
+ "frame": {"x":255,"y":595,"w":35,"h":39},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":35,"h":39},
+ "sourceSize": {"w":39,"h":39}
+},
+"sprites/misc/storage_overlay.png":
+{
+ "frame": {"x":708,"y":988,"w":89,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":89,"h":44},
+ "sourceSize": {"w":90,"h":45}
+},
+"sprites/misc/waypoint.png":
+{
+ "frame": {"x":1987,"y":1656,"w":38,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":38,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/boolean_false.png":
+{
+ "frame": {"x":255,"y":954,"w":31,"h":41},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":5,"w":31,"h":41},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/boolean_true.png":
+{
+ "frame": {"x":1650,"y":556,"w":22,"h":41},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":5,"w":22,"h":41},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/display/blue.png":
+{
+ "frame": {"x":1975,"y":288,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/cyan.png":
+{
+ "frame": {"x":1975,"y":339,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/green.png":
+{
+ "frame": {"x":1975,"y":390,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/purple.png":
+{
+ "frame": {"x":1975,"y":441,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/red.png":
+{
+ "frame": {"x":1975,"y":492,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/white.png":
+{
+ "frame": {"x":1975,"y":543,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/display/yellow.png":
+{
+ "frame": {"x":1793,"y":1570,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
+ "sourceSize": {"w":49,"h":49}
+},
+"sprites/wires/lever_on.png":
+{
+ "frame": {"x":1936,"y":890,"w":109,"h":127},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":18,"y":5,"w":109,"h":127},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/logical_acceptor.png":
+{
+ "frame": {"x":1975,"y":3,"w":62,"h":106},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":43,"y":0,"w":62,"h":106},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/logical_ejector.png":
+{
+ "frame": {"x":1975,"y":113,"w":60,"h":67},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":44,"y":0,"w":60,"h":67},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/network_conflict.png":
+{
+ "frame": {"x":1793,"y":1621,"w":47,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":2,"w":47,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/network_empty.png":
+{
+ "frame": {"x":1987,"y":1604,"w":41,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":41,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/overlay_tile.png":
+{
+ "frame": {"x":708,"y":888,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/color_cross.png":
+{
+ "frame": {"x":1101,"y":852,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/color_forward.png":
+{
+ "frame": {"x":2026,"y":332,"w":18,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/color_split.png":
+{
+ "frame": {"x":1397,"y":1075,"w":144,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/color_turn.png":
+{
+ "frame": {"x":1812,"y":1315,"w":81,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/conflict_cross.png":
+{
+ "frame": {"x":824,"y":885,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/conflict_forward.png":
+{
+ "frame": {"x":2026,"y":480,"w":18,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/conflict_split.png":
+{
+ "frame": {"x":1665,"y":1028,"w":144,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/conflict_turn.png":
+{
+ "frame": {"x":1799,"y":1400,"w":81,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/regular_cross.png":
+{
+ "frame": {"x":1672,"y":880,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/regular_forward.png":
+{
+ "frame": {"x":2027,"y":184,"w":18,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/regular_split.png":
+{
+ "frame": {"x":1098,"y":1000,"w":144,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/regular_turn.png":
+{
+ "frame": {"x":1812,"y":1230,"w":81,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/shape_cross.png":
+{
+ "frame": {"x":560,"y":888,"w":144,"h":144},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":144,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/shape_forward.png":
+{
+ "frame": {"x":1107,"y":299,"w":18,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/shape_split.png":
+{
+ "frame": {"x":1665,"y":1113,"w":144,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/sets/shape_turn.png":
+{
+ "frame": {"x":1799,"y":1485,"w":81,"h":81},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
+ "sourceSize": {"w":144,"h":144}
+},
+"sprites/wires/wires_preview.png":
+{
+ "frame": {"x":1975,"y":236,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+}},
+"meta": {
+ "app": "https://www.codeandweb.com/texturepacker",
+ "version": "1.0",
+ "image": "atlas0_hq.png",
+ "format": "RGBA8888",
+ "size": {"w":2048,"h":2048},
+ "scale": "0.75",
+ "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$"
+}
+}
diff --git a/res_built/atlas/atlas0_hq.png b/res_built/atlas/atlas0_hq.png
index ef9ab78b..5ffbe2bd 100644
Binary files a/res_built/atlas/atlas0_hq.png and b/res_built/atlas/atlas0_hq.png differ
diff --git a/res_built/atlas/atlas0_lq.json b/res_built/atlas/atlas0_lq.json
index 3896c6fd..419beb4d 100644
--- a/res_built/atlas/atlas0_lq.json
+++ b/res_built/atlas/atlas0_lq.json
@@ -1,1308 +1,1420 @@
-{"frames": {
-
-"sprites/belt/built/forward_0.png":
-{
- "frame": {"x":415,"y":463,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_1.png":
-{
- "frame": {"x":195,"y":828,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_2.png":
-{
- "frame": {"x":3,"y":959,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_3.png":
-{
- "frame": {"x":448,"y":705,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_4.png":
-{
- "frame": {"x":394,"y":752,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_5.png":
-{
- "frame": {"x":342,"y":762,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_6.png":
-{
- "frame": {"x":289,"y":795,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_7.png":
-{
- "frame": {"x":239,"y":836,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_8.png":
-{
- "frame": {"x":191,"y":880,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_9.png":
-{
- "frame": {"x":143,"y":883,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_10.png":
-{
- "frame": {"x":147,"y":831,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_11.png":
-{
- "frame": {"x":99,"y":862,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_12.png":
-{
- "frame": {"x":51,"y":870,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/forward_13.png":
-{
- "frame": {"x":3,"y":907,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_0.png":
-{
- "frame": {"x":3,"y":667,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_1.png":
-{
- "frame": {"x":208,"y":636,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_2.png":
-{
- "frame": {"x":256,"y":640,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_3.png":
-{
- "frame": {"x":202,"y":684,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_4.png":
-{
- "frame": {"x":150,"y":687,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_5.png":
-{
- "frame": {"x":99,"y":718,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_6.png":
-{
- "frame": {"x":51,"y":726,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_7.png":
-{
- "frame": {"x":3,"y":763,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_8.png":
-{
- "frame": {"x":304,"y":651,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_9.png":
-{
- "frame": {"x":250,"y":688,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_10.png":
-{
- "frame": {"x":154,"y":639,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_11.png":
-{
- "frame": {"x":102,"y":670,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_12.png":
-{
- "frame": {"x":51,"y":678,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/left_13.png":
-{
- "frame": {"x":3,"y":715,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_0.png":
-{
- "frame": {"x":198,"y":732,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_1.png":
-{
- "frame": {"x":147,"y":735,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_2.png":
-{
- "frame": {"x":298,"y":699,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_3.png":
-{
- "frame": {"x":246,"y":736,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_4.png":
-{
- "frame": {"x":195,"y":780,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_5.png":
-{
- "frame": {"x":147,"y":783,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_6.png":
-{
- "frame": {"x":99,"y":814,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_7.png":
-{
- "frame": {"x":51,"y":822,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_8.png":
-{
- "frame": {"x":3,"y":859,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_9.png":
-{
- "frame": {"x":400,"y":704,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_10.png":
-{
- "frame": {"x":99,"y":766,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_11.png":
-{
- "frame": {"x":51,"y":774,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_12.png":
-{
- "frame": {"x":3,"y":811,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/belt/built/right_13.png":
-{
- "frame": {"x":352,"y":666,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/belt_left.png":
-{
- "frame": {"x":346,"y":714,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/belt_right.png":
-{
- "frame": {"x":294,"y":747,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/belt_top.png":
-{
- "frame": {"x":95,"y":914,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/constant_signal.png":
-{
- "frame": {"x":329,"y":390,"w":36,"h":43},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/cutter-quad.png":
-{
- "frame": {"x":191,"y":55,"w":184,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":184,"h":48},
- "sourceSize": {"w":192,"h":48}
-},
-"sprites/blueprints/cutter.png":
-{
- "frame": {"x":187,"y":315,"w":87,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/display.png":
-{
- "frame": {"x":106,"y":620,"w":44,"h":46},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/filter.png":
-{
- "frame": {"x":3,"y":244,"w":91,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":91,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/lever.png":
-{
- "frame": {"x":470,"y":257,"w":38,"h":40},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":5,"w":38,"h":40},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/logic_gate-not.png":
-{
- "frame": {"x":243,"y":784,"w":42,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":42,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/logic_gate-or.png":
-{
- "frame": {"x":55,"y":500,"w":48,"h":42},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/logic_gate-transistor.png":
-{
- "frame": {"x":144,"y":448,"w":35,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/logic_gate-xor.png":
-{
- "frame": {"x":291,"y":159,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/logic_gate.png":
-{
- "frame": {"x":384,"y":566,"w":48,"h":45},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/miner-chainable.png":
-{
- "frame": {"x":462,"y":345,"w":47,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/miner.png":
-{
- "frame": {"x":211,"y":584,"w":47,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/mixer.png":
-{
- "frame": {"x":98,"y":244,"w":89,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":89,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/painter-double.png":
-{
- "frame": {"x":387,"y":3,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/painter-mirrored.png":
-{
- "frame": {"x":191,"y":159,"w":96,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/painter-quad.png":
-{
- "frame": {"x":3,"y":3,"w":188,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":188,"h":48},
- "sourceSize": {"w":192,"h":48}
-},
-"sprites/blueprints/painter.png":
-{
- "frame": {"x":375,"y":203,"w":96,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/rotater-ccw.png":
-{
- "frame": {"x":291,"y":211,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/rotater-fl.png":
-{
- "frame": {"x":285,"y":263,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/rotater.png":
-{
- "frame": {"x":460,"y":397,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/splitter-compact-inverse.png":
-{
- "frame": {"x":183,"y":367,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/splitter-compact.png":
-{
- "frame": {"x":364,"y":615,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/splitter.png":
-{
- "frame": {"x":278,"y":338,"w":87,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/stacker.png":
-{
- "frame": {"x":369,"y":307,"w":89,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":89,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/blueprints/trash-storage.png":
-{
- "frame": {"x":94,"y":348,"w":85,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/trash.png":
-{
- "frame": {"x":277,"y":390,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/underground_belt_entry-tier2.png":
-{
- "frame": {"x":3,"y":500,"w":48,"h":43},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":5,"w":48,"h":43},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/underground_belt_entry.png":
-{
- "frame": {"x":159,"y":523,"w":48,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":10,"w":48,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/underground_belt_exit-tier2.png":
-{
- "frame": {"x":211,"y":542,"w":48,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/underground_belt_exit.png":
-{
- "frame": {"x":263,"y":546,"w":48,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire-cross.png":
-{
- "frame": {"x":183,"y":419,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire-split.png":
-{
- "frame": {"x":315,"y":567,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire-turn.png":
-{
- "frame": {"x":479,"y":129,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire.png":
-{
- "frame": {"x":357,"y":286,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire_tunnel-coating.png":
-{
- "frame": {"x":235,"y":458,"w":13,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":17,"y":0,"w":13,"h":47},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/blueprints/wire_tunnel.png":
-{
- "frame": {"x":384,"y":515,"w":48,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/belt_left.png":
-{
- "frame": {"x":3,"y":667,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/belt_right.png":
-{
- "frame": {"x":198,"y":732,"w":44,"h":44},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/belt_top.png":
-{
- "frame": {"x":415,"y":463,"w":40,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/constant_signal.png":
-{
- "frame": {"x":235,"y":411,"w":36,"h":43},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/cutter-quad.png":
-{
- "frame": {"x":191,"y":107,"w":184,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":184,"h":48},
- "sourceSize": {"w":192,"h":48}
-},
-"sprites/buildings/cutter.png":
-{
- "frame": {"x":369,"y":359,"w":87,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/display.png":
-{
- "frame": {"x":54,"y":628,"w":44,"h":46},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/filter.png":
-{
- "frame": {"x":191,"y":263,"w":90,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":90,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/hub.png":
-{
- "frame": {"x":3,"y":55,"w":184,"h":185},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":4,"w":184,"h":185},
- "sourceSize": {"w":192,"h":192}
-},
-"sprites/buildings/lever.png":
-{
- "frame": {"x":470,"y":301,"w":38,"h":40},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":5,"w":38,"h":40},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/logic_gate-not.png":
-{
- "frame": {"x":466,"y":653,"w":43,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":43,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/logic_gate-or.png":
-{
- "frame": {"x":107,"y":500,"w":48,"h":42},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/logic_gate-transistor.png":
-{
- "frame": {"x":421,"y":411,"w":35,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/logic_gate-xor.png":
-{
- "frame": {"x":92,"y":448,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/logic_gate.png":
-{
- "frame": {"x":436,"y":604,"w":48,"h":45},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/miner-chainable.png":
-{
- "frame": {"x":262,"y":588,"w":47,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/miner.png":
-{
- "frame": {"x":313,"y":599,"w":47,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/mixer.png":
-{
- "frame": {"x":3,"y":296,"w":88,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/painter-double.png":
-{
- "frame": {"x":379,"y":103,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/painter-mirrored.png":
-{
- "frame": {"x":191,"y":211,"w":96,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/painter-quad.png":
-{
- "frame": {"x":195,"y":3,"w":188,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":188,"h":48},
- "sourceSize": {"w":192,"h":48}
-},
-"sprites/buildings/painter.png":
-{
- "frame": {"x":370,"y":255,"w":96,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/rotater-ccw.png":
-{
- "frame": {"x":369,"y":411,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/rotater-fl.png":
-{
- "frame": {"x":460,"y":449,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/rotater.png":
-{
- "frame": {"x":275,"y":442,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/splitter-compact-inverse.png":
-{
- "frame": {"x":436,"y":553,"w":48,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/splitter-compact.png":
-{
- "frame": {"x":415,"y":653,"w":47,"h":47},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/splitter.png":
-{
- "frame": {"x":3,"y":348,"w":87,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/stacker.png":
-{
- "frame": {"x":95,"y":296,"w":88,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48},
- "sourceSize": {"w":96,"h":48}
-},
-"sprites/buildings/trash-storage.png":
-{
- "frame": {"x":3,"y":400,"w":85,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/trash.png":
-{
- "frame": {"x":183,"y":471,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/underground_belt_entry-tier2.png":
-{
- "frame": {"x":3,"y":579,"w":47,"h":42},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":6,"w":47,"h":42},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/underground_belt_entry.png":
-{
- "frame": {"x":3,"y":625,"w":47,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":10,"w":47,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/underground_belt_exit-tier2.png":
-{
- "frame": {"x":106,"y":578,"w":47,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/underground_belt_exit.png":
-{
- "frame": {"x":157,"y":597,"w":47,"h":38},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire-cross.png":
-{
- "frame": {"x":268,"y":494,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire-split.png":
-{
- "frame": {"x":55,"y":546,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire-turn.png":
-{
- "frame": {"x":479,"y":161,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire.png":
-{
- "frame": {"x":327,"y":457,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire_tunnel-coating.png":
-{
- "frame": {"x":252,"y":492,"w":12,"h":46},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":18,"y":1,"w":12,"h":46},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/buildings/wire_tunnel.png":
-{
- "frame": {"x":55,"y":578,"w":47,"h":46},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":47,"h":46},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/debug/acceptor_slot.png":
-{
- "frame": {"x":379,"y":55,"w":4,"h":4},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":4,"h":4},
- "sourceSize": {"w":4,"h":4}
-},
-"sprites/debug/ejector_slot.png":
-{
- "frame": {"x":379,"y":63,"w":4,"h":4},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":4,"h":4},
- "sourceSize": {"w":4,"h":4}
-},
-"sprites/misc/hub_direction_indicator.png":
-{
- "frame": {"x":487,"y":30,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/misc/slot_bad_arrow.png":
-{
- "frame": {"x":252,"y":458,"w":13,"h":13},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
- "sourceSize": {"w":13,"h":13}
-},
-"sprites/misc/slot_good_arrow.png":
-{
- "frame": {"x":252,"y":475,"w":13,"h":13},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
- "sourceSize": {"w":13,"h":13}
-},
-"sprites/misc/storage_overlay.png":
-{
- "frame": {"x":479,"y":110,"w":30,"h":15},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":30,"h":15},
- "sourceSize": {"w":30,"h":15}
-},
-"sprites/misc/waypoint.png":
-{
- "frame": {"x":349,"y":437,"w":14,"h":16},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":14,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/boolean_false.png":
-{
- "frame": {"x":235,"y":509,"w":12,"h":15},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":1,"w":12,"h":15},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/boolean_true.png":
-{
- "frame": {"x":357,"y":267,"w":9,"h":15},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":1,"w":9,"h":15},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/blue.png":
-{
- "frame": {"x":487,"y":50,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/cyan.png":
-{
- "frame": {"x":487,"y":70,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/green.png":
-{
- "frame": {"x":487,"y":90,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/purple.png":
-{
- "frame": {"x":337,"y":267,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/red.png":
-{
- "frame": {"x":337,"y":287,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/white.png":
-{
- "frame": {"x":337,"y":307,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/display/yellow.png":
-{
- "frame": {"x":278,"y":315,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/lever_on.png":
-{
- "frame": {"x":235,"y":367,"w":38,"h":40},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":5,"w":38,"h":40},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/logical_acceptor.png":
-{
- "frame": {"x":343,"y":227,"w":23,"h":36},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":13,"y":0,"w":23,"h":36},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/logical_ejector.png":
-{
- "frame": {"x":487,"y":3,"w":22,"h":23},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":22,"h":23},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/network_conflict.png":
-{
- "frame": {"x":298,"y":315,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/network_empty.png":
-{
- "frame": {"x":318,"y":315,"w":15,"h":16},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":15,"h":16},
- "sourceSize": {"w":16,"h":16}
-},
-"sprites/wires/overlay_tile.png":
-{
- "frame": {"x":343,"y":159,"w":32,"h":32},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/sets/color_cross.png":
-{
- "frame": {"x":363,"y":463,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/color_forward.png":
-{
- "frame": {"x":339,"y":457,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/color_split.png":
-{
- "frame": {"x":3,"y":547,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/color_turn.png":
-{
- "frame": {"x":479,"y":193,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/conflict_cross.png":
-{
- "frame": {"x":459,"y":501,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/conflict_forward.png":
-{
- "frame": {"x":351,"y":457,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/conflict_split.png":
-{
- "frame": {"x":107,"y":546,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/conflict_turn.png":
-{
- "frame": {"x":343,"y":195,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/regular_cross.png":
-{
- "frame": {"x":268,"y":494,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/regular_forward.png":
-{
- "frame": {"x":327,"y":457,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/regular_split.png":
-{
- "frame": {"x":55,"y":546,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/regular_turn.png":
-{
- "frame": {"x":479,"y":161,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/shape_cross.png":
-{
- "frame": {"x":332,"y":515,"w":48,"h":48},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/shape_forward.png":
-{
- "frame": {"x":320,"y":509,"w":8,"h":48},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/shape_split.png":
-{
- "frame": {"x":159,"y":565,"w":48,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/sets/shape_turn.png":
-{
- "frame": {"x":475,"y":225,"w":28,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
- "sourceSize": {"w":48,"h":48}
-},
-"sprites/wires/wires_preview.png":
-{
- "frame": {"x":329,"y":437,"w":16,"h":16},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
- "sourceSize": {"w":16,"h":16}
-}},
-"meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "1.0",
- "image": "atlas0_lq.png",
- "format": "RGBA8888",
- "size": {"w":512,"h":1024},
- "scale": "0.25",
- "smartupdate": "$TexturePacker:SmartUpdate:876f0711b44fa7bbab8d2539e9651766:ff01f850e086ef31c114b036c3a32e6d:908b89f5ca8ff73e331a35a3b14d0604$"
-}
-}
+{"frames": {
+
+"sprites/belt/built/forward_0.png":
+{
+ "frame": {"x":98,"y":963,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_1.png":
+{
+ "frame": {"x":399,"y":759,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_2.png":
+{
+ "frame": {"x":142,"y":940,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_3.png":
+{
+ "frame": {"x":186,"y":962,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_4.png":
+{
+ "frame": {"x":230,"y":962,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_5.png":
+{
+ "frame": {"x":443,"y":765,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_6.png":
+{
+ "frame": {"x":389,"y":811,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_7.png":
+{
+ "frame": {"x":338,"y":858,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_8.png":
+{
+ "frame": {"x":287,"y":904,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_9.png":
+{
+ "frame": {"x":274,"y":956,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_10.png":
+{
+ "frame": {"x":345,"y":806,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_11.png":
+{
+ "frame": {"x":294,"y":852,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_12.png":
+{
+ "frame": {"x":243,"y":890,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/forward_13.png":
+{
+ "frame": {"x":195,"y":910,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_0.png":
+{
+ "frame": {"x":105,"y":730,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_1.png":
+{
+ "frame": {"x":54,"y":771,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_2.png":
+{
+ "frame": {"x":102,"y":778,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_3.png":
+{
+ "frame": {"x":51,"y":819,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_4.png":
+{
+ "frame": {"x":3,"y":851,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_5.png":
+{
+ "frame": {"x":255,"y":746,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_6.png":
+{
+ "frame": {"x":201,"y":766,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_7.png":
+{
+ "frame": {"x":150,"y":796,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_8.png":
+{
+ "frame": {"x":99,"y":826,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_9.png":
+{
+ "frame": {"x":51,"y":867,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_10.png":
+{
+ "frame": {"x":3,"y":803,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_11.png":
+{
+ "frame": {"x":207,"y":718,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_12.png":
+{
+ "frame": {"x":310,"y":708,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/left_13.png":
+{
+ "frame": {"x":153,"y":748,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_0.png":
+{
+ "frame": {"x":3,"y":899,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_1.png":
+{
+ "frame": {"x":358,"y":710,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_2.png":
+{
+ "frame": {"x":99,"y":874,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_3.png":
+{
+ "frame": {"x":51,"y":915,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_4.png":
+{
+ "frame": {"x":3,"y":947,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_5.png":
+{
+ "frame": {"x":406,"y":711,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_6.png":
+{
+ "frame": {"x":351,"y":758,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_7.png":
+{
+ "frame": {"x":297,"y":804,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_8.png":
+{
+ "frame": {"x":246,"y":842,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_9.png":
+{
+ "frame": {"x":195,"y":862,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_10.png":
+{
+ "frame": {"x":303,"y":756,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_11.png":
+{
+ "frame": {"x":249,"y":794,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_12.png":
+{
+ "frame": {"x":198,"y":814,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/belt/built/right_13.png":
+{
+ "frame": {"x":147,"y":844,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/belt_left.png":
+{
+ "frame": {"x":147,"y":892,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/belt_right.png":
+{
+ "frame": {"x":454,"y":717,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/belt_top.png":
+{
+ "frame": {"x":318,"y":956,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/constant_signal.png":
+{
+ "frame": {"x":329,"y":390,"w":36,"h":43},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/cutter-quad.png":
+{
+ "frame": {"x":191,"y":55,"w":184,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":184,"h":48},
+ "sourceSize": {"w":192,"h":48}
+},
+"sprites/blueprints/cutter.png":
+{
+ "frame": {"x":187,"y":315,"w":87,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/display.png":
+{
+ "frame": {"x":211,"y":567,"w":44,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/filter.png":
+{
+ "frame": {"x":3,"y":244,"w":91,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":91,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/lever.png":
+{
+ "frame": {"x":470,"y":257,"w":38,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/logic_gate-not.png":
+{
+ "frame": {"x":321,"y":509,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/logic_gate-or.png":
+{
+ "frame": {"x":311,"y":662,"w":48,"h":42},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/logic_gate-transistor.png":
+{
+ "frame": {"x":144,"y":448,"w":35,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/logic_gate-xor.png":
+{
+ "frame": {"x":291,"y":159,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/logic_gate.png":
+{
+ "frame": {"x":311,"y":613,"w":48,"h":45},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/miner-chainable.png":
+{
+ "frame": {"x":462,"y":353,"w":47,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/miner.png":
+{
+ "frame": {"x":55,"y":626,"w":47,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/mixer.png":
+{
+ "frame": {"x":98,"y":244,"w":89,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":89,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/painter-double.png":
+{
+ "frame": {"x":387,"y":3,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/painter-mirrored.png":
+{
+ "frame": {"x":191,"y":159,"w":96,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/painter-quad.png":
+{
+ "frame": {"x":3,"y":3,"w":188,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":188,"h":48},
+ "sourceSize": {"w":192,"h":48}
+},
+"sprites/blueprints/painter.png":
+{
+ "frame": {"x":375,"y":203,"w":96,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/rotater-ccw.png":
+{
+ "frame": {"x":291,"y":211,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/rotater-fl.png":
+{
+ "frame": {"x":285,"y":263,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/rotater.png":
+{
+ "frame": {"x":460,"y":405,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/splitter-compact-inverse.png":
+{
+ "frame": {"x":183,"y":367,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":277,"y":390,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/splitter-compact-merge.png":
+{
+ "frame": {"x":54,"y":678,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/splitter-compact.png":
+{
+ "frame": {"x":3,"y":710,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/splitter.png":
+{
+ "frame": {"x":278,"y":338,"w":87,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/stacker.png":
+{
+ "frame": {"x":369,"y":307,"w":89,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":89,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/blueprints/trash-storage.png":
+{
+ "frame": {"x":94,"y":348,"w":85,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/trash.png":
+{
+ "frame": {"x":183,"y":419,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":259,"y":649,"w":48,"h":43},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":5,"w":48,"h":43},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/underground_belt_entry.png":
+{
+ "frame": {"x":363,"y":668,"w":48,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":10,"w":48,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":3,"y":552,"w":48,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/underground_belt_exit.png":
+{
+ "frame": {"x":55,"y":552,"w":48,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/virtual_processor-analyzer.png":
+{
+ "frame": {"x":92,"y":448,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/virtual_processor-rotater.png":
+{
+ "frame": {"x":467,"y":613,"w":41,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":41,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":363,"y":619,"w":48,"h":45},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/virtual_processor-unstacker.png":
+{
+ "frame": {"x":369,"y":411,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/virtual_processor.png":
+{
+ "frame": {"x":460,"y":457,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire-cross.png":
+{
+ "frame": {"x":275,"y":442,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire-split.png":
+{
+ "frame": {"x":3,"y":594,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire-turn.png":
+{
+ "frame": {"x":479,"y":129,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire.png":
+{
+ "frame": {"x":357,"y":286,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire_tunnel-coating.png":
+{
+ "frame": {"x":235,"y":462,"w":13,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":17,"y":0,"w":13,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/blueprints/wire_tunnel.png":
+{
+ "frame": {"x":107,"y":500,"w":48,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/belt_left.png":
+{
+ "frame": {"x":105,"y":730,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/belt_right.png":
+{
+ "frame": {"x":3,"y":899,"w":44,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/belt_top.png":
+{
+ "frame": {"x":98,"y":963,"w":40,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/constant_signal.png":
+{
+ "frame": {"x":235,"y":415,"w":36,"h":43},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/cutter-quad.png":
+{
+ "frame": {"x":191,"y":107,"w":184,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":184,"h":48},
+ "sourceSize": {"w":192,"h":48}
+},
+"sprites/buildings/cutter.png":
+{
+ "frame": {"x":369,"y":359,"w":87,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/display.png":
+{
+ "frame": {"x":211,"y":617,"w":44,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/filter.png":
+{
+ "frame": {"x":191,"y":263,"w":90,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":90,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/hub.png":
+{
+ "frame": {"x":3,"y":55,"w":184,"h":185},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":4,"w":184,"h":185},
+ "sourceSize": {"w":192,"h":192}
+},
+"sprites/buildings/lever.png":
+{
+ "frame": {"x":470,"y":305,"w":38,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/logic_gate-not.png":
+{
+ "frame": {"x":51,"y":963,"w":43,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":43,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/logic_gate-or.png":
+{
+ "frame": {"x":415,"y":665,"w":48,"h":42},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/logic_gate-transistor.png":
+{
+ "frame": {"x":421,"y":411,"w":35,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/logic_gate-xor.png":
+{
+ "frame": {"x":183,"y":471,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/logic_gate.png":
+{
+ "frame": {"x":107,"y":551,"w":48,"h":45},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/miner-chainable.png":
+{
+ "frame": {"x":3,"y":658,"w":47,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/miner.png":
+{
+ "frame": {"x":106,"y":632,"w":47,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/mixer.png":
+{
+ "frame": {"x":3,"y":296,"w":88,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/painter-double.png":
+{
+ "frame": {"x":379,"y":103,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/painter-mirrored.png":
+{
+ "frame": {"x":191,"y":211,"w":96,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/painter-quad.png":
+{
+ "frame": {"x":195,"y":3,"w":188,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":188,"h":48},
+ "sourceSize": {"w":192,"h":48}
+},
+"sprites/buildings/painter.png":
+{
+ "frame": {"x":370,"y":255,"w":96,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/rotater-ccw.png":
+{
+ "frame": {"x":269,"y":494,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/rotater-fl.png":
+{
+ "frame": {"x":367,"y":463,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/rotater.png":
+{
+ "frame": {"x":259,"y":546,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/splitter-compact-inverse.png":
+{
+ "frame": {"x":159,"y":523,"w":48,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":259,"y":598,"w":48,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/splitter-compact-merge.png":
+{
+ "frame": {"x":157,"y":655,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/splitter-compact.png":
+{
+ "frame": {"x":208,"y":667,"w":47,"h":47},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/splitter.png":
+{
+ "frame": {"x":3,"y":348,"w":87,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/stacker.png":
+{
+ "frame": {"x":95,"y":296,"w":88,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48},
+ "sourceSize": {"w":96,"h":48}
+},
+"sprites/buildings/trash-storage.png":
+{
+ "frame": {"x":3,"y":400,"w":85,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/trash.png":
+{
+ "frame": {"x":419,"y":509,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":105,"y":684,"w":47,"h":42},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":6,"w":47,"h":42},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/underground_belt_entry.png":
+{
+ "frame": {"x":54,"y":729,"w":47,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":10,"w":47,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":3,"y":761,"w":47,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/underground_belt_exit.png":
+{
+ "frame": {"x":156,"y":706,"w":47,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/virtual_processor-analyzer.png":
+{
+ "frame": {"x":367,"y":515,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/virtual_processor-rotater.png":
+{
+ "frame": {"x":467,"y":665,"w":41,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":0,"w":41,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":159,"y":574,"w":48,"h":45},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/virtual_processor-unstacker.png":
+{
+ "frame": {"x":311,"y":561,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/virtual_processor.png":
+{
+ "frame": {"x":419,"y":561,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire-cross.png":
+{
+ "frame": {"x":363,"y":567,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire-split.png":
+{
+ "frame": {"x":55,"y":594,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire-turn.png":
+{
+ "frame": {"x":479,"y":161,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire.png":
+{
+ "frame": {"x":235,"y":513,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire_tunnel-coating.png":
+{
+ "frame": {"x":327,"y":457,"w":12,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":18,"y":1,"w":12,"h":46},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/buildings/wire_tunnel.png":
+{
+ "frame": {"x":259,"y":696,"w":47,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":47,"h":46},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/debug/acceptor_slot.png":
+{
+ "frame": {"x":379,"y":55,"w":4,"h":4},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":4,"h":4},
+ "sourceSize": {"w":4,"h":4}
+},
+"sprites/debug/ejector_slot.png":
+{
+ "frame": {"x":379,"y":63,"w":4,"h":4},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":4,"h":4},
+ "sourceSize": {"w":4,"h":4}
+},
+"sprites/misc/hub_direction_indicator.png":
+{
+ "frame": {"x":487,"y":30,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/misc/slot_bad_arrow.png":
+{
+ "frame": {"x":252,"y":462,"w":13,"h":13},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
+ "sourceSize": {"w":13,"h":13}
+},
+"sprites/misc/slot_good_arrow.png":
+{
+ "frame": {"x":252,"y":479,"w":13,"h":13},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
+ "sourceSize": {"w":13,"h":13}
+},
+"sprites/misc/storage_overlay.png":
+{
+ "frame": {"x":479,"y":110,"w":30,"h":15},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":15},
+ "sourceSize": {"w":30,"h":15}
+},
+"sprites/misc/waypoint.png":
+{
+ "frame": {"x":349,"y":437,"w":14,"h":16},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":14,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/boolean_false.png":
+{
+ "frame": {"x":252,"y":496,"w":12,"h":15},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":1,"w":12,"h":15},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/boolean_true.png":
+{
+ "frame": {"x":357,"y":267,"w":9,"h":15},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":1,"w":9,"h":15},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/blue.png":
+{
+ "frame": {"x":487,"y":50,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/cyan.png":
+{
+ "frame": {"x":487,"y":70,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/green.png":
+{
+ "frame": {"x":487,"y":90,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/purple.png":
+{
+ "frame": {"x":337,"y":267,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/red.png":
+{
+ "frame": {"x":337,"y":287,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/white.png":
+{
+ "frame": {"x":337,"y":307,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/display/yellow.png":
+{
+ "frame": {"x":278,"y":315,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/lever_on.png":
+{
+ "frame": {"x":235,"y":367,"w":38,"h":44},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/logical_acceptor.png":
+{
+ "frame": {"x":343,"y":227,"w":23,"h":36},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":13,"y":0,"w":23,"h":36},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/logical_ejector.png":
+{
+ "frame": {"x":487,"y":3,"w":22,"h":23},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":22,"h":23},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/network_conflict.png":
+{
+ "frame": {"x":298,"y":315,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/network_empty.png":
+{
+ "frame": {"x":318,"y":315,"w":15,"h":16},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":15,"h":16},
+ "sourceSize": {"w":16,"h":16}
+},
+"sprites/wires/overlay_tile.png":
+{
+ "frame": {"x":343,"y":159,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/sets/color_cross.png":
+{
+ "frame": {"x":415,"y":613,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/color_forward.png":
+{
+ "frame": {"x":247,"y":515,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/color_split.png":
+{
+ "frame": {"x":107,"y":600,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/color_turn.png":
+{
+ "frame": {"x":479,"y":193,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/conflict_cross.png":
+{
+ "frame": {"x":3,"y":500,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/conflict_forward.png":
+{
+ "frame": {"x":343,"y":457,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/conflict_split.png":
+{
+ "frame": {"x":159,"y":623,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/conflict_turn.png":
+{
+ "frame": {"x":343,"y":195,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/regular_cross.png":
+{
+ "frame": {"x":363,"y":567,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/regular_forward.png":
+{
+ "frame": {"x":235,"y":513,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/regular_split.png":
+{
+ "frame": {"x":55,"y":594,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/regular_turn.png":
+{
+ "frame": {"x":479,"y":161,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/shape_cross.png":
+{
+ "frame": {"x":55,"y":500,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/shape_forward.png":
+{
+ "frame": {"x":355,"y":457,"w":8,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/shape_split.png":
+{
+ "frame": {"x":3,"y":626,"w":48,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/sets/shape_turn.png":
+{
+ "frame": {"x":475,"y":225,"w":28,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28},
+ "sourceSize": {"w":48,"h":48}
+},
+"sprites/wires/wires_preview.png":
+{
+ "frame": {"x":329,"y":437,"w":16,"h":16},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16},
+ "sourceSize": {"w":16,"h":16}
+}},
+"meta": {
+ "app": "https://www.codeandweb.com/texturepacker",
+ "version": "1.0",
+ "image": "atlas0_lq.png",
+ "format": "RGBA8888",
+ "size": {"w":512,"h":1024},
+ "scale": "0.25",
+ "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$"
+}
+}
diff --git a/res_built/atlas/atlas0_lq.png b/res_built/atlas/atlas0_lq.png
index c6b4be55..fd8aa06b 100644
Binary files a/res_built/atlas/atlas0_lq.png and b/res_built/atlas/atlas0_lq.png differ
diff --git a/res_built/atlas/atlas0_mq.json b/res_built/atlas/atlas0_mq.json
index 89809854..14de3876 100644
--- a/res_built/atlas/atlas0_mq.json
+++ b/res_built/atlas/atlas0_mq.json
@@ -1,1308 +1,1420 @@
-{"frames": {
-
-"sprites/belt/built/forward_0.png":
-{
- "frame": {"x":943,"y":816,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_1.png":
-{
- "frame": {"x":439,"y":803,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_2.png":
-{
- "frame": {"x":850,"y":1402,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_3.png":
-{
- "frame": {"x":932,"y":1402,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_4.png":
-{
- "frame": {"x":751,"y":1431,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_5.png":
-{
- "frame": {"x":650,"y":1480,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_6.png":
-{
- "frame": {"x":551,"y":1494,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_7.png":
-{
- "frame": {"x":454,"y":1522,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_8.png":
-{
- "frame": {"x":358,"y":1553,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_9.png":
-{
- "frame": {"x":267,"y":1554,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_10.png":
-{
- "frame": {"x":276,"y":1454,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_11.png":
-{
- "frame": {"x":185,"y":1499,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_12.png":
-{
- "frame": {"x":94,"y":1514,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/forward_13.png":
-{
- "frame": {"x":3,"y":1585,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_0.png":
-{
- "frame": {"x":196,"y":1135,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_1.png":
-{
- "frame": {"x":99,"y":1150,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_2.png":
-{
- "frame": {"x":287,"y":1181,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_3.png":
-{
- "frame": {"x":190,"y":1226,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_4.png":
-{
- "frame": {"x":94,"y":1241,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_5.png":
-{
- "frame": {"x":3,"y":1312,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_6.png":
-{
- "frame": {"x":674,"y":1207,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_7.png":
-{
- "frame": {"x":577,"y":1217,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_8.png":
-{
- "frame": {"x":478,"y":1246,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_9.png":
-{
- "frame": {"x":378,"y":1271,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_10.png":
-{
- "frame": {"x":3,"y":1221,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_11.png":
-{
- "frame": {"x":583,"y":1126,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_12.png":
-{
- "frame": {"x":486,"y":1155,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/left_13.png":
-{
- "frame": {"x":387,"y":1180,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_0.png":
-{
- "frame": {"x":281,"y":1272,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_1.png":
-{
- "frame": {"x":185,"y":1317,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_2.png":
-{
- "frame": {"x":569,"y":1308,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_3.png":
-{
- "frame": {"x":469,"y":1337,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_4.png":
-{
- "frame": {"x":372,"y":1362,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_5.png":
-{
- "frame": {"x":276,"y":1363,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_6.png":
-{
- "frame": {"x":185,"y":1408,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_7.png":
-{
- "frame": {"x":94,"y":1423,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_8.png":
-{
- "frame": {"x":3,"y":1494,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_9.png":
-{
- "frame": {"x":856,"y":1311,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_10.png":
-{
- "frame": {"x":94,"y":1332,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_11.png":
-{
- "frame": {"x":3,"y":1403,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_12.png":
-{
- "frame": {"x":765,"y":1249,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/belt/built/right_13.png":
-{
- "frame": {"x":668,"y":1298,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/belt_left.png":
-{
- "frame": {"x":759,"y":1340,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/belt_right.png":
-{
- "frame": {"x":660,"y":1389,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/belt_top.png":
-{
- "frame": {"x":176,"y":1599,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/constant_signal.png":
-{
- "frame": {"x":949,"y":396,"w":71,"h":85},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":13,"y":0,"w":71,"h":85},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/cutter-quad.png":
-{
- "frame": {"x":373,"y":103,"w":366,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":366,"h":96},
- "sourceSize": {"w":384,"h":96}
-},
-"sprites/blueprints/cutter.png":
-{
- "frame": {"x":745,"y":594,"w":172,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":172,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/display.png":
-{
- "frame": {"x":560,"y":1399,"w":86,"h":91},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":5,"y":5,"w":86,"h":91},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/filter.png":
-{
- "frame": {"x":569,"y":303,"w":180,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":10,"y":0,"w":180,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/lever.png":
-{
- "frame": {"x":946,"y":734,"w":74,"h":78},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":11,"w":74,"h":78},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/logic_gate-not.png":
-{
- "frame": {"x":367,"y":1453,"w":83,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":7,"y":0,"w":83,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/logic_gate-or.png":
-{
- "frame": {"x":521,"y":991,"w":96,"h":82},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":82},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/logic_gate-transistor.png":
-{
- "frame": {"x":449,"y":703,"w":68,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/logic_gate-xor.png":
-{
- "frame": {"x":3,"y":674,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/logic_gate.png":
-{
- "frame": {"x":335,"y":902,"w":96,"h":89},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":89},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/miner-chainable.png":
-{
- "frame": {"x":929,"y":916,"w":92,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/miner.png":
-{
- "frame": {"x":100,"y":1050,"w":92,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/mixer.png":
-{
- "frame": {"x":3,"y":474,"w":175,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":8,"y":0,"w":175,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/painter-double.png":
-{
- "frame": {"x":759,"y":3,"w":192,"h":192},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":192},
- "sourceSize": {"w":192,"h":192}
-},
-"sprites/blueprints/painter-mirrored.png":
-{
- "frame": {"x":373,"y":303,"w":192,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/painter-quad.png":
-{
- "frame": {"x":3,"y":3,"w":374,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":374,"h":96},
- "sourceSize": {"w":384,"h":96}
-},
-"sprites/blueprints/painter.png":
-{
- "frame": {"x":753,"y":394,"w":192,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/rotater-ccw.png":
-{
- "frame": {"x":103,"y":674,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/rotater-fl.png":
-{
- "frame": {"x":921,"y":1016,"w":95,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/rotater.png":
-{
- "frame": {"x":203,"y":674,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/splitter-compact-inverse.png":
-{
- "frame": {"x":820,"y":1152,"w":95,"h":93},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":1,"w":95,"h":93},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/splitter-compact.png":
-{
- "frame": {"x":101,"y":874,"w":93,"h":93},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":1,"w":93,"h":93},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/splitter.png":
-{
- "frame": {"x":3,"y":574,"w":171,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/stacker.png":
-{
- "frame": {"x":182,"y":474,"w":175,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":8,"y":0,"w":175,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/blueprints/trash-storage.png":
-{
- "frame": {"x":528,"y":603,"w":167,"h":192},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":167,"h":192},
- "sourceSize": {"w":192,"h":192}
-},
-"sprites/blueprints/trash.png":
-{
- "frame": {"x":349,"y":703,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/underground_belt_entry-tier2.png":
-{
- "frame": {"x":198,"y":969,"w":93,"h":84},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":12,"w":93,"h":84},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/underground_belt_entry.png":
-{
- "frame": {"x":100,"y":971,"w":93,"h":75},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":21,"w":93,"h":75},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/underground_belt_exit-tier2.png":
-{
- "frame": {"x":3,"y":874,"w":94,"h":75},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":0,"w":94,"h":75},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/underground_belt_exit.png":
-{
- "frame": {"x":3,"y":1048,"w":93,"h":75},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":0,"w":93,"h":75},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire-cross.png":
-{
- "frame": {"x":521,"y":799,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire-split.png":
-{
- "frame": {"x":721,"y":990,"w":96,"h":55},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":41,"w":96,"h":55},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire-turn.png":
-{
- "frame": {"x":955,"y":105,"w":55,"h":55},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":41,"y":41,"w":55,"h":55},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire.png":
-{
- "frame": {"x":699,"y":603,"w":14,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":41,"y":0,"w":14,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire_tunnel-coating.png":
-{
- "frame": {"x":921,"y":594,"w":23,"h":91},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":36,"y":2,"w":23,"h":91},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/blueprints/wire_tunnel.png":
-{
- "frame": {"x":3,"y":953,"w":93,"h":91},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":2,"y":2,"w":93,"h":91},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/belt_left.png":
-{
- "frame": {"x":196,"y":1135,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/belt_right.png":
-{
- "frame": {"x":281,"y":1272,"w":87,"h":87},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/belt_top.png":
-{
- "frame": {"x":943,"y":816,"w":78,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/constant_signal.png":
-{
- "frame": {"x":949,"y":485,"w":70,"h":85},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":13,"y":0,"w":70,"h":85},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/cutter-quad.png":
-{
- "frame": {"x":373,"y":203,"w":366,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":366,"h":96},
- "sourceSize": {"w":384,"h":96}
-},
-"sprites/buildings/cutter.png":
-{
- "frame": {"x":178,"y":574,"w":171,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/display.png":
-{
- "frame": {"x":463,"y":1428,"w":84,"h":90},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":6,"w":84,"h":90},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/filter.png":
-{
- "frame": {"x":569,"y":403,"w":179,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":179,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/hub.png":
-{
- "frame": {"x":3,"y":103,"w":366,"h":367},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":10,"w":366,"h":367},
- "sourceSize": {"w":384,"h":384}
-},
-"sprites/buildings/lever.png":
-{
- "frame": {"x":948,"y":574,"w":73,"h":76},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":12,"y":12,"w":73,"h":76},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/logic_gate-not.png":
-{
- "frame": {"x":435,"y":903,"w":82,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":8,"y":0,"w":82,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/logic_gate-or.png":
-{
- "frame": {"x":621,"y":923,"w":96,"h":83},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":83},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/logic_gate-transistor.png":
-{
- "frame": {"x":621,"y":799,"w":68,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/logic_gate-xor.png":
-{
- "frame": {"x":339,"y":803,"w":96,"h":95},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":95},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/logic_gate.png":
-{
- "frame": {"x":521,"y":899,"w":96,"h":88},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":88},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/miner-chainable.png":
-{
- "frame": {"x":391,"y":1081,"w":91,"h":95},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/miner.png":
-{
- "frame": {"x":292,"y":1082,"w":91,"h":95},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/mixer.png":
-{
- "frame": {"x":361,"y":503,"w":174,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":174,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/painter-double.png":
-{
- "frame": {"x":759,"y":199,"w":192,"h":191},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":191},
- "sourceSize": {"w":192,"h":192}
-},
-"sprites/buildings/painter-mirrored.png":
-{
- "frame": {"x":373,"y":403,"w":192,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/painter-quad.png":
-{
- "frame": {"x":381,"y":3,"w":374,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":374,"h":96},
- "sourceSize": {"w":384,"h":96}
-},
-"sprites/buildings/painter.png":
-{
- "frame": {"x":752,"y":494,"w":192,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/rotater-ccw.png":
-{
- "frame": {"x":821,"y":1052,"w":95,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/rotater-fl.png":
-{
- "frame": {"x":721,"y":1107,"w":95,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/rotater.png":
-{
- "frame": {"x":920,"y":1116,"w":95,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/splitter-compact-inverse.png":
-{
- "frame": {"x":919,"y":1216,"w":94,"h":91},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":2,"w":94,"h":91},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/splitter-compact.png":
-{
- "frame": {"x":198,"y":874,"w":93,"h":91},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":2,"w":93,"h":91},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/splitter.png":
-{
- "frame": {"x":353,"y":603,"w":171,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/stacker.png":
-{
- "frame": {"x":539,"y":503,"w":174,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":9,"y":0,"w":174,"h":96},
- "sourceSize": {"w":192,"h":96}
-},
-"sprites/buildings/trash-storage.png":
-{
- "frame": {"x":736,"y":694,"w":166,"h":192},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":14,"y":0,"w":166,"h":192},
- "sourceSize": {"w":192,"h":192}
-},
-"sprites/buildings/trash.png":
-{
- "frame": {"x":729,"y":890,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/underground_belt_entry-tier2.png":
-{
- "frame": {"x":295,"y":995,"w":92,"h":83},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":13,"w":92,"h":83},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/underground_belt_entry.png":
-{
- "frame": {"x":196,"y":1057,"w":92,"h":74},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":22,"w":92,"h":74},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/underground_belt_exit-tier2.png":
-{
- "frame": {"x":391,"y":1003,"w":92,"h":74},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/underground_belt_exit.png":
-{
- "frame": {"x":487,"y":1077,"w":92,"h":74},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire-cross.png":
-{
- "frame": {"x":829,"y":894,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire-split.png":
-{
- "frame": {"x":621,"y":1010,"w":96,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire-turn.png":
-{
- "frame": {"x":955,"y":164,"w":54,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire.png":
-{
- "frame": {"x":743,"y":103,"w":12,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire_tunnel-coating.png":
-{
- "frame": {"x":921,"y":689,"w":21,"h":90},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":37,"y":3,"w":21,"h":90},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/buildings/wire_tunnel.png":
-{
- "frame": {"x":3,"y":1127,"w":92,"h":90},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":3,"w":92,"h":90},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/debug/acceptor_slot.png":
-{
- "frame": {"x":1013,"y":164,"w":8,"h":8},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":8,"h":8},
- "sourceSize": {"w":8,"h":8}
-},
-"sprites/debug/ejector_slot.png":
-{
- "frame": {"x":1013,"y":176,"w":8,"h":8},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":8,"h":8},
- "sourceSize": {"w":8,"h":8}
-},
-"sprites/misc/hub_direction_indicator.png":
-{
- "frame": {"x":693,"y":851,"w":32,"h":32},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/misc/slot_bad_arrow.png":
-{
- "frame": {"x":717,"y":605,"w":24,"h":24},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":1,"w":24,"h":24},
- "sourceSize": {"w":26,"h":26}
-},
-"sprites/misc/slot_good_arrow.png":
-{
- "frame": {"x":717,"y":575,"w":24,"h":26},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":1,"y":0,"w":24,"h":26},
- "sourceSize": {"w":26,"h":26}
-},
-"sprites/misc/storage_overlay.png":
-{
- "frame": {"x":955,"y":71,"w":60,"h":30},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":60,"h":30},
- "sourceSize": {"w":60,"h":30}
-},
-"sprites/misc/waypoint.png":
-{
- "frame": {"x":717,"y":539,"w":26,"h":32},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":26,"h":32},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/boolean_false.png":
-{
- "frame": {"x":717,"y":633,"w":21,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":6,"y":3,"w":21,"h":28},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/boolean_true.png":
-{
- "frame": {"x":717,"y":665,"w":15,"h":28},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":7,"y":3,"w":15,"h":28},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/display/blue.png":
-{
- "frame": {"x":699,"y":703,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/cyan.png":
-{
- "frame": {"x":699,"y":740,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/green.png":
-{
- "frame": {"x":699,"y":777,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/purple.png":
-{
- "frame": {"x":906,"y":783,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/red.png":
-{
- "frame": {"x":906,"y":820,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/white.png":
-{
- "frame": {"x":906,"y":857,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/display/yellow.png":
-{
- "frame": {"x":693,"y":814,"w":33,"h":33},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
- "sourceSize": {"w":33,"h":33}
-},
-"sprites/wires/lever_on.png":
-{
- "frame": {"x":948,"y":654,"w":73,"h":76},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":12,"y":12,"w":73,"h":76},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/logical_acceptor.png":
-{
- "frame": {"x":303,"y":674,"w":42,"h":71},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":28,"y":0,"w":42,"h":71},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/logical_ejector.png":
-{
- "frame": {"x":303,"y":749,"w":41,"h":45},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":29,"y":0,"w":41,"h":45},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/network_conflict.png":
-{
- "frame": {"x":303,"y":798,"w":32,"h":30},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":1,"w":32,"h":30},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/network_empty.png":
-{
- "frame": {"x":717,"y":503,"w":28,"h":32},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":3,"y":0,"w":28,"h":32},
- "sourceSize": {"w":32,"h":32}
-},
-"sprites/wires/overlay_tile.png":
-{
- "frame": {"x":955,"y":3,"w":64,"h":64},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
- "sourceSize": {"w":64,"h":64}
-},
-"sprites/wires/sets/color_cross.png":
-{
- "frame": {"x":3,"y":774,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/color_forward.png":
-{
- "frame": {"x":743,"y":203,"w":12,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/color_split.png":
-{
- "frame": {"x":821,"y":994,"w":96,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/color_turn.png":
-{
- "frame": {"x":955,"y":222,"w":54,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/conflict_cross.png":
-{
- "frame": {"x":103,"y":774,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/conflict_forward.png":
-{
- "frame": {"x":303,"y":832,"w":12,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/conflict_split.png":
-{
- "frame": {"x":721,"y":1049,"w":96,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/conflict_turn.png":
-{
- "frame": {"x":955,"y":280,"w":54,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/regular_cross.png":
-{
- "frame": {"x":829,"y":894,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/regular_forward.png":
-{
- "frame": {"x":743,"y":103,"w":12,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/regular_split.png":
-{
- "frame": {"x":621,"y":1010,"w":96,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/regular_turn.png":
-{
- "frame": {"x":955,"y":164,"w":54,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/shape_cross.png":
-{
- "frame": {"x":203,"y":774,"w":96,"h":96},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/shape_forward.png":
-{
- "frame": {"x":319,"y":832,"w":12,"h":96},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/shape_split.png":
-{
- "frame": {"x":621,"y":1068,"w":96,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/sets/shape_turn.png":
-{
- "frame": {"x":955,"y":338,"w":54,"h":54},
- "rotated": false,
- "trimmed": true,
- "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
- "sourceSize": {"w":96,"h":96}
-},
-"sprites/wires/wires_preview.png":
-{
- "frame": {"x":693,"y":887,"w":32,"h":32},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
- "sourceSize": {"w":32,"h":32}
-}},
-"meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "1.0",
- "image": "atlas0_mq.png",
- "format": "RGBA8888",
- "size": {"w":1024,"h":2048},
- "scale": "0.5",
- "smartupdate": "$TexturePacker:SmartUpdate:876f0711b44fa7bbab8d2539e9651766:ff01f850e086ef31c114b036c3a32e6d:908b89f5ca8ff73e331a35a3b14d0604$"
-}
-}
+{"frames": {
+
+"sprites/belt/built/forward_0.png":
+{
+ "frame": {"x":943,"y":842,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_1.png":
+{
+ "frame": {"x":439,"y":803,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_2.png":
+{
+ "frame": {"x":758,"y":1549,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_3.png":
+{
+ "frame": {"x":658,"y":1555,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_4.png":
+{
+ "frame": {"x":556,"y":1580,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_5.png":
+{
+ "frame": {"x":459,"y":1617,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_6.png":
+{
+ "frame": {"x":359,"y":1647,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_7.png":
+{
+ "frame": {"x":268,"y":1671,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_8.png":
+{
+ "frame": {"x":176,"y":1749,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_9.png":
+{
+ "frame": {"x":85,"y":1821,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_10.png":
+{
+ "frame": {"x":94,"y":1721,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_11.png":
+{
+ "frame": {"x":3,"y":1737,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_12.png":
+{
+ "frame": {"x":859,"y":1500,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/forward_13.png":
+{
+ "frame": {"x":941,"y":1500,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_0.png":
+{
+ "frame": {"x":495,"y":1250,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_1.png":
+{
+ "frame": {"x":394,"y":1274,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_2.png":
+{
+ "frame": {"x":789,"y":1185,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_3.png":
+{
+ "frame": {"x":880,"y":1227,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_4.png":
+{
+ "frame": {"x":789,"y":1276,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_5.png":
+{
+ "frame": {"x":690,"y":1282,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_6.png":
+{
+ "frame": {"x":586,"y":1303,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_7.png":
+{
+ "frame": {"x":485,"y":1341,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_8.png":
+{
+ "frame": {"x":386,"y":1365,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_9.png":
+{
+ "frame": {"x":286,"y":1389,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_10.png":
+{
+ "frame": {"x":295,"y":1298,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_11.png":
+{
+ "frame": {"x":195,"y":1376,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_12.png":
+{
+ "frame": {"x":99,"y":1448,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/left_13.png":
+{
+ "frame": {"x":3,"y":1464,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_0.png":
+{
+ "frame": {"x":190,"y":1467,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_1.png":
+{
+ "frame": {"x":94,"y":1539,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_2.png":
+{
+ "frame": {"x":576,"y":1394,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_3.png":
+{
+ "frame": {"x":477,"y":1432,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_4.png":
+{
+ "frame": {"x":377,"y":1456,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_5.png":
+{
+ "frame": {"x":281,"y":1480,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_6.png":
+{
+ "frame": {"x":185,"y":1558,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_7.png":
+{
+ "frame": {"x":94,"y":1630,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_8.png":
+{
+ "frame": {"x":3,"y":1646,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_9.png":
+{
+ "frame": {"x":872,"y":1409,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_10.png":
+{
+ "frame": {"x":3,"y":1555,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_11.png":
+{
+ "frame": {"x":880,"y":1318,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_12.png":
+{
+ "frame": {"x":781,"y":1367,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/belt/built/right_13.png":
+{
+ "frame": {"x":677,"y":1373,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/belt_left.png":
+{
+ "frame": {"x":768,"y":1458,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/belt_right.png":
+{
+ "frame": {"x":667,"y":1464,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/belt_top.png":
+{
+ "frame": {"x":3,"y":1837,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/constant_signal.png":
+{
+ "frame": {"x":949,"y":396,"w":71,"h":85},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":13,"y":0,"w":71,"h":85},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/cutter-quad.png":
+{
+ "frame": {"x":373,"y":103,"w":366,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":366,"h":96},
+ "sourceSize": {"w":384,"h":96}
+},
+"sprites/blueprints/cutter.png":
+{
+ "frame": {"x":745,"y":594,"w":172,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":172,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/display.png":
+{
+ "frame": {"x":568,"y":1485,"w":86,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":5,"y":5,"w":86,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/filter.png":
+{
+ "frame": {"x":569,"y":303,"w":180,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":10,"y":0,"w":180,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/lever.png":
+{
+ "frame": {"x":946,"y":752,"w":75,"h":86},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":3,"w":75,"h":86},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/logic_gate-not.png":
+{
+ "frame": {"x":372,"y":1547,"w":83,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":7,"y":0,"w":83,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/logic_gate-or.png":
+{
+ "frame": {"x":3,"y":874,"w":96,"h":82},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":82},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/logic_gate-transistor.png":
+{
+ "frame": {"x":449,"y":703,"w":68,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/logic_gate-xor.png":
+{
+ "frame": {"x":3,"y":674,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/logic_gate.png":
+{
+ "frame": {"x":521,"y":999,"w":96,"h":89},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":89},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/miner-chainable.png":
+{
+ "frame": {"x":929,"y":942,"w":92,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/miner.png":
+{
+ "frame": {"x":298,"y":1198,"w":92,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/mixer.png":
+{
+ "frame": {"x":3,"y":474,"w":175,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":8,"y":0,"w":175,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/painter-double.png":
+{
+ "frame": {"x":759,"y":3,"w":192,"h":192},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":192},
+ "sourceSize": {"w":192,"h":192}
+},
+"sprites/blueprints/painter-mirrored.png":
+{
+ "frame": {"x":373,"y":303,"w":192,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/painter-quad.png":
+{
+ "frame": {"x":3,"y":3,"w":374,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":374,"h":96},
+ "sourceSize": {"w":384,"h":96}
+},
+"sprites/blueprints/painter.png":
+{
+ "frame": {"x":753,"y":394,"w":192,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/rotater-ccw.png":
+{
+ "frame": {"x":103,"y":674,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/rotater-fl.png":
+{
+ "frame": {"x":203,"y":990,"w":95,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/rotater.png":
+{
+ "frame": {"x":203,"y":674,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/splitter-compact-inverse.png":
+{
+ "frame": {"x":202,"y":1090,"w":95,"h":93},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":95,"h":93},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":102,"y":1091,"w":95,"h":93},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":95,"h":93},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/splitter-compact-merge.png":
+{
+ "frame": {"x":401,"y":1098,"w":93,"h":93},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":1,"w":93,"h":93},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/splitter-compact.png":
+{
+ "frame": {"x":301,"y":1101,"w":93,"h":93},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":1,"w":93,"h":93},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/splitter.png":
+{
+ "frame": {"x":3,"y":574,"w":171,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/stacker.png":
+{
+ "frame": {"x":182,"y":474,"w":175,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":8,"y":0,"w":175,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/blueprints/trash-storage.png":
+{
+ "frame": {"x":528,"y":603,"w":167,"h":192},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":167,"h":192},
+ "sourceSize": {"w":192,"h":192}
+},
+"sprites/blueprints/trash.png":
+{
+ "frame": {"x":349,"y":703,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":597,"y":1116,"w":93,"h":84},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":12,"w":93,"h":84},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/underground_belt_entry.png":
+{
+ "frame": {"x":498,"y":1171,"w":93,"h":75},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":21,"w":93,"h":75},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":499,"y":1092,"w":94,"h":75},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":94,"h":75},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/underground_belt_exit.png":
+{
+ "frame": {"x":398,"y":1195,"w":93,"h":75},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":93,"h":75},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/virtual_processor-analyzer.png":
+{
+ "frame": {"x":521,"y":799,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/virtual_processor-rotater.png":
+{
+ "frame": {"x":276,"y":1571,"w":79,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":79,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":621,"y":1023,"w":96,"h":89},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":89},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/virtual_processor-unstacker.png":
+{
+ "frame": {"x":729,"y":890,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/virtual_processor.png":
+{
+ "frame": {"x":921,"y":1042,"w":96,"h":94},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":96,"h":94},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire-cross.png":
+{
+ "frame": {"x":829,"y":894,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire-split.png":
+{
+ "frame": {"x":103,"y":874,"w":96,"h":55},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":41,"w":96,"h":55},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire-turn.png":
+{
+ "frame": {"x":955,"y":105,"w":55,"h":55},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":41,"y":41,"w":55,"h":55},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire.png":
+{
+ "frame": {"x":699,"y":603,"w":14,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":41,"y":0,"w":14,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire_tunnel-coating.png":
+{
+ "frame": {"x":921,"y":594,"w":23,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":36,"y":2,"w":23,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/blueprints/wire_tunnel.png":
+{
+ "frame": {"x":201,"y":1187,"w":93,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":93,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/belt_left.png":
+{
+ "frame": {"x":495,"y":1250,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/belt_right.png":
+{
+ "frame": {"x":190,"y":1467,"w":87,"h":87},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/belt_top.png":
+{
+ "frame": {"x":943,"y":842,"w":78,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/constant_signal.png":
+{
+ "frame": {"x":949,"y":485,"w":70,"h":85},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":13,"y":0,"w":70,"h":85},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/cutter-quad.png":
+{
+ "frame": {"x":373,"y":203,"w":366,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":366,"h":96},
+ "sourceSize": {"w":384,"h":96}
+},
+"sprites/buildings/cutter.png":
+{
+ "frame": {"x":178,"y":574,"w":171,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/display.png":
+{
+ "frame": {"x":468,"y":1523,"w":84,"h":90},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":6,"w":84,"h":90},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/filter.png":
+{
+ "frame": {"x":569,"y":403,"w":179,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":179,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/hub.png":
+{
+ "frame": {"x":3,"y":103,"w":366,"h":367},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":10,"w":366,"h":367},
+ "sourceSize": {"w":384,"h":384}
+},
+"sprites/buildings/lever.png":
+{
+ "frame": {"x":948,"y":574,"w":73,"h":85},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":12,"y":3,"w":73,"h":85},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/logic_gate-not.png":
+{
+ "frame": {"x":435,"y":903,"w":82,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":8,"y":0,"w":82,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/logic_gate-or.png":
+{
+ "frame": {"x":921,"y":1140,"w":96,"h":83},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":83},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/logic_gate-transistor.png":
+{
+ "frame": {"x":621,"y":799,"w":68,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/logic_gate-xor.png":
+{
+ "frame": {"x":821,"y":994,"w":96,"h":95},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":95},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/logic_gate.png":
+{
+ "frame": {"x":821,"y":1093,"w":96,"h":88},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":88},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/miner-chainable.png":
+{
+ "frame": {"x":694,"y":1183,"w":91,"h":95},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/miner.png":
+{
+ "frame": {"x":595,"y":1204,"w":91,"h":95},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/mixer.png":
+{
+ "frame": {"x":361,"y":503,"w":174,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":174,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/painter-double.png":
+{
+ "frame": {"x":759,"y":199,"w":192,"h":191},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":191},
+ "sourceSize": {"w":192,"h":192}
+},
+"sprites/buildings/painter-mirrored.png":
+{
+ "frame": {"x":373,"y":403,"w":192,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/painter-quad.png":
+{
+ "frame": {"x":381,"y":3,"w":374,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":374,"h":96},
+ "sourceSize": {"w":384,"h":96}
+},
+"sprites/buildings/painter.png":
+{
+ "frame": {"x":752,"y":494,"w":192,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/rotater-ccw.png":
+{
+ "frame": {"x":103,"y":991,"w":95,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/rotater-fl.png":
+{
+ "frame": {"x":3,"y":1018,"w":95,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/rotater.png":
+{
+ "frame": {"x":302,"y":1001,"w":95,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/splitter-compact-inverse.png":
+{
+ "frame": {"x":401,"y":1003,"w":94,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":94,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/splitter-compact-merge-inverse.png":
+{
+ "frame": {"x":3,"y":1118,"w":95,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":95,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/splitter-compact-merge.png":
+{
+ "frame": {"x":102,"y":1188,"w":93,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":2,"w":93,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/splitter-compact.png":
+{
+ "frame": {"x":3,"y":1213,"w":93,"h":91},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":2,"w":93,"h":91},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/splitter.png":
+{
+ "frame": {"x":353,"y":603,"w":171,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":171,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/stacker.png":
+{
+ "frame": {"x":539,"y":503,"w":174,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":174,"h":96},
+ "sourceSize": {"w":192,"h":96}
+},
+"sprites/buildings/trash-storage.png":
+{
+ "frame": {"x":736,"y":694,"w":166,"h":192},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":14,"y":0,"w":166,"h":192},
+ "sourceSize": {"w":192,"h":192}
+},
+"sprites/buildings/trash.png":
+{
+ "frame": {"x":3,"y":774,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/underground_belt_entry-tier2.png":
+{
+ "frame": {"x":100,"y":1283,"w":92,"h":83},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":13,"w":92,"h":83},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/underground_belt_entry.png":
+{
+ "frame": {"x":3,"y":1308,"w":92,"h":74},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":22,"w":92,"h":74},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/underground_belt_exit-tier2.png":
+{
+ "frame": {"x":99,"y":1370,"w":92,"h":74},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/underground_belt_exit.png":
+{
+ "frame": {"x":3,"y":1386,"w":92,"h":74},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/virtual_processor-analyzer.png":
+{
+ "frame": {"x":103,"y":774,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/virtual_processor-rotater.png":
+{
+ "frame": {"x":185,"y":1649,"w":79,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":9,"y":0,"w":79,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/virtual_processor-shapecompare.png":
+{
+ "frame": {"x":721,"y":1090,"w":96,"h":89},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":89},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/virtual_processor-unstacker.png":
+{
+ "frame": {"x":203,"y":774,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/virtual_processor.png":
+{
+ "frame": {"x":335,"y":903,"w":96,"h":94},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":96,"h":94},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire-cross.png":
+{
+ "frame": {"x":339,"y":803,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire-split.png":
+{
+ "frame": {"x":203,"y":874,"w":96,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire-turn.png":
+{
+ "frame": {"x":955,"y":164,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire.png":
+{
+ "frame": {"x":743,"y":103,"w":12,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire_tunnel-coating.png":
+{
+ "frame": {"x":921,"y":689,"w":21,"h":90},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":37,"y":3,"w":21,"h":90},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/buildings/wire_tunnel.png":
+{
+ "frame": {"x":199,"y":1282,"w":92,"h":90},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":3,"w":92,"h":90},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/debug/acceptor_slot.png":
+{
+ "frame": {"x":1013,"y":164,"w":8,"h":8},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":8,"h":8},
+ "sourceSize": {"w":8,"h":8}
+},
+"sprites/debug/ejector_slot.png":
+{
+ "frame": {"x":1013,"y":176,"w":8,"h":8},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":8,"h":8},
+ "sourceSize": {"w":8,"h":8}
+},
+"sprites/misc/hub_direction_indicator.png":
+{
+ "frame": {"x":693,"y":851,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/misc/slot_bad_arrow.png":
+{
+ "frame": {"x":717,"y":605,"w":24,"h":24},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":24,"h":24},
+ "sourceSize": {"w":26,"h":26}
+},
+"sprites/misc/slot_good_arrow.png":
+{
+ "frame": {"x":717,"y":575,"w":24,"h":26},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":24,"h":26},
+ "sourceSize": {"w":26,"h":26}
+},
+"sprites/misc/storage_overlay.png":
+{
+ "frame": {"x":955,"y":71,"w":60,"h":30},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":60,"h":30},
+ "sourceSize": {"w":60,"h":30}
+},
+"sprites/misc/waypoint.png":
+{
+ "frame": {"x":717,"y":539,"w":26,"h":32},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":26,"h":32},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/boolean_false.png":
+{
+ "frame": {"x":717,"y":633,"w":21,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":3,"w":21,"h":28},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/boolean_true.png":
+{
+ "frame": {"x":717,"y":665,"w":15,"h":28},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":7,"y":3,"w":15,"h":28},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/display/blue.png":
+{
+ "frame": {"x":699,"y":703,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/cyan.png":
+{
+ "frame": {"x":699,"y":740,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/green.png":
+{
+ "frame": {"x":699,"y":777,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/purple.png":
+{
+ "frame": {"x":906,"y":783,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/red.png":
+{
+ "frame": {"x":906,"y":820,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/white.png":
+{
+ "frame": {"x":906,"y":857,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/display/yellow.png":
+{
+ "frame": {"x":693,"y":814,"w":33,"h":33},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33},
+ "sourceSize": {"w":33,"h":33}
+},
+"sprites/wires/lever_on.png":
+{
+ "frame": {"x":948,"y":663,"w":73,"h":85},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":12,"y":3,"w":73,"h":85},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/logical_acceptor.png":
+{
+ "frame": {"x":303,"y":674,"w":42,"h":71},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":28,"y":0,"w":42,"h":71},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/logical_ejector.png":
+{
+ "frame": {"x":303,"y":749,"w":41,"h":45},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":29,"y":0,"w":41,"h":45},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/network_conflict.png":
+{
+ "frame": {"x":303,"y":798,"w":32,"h":30},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":32,"h":30},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/network_empty.png":
+{
+ "frame": {"x":717,"y":503,"w":28,"h":32},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":0,"w":28,"h":32},
+ "sourceSize": {"w":32,"h":32}
+},
+"sprites/wires/overlay_tile.png":
+{
+ "frame": {"x":955,"y":3,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"sprites/wires/sets/color_cross.png":
+{
+ "frame": {"x":521,"y":899,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/color_forward.png":
+{
+ "frame": {"x":743,"y":203,"w":12,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/color_split.png":
+{
+ "frame": {"x":203,"y":932,"w":96,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/color_turn.png":
+{
+ "frame": {"x":955,"y":222,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/conflict_cross.png":
+{
+ "frame": {"x":621,"y":923,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/conflict_forward.png":
+{
+ "frame": {"x":303,"y":832,"w":12,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/conflict_split.png":
+{
+ "frame": {"x":103,"y":933,"w":96,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/conflict_turn.png":
+{
+ "frame": {"x":955,"y":280,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/regular_cross.png":
+{
+ "frame": {"x":339,"y":803,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/regular_forward.png":
+{
+ "frame": {"x":743,"y":103,"w":12,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/regular_split.png":
+{
+ "frame": {"x":203,"y":874,"w":96,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/regular_turn.png":
+{
+ "frame": {"x":955,"y":164,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/shape_cross.png":
+{
+ "frame": {"x":721,"y":990,"w":96,"h":96},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/shape_forward.png":
+{
+ "frame": {"x":319,"y":832,"w":12,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/shape_split.png":
+{
+ "frame": {"x":3,"y":960,"w":96,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/sets/shape_turn.png":
+{
+ "frame": {"x":955,"y":338,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":42,"w":54,"h":54},
+ "sourceSize": {"w":96,"h":96}
+},
+"sprites/wires/wires_preview.png":
+{
+ "frame": {"x":693,"y":887,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32}
+}},
+"meta": {
+ "app": "https://www.codeandweb.com/texturepacker",
+ "version": "1.0",
+ "image": "atlas0_mq.png",
+ "format": "RGBA8888",
+ "size": {"w":1024,"h":2048},
+ "scale": "0.5",
+ "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$"
+}
+}
diff --git a/res_built/atlas/atlas0_mq.png b/res_built/atlas/atlas0_mq.png
index e05b73e3..5f962bcb 100644
Binary files a/res_built/atlas/atlas0_mq.png and b/res_built/atlas/atlas0_mq.png differ
diff --git a/res_raw/atlas.tps b/res_raw/atlas.tps
index 28809d93..d2332b2d 100644
--- a/res_raw/atlas.tps
+++ b/res_raw/atlas.tps
@@ -277,6 +277,12 @@
sprites/blueprints/underground_belt_entry.png
sprites/blueprints/underground_belt_exit-tier2.png
sprites/blueprints/underground_belt_exit.png
+ sprites/blueprints/virtual_processor-analyzer.png
+ sprites/blueprints/virtual_processor-rotater.png
+ sprites/blueprints/virtual_processor-shapecompare.png
+ sprites/blueprints/virtual_processor-unstacker.png
+ sprites/blueprints/virtual_processor.png
+ sprites/blueprints/wire_tunnel-coating.png
sprites/blueprints/wire_tunnel.png
sprites/buildings/constant_signal.png
sprites/buildings/display.png
@@ -295,6 +301,12 @@
sprites/buildings/underground_belt_entry.png
sprites/buildings/underground_belt_exit-tier2.png
sprites/buildings/underground_belt_exit.png
+ sprites/buildings/virtual_processor-analyzer.png
+ sprites/buildings/virtual_processor-rotater.png
+ sprites/buildings/virtual_processor-shapecompare.png
+ sprites/buildings/virtual_processor-unstacker.png
+ sprites/buildings/virtual_processor.png
+ sprites/buildings/wire_tunnel-coating.png
sprites/buildings/wire_tunnel.png
sprites/wires/lever_on.png
sprites/wires/sets/color_cross.png
@@ -533,6 +545,8 @@
sprites/wires/boolean_false.png
sprites/wires/boolean_true.png
+ sprites/wires/network_conflict.png
+ sprites/wires/network_empty.png
sprites/wires/wires_preview.png
pivotPoint
diff --git a/res_raw/sprites/blueprints/lever.png b/res_raw/sprites/blueprints/lever.png
index 7190b1f2..84ef5f03 100644
Binary files a/res_raw/sprites/blueprints/lever.png and b/res_raw/sprites/blueprints/lever.png differ
diff --git a/res_raw/sprites/blueprints/splitter-compact-merge-inverse.png b/res_raw/sprites/blueprints/splitter-compact-merge-inverse.png
new file mode 100644
index 00000000..90a429c0
Binary files /dev/null and b/res_raw/sprites/blueprints/splitter-compact-merge-inverse.png differ
diff --git a/res_raw/sprites/blueprints/splitter-compact-merge.png b/res_raw/sprites/blueprints/splitter-compact-merge.png
new file mode 100644
index 00000000..93488fd9
Binary files /dev/null and b/res_raw/sprites/blueprints/splitter-compact-merge.png differ
diff --git a/res_raw/sprites/blueprints/trash-storage.png b/res_raw/sprites/blueprints/trash-storage.png
index a4e6122f..cc719a5a 100644
Binary files a/res_raw/sprites/blueprints/trash-storage.png and b/res_raw/sprites/blueprints/trash-storage.png differ
diff --git a/res_raw/sprites/blueprints/virtual_processor-analyzer.png b/res_raw/sprites/blueprints/virtual_processor-analyzer.png
new file mode 100644
index 00000000..9aa5d298
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor-analyzer.png differ
diff --git a/res_raw/sprites/blueprints/virtual_processor-rotater.png b/res_raw/sprites/blueprints/virtual_processor-rotater.png
new file mode 100644
index 00000000..d50da022
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor-rotater.png differ
diff --git a/res_raw/sprites/blueprints/virtual_processor-shapecompare.png b/res_raw/sprites/blueprints/virtual_processor-shapecompare.png
new file mode 100644
index 00000000..6fe8abfb
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor-shapecompare.png differ
diff --git a/res_raw/sprites/blueprints/virtual_processor-unstacker.png b/res_raw/sprites/blueprints/virtual_processor-unstacker.png
new file mode 100644
index 00000000..a9ce56dd
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor-unstacker.png differ
diff --git a/res_raw/sprites/blueprints/virtual_processor.png b/res_raw/sprites/blueprints/virtual_processor.png
new file mode 100644
index 00000000..230c4a23
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor.png differ
diff --git a/res_raw/sprites/buildings/lever.png b/res_raw/sprites/buildings/lever.png
index eda46bfa..bdb6c8c8 100644
Binary files a/res_raw/sprites/buildings/lever.png and b/res_raw/sprites/buildings/lever.png differ
diff --git a/res_raw/sprites/buildings/splitter-compact-merge-inverse.png b/res_raw/sprites/buildings/splitter-compact-merge-inverse.png
new file mode 100644
index 00000000..704663c0
Binary files /dev/null and b/res_raw/sprites/buildings/splitter-compact-merge-inverse.png differ
diff --git a/res_raw/sprites/buildings/splitter-compact-merge.png b/res_raw/sprites/buildings/splitter-compact-merge.png
new file mode 100644
index 00000000..d5a72dd4
Binary files /dev/null and b/res_raw/sprites/buildings/splitter-compact-merge.png differ
diff --git a/res_raw/sprites/buildings/trash-storage.png b/res_raw/sprites/buildings/trash-storage.png
index 57b1519e..39a4df1f 100644
Binary files a/res_raw/sprites/buildings/trash-storage.png and b/res_raw/sprites/buildings/trash-storage.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor-analyzer.png b/res_raw/sprites/buildings/virtual_processor-analyzer.png
new file mode 100644
index 00000000..f30c8d3b
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor-analyzer.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor-rotater.png b/res_raw/sprites/buildings/virtual_processor-rotater.png
new file mode 100644
index 00000000..8c9dcbdc
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor-rotater.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor-shapecompare.png b/res_raw/sprites/buildings/virtual_processor-shapecompare.png
new file mode 100644
index 00000000..dad1e4bf
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor-shapecompare.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor-unstacker.png b/res_raw/sprites/buildings/virtual_processor-unstacker.png
new file mode 100644
index 00000000..a51edf6d
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor-unstacker.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor.png b/res_raw/sprites/buildings/virtual_processor.png
new file mode 100644
index 00000000..87093df4
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor.png differ
diff --git a/res_raw/sprites/wires/lever_on.png b/res_raw/sprites/wires/lever_on.png
index 6e5101de..f6a04e16 100644
Binary files a/res_raw/sprites/wires/lever_on.png and b/res_raw/sprites/wires/lever_on.png differ
diff --git a/src/css/common.scss b/src/css/common.scss
index 368af699..c768b117 100644
--- a/src/css/common.scss
+++ b/src/css/common.scss
@@ -664,7 +664,7 @@ iframe {
user-select: all;
}
-// Steam overlay fiy
+// Steam overlay fix
#steamOverlayCanvasFix {
position: fixed;
top: 0px;
diff --git a/src/css/icons.scss b/src/css/icons.scss
index 38c32fd5..85054264 100644
--- a/src/css/icons.scss
+++ b/src/css/icons.scss
@@ -1,38 +1,38 @@
-$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
- constant_signal, logic_gate, lever, filter, wire_tunnel, display;
-
-@each $building in $buildings {
- [data-icon="building_icons/#{$building}.png"] {
- background-image: uiResource("res/ui/building_icons/#{$building}.png") !important;
- }
-}
-
-$buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt,
- underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl,
- stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage;
-@each $building in $buildingsAndVariants {
- [data-icon="building_tutorials/#{$building}.png"] {
- background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important;
- }
-}
-
-// Special case
-[data-icon="building_tutorials/painter-mirrored.png"] {
- background-image: uiResource("res/ui/building_tutorials/painter.png") !important;
-}
-
-$icons: notification_saved, notification_success, notification_upgrade;
-@each $icon in $icons {
- [data-icon="icons/#{$icon}.png"] {
- background-image: uiResource("res/ui/icons/#{$icon}.png") !important;
- }
-}
-
-$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
- th, hu, pl, ja, kor, no, pt-PT;
-
-@each $language in $languages {
- [data-languageicon="#{$language}"] {
- background-image: uiResource("languages/#{$language}.svg") !important;
- }
-}
+$buildings: belt, cutter, miner, mixer, painter, rotater, splitter, stacker, trash, underground_belt, wire,
+ constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor;
+
+@each $building in $buildings {
+ [data-icon="building_icons/#{$building}.png"] {
+ background-image: uiResource("res/ui/building_icons/#{$building}.png") !important;
+ }
+}
+
+$buildingsAndVariants: belt, splitter, splitter-compact, splitter-compact-inverse, underground_belt,
+ underground_belt-tier2, miner, miner-chainable, cutter, cutter-quad, rotater, rotater-ccw, rotater-fl,
+ stacker, mixer, painter, painter-double, painter-quad, trash, trash-storage;
+@each $building in $buildingsAndVariants {
+ [data-icon="building_tutorials/#{$building}.png"] {
+ background-image: uiResource("res/ui/building_tutorials/#{$building}.png") !important;
+ }
+}
+
+// Special case
+[data-icon="building_tutorials/painter-mirrored.png"] {
+ background-image: uiResource("res/ui/building_tutorials/painter.png") !important;
+}
+
+$icons: notification_saved, notification_success, notification_upgrade;
+@each $icon in $icons {
+ [data-icon="icons/#{$icon}.png"] {
+ background-image: uiResource("res/ui/icons/#{$icon}.png") !important;
+ }
+}
+
+$languages: en, de, cs, da, et, es-419, fr, it, pt-BR, sv, tr, el, ru, uk, zh-TW, zh-CN, nb, mt-MT, ar, nl, vi,
+ th, hu, pl, ja, kor, no, pt-PT;
+
+@each $language in $languages {
+ [data-languageicon="#{$language}"] {
+ background-image: uiResource("languages/#{$language}.svg") !important;
+ }
+}
diff --git a/src/css/ingame_hud/statistics.scss b/src/css/ingame_hud/statistics.scss
index e53a80d1..12b8c1aa 100644
--- a/src/css/ingame_hud/statistics.scss
+++ b/src/css/ingame_hud/statistics.scss
@@ -14,20 +14,37 @@
@include S(padding, 1px, 10px);
border: 0;
box-shadow: none;
- border-radius: 0;
@include IncreasedClickArea(1px);
@include S(min-width, 30px);
color: #fff;
opacity: 0.25;
- @include S(margin-left, 1px);
+ @include S(border-radius, $globalBorderRadius);
+
+ border-radius: 0;
+ &:first-child {
+ @include S(border-top-left-radius, $globalBorderRadius);
+ @include S(border-bottom-left-radius, $globalBorderRadius);
+ }
+
+ &:last-child {
+ @include S(border-top-right-radius, $globalBorderRadius);
+ @include S(border-bottom-right-radius, $globalBorderRadius);
+ }
&.displayIcons,
- &.displayDetailed {
+ &.displayDetailed,
+ &.displaySorted {
background: uiResource("icons/display_list.png") center center / #{D(15px)} no-repeat;
&.displayIcons {
background-image: uiResource("icons/display_icons.png");
background-size: #{D(11.5px)};
}
+ &.displaySorted {
+ background-image: uiResource("icons/display_sorted.png");
+ background-size: #{D(11.5px)};
+ margin-right: 4px;
+ @include S(padding, 1px, 0);
+ }
}
background-color: #44484a !important;
@@ -80,10 +97,7 @@
background: #f4f4f4;
@include S(margin-bottom, 4px);
display: grid;
-
- @include DarkThemeOverride {
- background: #222428;
- }
+ @include S(border-radius, $globalBorderRadius);
grid-template-columns: 1fr auto;
@include S(padding, 5px);
@@ -91,6 +105,18 @@
margin-bottom: 0;
}
+ &.pinned {
+ background: #e3e5e9;
+ }
+
+ @include DarkThemeOverride {
+ background: #222428;
+
+ &.pinned {
+ background: darken(#222428, 10);
+ }
+ }
+
canvas.icon {
grid-column: 1 / 2;
grid-row: 1 / 2;
@@ -100,7 +126,6 @@
.counter {
@include SuperSmallText;
-
@include S(padding, 0, 3px);
}
}
@@ -109,6 +134,7 @@
.dialogInner {
&[data-displaymode="detailed"] .displayDetailed,
&[data-displaymode="icons"] .displayIcons,
+ &[data-sorted="true"] .displaySorted,
&[data-datasource="produced"] .modeProduced,
&[data-datasource="delivered"] .modeDelivered,
&[data-datasource="stored"] .modeStored {
@@ -132,7 +158,6 @@
.counter {
grid-column: 1 / 2;
grid-row: 2 / 3;
- background: rgba(0, 10, 20, 0.05);
justify-self: end;
}
}
diff --git a/src/css/main.scss b/src/css/main.scss
index fa3ed356..5835a84d 100644
--- a/src/css/main.scss
+++ b/src/css/main.scss
@@ -1,124 +1,124 @@
-// Control here whether to inline all resources or instead load them
-@function uiResource($pth) {
- @if (str-index($string: $pth, $substring: ".noinline")) {
- @return resolve($pth);
- }
- @return inline($pth);
-}
-
-@import "icons";
-@import "trigonometry";
-@import "material_colors";
-@import "dynamic_ui";
-@import "variables";
-
-@import "mixins";
-@import "common";
-@import "animations";
-@import "game_state";
-@import "application_error";
-@import "textual_game_state";
-@import "adinplay";
-
-@import "states/preload";
-@import "states/main_menu";
-@import "states/ingame";
-@import "states/keybindings";
-@import "states/settings";
-@import "states/about";
-@import "states/mobile_warning";
-@import "states/changelog";
-
-@import "ingame_hud/buildings_toolbar";
-@import "ingame_hud/building_placer";
-@import "ingame_hud/beta_overlay";
-@import "ingame_hud/keybindings_overlay";
-@import "ingame_hud/unlock_notification";
-@import "ingame_hud/shop";
-@import "ingame_hud/game_menu";
-@import "ingame_hud/dialogs";
-@import "ingame_hud/vignette_overlay";
-@import "ingame_hud/statistics";
-@import "ingame_hud/pinned_shapes";
-@import "ingame_hud/notifications";
-@import "ingame_hud/settings_menu";
-@import "ingame_hud/debug_info";
-@import "ingame_hud/entity_debugger";
-@import "ingame_hud/tutorial_hints";
-@import "ingame_hud/watermark";
-@import "ingame_hud/blueprint_placer";
-@import "ingame_hud/waypoints";
-@import "ingame_hud/interactive_tutorial";
-@import "ingame_hud/color_blind_helper";
-@import "ingame_hud/shape_viewer";
-@import "ingame_hud/sandbox_controller";
-
-// prettier-ignore
-$elements:
-// Base
-ingame_Canvas,
-ingame_VignetteOverlay,
-
-// Ingame overlays
-ingame_HUD_Waypoints,
-ingame_HUD_PlacementHints,
-ingame_HUD_PlacerVariants,
-
-// Regular hud
-ingame_HUD_PinnedShapes,
-ingame_HUD_GameMenu,
-ingame_HUD_KeybindingOverlay,
-ingame_HUD_Notifications,
-ingame_HUD_DebugInfo,
-ingame_HUD_EntityDebugger,
-ingame_HUD_InteractiveTutorial,
-ingame_HUD_TutorialHints,
-ingame_HUD_buildings_toolbar,
-ingame_HUD_wires_toolbar,
-ingame_HUD_BlueprintPlacer,
-ingame_HUD_Waypoints_Hint,
-ingame_HUD_Watermark,
-ingame_HUD_ColorBlindBelowTileHelper,
-ingame_HUD_SandboxController,
-
-// Overlays
-ingame_HUD_BetaOverlay,
-
-// Dialogs
-ingame_HUD_Shop,
-ingame_HUD_Statistics,
-ingame_HUD_ShapeViewer,
-ingame_HUD_UnlockNotification,
-ingame_HUD_SettingsMenu,
-ingame_HUD_ModalDialogs;
-
-$zindex: 100;
-
-@each $elem in $elements {
- ##{$elem} {
- z-index: $zindex;
- }
-
- $zindex: $zindex + 10;
-}
-
-body.uiHidden {
- #ingame_HUD_buildings_toolbar,
- #ingame_HUD_PlacementHints,
- #ingame_HUD_GameMenu,
- #ingame_HUD_PinnedShapes,
- #ingame_HUD_Notifications,
- #ingame_HUD_TutorialHints,
- #ingame_HUD_Waypoints,
- #ingame_HUD_Waypoints_Hint {
- display: none !important;
- }
-}
-
-body.modalDialogActive,
-body.externalAdOpen,
-body.ingameDialogOpen {
- > *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs):not(.noBlur) {
- // filter: blur(5px) !important;
- }
-}
+// Control here whether to inline all resources or instead load them
+@function uiResource($pth) {
+ @if (str-index($string: $pth, $substring: ".noinline")) {
+ @return resolve($pth);
+ }
+ @return inline($pth);
+}
+
+@import "icons";
+@import "trigonometry";
+@import "material_colors";
+@import "dynamic_ui";
+@import "variables";
+
+@import "mixins";
+@import "common";
+@import "animations";
+@import "game_state";
+@import "application_error";
+@import "textual_game_state";
+@import "adinplay";
+
+@import "states/preload";
+@import "states/main_menu";
+@import "states/ingame";
+@import "states/keybindings";
+@import "states/settings";
+@import "states/about";
+@import "states/mobile_warning";
+@import "states/changelog";
+
+@import "ingame_hud/buildings_toolbar";
+@import "ingame_hud/building_placer";
+@import "ingame_hud/beta_overlay";
+@import "ingame_hud/keybindings_overlay";
+@import "ingame_hud/unlock_notification";
+@import "ingame_hud/shop";
+@import "ingame_hud/game_menu";
+@import "ingame_hud/dialogs";
+@import "ingame_hud/vignette_overlay";
+@import "ingame_hud/statistics";
+@import "ingame_hud/pinned_shapes";
+@import "ingame_hud/notifications";
+@import "ingame_hud/settings_menu";
+@import "ingame_hud/debug_info";
+@import "ingame_hud/entity_debugger";
+@import "ingame_hud/tutorial_hints";
+@import "ingame_hud/watermark";
+@import "ingame_hud/blueprint_placer";
+@import "ingame_hud/waypoints";
+@import "ingame_hud/interactive_tutorial";
+@import "ingame_hud/color_blind_helper";
+@import "ingame_hud/shape_viewer";
+@import "ingame_hud/sandbox_controller";
+
+// prettier-ignore
+$elements:
+// Base
+ingame_Canvas,
+ingame_VignetteOverlay,
+
+// Ingame overlays
+ingame_HUD_Waypoints,
+ingame_HUD_PlacementHints,
+ingame_HUD_PlacerVariants,
+
+// Regular hud
+ingame_HUD_PinnedShapes,
+ingame_HUD_GameMenu,
+ingame_HUD_KeybindingOverlay,
+ingame_HUD_Notifications,
+ingame_HUD_DebugInfo,
+ingame_HUD_EntityDebugger,
+ingame_HUD_InteractiveTutorial,
+ingame_HUD_TutorialHints,
+ingame_HUD_buildings_toolbar,
+ingame_HUD_wires_toolbar,
+ingame_HUD_BlueprintPlacer,
+ingame_HUD_Waypoints_Hint,
+ingame_HUD_Watermark,
+ingame_HUD_ColorBlindBelowTileHelper,
+ingame_HUD_SandboxController,
+
+// Overlays
+ingame_HUD_BetaOverlay,
+
+// Dialogs
+ingame_HUD_Shop,
+ingame_HUD_Statistics,
+ingame_HUD_ShapeViewer,
+ingame_HUD_UnlockNotification,
+ingame_HUD_SettingsMenu,
+ingame_HUD_ModalDialogs;
+
+$zindex: 100;
+
+@each $elem in $elements {
+ ##{$elem} {
+ z-index: $zindex;
+ }
+
+ $zindex: $zindex + 10;
+}
+
+body.uiHidden {
+ .ingame_buildingsToolbar,
+ #ingame_HUD_PlacementHints,
+ #ingame_HUD_GameMenu,
+ #ingame_HUD_PinnedShapes,
+ #ingame_HUD_Notifications,
+ #ingame_HUD_TutorialHints,
+ #ingame_HUD_Waypoints,
+ #ingame_HUD_Waypoints_Hint {
+ display: none !important;
+ }
+}
+
+body.modalDialogActive,
+body.externalAdOpen,
+body.ingameDialogOpen {
+ > *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs):not(.noBlur) {
+ // filter: blur(5px) !important;
+ }
+}
diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss
index d61bc9d0..0d0a289e 100644
--- a/src/css/states/main_menu.scss
+++ b/src/css/states/main_menu.scss
@@ -1,507 +1,543 @@
-#state_MainMenuState {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
-
- // background: #aaacb4 center center / cover !important;
- background: #bbc2cf center center / cover !important;
-
- .topButtons {
- position: absolute;
- @include S(top, 20px);
- @include S(right, 20px);
- display: grid;
- grid-auto-flow: column;
- @include S(grid-gap, 15px);
-
- .settingsButton,
- .exitAppButton,
- .languageChoose {
- @include S(width, 25px);
- @include S(height, 25px);
- pointer-events: all;
- cursor: pointer;
- background: uiResource("icons/main_menu_settings.png") center center / contain no-repeat;
- transition: opacity 0.12s ease-in-out;
- @include IncreasedClickArea(2px);
- &:hover {
- opacity: 0.9;
- }
- }
-
- .exitAppButton {
- background-image: uiResource("icons/main_menu_exit.png");
- }
-
- .languageChoose {
- @include S(border-radius, 8px);
- border: solid #222428;
- background-color: #fff;
- @include S(border-width, 2px);
- background-size: cover;
- }
- }
-
- .fullscreenBackgroundVideo {
- // display: none !important;
- z-index: -1;
- position: fixed;
- right: 50%;
- bottom: 50%;
- min-width: 100%;
- min-height: 100%;
-
- opacity: 0;
- display: none;
- transform: translate(50%, 50%);
- filter: blur(D(3px));
-
- $opacity: 0.2;
- &.loaded {
- display: block;
- opacity: $opacity;
-
- @include InlineAnimation(0.1s ease-in-out) {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: $opacity;
- }
- }
- }
- }
-
- .mainWrapper {
- @include S(padding, 0, 10px);
- align-items: start;
- justify-items: center;
-
- @include S(grid-column-gap, 10px);
- display: grid;
- grid-template-columns: 1fr;
-
- &.demo {
- grid-template-columns: 1fr 1fr;
- }
-
- .standaloneBanner {
- background: rgb(255, 234, 245);
- @include S(border-radius, $globalBorderRadius);
- box-sizing: border-box;
- @include S(padding, 15px);
-
- display: flex;
- flex-direction: column;
-
- strong {
- font-weight: bold;
- @include S(margin, 0, 4px);
- }
-
- h3 {
- @include Heading;
- font-weight: bold;
- @include S(margin-bottom, 5px);
- text-transform: uppercase;
- color: $colorRedBright;
- }
-
- p {
- @include Text;
- }
-
- ul {
- @include S(margin-top, 5px);
- @include S(padding-left, 20px);
- li {
- @include Text;
- }
- }
-
- .steamLink {
- width: 100%;
- @include S(height, 40px);
-
- background: uiResource("get_on_steam.png") center center / contain no-repeat;
- overflow: hidden;
- display: block;
- text-indent: -999em;
- cursor: pointer;
- @include S(margin-top, 20px);
- pointer-events: all;
- transition: all 0.12s ease-in;
- transition-property: opacity, transform;
- transform: skewX(-0.5deg);
- &:hover {
- transform: skewX(-1deg) scale(1.02);
- opacity: 0.9;
- }
- }
- }
- }
-
- .logo {
- display: flex;
- flex-grow: 1;
- align-items: center;
- justify-content: center;
-
- flex-direction: column;
- @include S(padding-top, 20px);
- img {
- @include S(width, 350px);
- }
-
- .demoBadge {
- @include S(margin, 10px, 0);
- @include S(width, 100px);
- @include S(height, 30px);
- background: uiResource("demo_badge.png") center center / contain no-repeat;
- display: inline-block;
- }
-
- position: relative;
- .updateLabel {
- position: absolute;
- transform: translateX(50%) rotate(-5deg);
- color: $colorRedBright;
- @include Heading;
- text-transform: uppercase;
- font-weight: bold;
- @include S(right, 40px);
- @include S(bottom, 20px);
-
- @include InlineAnimation(1.3s ease-in-out infinite) {
- 50% {
- transform: translateX(50%) rotate(-7deg) scale(1.1);
- }
- }
-
- @include DarkThemeOverride {
- color: $colorBlueBright;
- }
- }
- }
-
- .betaWarning {
- @include S(width, 400px);
- @include PlainText;
- background: $colorRedBright;
- @include S(padding, 10px);
- @include S(border-radius, $globalBorderRadius);
- color: #fff;
- @include S(margin-top, 10px);
- border: #{D(2px)} solid rgba(0, 10, 20, 0.1);
- }
-
- .sideContainer {
- display: flex;
- flex-direction: column;
- @include S(width, 300px);
-
- .standaloneBanner {
- flex-grow: 1;
- @include S(margin-bottom, 10px);
- }
- }
-
- .mainContainer {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- background: #fafafa;
- @include S(padding, 20px);
- @include S(border-radius, $globalBorderRadius);
- // border: #{D(2px)} solid rgba(0, 10, 20, 0.1);
- height: 100%;
- width: 100%;
- box-sizing: border-box;
-
- .buttons {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .browserWarning {
- @include S(margin-bottom, 10px);
- background-color: $colorRedBright;
- @include PlainText;
- color: #fff;
- @include S(border-radius, $globalBorderRadius);
- @include S(padding, 5px);
- @include S(width, 300px);
- }
-
- .playButton,
- .continueButton {
- @include SuperHeading;
- @include S(min-width, 130px);
- @include S(padding, 15px, 20px);
- letter-spacing: 0.3em !important;
- @include IncreasedClickArea(0px);
- font-weight: bold;
- color: #fff;
- background-color: $colorGreenBright;
- transition: transform 0.12s ease-in-out, background-color 0.12s ease-in-out;
-
- &:hover {
- background-color: darken($colorGreenBright, 4);
- opacity: 1;
- }
-
- &.continueButton {
- @include Heading;
- }
- }
-
- .importButton {
- @include S(margin-top, 15px);
- @include IncreasedClickArea(0px);
- }
-
- .newGameButton {
- @include IncreasedClickArea(0px);
- @include S(margin-top, 15px);
- @include S(margin-left, 15px);
- }
-
- .savegames {
- @include S(max-height, 105px);
- overflow-y: auto;
- @include S(width, 250px);
- pointer-events: all;
- @include S(padding-right, 5px);
- display: grid;
- grid-auto-flow: row;
- @include S(grid-gap, 5px);
- @include S(margin-top, 10px);
-
- .savegame {
- background: #eee;
- @include S(border-radius, $globalBorderRadius);
- @include S(padding, 5px);
- display: grid;
- grid-template-columns: 1fr auto auto;
- grid-template-rows: auto auto;
- @include S(grid-column-gap, 4px);
- @include S(grid-row-gap, 1px);
-
- .playtime {
- grid-column: 1 / 2;
- grid-row: 2 / 3;
- @include SuperSmallText;
- opacity: 0.5;
- }
-
- .level {
- grid-column: 1 / 2;
- grid-row: 1 / 2;
- @include PlainText;
- }
-
- button.resumeGame,
- button.downloadGame,
- button.deleteGame {
- padding: 0;
- align-self: center;
- justify-self: center;
- @include IncreasedClickArea(0px);
- background: #44484a uiResource("icons/play.png") center center / 40% no-repeat;
- }
-
- button.downloadGame {
- grid-column: 2 / 3;
- grid-row: 1 / 2;
- background-color: $colorBlueBright;
- background-image: uiResource("icons/download.png");
- @include S(width, 15px);
- @include IncreasedClickArea(0px);
- @include S(height, 15px);
- background-size: 60%;
- align-self: start;
- }
-
- button.deleteGame {
- grid-column: 2 / 3;
- grid-row: 2 / 3;
- background-color: $colorRedBright;
- @include IncreasedClickArea(0px);
- background-image: uiResource("icons/delete.png");
- @include S(width, 15px);
- @include S(height, 15px);
- align-self: end;
- background-size: 60%;
- }
-
- button.resumeGame {
- grid-column: 3 / 4;
- grid-row: 1 / 3;
- margin: 0;
- @include S(width, 32px);
- height: 100%;
- }
- }
- }
- }
-
- .footer {
- display: grid;
- flex-grow: 1;
- justify-content: center;
- align-items: flex-end;
- width: 100%;
- grid-template-columns: auto auto auto 1fr;
- @include S(padding, 10px);
- box-sizing: border-box;
- @include S(grid-gap, 4px);
-
- .author {
- flex-grow: 1;
- text-align: right;
- @include PlainText;
- color: #888a8f;
- a {
- color: #333438;
- }
- }
-
- @include S(padding, 15px);
-
- > .boxLink {
- display: grid;
- align-items: center;
- grid-template-columns: 1fr auto;
-
- justify-content: center;
- background: #fdfdfd uiResource("icons/link.png") top D(3px) right D(3px) / D(9px) no-repeat;
- @include S(padding, 5px);
- @include S(padding-left, 10px);
- @include S(border-radius, $globalBorderRadius);
- @include SuperSmallText();
-
- font-weight: bold;
- box-sizing: border-box;
- text-transform: uppercase;
- color: #616266;
-
- transition: background-color 0.12s ease-in-out;
- pointer-events: all;
- @include S(width, 120px);
- @include S(height, 60px);
-
- cursor: pointer;
- &:hover {
- background-color: #f0f6ff;
- }
-
- .thirdpartyLogo {
- display: inline-block;
- @include S(width, 50px);
- @include S(height, 50px);
- background: center center / 80% no-repeat;
- &.githubLogo {
- background-image: uiResource("main_menu/github.png");
- }
- &.discordLogo {
- background-image: uiResource("main_menu/discord.png");
- background-size: 95%;
- }
- }
- }
-
- > .sidelinks {
- display: grid;
- align-items: flex-start;
- justify-content: flex-start;
- grid-template-rows: 1fr 1fr 1fr;
- @include S(grid-gap, 3px);
- @include S(height, 60px);
-
- > a {
- color: #616266;
- background: #fdfdfd;
- height: 100%;
-
- &:hover {
- background-color: #f0f6ff;
- }
- @include SuperSmallText;
- text-transform: uppercase;
- width: 100%;
- @include S(padding, 2px, 10px);
- display: flex;
- align-items: center;
- justify-content: flex-start;
-
- @include S(padding-left, 25px);
- box-sizing: border-box;
- font-weight: bold;
- background-position: #{D(5px)} center;
- background-size: #{D(12px)};
- background-repeat: no-repeat;
- @include S(border-radius, $globalBorderRadius);
-
- transition: background-color 0.12s ease-in-out;
-
- &.redditLink {
- background-image: uiResource("main_menu/reddit.svg");
- }
- &.changelog {
- background-image: uiResource("main_menu/changelog.svg");
- }
- &.helpTranslate {
- background-image: uiResource("main_menu/translate.svg");
- }
- }
- }
- }
-
- @include DarkThemeOverride {
- background: $darkModeGameBackground center center / cover !important;
-
- .topButtons {
- filter: invert(1);
-
- .languageChoose {
- filter: invert(1);
- }
- }
-
- .mainContainer {
- background: darken($darkModeGameBackground, 10);
-
- .savegames .savegame {
- background: darken($darkModeGameBackground, 15);
- color: white;
- }
- }
-
- .footer {
- > a,
- .sidelinks > a {
- background-color: darken($darkModeGameBackground, 10);
- color: #eee;
-
- &:hover {
- background-color: darken($darkModeGameBackground, 8);
- }
- }
-
- .author {
- color: #bdbdbd;
-
- > a {
- color: white;
- }
- }
-
- .thirdpartyLogo.githubLogo {
- filter: invert(1);
- }
- }
- }
-}
+#state_MainMenuState {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+
+ // background: #aaacb4 center center / cover !important;
+ background: #bbc2cf center center / cover !important;
+
+ .topButtons {
+ position: absolute;
+ @include S(top, 20px);
+ @include S(right, 20px);
+ display: grid;
+ grid-auto-flow: column;
+ @include S(grid-gap, 15px);
+
+ .settingsButton,
+ .exitAppButton,
+ .languageChoose {
+ @include S(width, 25px);
+ @include S(height, 25px);
+ pointer-events: all;
+ cursor: pointer;
+ background: uiResource("icons/main_menu_settings.png") center center / contain no-repeat;
+ transition: opacity 0.12s ease-in-out;
+ @include IncreasedClickArea(2px);
+ &:hover {
+ opacity: 0.9;
+ }
+ }
+
+ .exitAppButton {
+ background-image: uiResource("icons/main_menu_exit.png");
+ }
+
+ .languageChoose {
+ @include S(border-radius, 8px);
+ border: solid #222428;
+ background-color: #fff;
+ @include S(border-width, 2px);
+ background-size: cover;
+ }
+ }
+
+ .fullscreenBackgroundVideo {
+ // display: none !important;
+ z-index: -1;
+ position: fixed;
+ right: 50%;
+ bottom: 50%;
+ min-width: 100%;
+ min-height: 100%;
+
+ opacity: 0;
+ display: none;
+ transform: translate(50%, 50%);
+ filter: blur(D(3px));
+
+ $opacity: 0.2;
+ &.loaded {
+ display: block;
+ opacity: $opacity;
+
+ @include InlineAnimation(0.1s ease-in-out) {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: $opacity;
+ }
+ }
+ }
+ }
+
+ .mainWrapper {
+ @include S(padding, 0, 10px);
+ align-items: start;
+ justify-items: center;
+
+ @include S(grid-column-gap, 10px);
+ display: grid;
+ grid-template-columns: 1fr;
+
+ &.demo {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .standaloneBanner {
+ background: rgb(255, 234, 245);
+ @include S(border-radius, $globalBorderRadius);
+ box-sizing: border-box;
+ @include S(padding, 15px);
+
+ display: flex;
+ flex-direction: column;
+
+ strong {
+ font-weight: bold;
+ @include S(margin, 0, 4px);
+ }
+
+ h3 {
+ @include Heading;
+ font-weight: bold;
+ @include S(margin-bottom, 5px);
+ text-transform: uppercase;
+ color: $colorRedBright;
+ }
+
+ p {
+ @include Text;
+ }
+
+ ul {
+ @include S(margin-top, 5px);
+ @include S(padding-left, 20px);
+ li {
+ @include Text;
+ }
+ }
+
+ .steamLink {
+ width: 100%;
+ @include S(height, 40px);
+
+ background: uiResource("get_on_steam.png") center center / contain no-repeat;
+ overflow: hidden;
+ display: block;
+ text-indent: -999em;
+ cursor: pointer;
+ @include S(margin-top, 20px);
+ pointer-events: all;
+ transition: all 0.12s ease-in;
+ transition-property: opacity, transform;
+ transform: skewX(-0.5deg);
+ &:hover {
+ transform: skewX(-1deg) scale(1.02);
+ opacity: 0.9;
+ }
+ }
+ }
+ }
+
+ .logo {
+ display: flex;
+ flex-grow: 1;
+ align-items: center;
+ justify-content: center;
+
+ flex-direction: column;
+ @include S(padding-top, 20px);
+ img {
+ @include S(width, 350px);
+ }
+
+ .demoBadge {
+ @include S(margin, 10px, 0);
+ @include S(width, 100px);
+ @include S(height, 30px);
+ background: uiResource("demo_badge.png") center center / contain no-repeat;
+ display: inline-block;
+ }
+
+ position: relative;
+ .updateLabel {
+ position: absolute;
+ transform: translateX(50%) rotate(-5deg);
+ color: $colorRedBright;
+ @include Heading;
+ text-transform: uppercase;
+ font-weight: bold;
+ @include S(right, 40px);
+ @include S(bottom, 20px);
+
+ @include InlineAnimation(1.3s ease-in-out infinite) {
+ 50% {
+ transform: translateX(50%) rotate(-7deg) scale(1.1);
+ }
+ }
+
+ @include DarkThemeOverride {
+ color: $colorBlueBright;
+ }
+ }
+ }
+
+ .betaWarning {
+ @include S(width, 400px);
+ @include PlainText;
+ background: $colorRedBright;
+ @include S(padding, 10px);
+ @include S(border-radius, $globalBorderRadius);
+ color: #fff;
+ @include S(margin-top, 10px);
+ border: #{D(2px)} solid rgba(0, 10, 20, 0.1);
+ }
+
+ .sideContainer {
+ display: flex;
+ flex-direction: column;
+ @include S(width, 300px);
+
+ .standaloneBanner {
+ flex-grow: 1;
+ @include S(margin-bottom, 10px);
+ }
+ }
+
+ .mainContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ background: #fafafa;
+ @include S(padding, 20px);
+ @include S(border-radius, $globalBorderRadius);
+ // border: #{D(2px)} solid rgba(0, 10, 20, 0.1);
+ height: 100%;
+ width: 100%;
+ box-sizing: border-box;
+
+ .buttons {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .browserWarning {
+ @include S(margin-bottom, 10px);
+ background-color: $colorRedBright;
+ @include PlainText;
+ color: #fff;
+ @include S(border-radius, $globalBorderRadius);
+ @include S(padding, 5px);
+ @include S(width, 300px);
+ }
+
+ .playButton,
+ .continueButton {
+ @include SuperHeading;
+ @include S(min-width, 130px);
+ @include S(padding, 15px, 20px);
+ letter-spacing: 0.3em !important;
+ @include IncreasedClickArea(0px);
+ font-weight: bold;
+ color: #fff;
+ background-color: $colorGreenBright;
+ transition: transform 0.12s ease-in-out, background-color 0.12s ease-in-out;
+
+ &:hover {
+ background-color: darken($colorGreenBright, 4);
+ opacity: 1;
+ }
+
+ &.continueButton {
+ @include Heading;
+ }
+ }
+
+ .importButton {
+ @include S(margin-top, 15px);
+ @include IncreasedClickArea(0px);
+ }
+
+ .newGameButton {
+ @include IncreasedClickArea(0px);
+ @include S(margin-top, 15px);
+ @include S(margin-left, 15px);
+ }
+
+ .savegames {
+ @include S(max-height, 105px);
+ overflow-y: auto;
+ @include S(width, 250px);
+ pointer-events: all;
+ @include S(padding-right, 5px);
+ display: grid;
+ grid-auto-flow: row;
+ @include S(grid-gap, 5px);
+ @include S(margin-top, 10px);
+
+ .savegame {
+ background: #eee;
+ @include S(border-radius, $globalBorderRadius);
+ @include S(padding, 5px);
+ display: grid;
+ grid-template-columns: 1fr 1fr auto auto;
+ grid-template-rows: auto auto;
+ @include S(grid-column-gap, 4px);
+ @include S(grid-row-gap, 1px);
+
+ .playtime {
+ grid-column: 2 / 3;
+ grid-row: 2 / 3;
+ @include SuperSmallText;
+ opacity: 0.5;
+ }
+
+ .level {
+ grid-column: 1 / 2;
+ grid-row: 2 / 3;
+ @include SuperSmallText;
+ opacity: 0.5;
+ }
+
+ .name {
+ grid-column: 1 / 3;
+ grid-row: 1 / 2;
+ @include PlainText;
+ display: inline-flex;
+ align-items: center;
+
+ > span {
+ display: inline-flex;
+ @include S(max-width, 140px);
+ overflow: hidden;
+ }
+ }
+
+ button.resumeGame,
+ button.downloadGame,
+ button.deleteGame,
+ button.renameGame {
+ padding: 0;
+ align-self: center;
+ justify-self: center;
+ @include IncreasedClickArea(0px);
+ background: #44484a uiResource("icons/play.png") center center / 40% no-repeat;
+ }
+
+ button.downloadGame {
+ grid-column: 3 / 4;
+ grid-row: 1 / 2;
+ background-color: $colorBlueBright;
+ background-image: uiResource("icons/download.png");
+ @include S(width, 15px);
+ @include IncreasedClickArea(0px);
+ @include S(height, 15px);
+ background-size: 60%;
+ align-self: start;
+ }
+
+ button.deleteGame {
+ grid-column: 3 / 4;
+ grid-row: 2 / 3;
+ background-color: $colorRedBright;
+ @include IncreasedClickArea(0px);
+ background-image: uiResource("icons/delete.png");
+ @include S(width, 15px);
+ @include S(height, 15px);
+ align-self: end;
+ background-size: 60%;
+ }
+
+ button.renameGame {
+ background-color: transparent;
+ @include IncreasedClickArea(2px);
+ background-image: uiResource("icons/edit_key.png");
+ @include S(width, 10px);
+ @include S(height, 10px);
+ align-self: center;
+ justify-self: center;
+
+ background-size: 90%;
+ opacity: 0.25;
+ @include S(margin-left, 4px);
+
+ &:hover {
+ opacity: 0.35;
+ }
+
+ @include DarkThemeInvert;
+ }
+
+ button.resumeGame {
+ grid-column: 4 / 5;
+ grid-row: 1 / 3;
+ margin: 0;
+ @include S(width, 32px);
+ height: 100%;
+ }
+ }
+ }
+ }
+
+ .footer {
+ display: grid;
+ flex-grow: 1;
+ justify-content: center;
+ align-items: flex-end;
+ width: 100%;
+ grid-template-columns: auto auto auto 1fr;
+ @include S(padding, 10px);
+ box-sizing: border-box;
+ @include S(grid-gap, 4px);
+
+ .author {
+ flex-grow: 1;
+ text-align: right;
+ @include PlainText;
+ color: #888a8f;
+ a {
+ color: #333438;
+ }
+ }
+
+ @include S(padding, 15px);
+
+ > .boxLink {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 1fr auto;
+
+ justify-content: center;
+ background: #fdfdfd uiResource("icons/link.png") top D(3px) right D(3px) / D(9px) no-repeat;
+ @include S(padding, 5px);
+ @include S(padding-left, 10px);
+ @include S(border-radius, $globalBorderRadius);
+ @include SuperSmallText();
+
+ font-weight: bold;
+ box-sizing: border-box;
+ text-transform: uppercase;
+ color: #616266;
+
+ transition: background-color 0.12s ease-in-out;
+ pointer-events: all;
+ @include S(width, 120px);
+ @include S(height, 60px);
+
+ cursor: pointer;
+ &:hover {
+ background-color: #f0f6ff;
+ }
+
+ .thirdpartyLogo {
+ display: inline-block;
+ @include S(width, 50px);
+ @include S(height, 50px);
+ background: center center / 80% no-repeat;
+ &.githubLogo {
+ background-image: uiResource("main_menu/github.png");
+ }
+ &.discordLogo {
+ background-image: uiResource("main_menu/discord.png");
+ background-size: 95%;
+ }
+ }
+ }
+
+ > .sidelinks {
+ display: grid;
+ align-items: flex-start;
+ justify-content: flex-start;
+ grid-template-rows: 1fr 1fr 1fr;
+ @include S(grid-gap, 3px);
+ @include S(height, 60px);
+
+ > a {
+ color: #616266;
+ background: #fdfdfd;
+ height: 100%;
+
+ &:hover {
+ background-color: #f0f6ff;
+ }
+ @include SuperSmallText;
+ text-transform: uppercase;
+ width: 100%;
+ @include S(padding, 2px, 10px);
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+
+ @include S(padding-left, 25px);
+ box-sizing: border-box;
+ font-weight: bold;
+ background-position: #{D(5px)} center;
+ background-size: #{D(12px)};
+ background-repeat: no-repeat;
+ @include S(border-radius, $globalBorderRadius);
+
+ transition: background-color 0.12s ease-in-out;
+
+ &.redditLink {
+ background-image: uiResource("main_menu/reddit.svg");
+ }
+ &.changelog {
+ background-image: uiResource("main_menu/changelog.svg");
+ }
+ &.helpTranslate {
+ background-image: uiResource("main_menu/translate.svg");
+ }
+ }
+ }
+ }
+
+ @include DarkThemeOverride {
+ background: $darkModeGameBackground center center / cover !important;
+
+ .topButtons {
+ filter: invert(1);
+
+ .languageChoose {
+ filter: invert(1);
+ }
+ }
+
+ .mainContainer {
+ background: darken($darkModeGameBackground, 10);
+
+ .savegames .savegame {
+ background: darken($darkModeGameBackground, 15);
+ color: white;
+ }
+ }
+
+ .footer {
+ > a,
+ .sidelinks > a {
+ background-color: darken($darkModeGameBackground, 10);
+ color: #eee;
+
+ &:hover {
+ background-color: darken($darkModeGameBackground, 8);
+ }
+ }
+
+ .author {
+ color: #bdbdbd;
+
+ > a {
+ color: white;
+ }
+ }
+
+ .thirdpartyLogo.githubLogo {
+ filter: invert(1);
+ }
+ }
+ }
+}
diff --git a/src/js/changelog.js b/src/js/changelog.js
index 07902b80..dd33759b 100644
--- a/src/js/changelog.js
+++ b/src/js/changelog.js
@@ -1,300 +1,304 @@
-export const CHANGELOG = [
- {
- version: "1.2.0",
- date: "unreleased",
- entries: [
- "WIRES",
- "Reworked menu UI design (by dengr1605)",
- "Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
- "Clear cursor when trying to pipette the same building twice (by hexy)",
- "Fixed level 18 stacker bug: If you experienced it already, you know it, if not, I don't want to spoiler (by hexy)",
- "Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
- "Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
- "Fixed some phrases (by EnderDoom77)",
- "Zoom towards mouse cursor (by Dimava)",
- "Added multiple settings to optimize the performance",
- "Updated the soundtrack again, it is now 40 minutes in total!",
- "Updated and added new translations (Thanks to all contributors!)",
- "Allow editing waypoints (by isaisstillalive)",
- "Show confirmation when cutting area which is too expensive to get pasted again (by isaisstillalive)",
- "Show mouse and camera tile on debug overlay (F4) (by dengr)",
- "Fix tunnels entrances connecting to exits sometimes when they shouldn't",
- "The initial belt planner direction is now based on the cursor movement (by MizardX)",
- "Fix preferred variant not getting saved when clicking on the hud (by Danacus)",
- ],
- },
- {
- version: "1.1.19",
- date: "02.07.2020",
- entries: [
- "There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the Discord)",
- "I'm still working on the wires update, I hope to release it mid july!",
- ],
- },
- {
- version: "1.1.18",
- date: "27.06.2020",
- entries: [
- "Huge performance improvements - up to double fps and tick-rate! This will wipe out all current items on belts.",
- "Reduce story shapes required until unlocking blueprints",
- "Allow clicking on variants to select them",
- "Add 'copy key' button to shape viewer",
- "Add more FPS to the belt animation and fix belt animation seeming to go 'backwards' on high belt speeds",
- "Fix deconstruct sound being played when right clicking hub",
- "Allow clicking 'Q' over a shape or color patch to automatically select the miner building (by Gerdon262)",
- "Update belt placement performance on huge factories (by Phlosioneer)",
- "Fix duplicate waypoints with a shape not rendering (by hexy)",
- "Fix smart tunnel placement deleting wrong tunnels (by mordof)",
- "Add setting (on by default) to store the last used rotation per building instead of globally storing it (by Magos)",
- "Added chinese (traditional) translation",
- "Updated translations",
- ],
- },
- {
- version: "1.1.17",
- date: "22.06.2020",
- entries: [
- "Color blind mode! You can now activate it in the settings and it will show you which color is below your cursor (Either resource or on the belt)",
- "Add info buttons to all shapes so you can figure out how they are built! (And also, which colors they have)",
- "Allow configuring autosave interval and disabling it in the settings",
- "The smart-tunnel placement has been reworked to properly replace belts. Thus the setting has been turned on again by default",
- "The soundtrack now has a higher quality on the standalone version than the web version",
- "Add setting to disable cut/delete warnings (by hexy)",
- "Fix bug where belts in blueprints don't orient correctly (by hexy)",
- "Fix camera moving weird after dragging and holding (by hexy)",
- "Fix keybinding for pipette showing while pasting blueprints",
- "Improve visibility of shape background in dark mode",
- "Added sound when destroying a building",
- "Added swedish translation",
- "Update tutorial image for tier 2 tunnels to explain mix/match (by jimmyshadow1)",
- ],
- },
- {
- version: "1.1.16",
- date: "21.06.2020",
- entries: [
- "You can now pickup buildings below your cursor with 'Q'!",
- "The game soundtrack has been extended! There are now 4 songs with over 13 minutes of playtime from Peppsen !",
- "Refactor keybindings overlay to show more appropriate keybindings",
- "Show keybindings for area-select in the upper left instead",
- "Automatically deselect area when selecting a new building",
- "Raise markers limit from 14 characters to 71 (by Joker-vD)",
- "Optimize performance by caching extractor items (by Phlosioneer)",
- "Added setting to enable compact building infos, which only show ratios and hide the image / description",
- "Apply dark theme to menu as well (by dengr1065)",
- "Fix belt planner not placing the last belt",
- "Fix buildings getting deleted when right clicking while placing a blueprint",
- "Fix for exporting screenshots for huge bases (It was showing an empty file) (by xSparfuchs)",
- "Fix buttons not responding when using right click directly after left click (by davidburhans)",
- "Fix hub marker being hidden by building info panel",
- "Disable dialog background blur since it can cause performance issues",
- "Added simplified chinese translations",
- "Update translations (Thanks to all translators!)",
- ],
- },
- {
- version: "1.1.15",
- date: "17.06.2020",
- entries: [
- "You can now place straight belts (and tunnels) by holding SHIFT! (For you, @giantwaffle ❤️)",
- "Added continue button to main menu and add seperate 'New game' button (by jaysc)",
- "Added setting to disable smart tunnel placement introduced with the last update",
- "Added setting to disable vignette",
- "Update translations",
- ],
- },
- {
- version: "1.1.14",
- date: "16.06.2020",
- entries: [
- "There is now an indicator (compass) to the HUB for the HUB Marker!",
- "You can now include shape short keys in markers to render shape icons instead of text!",
- "Added mirrored variant of the painter",
- "When placing tunnels, unnecessary belts inbetween are now removed!",
- "You can now drag tunnels and they will automatically expand! (Just try it out, its intuitive)",
- ],
- },
- {
- version: "1.1.13",
- date: "15.06.2020",
- entries: [
- "Added shift modifier for faster pan (by jaysc)",
- "Added Japanese translations",
- "Added Portuguese (Portugal) translations",
- "Updated icon for Spanish (Latin America) - It was showing a Spanish flag before",
- "Updated existing translations",
- ],
- },
- {
- version: "1.1.12",
- date: "14.06.2020",
- entries: [
- "Huge performance improvements! The game should now run up to 60% faster!",
- "Added norwegian translation",
- ],
- },
- {
- version: "1.1.11",
- date: "13.06.2020",
- entries: [
- "Pinned shapes are now smart, they dynamically update their goal and also unpin when no longer required. Completed objectives are now rendered transparent.",
- "You can now cut areas, and also paste the last blueprint again! (by hexy)",
- "You can now export your whole base as an image by pressing F3!",
- "Improve upgrade number rounding, so there are no goals like '37.4k', instead it will now be '35k'",
- "You can now configure the camera movement speed when using WASD (by mini-bomba)",
- "Selecting an area now is relative to the world and thus does not move when moving the screen (by Dimava)",
- "Allow higher tick-rates up to 500hz (This will burn your PC!)",
- "Fix bug regarding number rounding",
- "Fix dialog text being hardly readable in dark theme",
- "Fix app not starting when the savegames were corrupted - there is now a better error message as well.",
- "Further translation updates - Big thanks to all contributors!",
- ],
- },
- {
- version: "1.1.10",
- date: "12.06.2020",
- entries: [
- "There are now linux builds on steam! Please report any issues in the Discord!",
- "Steam cloud saves are now available!",
- "Added and update more translations (Big thank you to all translators!)",
- "Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)",
- ],
- },
- {
- version: "1.1.9",
- date: "11.06.2020",
- entries: [
- "Support for translations! Interested in helping out? Check out the translation guide !",
- "Update stacker artwork to clarify how it works",
- "Update keybinding hints on the top left to be more accurate",
- "Make it more clear when blueprints are unlocked when trying to use them",
- "Fix pinned shape icons not being visible in dark mode",
- "Fix being able to select buildings via hotkeys in map overview mode",
- "Make shapes unpinnable in the upgrades tab (By hexy)",
- ],
- },
- {
- version: "1.1.8",
- date: "07.06.2020",
- entries: [
- "You can now purchase the standalone on steam! View steam page ",
- "Added ability to create markers in the demo, but only two.",
- "Contest #01 has ended! I'll now work through the entries, select the 5 I like most and present them to the community to vote for!",
- ],
- },
- {
- version: "1.1.7",
- date: "04.06.2020",
- entries: ["HOTFIX: Fix savegames not showing up on the standalone version"],
- },
- {
- version: "1.1.6",
- date: "04.06.2020",
- entries: [
- "The steam release will happen on the 7th of June - Be sure to add it to your wishlist! View on steam ",
- "Fixed level complete dialog being blurred when the shop was opened before",
- "Standalone: Increased icon visibility for windows builds",
- "Web version: Fixed firefox not loading the game when browsing in private mode",
- ],
- },
-
- {
- version: "1.1.5",
- date: "03.06.2020",
- entries: ["Added weekly contests!"],
- },
- {
- version: "1.1.4",
- date: "01.06.2020",
- entries: ["Add 'interactive' tutorial for the first level to improve onboarding experience"],
- },
- {
- version: "1.1.3",
- date: "01.06.2020",
- entries: [
- "Added setting to configure zoom / mouse wheel / touchpad sensitivity",
- "Fix belts being too slow when copied via blueprint (by Dimava)",
- "Allow binding mouse buttons to actions (by Dimava)",
- "Increase readability of certain HUD elements",
- ],
- },
- {
- version: "1.1.2",
- date: "30.05.2020",
- entries: [
- "The official trailer is now ready! Check it out here !",
- "The steam page is now live!",
- "Experimental linux builds are now available! Please give me feedback on them in the Discord",
- "Allow hovering pinned shapes to enlarge them",
- "Allow deselecting blueprints with right click and 'Q'",
- "Move default key for deleting from 'X' to 'DEL'",
- "Show confirmation when deleting more than 100 buildings",
- "Reintroduce 'SPACE' keybinding to center on map",
- "Improved keybinding hints",
- "Fixed some keybindings showing as 'undefined'",
- ],
- },
- {
- version: "1.1.1",
- date: "28.05.2020",
- entries: ["Fix crash when 'Show Hints' setting was turned off"],
- },
- {
- version: "1.1.0",
- date: "28.05.2020",
- entries: [
- "BLUEPRINTS! They are unlocked at level 12 and cost a special shape to build.",
- "MAP MARKERS! Press 'M' to create a waypoint and be able to jump to it",
- "Savegame levels are now shown in the main menu. For existing games, save them again to make the level show up.",
- "Allow holding SHIFT to rotate counter clockwise",
- "Added confirmation when deleting more than 500 buildings at a time",
- "Added background to toolbar to increase contrast",
- "Further decerase requirements of first levels",
- "Pinned shapes now are saved",
- "Allow placing extractors anywhere again, but they don't work at all if not placed on a resource",
- "Show dialog explaining some keybindings after completing level 4",
- "Fix keys being stuck when opening a dialog",
- "Swapped shape order for painting upgrades",
- "Allow changing all keybindings, including CTRL, ALT and SHIFT (by Dimava)",
- "Fix cycling through keybindings selecting locked buildings as well (by Dimava)",
- "There is now a github action, checking all pull requests with eslint. (by mrHedgehog)",
- ],
- },
- {
- version: "1.0.4",
- date: "26.05.2020",
- entries: [
- "Reduce cost of first painting upgrade, and change 'Shape Processing' to 'Cutting, Rotating & Stacking'",
- "Add dialog after completing level 2 to check out the upgrades tab.",
- "Allow changing the keybindings in the demo version",
- ],
- },
- {
- version: "1.0.3",
- date: "24.05.2020",
- entries: [
- "Reduced the amount of shapes required for the first 5 levels to make it easier to get into the game.",
- ],
- },
- {
- version: "1.0.2",
- date: "23.05.2020",
- entries: [
- "Introduced changelog",
- "Removed 'early access' label because the game isn't actually early access - its in a pretty good state already! (No worries, a lot more updates will follow!)",
- "Added a 'Show hint' button which shows a small video for almost all levels to help out",
- "Now showing proper descriptions when completing levels, with instructions on what the gained reward does.",
- "Show a landing page on mobile devices about the game not being ready to be played on mobile yet",
- "Fix painters and mixers being affected by the shape processors upgrade and not the painter one",
- "Added 'multiplace' setting which is equivalent to holding SHIFT all the time",
- "Added keybindings to zoom in / zoom out",
- "Tunnels now also show connection lines to tunnel exits, instead of just tunnel entries",
- "Lots of minor fixes and improvements",
- ],
- },
- {
- version: "1.0.1",
- date: "21.05.2020",
- entries: ["Initial release!"],
- },
-];
+export const CHANGELOG = [
+ {
+ version: "1.2.0",
+ date: "unreleased",
+ entries: [
+ "WIRES",
+ "Reworked menu UI design (by dengr1605)",
+ "Allow holding ALT in belt planner to reverse direction (by jakobhellermann)",
+ "Clear cursor when trying to pipette the same building twice (by hexy)",
+ "Fixed level 18 stacker bug: If you experienced it already, you know it, if not, I don't want to spoiler (by hexy)",
+ "Added keybinding to close menus (by isaisstillalive / Sandwichs-del)",
+ "Fix rare crash regarding the buildings toolbar (by isaisstillalive)",
+ "Fixed some phrases (by EnderDoom77)",
+ "Zoom towards mouse cursor (by Dimava)",
+ "Added multiple settings to optimize the performance",
+ "Updated the soundtrack again, it is now 40 minutes in total!",
+ "Added a button to the statistics dialog to disable the sorting (by squeek502)",
+ "Updated and added new translations (Thanks to all contributors!)",
+ "Added setting to be able to delete buildings while placing (inspired by hexy)",
+ "Mark pinned shapes in statistics dialog and show them first (inspired by davidburhans)",
+ "There are now compact 1x1 splitters available to be unlocked!",
+ "Allow editing waypoints (by isaisstillalive)",
+ "Show confirmation when cutting area which is too expensive to get pasted again (by isaisstillalive)",
+ "Show mouse and camera tile on debug overlay (F4) (by dengr)",
+ "Fix tunnels entrances connecting to exits sometimes when they shouldn't",
+ "The initial belt planner direction is now based on the cursor movement (by MizardX)",
+ "Fix preferred variant not getting saved when clicking on the hud (by Danacus)",
+ ],
+ },
+ {
+ version: "1.1.19",
+ date: "02.07.2020",
+ entries: [
+ "There are now notifications every 15 minutes in the demo version to buy the full version (For further details and the reason, check the #surveys channel in the Discord)",
+ "I'm still working on the wires update, I hope to release it mid july!",
+ ],
+ },
+ {
+ version: "1.1.18",
+ date: "27.06.2020",
+ entries: [
+ "Huge performance improvements - up to double fps and tick-rate! This will wipe out all current items on belts.",
+ "Reduce story shapes required until unlocking blueprints",
+ "Allow clicking on variants to select them",
+ "Add 'copy key' button to shape viewer",
+ "Add more FPS to the belt animation and fix belt animation seeming to go 'backwards' on high belt speeds",
+ "Fix deconstruct sound being played when right clicking hub",
+ "Allow clicking 'Q' over a shape or color patch to automatically select the miner building (by Gerdon262)",
+ "Update belt placement performance on huge factories (by Phlosioneer)",
+ "Fix duplicate waypoints with a shape not rendering (by hexy)",
+ "Fix smart tunnel placement deleting wrong tunnels (by mordof)",
+ "Add setting (on by default) to store the last used rotation per building instead of globally storing it (by Magos)",
+ "Added chinese (traditional) translation",
+ "Updated translations",
+ ],
+ },
+ {
+ version: "1.1.17",
+ date: "22.06.2020",
+ entries: [
+ "Color blind mode! You can now activate it in the settings and it will show you which color is below your cursor (Either resource or on the belt)",
+ "Add info buttons to all shapes so you can figure out how they are built! (And also, which colors they have)",
+ "Allow configuring autosave interval and disabling it in the settings",
+ "The smart-tunnel placement has been reworked to properly replace belts. Thus the setting has been turned on again by default",
+ "The soundtrack now has a higher quality on the standalone version than the web version",
+ "Add setting to disable cut/delete warnings (by hexy)",
+ "Fix bug where belts in blueprints don't orient correctly (by hexy)",
+ "Fix camera moving weird after dragging and holding (by hexy)",
+ "Fix keybinding for pipette showing while pasting blueprints",
+ "Improve visibility of shape background in dark mode",
+ "Added sound when destroying a building",
+ "Added swedish translation",
+ "Update tutorial image for tier 2 tunnels to explain mix/match (by jimmyshadow1)",
+ ],
+ },
+ {
+ version: "1.1.16",
+ date: "21.06.2020",
+ entries: [
+ "You can now pickup buildings below your cursor with 'Q'!",
+ "The game soundtrack has been extended! There are now 4 songs with over 13 minutes of playtime from Peppsen !",
+ "Refactor keybindings overlay to show more appropriate keybindings",
+ "Show keybindings for area-select in the upper left instead",
+ "Automatically deselect area when selecting a new building",
+ "Raise markers limit from 14 characters to 71 (by Joker-vD)",
+ "Optimize performance by caching extractor items (by Phlosioneer)",
+ "Added setting to enable compact building infos, which only show ratios and hide the image / description",
+ "Apply dark theme to menu as well (by dengr1065)",
+ "Fix belt planner not placing the last belt",
+ "Fix buildings getting deleted when right clicking while placing a blueprint",
+ "Fix for exporting screenshots for huge bases (It was showing an empty file) (by xSparfuchs)",
+ "Fix buttons not responding when using right click directly after left click (by davidburhans)",
+ "Fix hub marker being hidden by building info panel",
+ "Disable dialog background blur since it can cause performance issues",
+ "Added simplified chinese translations",
+ "Update translations (Thanks to all translators!)",
+ ],
+ },
+ {
+ version: "1.1.15",
+ date: "17.06.2020",
+ entries: [
+ "You can now place straight belts (and tunnels) by holding SHIFT! (For you, @giantwaffle ❤️)",
+ "Added continue button to main menu and add seperate 'New game' button (by jaysc)",
+ "Added setting to disable smart tunnel placement introduced with the last update",
+ "Added setting to disable vignette",
+ "Update translations",
+ ],
+ },
+ {
+ version: "1.1.14",
+ date: "16.06.2020",
+ entries: [
+ "There is now an indicator (compass) to the HUB for the HUB Marker!",
+ "You can now include shape short keys in markers to render shape icons instead of text!",
+ "Added mirrored variant of the painter",
+ "When placing tunnels, unnecessary belts inbetween are now removed!",
+ "You can now drag tunnels and they will automatically expand! (Just try it out, its intuitive)",
+ ],
+ },
+ {
+ version: "1.1.13",
+ date: "15.06.2020",
+ entries: [
+ "Added shift modifier for faster pan (by jaysc)",
+ "Added Japanese translations",
+ "Added Portuguese (Portugal) translations",
+ "Updated icon for Spanish (Latin America) - It was showing a Spanish flag before",
+ "Updated existing translations",
+ ],
+ },
+ {
+ version: "1.1.12",
+ date: "14.06.2020",
+ entries: [
+ "Huge performance improvements! The game should now run up to 60% faster!",
+ "Added norwegian translation",
+ ],
+ },
+ {
+ version: "1.1.11",
+ date: "13.06.2020",
+ entries: [
+ "Pinned shapes are now smart, they dynamically update their goal and also unpin when no longer required. Completed objectives are now rendered transparent.",
+ "You can now cut areas, and also paste the last blueprint again! (by hexy)",
+ "You can now export your whole base as an image by pressing F3!",
+ "Improve upgrade number rounding, so there are no goals like '37.4k', instead it will now be '35k'",
+ "You can now configure the camera movement speed when using WASD (by mini-bomba)",
+ "Selecting an area now is relative to the world and thus does not move when moving the screen (by Dimava)",
+ "Allow higher tick-rates up to 500hz (This will burn your PC!)",
+ "Fix bug regarding number rounding",
+ "Fix dialog text being hardly readable in dark theme",
+ "Fix app not starting when the savegames were corrupted - there is now a better error message as well.",
+ "Further translation updates - Big thanks to all contributors!",
+ ],
+ },
+ {
+ version: "1.1.10",
+ date: "12.06.2020",
+ entries: [
+ "There are now linux builds on steam! Please report any issues in the Discord!",
+ "Steam cloud saves are now available!",
+ "Added and update more translations (Big thank you to all translators!)",
+ "Prevent invalid connection if existing underground tunnel entrance exists (by jaysc)",
+ ],
+ },
+ {
+ version: "1.1.9",
+ date: "11.06.2020",
+ entries: [
+ "Support for translations! Interested in helping out? Check out the translation guide !",
+ "Update stacker artwork to clarify how it works",
+ "Update keybinding hints on the top left to be more accurate",
+ "Make it more clear when blueprints are unlocked when trying to use them",
+ "Fix pinned shape icons not being visible in dark mode",
+ "Fix being able to select buildings via hotkeys in map overview mode",
+ "Make shapes unpinnable in the upgrades tab (By hexy)",
+ ],
+ },
+ {
+ version: "1.1.8",
+ date: "07.06.2020",
+ entries: [
+ "You can now purchase the standalone on steam! View steam page ",
+ "Added ability to create markers in the demo, but only two.",
+ "Contest #01 has ended! I'll now work through the entries, select the 5 I like most and present them to the community to vote for!",
+ ],
+ },
+ {
+ version: "1.1.7",
+ date: "04.06.2020",
+ entries: ["HOTFIX: Fix savegames not showing up on the standalone version"],
+ },
+ {
+ version: "1.1.6",
+ date: "04.06.2020",
+ entries: [
+ "The steam release will happen on the 7th of June - Be sure to add it to your wishlist! View on steam ",
+ "Fixed level complete dialog being blurred when the shop was opened before",
+ "Standalone: Increased icon visibility for windows builds",
+ "Web version: Fixed firefox not loading the game when browsing in private mode",
+ ],
+ },
+
+ {
+ version: "1.1.5",
+ date: "03.06.2020",
+ entries: ["Added weekly contests!"],
+ },
+ {
+ version: "1.1.4",
+ date: "01.06.2020",
+ entries: ["Add 'interactive' tutorial for the first level to improve onboarding experience"],
+ },
+ {
+ version: "1.1.3",
+ date: "01.06.2020",
+ entries: [
+ "Added setting to configure zoom / mouse wheel / touchpad sensitivity",
+ "Fix belts being too slow when copied via blueprint (by Dimava)",
+ "Allow binding mouse buttons to actions (by Dimava)",
+ "Increase readability of certain HUD elements",
+ ],
+ },
+ {
+ version: "1.1.2",
+ date: "30.05.2020",
+ entries: [
+ "The official trailer is now ready! Check it out here !",
+ "The steam page is now live!",
+ "Experimental linux builds are now available! Please give me feedback on them in the Discord",
+ "Allow hovering pinned shapes to enlarge them",
+ "Allow deselecting blueprints with right click and 'Q'",
+ "Move default key for deleting from 'X' to 'DEL'",
+ "Show confirmation when deleting more than 100 buildings",
+ "Reintroduce 'SPACE' keybinding to center on map",
+ "Improved keybinding hints",
+ "Fixed some keybindings showing as 'undefined'",
+ ],
+ },
+ {
+ version: "1.1.1",
+ date: "28.05.2020",
+ entries: ["Fix crash when 'Show Hints' setting was turned off"],
+ },
+ {
+ version: "1.1.0",
+ date: "28.05.2020",
+ entries: [
+ "BLUEPRINTS! They are unlocked at level 12 and cost a special shape to build.",
+ "MAP MARKERS! Press 'M' to create a waypoint and be able to jump to it",
+ "Savegame levels are now shown in the main menu. For existing games, save them again to make the level show up.",
+ "Allow holding SHIFT to rotate counter clockwise",
+ "Added confirmation when deleting more than 500 buildings at a time",
+ "Added background to toolbar to increase contrast",
+ "Further decerase requirements of first levels",
+ "Pinned shapes now are saved",
+ "Allow placing extractors anywhere again, but they don't work at all if not placed on a resource",
+ "Show dialog explaining some keybindings after completing level 4",
+ "Fix keys being stuck when opening a dialog",
+ "Swapped shape order for painting upgrades",
+ "Allow changing all keybindings, including CTRL, ALT and SHIFT (by Dimava)",
+ "Fix cycling through keybindings selecting locked buildings as well (by Dimava)",
+ "There is now a github action, checking all pull requests with eslint. (by mrHedgehog)",
+ ],
+ },
+ {
+ version: "1.0.4",
+ date: "26.05.2020",
+ entries: [
+ "Reduce cost of first painting upgrade, and change 'Shape Processing' to 'Cutting, Rotating & Stacking'",
+ "Add dialog after completing level 2 to check out the upgrades tab.",
+ "Allow changing the keybindings in the demo version",
+ ],
+ },
+ {
+ version: "1.0.3",
+ date: "24.05.2020",
+ entries: [
+ "Reduced the amount of shapes required for the first 5 levels to make it easier to get into the game.",
+ ],
+ },
+ {
+ version: "1.0.2",
+ date: "23.05.2020",
+ entries: [
+ "Introduced changelog",
+ "Removed 'early access' label because the game isn't actually early access - its in a pretty good state already! (No worries, a lot more updates will follow!)",
+ "Added a 'Show hint' button which shows a small video for almost all levels to help out",
+ "Now showing proper descriptions when completing levels, with instructions on what the gained reward does.",
+ "Show a landing page on mobile devices about the game not being ready to be played on mobile yet",
+ "Fix painters and mixers being affected by the shape processors upgrade and not the painter one",
+ "Added 'multiplace' setting which is equivalent to holding SHIFT all the time",
+ "Added keybindings to zoom in / zoom out",
+ "Tunnels now also show connection lines to tunnel exits, instead of just tunnel entries",
+ "Lots of minor fixes and improvements",
+ ],
+ },
+ {
+ version: "1.0.1",
+ date: "21.05.2020",
+ entries: ["Initial release!"],
+ },
+];
diff --git a/src/js/core/modal_dialog_elements.js b/src/js/core/modal_dialog_elements.js
index 8252487a..54b69402 100644
--- a/src/js/core/modal_dialog_elements.js
+++ b/src/js/core/modal_dialog_elements.js
@@ -1,434 +1,443 @@
-/* typehints:start */
-import { Application } from "../application";
-/* typehints:end */
-
-import { Signal, STOP_PROPAGATION } from "./signal";
-import { arrayDeleteValue, waitNextFrame } from "./utils";
-import { ClickDetector } from "./click_detector";
-import { SOUNDS } from "../platform/sound";
-import { InputReceiver } from "./input_receiver";
-import { FormElement } from "./modal_dialog_forms";
-import { globalConfig } from "./config";
-import { getStringForKeyCode } from "../game/key_action_mapper";
-import { createLogger } from "./logging";
-import { T } from "../translations";
-
-const kbEnter = 13;
-const kbCancel = 27;
-
-const logger = createLogger("dialogs");
-
-/**
- * Basic text based dialog
- */
-export class Dialog {
- /**
- *
- * Constructs a new dialog with the given options
- * @param {object} param0
- * @param {Application} param0.app
- * @param {string} param0.title Title of the dialog
- * @param {string} param0.contentHTML Inner dialog html
- * @param {Array} param0.buttons
- * Button list, each button contains of up to 3 parts seperated by ':'.
- * Part 0: The id, one of the one defined in dialog_buttons.yaml
- * Part 1: The style, either good, bad or misc
- * Part 2 (optional): Additional parameters seperated by '/', available are:
- * timeout: This button is only available after some waiting time
- * kb_enter: This button is triggered by the enter key
- * kb_escape This button is triggered by the escape key
- * @param {string=} param0.type The dialog type, either "info" or "warn"
- * @param {boolean=} param0.closeButton Whether this dialog has a close button
- */
- constructor({ app, title, contentHTML, buttons, type = "info", closeButton = false }) {
- this.app = app;
- this.title = title;
- this.contentHTML = contentHTML;
- this.type = type;
- this.buttonIds = buttons;
- this.closeButton = closeButton;
-
- this.closeRequested = new Signal();
- this.buttonSignals = {};
-
- for (let i = 0; i < buttons.length; ++i) {
- if (G_IS_DEV && globalConfig.debug.disableTimedButtons) {
- this.buttonIds[i] = this.buttonIds[i].replace(":timeout", "");
- }
-
- const buttonId = this.buttonIds[i].split(":")[0];
- this.buttonSignals[buttonId] = new Signal();
- }
-
- this.timeouts = [];
- this.clickDetectors = [];
-
- this.inputReciever = new InputReceiver("dialog-" + this.title);
-
- this.inputReciever.keydown.add(this.handleKeydown, this);
-
- this.enterHandler = null;
- this.escapeHandler = null;
- }
-
- /**
- * Internal keydown handler
- * @param {object} param0
- * @param {number} param0.keyCode
- * @param {boolean} param0.shift
- * @param {boolean} param0.alt
- */
- handleKeydown({ keyCode, shift, alt }) {
- if (keyCode === kbEnter && this.enterHandler) {
- this.internalButtonHandler(this.enterHandler);
- return STOP_PROPAGATION;
- }
-
- if (keyCode === kbCancel && this.escapeHandler) {
- this.internalButtonHandler(this.escapeHandler);
- return STOP_PROPAGATION;
- }
- }
-
- internalButtonHandler(id, ...payload) {
- this.app.inputMgr.popReciever(this.inputReciever);
-
- if (id !== "close-button") {
- this.buttonSignals[id].dispatch(...payload);
- }
- this.closeRequested.dispatch();
- }
-
- createElement() {
- const elem = document.createElement("div");
- elem.classList.add("ingameDialog");
-
- this.dialogElem = document.createElement("div");
- this.dialogElem.classList.add("dialogInner");
-
- if (this.type) {
- this.dialogElem.classList.add(this.type);
- }
- elem.appendChild(this.dialogElem);
-
- const title = document.createElement("h1");
- title.innerText = this.title;
- title.classList.add("title");
- this.dialogElem.appendChild(title);
-
- if (this.closeButton) {
- this.dialogElem.classList.add("hasCloseButton");
-
- const closeBtn = document.createElement("button");
- closeBtn.classList.add("closeButton");
-
- this.trackClicks(closeBtn, () => this.internalButtonHandler("close-button"), {
- applyCssClass: "pressedSmallElement",
- });
-
- title.appendChild(closeBtn);
- this.inputReciever.backButton.add(() => this.internalButtonHandler("close-button"));
- }
-
- const content = document.createElement("div");
- content.classList.add("content");
- content.innerHTML = this.contentHTML;
- this.dialogElem.appendChild(content);
-
- if (this.buttonIds.length > 0) {
- const buttons = document.createElement("div");
- buttons.classList.add("buttons");
-
- // Create buttons
- for (let i = 0; i < this.buttonIds.length; ++i) {
- const [buttonId, buttonStyle, rawParams] = this.buttonIds[i].split(":");
-
- const button = document.createElement("button");
- button.classList.add("button");
- button.classList.add("styledButton");
- button.classList.add(buttonStyle);
- button.innerText = T.dialogs.buttons[buttonId];
-
- const params = (rawParams || "").split("/");
- const useTimeout = params.indexOf("timeout") >= 0;
-
- const isEnter = params.indexOf("enter") >= 0;
- const isEscape = params.indexOf("escape") >= 0;
-
- if (isEscape && this.closeButton) {
- logger.warn("Showing dialog with close button, and additional cancel button");
- }
-
- if (useTimeout) {
- button.classList.add("timedButton");
- const timeout = setTimeout(() => {
- button.classList.remove("timedButton");
- arrayDeleteValue(this.timeouts, timeout);
- }, 5000);
- this.timeouts.push(timeout);
- }
- if (isEnter || isEscape) {
- // if (this.app.settings.getShowKeyboardShortcuts()) {
- // Show keybinding
- const spacer = document.createElement("code");
- spacer.classList.add("keybinding");
- spacer.innerHTML = getStringForKeyCode(isEnter ? kbEnter : kbCancel);
- button.appendChild(spacer);
- // }
-
- if (isEnter) {
- this.enterHandler = buttonId;
- }
- if (isEscape) {
- this.escapeHandler = buttonId;
- }
- }
-
- this.trackClicks(button, () => this.internalButtonHandler(buttonId));
- buttons.appendChild(button);
- }
-
- this.dialogElem.appendChild(buttons);
- } else {
- this.dialogElem.classList.add("buttonless");
- }
-
- this.element = elem;
- this.app.inputMgr.pushReciever(this.inputReciever);
-
- return this.element;
- }
-
- setIndex(index) {
- this.element.style.zIndex = index;
- }
-
- destroy() {
- if (!this.element) {
- assert(false, "Tried to destroy dialog twice");
- return;
- }
- // We need to do this here, because if the backbutton event gets
- // dispatched to the modal dialogs, it will not call the internalButtonHandler,
- // and thus our receiver stays attached the whole time
- this.app.inputMgr.destroyReceiver(this.inputReciever);
-
- for (let i = 0; i < this.clickDetectors.length; ++i) {
- this.clickDetectors[i].cleanup();
- }
- this.clickDetectors = [];
-
- this.element.remove();
- this.element = null;
-
- for (let i = 0; i < this.timeouts.length; ++i) {
- clearTimeout(this.timeouts[i]);
- }
- this.timeouts = [];
- }
-
- hide() {
- this.element.classList.remove("visible");
- }
-
- show() {
- this.element.classList.add("visible");
- }
-
- /**
- * Helper method to track clicks on an element
- * @param {Element} elem
- * @param {function():void} handler
- * @param {import("./click_detector").ClickDetectorConstructorArgs=} args
- * @returns {ClickDetector}
- */
- trackClicks(elem, handler, args = {}) {
- const detector = new ClickDetector(elem, args);
- detector.click.add(handler, this);
- this.clickDetectors.push(detector);
- return detector;
- }
-}
-
-/**
- * Dialog which simply shows a loading spinner
- */
-export class DialogLoading extends Dialog {
- constructor(app) {
- super({
- app,
- title: "",
- contentHTML: "",
- buttons: [],
- type: "loading",
- });
-
- // Loading dialog can not get closed with back button
- this.inputReciever.backButton.removeAll();
- this.inputReciever.context = "dialog-loading";
- }
-
- createElement() {
- const elem = document.createElement("div");
- elem.classList.add("ingameDialog");
- elem.classList.add("loadingDialog");
- this.element = elem;
-
- const loader = document.createElement("div");
- loader.classList.add("prefab_LoadingTextWithAnim");
- loader.classList.add("loadingIndicator");
- loader.innerText = T.global.loading;
- elem.appendChild(loader);
-
- this.app.inputMgr.pushReciever(this.inputReciever);
-
- return elem;
- }
-}
-
-export class DialogOptionChooser extends Dialog {
- constructor({ app, title, options }) {
- let html = "";
-
- options.options.forEach(({ value, text, desc = null, iconPrefix = null }) => {
- const descHtml = desc ? `
${desc} ` : "";
- let iconHtml = iconPrefix ? `
` : "";
- html += `
-
- ${iconHtml}
- ${text}
- ${descHtml}
-
- `;
- });
-
- html += "
";
- super({
- app,
- title,
- contentHTML: html,
- buttons: [],
- type: "info",
- closeButton: true,
- });
-
- this.options = options;
- this.initialOption = options.active;
-
- this.buttonSignals.optionSelected = new Signal();
- }
-
- createElement() {
- const div = super.createElement();
- this.dialogElem.classList.add("optionChooserDialog");
-
- div.querySelectorAll("[data-optionvalue]").forEach(handle => {
- const value = handle.getAttribute("data-optionvalue");
- if (!handle) {
- logger.error("Failed to bind option value in dialog:", value);
- return;
- }
- // Need click detector here to forward elements, otherwise scrolling does not work
- const detector = new ClickDetector(handle, {
- consumeEvents: false,
- preventDefault: false,
- clickSound: null,
- applyCssClass: "pressedOption",
- targetOnly: true,
- });
- this.clickDetectors.push(detector);
-
- if (value !== this.initialOption) {
- detector.click.add(() => {
- const selected = div.querySelector(".option.active");
- if (selected) {
- selected.classList.remove("active");
- } else {
- logger.warn("No selected option");
- }
- handle.classList.add("active");
- this.app.sound.playUiSound(SOUNDS.uiClick);
- this.internalButtonHandler("optionSelected", value);
- });
- }
- });
- return div;
- }
-}
-
-export class DialogWithForm extends Dialog {
- /**
- *
- * @param {object} param0
- * @param {Application} param0.app
- * @param {string} param0.title
- * @param {string} param0.desc
- * @param {array=} param0.buttons
- * @param {string=} param0.confirmButtonId
- * @param {string=} param0.extraButton
- * @param {Array} param0.formElements
- */
- constructor({ app, title, desc, formElements, buttons = ["cancel", "ok:good"], confirmButtonId = "ok" }) {
- let html = "";
- html += desc + " ";
- for (let i = 0; i < formElements.length; ++i) {
- html += formElements[i].getHtml();
- }
-
- super({
- app,
- title: title,
- contentHTML: html,
- buttons: buttons,
- type: "info",
- closeButton: true,
- });
- this.confirmButtonId = confirmButtonId;
- this.formElements = formElements;
-
- this.enterHandler = confirmButtonId;
- }
-
- internalButtonHandler(id, ...payload) {
- if (id === this.confirmButtonId) {
- if (this.hasAnyInvalid()) {
- this.dialogElem.classList.remove("errorShake");
- waitNextFrame().then(() => {
- if (this.dialogElem) {
- this.dialogElem.classList.add("errorShake");
- }
- });
- this.app.sound.playUiSound(SOUNDS.uiError);
- return;
- }
- }
-
- super.internalButtonHandler(id, payload);
- }
-
- hasAnyInvalid() {
- for (let i = 0; i < this.formElements.length; ++i) {
- if (!this.formElements[i].isValid()) {
- return true;
- }
- }
- return false;
- }
-
- createElement() {
- const div = super.createElement();
-
- for (let i = 0; i < this.formElements.length; ++i) {
- const elem = this.formElements[i];
- elem.bindEvents(div, this.clickDetectors);
- }
-
- waitNextFrame().then(() => {
- this.formElements[0].focus();
- });
-
- return div;
- }
-}
+/* typehints:start */
+import { Application } from "../application";
+/* typehints:end */
+
+import { Signal, STOP_PROPAGATION } from "./signal";
+import { arrayDeleteValue, waitNextFrame } from "./utils";
+import { ClickDetector } from "./click_detector";
+import { SOUNDS } from "../platform/sound";
+import { InputReceiver } from "./input_receiver";
+import { FormElement } from "./modal_dialog_forms";
+import { globalConfig } from "./config";
+import { getStringForKeyCode } from "../game/key_action_mapper";
+import { createLogger } from "./logging";
+import { T } from "../translations";
+
+const kbEnter = 13;
+const kbCancel = 27;
+
+const logger = createLogger("dialogs");
+
+/**
+ * Basic text based dialog
+ */
+export class Dialog {
+ /**
+ *
+ * Constructs a new dialog with the given options
+ * @param {object} param0
+ * @param {Application} param0.app
+ * @param {string} param0.title Title of the dialog
+ * @param {string} param0.contentHTML Inner dialog html
+ * @param {Array} param0.buttons
+ * Button list, each button contains of up to 3 parts seperated by ':'.
+ * Part 0: The id, one of the one defined in dialog_buttons.yaml
+ * Part 1: The style, either good, bad or misc
+ * Part 2 (optional): Additional parameters seperated by '/', available are:
+ * timeout: This button is only available after some waiting time
+ * kb_enter: This button is triggered by the enter key
+ * kb_escape This button is triggered by the escape key
+ * @param {string=} param0.type The dialog type, either "info" or "warn"
+ * @param {boolean=} param0.closeButton Whether this dialog has a close button
+ */
+ constructor({ app, title, contentHTML, buttons, type = "info", closeButton = false }) {
+ this.app = app;
+ this.title = title;
+ this.contentHTML = contentHTML;
+ this.type = type;
+ this.buttonIds = buttons;
+ this.closeButton = closeButton;
+
+ this.closeRequested = new Signal();
+ this.buttonSignals = {};
+
+ for (let i = 0; i < buttons.length; ++i) {
+ if (G_IS_DEV && globalConfig.debug.disableTimedButtons) {
+ this.buttonIds[i] = this.buttonIds[i].replace(":timeout", "");
+ }
+
+ const buttonId = this.buttonIds[i].split(":")[0];
+ this.buttonSignals[buttonId] = new Signal();
+ }
+
+ this.timeouts = [];
+ this.clickDetectors = [];
+
+ this.inputReciever = new InputReceiver("dialog-" + this.title);
+
+ this.inputReciever.keydown.add(this.handleKeydown, this);
+
+ this.enterHandler = null;
+ this.escapeHandler = null;
+ }
+
+ /**
+ * Internal keydown handler
+ * @param {object} param0
+ * @param {number} param0.keyCode
+ * @param {boolean} param0.shift
+ * @param {boolean} param0.alt
+ */
+ handleKeydown({ keyCode, shift, alt }) {
+ if (keyCode === kbEnter && this.enterHandler) {
+ this.internalButtonHandler(this.enterHandler);
+ return STOP_PROPAGATION;
+ }
+
+ if (keyCode === kbCancel && this.escapeHandler) {
+ this.internalButtonHandler(this.escapeHandler);
+ return STOP_PROPAGATION;
+ }
+ }
+
+ internalButtonHandler(id, ...payload) {
+ this.app.inputMgr.popReciever(this.inputReciever);
+
+ if (id !== "close-button") {
+ this.buttonSignals[id].dispatch(...payload);
+ }
+ this.closeRequested.dispatch();
+ }
+
+ createElement() {
+ const elem = document.createElement("div");
+ elem.classList.add("ingameDialog");
+
+ this.dialogElem = document.createElement("div");
+ this.dialogElem.classList.add("dialogInner");
+
+ if (this.type) {
+ this.dialogElem.classList.add(this.type);
+ }
+ elem.appendChild(this.dialogElem);
+
+ const title = document.createElement("h1");
+ title.innerText = this.title;
+ title.classList.add("title");
+ this.dialogElem.appendChild(title);
+
+ if (this.closeButton) {
+ this.dialogElem.classList.add("hasCloseButton");
+
+ const closeBtn = document.createElement("button");
+ closeBtn.classList.add("closeButton");
+
+ this.trackClicks(closeBtn, () => this.internalButtonHandler("close-button"), {
+ applyCssClass: "pressedSmallElement",
+ });
+
+ title.appendChild(closeBtn);
+ this.inputReciever.backButton.add(() => this.internalButtonHandler("close-button"));
+ }
+
+ const content = document.createElement("div");
+ content.classList.add("content");
+ content.innerHTML = this.contentHTML;
+ this.dialogElem.appendChild(content);
+
+ if (this.buttonIds.length > 0) {
+ const buttons = document.createElement("div");
+ buttons.classList.add("buttons");
+
+ // Create buttons
+ for (let i = 0; i < this.buttonIds.length; ++i) {
+ const [buttonId, buttonStyle, rawParams] = this.buttonIds[i].split(":");
+
+ const button = document.createElement("button");
+ button.classList.add("button");
+ button.classList.add("styledButton");
+ button.classList.add(buttonStyle);
+ button.innerText = T.dialogs.buttons[buttonId];
+
+ const params = (rawParams || "").split("/");
+ const useTimeout = params.indexOf("timeout") >= 0;
+
+ const isEnter = params.indexOf("enter") >= 0;
+ const isEscape = params.indexOf("escape") >= 0;
+
+ if (isEscape && this.closeButton) {
+ logger.warn("Showing dialog with close button, and additional cancel button");
+ }
+
+ if (useTimeout) {
+ button.classList.add("timedButton");
+ const timeout = setTimeout(() => {
+ button.classList.remove("timedButton");
+ arrayDeleteValue(this.timeouts, timeout);
+ }, 5000);
+ this.timeouts.push(timeout);
+ }
+ if (isEnter || isEscape) {
+ // if (this.app.settings.getShowKeyboardShortcuts()) {
+ // Show keybinding
+ const spacer = document.createElement("code");
+ spacer.classList.add("keybinding");
+ spacer.innerHTML = getStringForKeyCode(isEnter ? kbEnter : kbCancel);
+ button.appendChild(spacer);
+ // }
+
+ if (isEnter) {
+ this.enterHandler = buttonId;
+ }
+ if (isEscape) {
+ this.escapeHandler = buttonId;
+ }
+ }
+
+ this.trackClicks(button, () => this.internalButtonHandler(buttonId));
+ buttons.appendChild(button);
+ }
+
+ this.dialogElem.appendChild(buttons);
+ } else {
+ this.dialogElem.classList.add("buttonless");
+ }
+
+ this.element = elem;
+ this.app.inputMgr.pushReciever(this.inputReciever);
+
+ return this.element;
+ }
+
+ setIndex(index) {
+ this.element.style.zIndex = index;
+ }
+
+ destroy() {
+ if (!this.element) {
+ assert(false, "Tried to destroy dialog twice");
+ return;
+ }
+ // We need to do this here, because if the backbutton event gets
+ // dispatched to the modal dialogs, it will not call the internalButtonHandler,
+ // and thus our receiver stays attached the whole time
+ this.app.inputMgr.destroyReceiver(this.inputReciever);
+
+ for (let i = 0; i < this.clickDetectors.length; ++i) {
+ this.clickDetectors[i].cleanup();
+ }
+ this.clickDetectors = [];
+
+ this.element.remove();
+ this.element = null;
+
+ for (let i = 0; i < this.timeouts.length; ++i) {
+ clearTimeout(this.timeouts[i]);
+ }
+ this.timeouts = [];
+ }
+
+ hide() {
+ this.element.classList.remove("visible");
+ }
+
+ show() {
+ this.element.classList.add("visible");
+ }
+
+ /**
+ * Helper method to track clicks on an element
+ * @param {Element} elem
+ * @param {function():void} handler
+ * @param {import("./click_detector").ClickDetectorConstructorArgs=} args
+ * @returns {ClickDetector}
+ */
+ trackClicks(elem, handler, args = {}) {
+ const detector = new ClickDetector(elem, args);
+ detector.click.add(handler, this);
+ this.clickDetectors.push(detector);
+ return detector;
+ }
+}
+
+/**
+ * Dialog which simply shows a loading spinner
+ */
+export class DialogLoading extends Dialog {
+ constructor(app) {
+ super({
+ app,
+ title: "",
+ contentHTML: "",
+ buttons: [],
+ type: "loading",
+ });
+
+ // Loading dialog can not get closed with back button
+ this.inputReciever.backButton.removeAll();
+ this.inputReciever.context = "dialog-loading";
+ }
+
+ createElement() {
+ const elem = document.createElement("div");
+ elem.classList.add("ingameDialog");
+ elem.classList.add("loadingDialog");
+ this.element = elem;
+
+ const loader = document.createElement("div");
+ loader.classList.add("prefab_LoadingTextWithAnim");
+ loader.classList.add("loadingIndicator");
+ loader.innerText = T.global.loading;
+ elem.appendChild(loader);
+
+ this.app.inputMgr.pushReciever(this.inputReciever);
+
+ return elem;
+ }
+}
+
+export class DialogOptionChooser extends Dialog {
+ constructor({ app, title, options }) {
+ let html = "";
+
+ options.options.forEach(({ value, text, desc = null, iconPrefix = null }) => {
+ const descHtml = desc ? `
${desc} ` : "";
+ let iconHtml = iconPrefix ? `
` : "";
+ html += `
+
+ ${iconHtml}
+ ${text}
+ ${descHtml}
+
+ `;
+ });
+
+ html += "
";
+ super({
+ app,
+ title,
+ contentHTML: html,
+ buttons: [],
+ type: "info",
+ closeButton: true,
+ });
+
+ this.options = options;
+ this.initialOption = options.active;
+
+ this.buttonSignals.optionSelected = new Signal();
+ }
+
+ createElement() {
+ const div = super.createElement();
+ this.dialogElem.classList.add("optionChooserDialog");
+
+ div.querySelectorAll("[data-optionvalue]").forEach(handle => {
+ const value = handle.getAttribute("data-optionvalue");
+ if (!handle) {
+ logger.error("Failed to bind option value in dialog:", value);
+ return;
+ }
+ // Need click detector here to forward elements, otherwise scrolling does not work
+ const detector = new ClickDetector(handle, {
+ consumeEvents: false,
+ preventDefault: false,
+ clickSound: null,
+ applyCssClass: "pressedOption",
+ targetOnly: true,
+ });
+ this.clickDetectors.push(detector);
+
+ if (value !== this.initialOption) {
+ detector.click.add(() => {
+ const selected = div.querySelector(".option.active");
+ if (selected) {
+ selected.classList.remove("active");
+ } else {
+ logger.warn("No selected option");
+ }
+ handle.classList.add("active");
+ this.app.sound.playUiSound(SOUNDS.uiClick);
+ this.internalButtonHandler("optionSelected", value);
+ });
+ }
+ });
+ return div;
+ }
+}
+
+export class DialogWithForm extends Dialog {
+ /**
+ *
+ * @param {object} param0
+ * @param {Application} param0.app
+ * @param {string} param0.title
+ * @param {string} param0.desc
+ * @param {array=} param0.buttons
+ * @param {string=} param0.confirmButtonId
+ * @param {string=} param0.extraButton
+ * @param {boolean=} param0.closeButton
+ * @param {Array} param0.formElements
+ */
+ constructor({
+ app,
+ title,
+ desc,
+ formElements,
+ buttons = ["cancel", "ok:good"],
+ confirmButtonId = "ok",
+ closeButton = true,
+ }) {
+ let html = "";
+ html += desc + " ";
+ for (let i = 0; i < formElements.length; ++i) {
+ html += formElements[i].getHtml();
+ }
+
+ super({
+ app,
+ title: title,
+ contentHTML: html,
+ buttons: buttons,
+ type: "info",
+ closeButton,
+ });
+ this.confirmButtonId = confirmButtonId;
+ this.formElements = formElements;
+
+ this.enterHandler = confirmButtonId;
+ }
+
+ internalButtonHandler(id, ...payload) {
+ if (id === this.confirmButtonId) {
+ if (this.hasAnyInvalid()) {
+ this.dialogElem.classList.remove("errorShake");
+ waitNextFrame().then(() => {
+ if (this.dialogElem) {
+ this.dialogElem.classList.add("errorShake");
+ }
+ });
+ this.app.sound.playUiSound(SOUNDS.uiError);
+ return;
+ }
+ }
+
+ super.internalButtonHandler(id, payload);
+ }
+
+ hasAnyInvalid() {
+ for (let i = 0; i < this.formElements.length; ++i) {
+ if (!this.formElements[i].isValid()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ createElement() {
+ const div = super.createElement();
+
+ for (let i = 0; i < this.formElements.length; ++i) {
+ const elem = this.formElements[i];
+ elem.bindEvents(div, this.clickDetectors);
+ }
+
+ waitNextFrame().then(() => {
+ this.formElements[0].focus();
+ });
+
+ return div;
+ }
+}
diff --git a/src/js/core/rectangle.js b/src/js/core/rectangle.js
index 1cbfdc27..f17825ca 100644
--- a/src/js/core/rectangle.js
+++ b/src/js/core/rectangle.js
@@ -53,27 +53,6 @@ export class Rectangle {
return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
}
- /**
- * Returns a rectangle arround a rotated point
- * @param {Array} points
- * @param {number} angle
- * @returns {Rectangle}
- */
- static getAroundPointsRotated(points, angle) {
- let minX = 1e10;
- let minY = 1e10;
- let maxX = -1e10;
- let maxY = -1e10;
- for (let i = 0; i < points.length; ++i) {
- const rotated = points[i].rotated(angle);
- minX = Math.min(minX, rotated.x);
- minY = Math.min(minY, rotated.y);
- maxX = Math.max(maxX, rotated.x);
- maxY = Math.max(maxY, rotated.y);
- }
- return new Rectangle(minX, minY, maxX - minX, maxY - minY);
- }
-
/**
* Copies this instance
* @returns {Rectangle}
@@ -82,28 +61,6 @@ export class Rectangle {
return new Rectangle(this.x, this.y, this.w, this.h);
}
- /**
- * Ensures the rectangle contains the given square
- * @param {number} centerX
- * @param {number} centerY
- * @param {number} halfWidth
- * @param {number} halfHeight
- */
- extendBySquare(centerX, centerY, halfWidth, halfHeight) {
- if (this.isEmpty()) {
- // Just assign values since this rectangle is empty
- this.x = centerX - halfWidth;
- this.y = centerY - halfHeight;
- this.w = halfWidth * 2;
- this.h = halfHeight * 2;
- } else {
- this.setLeft(Math.min(this.x, centerX - halfWidth));
- this.setRight(Math.max(this.right(), centerX + halfWidth));
- this.setTop(Math.min(this.y, centerY - halfHeight));
- this.setBottom(Math.max(this.bottom(), centerY + halfHeight));
- }
- }
-
/**
* Returns if this rectangle is empty
* @returns {boolean}
@@ -259,14 +216,6 @@ export class Rectangle {
return new Rectangle(this.x - amount, this.y - amount, this.w + 2 * amount, this.h + 2 * amount);
}
- /**
- * Helper for computing a culling area. Returns the top left tile
- * @returns {Vector}
- */
- getMinStartTile() {
- return new Vector(this.x, this.y).snapWorldToTile();
- }
-
/**
* Returns if the given rectangle is contained
* @param {Rectangle} rect
@@ -394,7 +343,7 @@ export class Rectangle {
}
/**
- * Returns a new recangle in tile space which includes all tiles which are visible in this rect
+ * Returns a new rectangle in tile space which includes all tiles which are visible in this rect
* @returns {Rectangle}
*/
toTileCullRectangle() {
diff --git a/src/js/game/buildings/cutter.js b/src/js/game/buildings/cutter.js
index 739f4a05..7dee4449 100644
--- a/src/js/game/buildings/cutter.js
+++ b/src/js/game/buildings/cutter.js
@@ -1,122 +1,122 @@
-import { formatItemsPerSecond } from "../../core/utils";
-import { enumDirection, Vector } from "../../core/vector";
-import { T } from "../../translations";
-import { ItemAcceptorComponent } from "../components/item_acceptor";
-import { ItemEjectorComponent } from "../components/item_ejector";
-import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
-import { Entity } from "../entity";
-import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
-import { GameRoot } from "../root";
-import { enumHubGoalRewards } from "../tutorial_goals";
-
-/** @enum {string} */
-export const enumCutterVariants = { quad: "quad" };
-
-export class MetaCutterBuilding extends MetaBuilding {
- constructor() {
- super("cutter");
- }
-
- getSilhouetteColor() {
- return "#7dcda2";
- }
-
- getDimensions(variant) {
- switch (variant) {
- case defaultBuildingVariant:
- return new Vector(2, 1);
- case enumCutterVariants.quad:
- return new Vector(4, 1);
- default:
- assertAlways(false, "Unknown splitter variant: " + variant);
- }
- }
-
- /**
- * @param {GameRoot} root
- * @param {string} variant
- * @returns {Array<[string, string]>}
- */
- getAdditionalStatistics(root, variant) {
- const speed = root.hubGoals.getProcessorBaseSpeed(
- variant === enumCutterVariants.quad
- ? enumItemProcessorTypes.cutterQuad
- : enumItemProcessorTypes.cutter
- );
- return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
- }
-
- /**
- * @param {GameRoot} root
- */
- getAvailableVariants(root) {
- if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_quad)) {
- return [defaultBuildingVariant, enumCutterVariants.quad];
- }
- return super.getAvailableVariants(root);
- }
-
- /**
- * @param {GameRoot} root
- */
- getIsUnlocked(root) {
- return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
- }
-
- /**
- * Creates the entity at the given location
- * @param {Entity} entity
- */
- setupEntityComponents(entity) {
- entity.addComponent(
- new ItemProcessorComponent({
- inputsPerCharge: 1,
- processorType: enumItemProcessorTypes.cutter,
- })
- );
- entity.addComponent(new ItemEjectorComponent({}));
- entity.addComponent(
- new ItemAcceptorComponent({
- slots: [
- {
- pos: new Vector(0, 0),
- directions: [enumDirection.bottom],
- filter: "shape",
- },
- ],
- })
- );
- }
-
- /**
- *
- * @param {Entity} entity
- * @param {number} rotationVariant
- * @param {string} variant
- */
- updateVariants(entity, rotationVariant, variant) {
- switch (variant) {
- case defaultBuildingVariant: {
- entity.components.ItemEjector.setSlots([
- { pos: new Vector(0, 0), direction: enumDirection.top },
- { pos: new Vector(1, 0), direction: enumDirection.top },
- ]);
- entity.components.ItemProcessor.type = enumItemProcessorTypes.cutter;
- break;
- }
- case enumCutterVariants.quad: {
- entity.components.ItemEjector.setSlots([
- { pos: new Vector(0, 0), direction: enumDirection.top },
- { pos: new Vector(1, 0), direction: enumDirection.top },
- { pos: new Vector(2, 0), direction: enumDirection.top },
- { pos: new Vector(3, 0), direction: enumDirection.top },
- ]);
- entity.components.ItemProcessor.type = enumItemProcessorTypes.cutterQuad;
- break;
- }
-
- default:
- assertAlways(false, "Unknown painter variant: " + variant);
- }
- }
-}
+import { formatItemsPerSecond } from "../../core/utils";
+import { enumDirection, Vector } from "../../core/vector";
+import { T } from "../../translations";
+import { ItemAcceptorComponent } from "../components/item_acceptor";
+import { ItemEjectorComponent } from "../components/item_ejector";
+import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
+import { Entity } from "../entity";
+import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
+import { GameRoot } from "../root";
+import { enumHubGoalRewards } from "../tutorial_goals";
+
+/** @enum {string} */
+export const enumCutterVariants = { quad: "quad" };
+
+export class MetaCutterBuilding extends MetaBuilding {
+ constructor() {
+ super("cutter");
+ }
+
+ getSilhouetteColor() {
+ return "#7dcda2";
+ }
+
+ getDimensions(variant) {
+ switch (variant) {
+ case defaultBuildingVariant:
+ return new Vector(2, 1);
+ case enumCutterVariants.quad:
+ return new Vector(4, 1);
+ default:
+ assertAlways(false, "Unknown cutter variant: " + variant);
+ }
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {string} variant
+ * @returns {Array<[string, string]>}
+ */
+ getAdditionalStatistics(root, variant) {
+ const speed = root.hubGoals.getProcessorBaseSpeed(
+ variant === enumCutterVariants.quad
+ ? enumItemProcessorTypes.cutterQuad
+ : enumItemProcessorTypes.cutter
+ );
+ return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getAvailableVariants(root) {
+ if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_quad)) {
+ return [defaultBuildingVariant, enumCutterVariants.quad];
+ }
+ return super.getAvailableVariants(root);
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getIsUnlocked(root) {
+ return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_cutter_and_trash);
+ }
+
+ /**
+ * Creates the entity at the given location
+ * @param {Entity} entity
+ */
+ setupEntityComponents(entity) {
+ entity.addComponent(
+ new ItemProcessorComponent({
+ inputsPerCharge: 1,
+ processorType: enumItemProcessorTypes.cutter,
+ })
+ );
+ entity.addComponent(new ItemEjectorComponent({}));
+ entity.addComponent(
+ new ItemAcceptorComponent({
+ slots: [
+ {
+ pos: new Vector(0, 0),
+ directions: [enumDirection.bottom],
+ filter: "shape",
+ },
+ ],
+ })
+ );
+ }
+
+ /**
+ *
+ * @param {Entity} entity
+ * @param {number} rotationVariant
+ * @param {string} variant
+ */
+ updateVariants(entity, rotationVariant, variant) {
+ switch (variant) {
+ case defaultBuildingVariant: {
+ entity.components.ItemEjector.setSlots([
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ { pos: new Vector(1, 0), direction: enumDirection.top },
+ ]);
+ entity.components.ItemProcessor.type = enumItemProcessorTypes.cutter;
+ break;
+ }
+ case enumCutterVariants.quad: {
+ entity.components.ItemEjector.setSlots([
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ { pos: new Vector(1, 0), direction: enumDirection.top },
+ { pos: new Vector(2, 0), direction: enumDirection.top },
+ { pos: new Vector(3, 0), direction: enumDirection.top },
+ ]);
+ entity.components.ItemProcessor.type = enumItemProcessorTypes.cutterQuad;
+ break;
+ }
+
+ default:
+ assertAlways(false, "Unknown painter variant: " + variant);
+ }
+ }
+}
diff --git a/src/js/game/buildings/lever.js b/src/js/game/buildings/lever.js
index e7e35888..244a9b17 100644
--- a/src/js/game/buildings/lever.js
+++ b/src/js/game/buildings/lever.js
@@ -1,61 +1,57 @@
-import { enumDirection, Vector } from "../../core/vector";
-import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
-import { Entity } from "../entity";
-import { MetaBuilding } from "../meta_building";
-import { GameRoot } from "../root";
-import { LeverComponent } from "../components/lever";
-
-export class MetaLeverBuilding extends MetaBuilding {
- constructor() {
- super("lever");
- }
-
- getSilhouetteColor() {
- // @todo: Render differently based on if its activated or not
- return "#1a678b";
- }
-
- /**
- * @param {GameRoot} root
- */
- getIsUnlocked(root) {
- // @todo
- return true;
- }
-
- getIsRotateable() {
- return false;
- }
-
- getDimensions() {
- return new Vector(1, 1);
- }
-
- getSprite() {
- return null;
- }
-
- getShowWiresLayerPreview() {
- return true;
- }
-
- /**
- * Creates the entity at the given location
- * @param {Entity} entity
- */
- setupEntityComponents(entity) {
- entity.addComponent(
- new WiredPinsComponent({
- slots: [
- {
- pos: new Vector(0, 0),
- direction: enumDirection.top,
- type: enumPinSlotType.logicalEjector,
- },
- ],
- })
- );
-
- entity.addComponent(new LeverComponent({}));
- }
-}
+import { enumDirection, Vector } from "../../core/vector";
+import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins";
+import { Entity } from "../entity";
+import { MetaBuilding } from "../meta_building";
+import { GameRoot } from "../root";
+import { LeverComponent } from "../components/lever";
+
+export class MetaLeverBuilding extends MetaBuilding {
+ constructor() {
+ super("lever");
+ }
+
+ getSilhouetteColor() {
+ // @todo: Render differently based on if its activated or not
+ return "#1a678b";
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getIsUnlocked(root) {
+ // @todo
+ return true;
+ }
+
+ getDimensions() {
+ return new Vector(1, 1);
+ }
+
+ getSprite() {
+ return null;
+ }
+
+ getShowWiresLayerPreview() {
+ return true;
+ }
+
+ /**
+ * Creates the entity at the given location
+ * @param {Entity} entity
+ */
+ setupEntityComponents(entity) {
+ entity.addComponent(
+ new WiredPinsComponent({
+ slots: [
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.top,
+ type: enumPinSlotType.logicalEjector,
+ },
+ ],
+ })
+ );
+
+ entity.addComponent(new LeverComponent({}));
+ }
+}
diff --git a/src/js/game/buildings/splitter.js b/src/js/game/buildings/splitter.js
index 219f02cf..d512e002 100644
--- a/src/js/game/buildings/splitter.js
+++ b/src/js/game/buildings/splitter.js
@@ -1,158 +1,197 @@
-import { enumDirection, Vector } from "../../core/vector";
-import { ItemAcceptorComponent } from "../components/item_acceptor";
-import { ItemEjectorComponent } from "../components/item_ejector";
-import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
-import { Entity } from "../entity";
-import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
-import { GameRoot } from "../root";
-import { enumHubGoalRewards } from "../tutorial_goals";
-import { T } from "../../translations";
-import { formatItemsPerSecond } from "../../core/utils";
-import { BeltUnderlaysComponent } from "../components/belt_underlays";
-
-/** @enum {string} */
-export const enumSplitterVariants = { compact: "compact", compactInverse: "compact-inverse" };
-
-export class MetaSplitterBuilding extends MetaBuilding {
- constructor() {
- super("splitter");
- }
-
- getDimensions(variant) {
- switch (variant) {
- case defaultBuildingVariant:
- return new Vector(2, 1);
- case enumSplitterVariants.compact:
- case enumSplitterVariants.compactInverse:
- return new Vector(1, 1);
- default:
- assertAlways(false, "Unknown splitter variant: " + variant);
- }
- }
-
- /**
- * @param {GameRoot} root
- * @param {string} variant
- * @returns {Array<[string, string]>}
- */
- getAdditionalStatistics(root, variant) {
- const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.splitter);
- return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
- }
-
- getSilhouetteColor() {
- return "#444";
- }
-
- /**
- * @param {GameRoot} root
- */
- getAvailableVariants(root) {
- if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter_compact)) {
- return [
- defaultBuildingVariant,
- enumSplitterVariants.compact,
- enumSplitterVariants.compactInverse,
- ];
- }
- return super.getAvailableVariants(root);
- }
-
- /**
- * @param {GameRoot} root
- */
- getIsUnlocked(root) {
- return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter);
- }
-
- /**
- * Creates the entity at the given location
- * @param {Entity} entity
- */
- setupEntityComponents(entity) {
- entity.addComponent(
- new ItemAcceptorComponent({
- slots: [], // set later
- })
- );
-
- entity.addComponent(
- new ItemProcessorComponent({
- inputsPerCharge: 1,
- processorType: enumItemProcessorTypes.splitter,
- })
- );
-
- entity.addComponent(
- new ItemEjectorComponent({
- slots: [], // set later
- })
- );
-
- entity.addComponent(new BeltUnderlaysComponent({ underlays: [] }));
- }
-
- /**
- *
- * @param {Entity} entity
- * @param {number} rotationVariant
- * @param {string} variant
- */
- updateVariants(entity, rotationVariant, variant) {
- switch (variant) {
- case defaultBuildingVariant: {
- entity.components.ItemAcceptor.setSlots([
- {
- pos: new Vector(0, 0),
- directions: [enumDirection.bottom],
- },
- {
- pos: new Vector(1, 0),
- directions: [enumDirection.bottom],
- },
- ]);
-
- entity.components.ItemEjector.setSlots([
- { pos: new Vector(0, 0), direction: enumDirection.top },
- { pos: new Vector(1, 0), direction: enumDirection.top },
- ]);
-
- entity.components.BeltUnderlays.underlays = [
- { pos: new Vector(0, 0), direction: enumDirection.top },
- { pos: new Vector(1, 0), direction: enumDirection.top },
- ];
-
- break;
- }
- case enumSplitterVariants.compact:
- case enumSplitterVariants.compactInverse: {
- entity.components.ItemAcceptor.setSlots([
- {
- pos: new Vector(0, 0),
- directions: [enumDirection.bottom],
- },
- {
- pos: new Vector(0, 0),
- directions: [
- variant === enumSplitterVariants.compactInverse
- ? enumDirection.left
- : enumDirection.right,
- ],
- },
- ]);
-
- entity.components.ItemEjector.setSlots([
- { pos: new Vector(0, 0), direction: enumDirection.top },
- ]);
-
- entity.components.BeltUnderlays.underlays = [
- { pos: new Vector(0, 0), direction: enumDirection.top },
- ];
-
- break;
- }
- default:
- assertAlways(false, "Unknown painter variant: " + variant);
- }
- }
-}
+import { enumDirection, Vector } from "../../core/vector";
+import { ItemAcceptorComponent } from "../components/item_acceptor";
+import { ItemEjectorComponent } from "../components/item_ejector";
+import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
+import { Entity } from "../entity";
+import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
+import { GameRoot } from "../root";
+import { enumHubGoalRewards } from "../tutorial_goals";
+import { T } from "../../translations";
+import { formatItemsPerSecond } from "../../core/utils";
+import { BeltUnderlaysComponent } from "../components/belt_underlays";
+
+/** @enum {string} */
+export const enumSplitterVariants = {
+ compact: "compact",
+ compactInverse: "compact-inverse",
+ compactMerge: "compact-merge",
+ compactMergeInverse: "compact-merge-inverse",
+};
+
+export class MetaSplitterBuilding extends MetaBuilding {
+ constructor() {
+ super("splitter");
+ }
+
+ getDimensions(variant) {
+ switch (variant) {
+ case defaultBuildingVariant:
+ return new Vector(2, 1);
+ case enumSplitterVariants.compact:
+ case enumSplitterVariants.compactInverse:
+ case enumSplitterVariants.compactMerge:
+ case enumSplitterVariants.compactMergeInverse:
+ return new Vector(1, 1);
+ default:
+ assertAlways(false, "Unknown splitter variant: " + variant);
+ }
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {string} variant
+ * @returns {Array<[string, string]>}
+ */
+ getAdditionalStatistics(root, variant) {
+ const speed = root.hubGoals.getProcessorBaseSpeed(enumItemProcessorTypes.splitter);
+ return [[T.ingame.buildingPlacement.infoTexts.speed, formatItemsPerSecond(speed)]];
+ }
+
+ getSilhouetteColor() {
+ return "#444";
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getAvailableVariants(root) {
+ let available = [defaultBuildingVariant];
+
+ if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter_compact)) {
+ available.push(enumSplitterVariants.compact, enumSplitterVariants.compactInverse);
+ }
+
+ if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_merger_compact)) {
+ available.push(enumSplitterVariants.compactMerge, enumSplitterVariants.compactMergeInverse);
+ }
+
+ return available;
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getIsUnlocked(root) {
+ return root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_splitter);
+ }
+
+ /**
+ * Creates the entity at the given location
+ * @param {Entity} entity
+ */
+ setupEntityComponents(entity) {
+ entity.addComponent(
+ new ItemAcceptorComponent({
+ slots: [], // set later
+ })
+ );
+
+ entity.addComponent(
+ new ItemProcessorComponent({
+ inputsPerCharge: 1,
+ processorType: enumItemProcessorTypes.splitter,
+ })
+ );
+
+ entity.addComponent(
+ new ItemEjectorComponent({
+ slots: [], // set later
+ })
+ );
+
+ entity.addComponent(new BeltUnderlaysComponent({ underlays: [] }));
+ }
+
+ /**
+ *
+ * @param {Entity} entity
+ * @param {number} rotationVariant
+ * @param {string} variant
+ */
+ updateVariants(entity, rotationVariant, variant) {
+ switch (variant) {
+ case defaultBuildingVariant: {
+ entity.components.ItemAcceptor.setSlots([
+ {
+ pos: new Vector(0, 0),
+ directions: [enumDirection.bottom],
+ },
+ {
+ pos: new Vector(1, 0),
+ directions: [enumDirection.bottom],
+ },
+ ]);
+
+ entity.components.ItemEjector.setSlots([
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ { pos: new Vector(1, 0), direction: enumDirection.top },
+ ]);
+
+ entity.components.BeltUnderlays.underlays = [
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ { pos: new Vector(1, 0), direction: enumDirection.top },
+ ];
+
+ break;
+ }
+ case enumSplitterVariants.compact:
+ case enumSplitterVariants.compactInverse: {
+ entity.components.ItemAcceptor.setSlots([
+ {
+ pos: new Vector(0, 0),
+ directions: [enumDirection.bottom],
+ },
+ {
+ pos: new Vector(0, 0),
+ directions: [
+ variant === enumSplitterVariants.compactInverse
+ ? enumDirection.left
+ : enumDirection.right,
+ ],
+ },
+ ]);
+
+ entity.components.ItemEjector.setSlots([
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ ]);
+
+ entity.components.BeltUnderlays.underlays = [
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ ];
+
+ break;
+ }
+ case enumSplitterVariants.compactMerge:
+ case enumSplitterVariants.compactMergeInverse: {
+ entity.components.ItemAcceptor.setSlots([
+ {
+ pos: new Vector(0, 0),
+ directions: [enumDirection.bottom],
+ },
+ ]);
+
+ entity.components.ItemEjector.setSlots([
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.top,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction:
+ variant === enumSplitterVariants.compactMergeInverse
+ ? enumDirection.left
+ : enumDirection.right,
+ },
+ ]);
+
+ entity.components.BeltUnderlays.underlays = [
+ { pos: new Vector(0, 0), direction: enumDirection.top },
+ ];
+
+ break;
+ }
+ default:
+ assertAlways(false, "Unknown splitter variant: " + variant);
+ }
+ }
+}
diff --git a/src/js/game/buildings/virtual_processor.js b/src/js/game/buildings/virtual_processor.js
new file mode 100644
index 00000000..dba8978a
--- /dev/null
+++ b/src/js/game/buildings/virtual_processor.js
@@ -0,0 +1,151 @@
+import { Vector, enumDirection } from "../../core/vector";
+import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate";
+import { WiredPinsComponent, enumPinSlotType } from "../components/wired_pins";
+import { Entity } from "../entity";
+import { defaultBuildingVariant, MetaBuilding } from "../meta_building";
+import { GameRoot } from "../root";
+
+/** @enum {string} */
+export const enumVirtualProcessorVariants = {
+ analyzer: "analyzer",
+ rotater: "rotater",
+ unstacker: "unstacker",
+ shapecompare: "shapecompare",
+};
+
+/** @enum {string} */
+export const enumVariantToGate = {
+ [defaultBuildingVariant]: enumLogicGateType.cutter,
+ [enumVirtualProcessorVariants.analyzer]: enumLogicGateType.analyzer,
+ [enumVirtualProcessorVariants.rotater]: enumLogicGateType.rotater,
+ [enumVirtualProcessorVariants.unstacker]: enumLogicGateType.unstacker,
+ [enumVirtualProcessorVariants.shapecompare]: enumLogicGateType.shapecompare,
+};
+
+export class MetaVirtualProcessorBuilding extends MetaBuilding {
+ constructor() {
+ super("virtual_processor");
+ }
+
+ getSilhouetteColor() {
+ return "#823cab";
+ }
+
+ /**
+ * @param {GameRoot} root
+ */
+ getIsUnlocked(root) {
+ // @todo
+ return true;
+ }
+
+ /** @returns {"wires"} **/
+ getLayer() {
+ return "wires";
+ }
+
+ getDimensions() {
+ return new Vector(1, 1);
+ }
+
+ getAvailableVariants() {
+ return [
+ defaultBuildingVariant,
+ enumVirtualProcessorVariants.rotater,
+ enumVirtualProcessorVariants.unstacker,
+ enumVirtualProcessorVariants.analyzer,
+ enumVirtualProcessorVariants.shapecompare,
+ ];
+ }
+
+ getRenderPins() {
+ // We already have it included
+ return false;
+ }
+
+ /**
+ *
+ * @param {Entity} entity
+ * @param {number} rotationVariant
+ */
+ updateVariants(entity, rotationVariant, variant) {
+ const gateType = enumVariantToGate[variant];
+ entity.components.LogicGate.type = gateType;
+ const pinComp = entity.components.WiredPins;
+ switch (gateType) {
+ case enumLogicGateType.cutter:
+ case enumLogicGateType.analyzer:
+ case enumLogicGateType.unstacker: {
+ pinComp.setSlots([
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.left,
+ type: enumPinSlotType.logicalEjector,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.right,
+ type: enumPinSlotType.logicalEjector,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.bottom,
+ type: enumPinSlotType.logicalAcceptor,
+ },
+ ]);
+ break;
+ }
+ case enumLogicGateType.rotater: {
+ pinComp.setSlots([
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.top,
+ type: enumPinSlotType.logicalEjector,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.bottom,
+ type: enumPinSlotType.logicalAcceptor,
+ },
+ ]);
+ break;
+ }
+ case enumLogicGateType.shapecompare: {
+ pinComp.setSlots([
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.top,
+ type: enumPinSlotType.logicalEjector,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.left,
+ type: enumPinSlotType.logicalAcceptor,
+ },
+ {
+ pos: new Vector(0, 0),
+ direction: enumDirection.right,
+ type: enumPinSlotType.logicalAcceptor,
+ },
+ ]);
+ break;
+ }
+ default:
+ assertAlways("unknown logic gate type: " + gateType);
+ }
+ }
+
+ /**
+ * Creates the entity at the given location
+ * @param {Entity} entity
+ */
+ setupEntityComponents(entity) {
+ entity.addComponent(
+ new WiredPinsComponent({
+ slots: [],
+ })
+ );
+
+ entity.addComponent(new LogicGateComponent({}));
+ }
+}
diff --git a/src/js/game/components/logic_gate.js b/src/js/game/components/logic_gate.js
index bf3f3477..fe151184 100644
--- a/src/js/game/components/logic_gate.js
+++ b/src/js/game/components/logic_gate.js
@@ -1,30 +1,36 @@
-import { Component } from "../component";
-
-/** @enum {string} */
-export const enumLogicGateType = {
- and: "and",
- not: "not",
- xor: "xor",
- or: "or",
- transistor: "transistor",
-};
-
-export class LogicGateComponent extends Component {
- static getId() {
- return "LogicGate";
- }
-
- duplicateWithoutContents() {
- return new LogicGateComponent({ type: this.type });
- }
-
- /**
- *
- * @param {object} param0
- * @param {enumLogicGateType=} param0.type
- */
- constructor({ type = enumLogicGateType.and }) {
- super();
- this.type = type;
- }
-}
+import { Component } from "../component";
+
+/** @enum {string} */
+export const enumLogicGateType = {
+ and: "and",
+ not: "not",
+ xor: "xor",
+ or: "or",
+ transistor: "transistor",
+
+ analyzer: "analyzer",
+ rotater: "rotater",
+ unstacker: "unstacker",
+ cutter: "cutter",
+ shapecompare: "shapecompare",
+};
+
+export class LogicGateComponent extends Component {
+ static getId() {
+ return "LogicGate";
+ }
+
+ duplicateWithoutContents() {
+ return new LogicGateComponent({ type: this.type });
+ }
+
+ /**
+ *
+ * @param {object} param0
+ * @param {enumLogicGateType=} param0.type
+ */
+ constructor({ type = enumLogicGateType.and }) {
+ super();
+ this.type = type;
+ }
+}
diff --git a/src/js/game/core.js b/src/js/game/core.js
index 3ca70bb6..bb8f2570 100644
--- a/src/js/game/core.js
+++ b/src/js/game/core.js
@@ -1,492 +1,492 @@
-/* typehints:start */
-import { Application } from "../application";
-/* typehints:end */
-import { BufferMaintainer } from "../core/buffer_maintainer";
-import { disableImageSmoothing, enableImageSmoothing, registerCanvas } from "../core/buffer_utils";
-import { globalConfig } from "../core/config";
-import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
-import { DrawParameters } from "../core/draw_parameters";
-import { gMetaBuildingRegistry } from "../core/global_registries";
-import { createLogger } from "../core/logging";
-import { Rectangle } from "../core/rectangle";
-import { randomInt, round2Digits, round3Digits } from "../core/utils";
-import { Vector } from "../core/vector";
-import { Savegame } from "../savegame/savegame";
-import { SavegameSerializer } from "../savegame/savegame_serializer";
-import { AutomaticSave } from "./automatic_save";
-import { MetaHubBuilding } from "./buildings/hub";
-import { Camera } from "./camera";
-import { DynamicTickrate } from "./dynamic_tickrate";
-import { EntityManager } from "./entity_manager";
-import { GameSystemManager } from "./game_system_manager";
-import { HubGoals } from "./hub_goals";
-import { GameHUD } from "./hud/hud";
-import { KeyActionMapper } from "./key_action_mapper";
-import { GameLogic } from "./logic";
-import { MapView } from "./map_view";
-import { defaultBuildingVariant } from "./meta_building";
-import { ProductionAnalytics } from "./production_analytics";
-import { GameRoot } from "./root";
-import { ShapeDefinitionManager } from "./shape_definition_manager";
-import { SoundProxy } from "./sound_proxy";
-import { GameTime } from "./time/game_time";
-import { ORIGINAL_SPRITE_SCALE } from "../core/sprites";
-
-const logger = createLogger("ingame/core");
-
-// Store the canvas so we can reuse it later
-/** @type {HTMLCanvasElement} */
-let lastCanvas = null;
-/** @type {CanvasRenderingContext2D} */
-let lastContext = null;
-
-/**
- * The core manages the root and represents the whole game. It wraps the root, since
- * the root class is just a data holder.
- */
-export class GameCore {
- /** @param {Application} app */
- constructor(app) {
- this.app = app;
-
- /** @type {GameRoot} */
- this.root = null;
-
- /**
- * Set to true at the beginning of a logic update and cleared when its finished.
- * This is to prevent doing a recursive logic update which can lead to unexpected
- * behaviour.
- */
- this.duringLogicUpdate = false;
-
- // Cached
- this.boundInternalTick = this.updateLogic.bind(this);
- }
-
- /**
- * Initializes the root object which stores all game related data. The state
- * is required as a back reference (used sometimes)
- * @param {import("../states/ingame").InGameState} parentState
- * @param {Savegame} savegame
- */
- initializeRoot(parentState, savegame) {
- // Construct the root element, this is the data representation of the game
- this.root = new GameRoot(this.app);
- this.root.gameState = parentState;
- this.root.keyMapper = parentState.keyActionMapper;
- this.root.savegame = savegame;
- this.root.gameWidth = this.app.screenWidth;
- this.root.gameHeight = this.app.screenHeight;
-
- // Initialize canvas element & context
- this.internalInitCanvas();
-
- // Members
- const root = this.root;
-
- // This isn't nice, but we need it right here
- root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever);
-
- // Needs to come first
- root.dynamicTickrate = new DynamicTickrate(root);
-
- // Init classes
- root.camera = new Camera(root);
- root.map = new MapView(root);
- root.logic = new GameLogic(root);
- root.hud = new GameHUD(root);
- root.time = new GameTime(root);
- root.automaticSave = new AutomaticSave(root);
- root.soundProxy = new SoundProxy(root);
-
- // Init managers
- root.entityMgr = new EntityManager(root);
- root.systemMgr = new GameSystemManager(root);
- root.shapeDefinitionMgr = new ShapeDefinitionManager(root);
- root.hubGoals = new HubGoals(root);
- root.productionAnalytics = new ProductionAnalytics(root);
- root.buffers = new BufferMaintainer(root);
-
- // Initialize the hud once everything is loaded
- this.root.hud.initialize();
-
- // Initial resize event, it might be possible that the screen
- // resized later during init tho, which is why will emit it later
- // again anyways
- this.resize(this.app.screenWidth, this.app.screenHeight);
-
- if (G_IS_DEV) {
- // @ts-ignore
- window.globalRoot = root;
- }
- }
-
- /**
- * Initializes a new game, this means creating a new map and centering on the
- * playerbase
- * */
- initNewGame() {
- logger.log("Initializing new game");
- this.root.gameIsFresh = true;
- this.root.map.seed = randomInt(0, 100000);
-
- // Place the hub
- const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
- root: this.root,
- origin: new Vector(-2, -2),
- rotation: 0,
- originalRotation: 0,
- rotationVariant: 0,
- variant: defaultBuildingVariant,
- });
- this.root.map.placeStaticEntity(hub);
- this.root.entityMgr.registerEntity(hub);
- }
-
- /**
- * Inits an existing game by loading the raw savegame data and deserializing it.
- * Also runs basic validity checks.
- */
- initExistingGame() {
- logger.log("Initializing existing game");
- const serializer = new SavegameSerializer();
-
- try {
- const status = serializer.deserialize(this.root.savegame.getCurrentDump(), this.root);
- if (!status.isGood()) {
- logger.error("savegame-deserialize-failed:" + status.reason);
- return false;
- }
- } catch (ex) {
- logger.error("Exception during deserialization:", ex);
- return false;
- }
- this.root.gameIsFresh = false;
- return true;
- }
-
- /**
- * Initializes the render canvas
- */
- internalInitCanvas() {
- let canvas, context;
- if (!lastCanvas) {
- logger.log("Creating new canvas");
- canvas = document.createElement("canvas");
- canvas.id = "ingame_Canvas";
- canvas.setAttribute("opaque", "true");
- canvas.setAttribute("webkitOpaque", "true");
- canvas.setAttribute("mozOpaque", "true");
- this.root.gameState.getDivElement().appendChild(canvas);
- context = canvas.getContext("2d", { alpha: false });
-
- lastCanvas = canvas;
- lastContext = context;
- } else {
- logger.log("Reusing canvas");
- if (lastCanvas.parentElement) {
- lastCanvas.parentElement.removeChild(lastCanvas);
- }
- this.root.gameState.getDivElement().appendChild(lastCanvas);
-
- canvas = lastCanvas;
- context = lastContext;
-
- lastContext.clearRect(0, 0, lastCanvas.width, lastCanvas.height);
- }
-
- // globalConfig.smoothing.smoothMainCanvas = getDeviceDPI() < 1.5;
- // globalConfig.smoothing.smoothMainCanvas = true;
-
- canvas.classList.toggle("smoothed", globalConfig.smoothing.smoothMainCanvas);
-
- // Oof, use :not() instead
- canvas.classList.toggle("unsmoothed", !globalConfig.smoothing.smoothMainCanvas);
-
- if (globalConfig.smoothing.smoothMainCanvas) {
- enableImageSmoothing(context);
- } else {
- disableImageSmoothing(context);
- }
-
- this.root.canvas = canvas;
- this.root.context = context;
-
- registerCanvas(canvas, context);
- }
-
- /**
- * Destructs the root, freeing all resources
- */
- destruct() {
- if (lastCanvas && lastCanvas.parentElement) {
- lastCanvas.parentElement.removeChild(lastCanvas);
- }
-
- this.root.destruct();
- delete this.root;
- this.root = null;
- this.app = null;
- }
-
- tick(deltaMs) {
- const root = this.root;
-
- // Extract current real time
- root.time.updateRealtimeNow();
-
- // Camera is always updated, no matter what
- root.camera.update(deltaMs);
-
- // Perform logic ticks
- this.root.time.performTicks(deltaMs, this.boundInternalTick);
-
- // Update analytics
- root.productionAnalytics.update();
-
- // Update automatic save after everything finished
- root.automaticSave.update();
-
- return true;
- }
-
- shouldRender() {
- if (this.root.queue.requireRedraw) {
- return true;
- }
- if (this.root.hud.shouldPauseRendering()) {
- return false;
- }
-
- // Do not render
- if (!this.app.isRenderable()) {
- return false;
- }
-
- return true;
- }
-
- updateLogic() {
- const root = this.root;
-
- root.dynamicTickrate.beginTick();
-
- if (G_IS_DEV && globalConfig.debug.disableLogicTicks) {
- root.dynamicTickrate.endTick();
- return true;
- }
-
- this.duringLogicUpdate = true;
-
- // Update entities, this removes destroyed entities
- root.entityMgr.update();
-
- // IMPORTANT: At this point, the game might be game over. Stop if this is the case
- if (!this.root) {
- logger.log("Root destructed, returning false");
- root.dynamicTickrate.endTick();
-
- return false;
- }
-
- root.systemMgr.update();
- // root.particleMgr.update();
-
- this.duringLogicUpdate = false;
- root.dynamicTickrate.endTick();
- return true;
- }
-
- resize(w, h) {
- this.root.gameWidth = w;
- this.root.gameHeight = h;
- resizeHighDPICanvas(this.root.canvas, w, h, globalConfig.smoothing.smoothMainCanvas);
- this.root.signals.resized.dispatch(w, h);
- this.root.queue.requireRedraw = true;
- }
-
- postLoadHook() {
- logger.log("Dispatching post load hook");
- this.root.signals.postLoadHook.dispatch();
-
- if (!this.root.gameIsFresh) {
- // Also dispatch game restored hook on restored savegames
- this.root.signals.gameRestored.dispatch();
- }
-
- this.root.gameInitialized = true;
- }
-
- draw() {
- const root = this.root;
- const systems = root.systemMgr.systems;
-
- this.root.dynamicTickrate.onFrameRendered();
-
- if (!this.shouldRender()) {
- // Always update hud tho
- root.hud.update();
- return;
- }
-
- this.root.signals.gameFrameStarted.dispatch();
-
- root.queue.requireRedraw = false;
-
- // Gather context and save all state
- const context = root.context;
- context.save();
- if (G_IS_DEV) {
- context.fillStyle = "#a10000";
- context.fillRect(0, 0, window.innerWidth * 3, window.innerHeight * 3);
- }
-
- // Compute optimal zoom level and atlas scale
- const zoomLevel = root.camera.zoomLevel;
- const lowQuality = root.app.settings.getAllSettings().lowQualityTextures;
- const effectiveZoomLevel =
- (zoomLevel / globalConfig.assetsDpi) * getDeviceDPI() * globalConfig.assetsSharpness;
-
- let desiredAtlasScale = "0.25";
- if (effectiveZoomLevel > 0.8 && !lowQuality) {
- desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
- } else if (effectiveZoomLevel > 0.4 && !lowQuality) {
- desiredAtlasScale = "0.5";
- }
-
- // Construct parameters required for drawing
- const params = new DrawParameters({
- context: context,
- visibleRect: root.camera.getVisibleRect(),
- desiredAtlasScale,
- zoomLevel,
- root: root,
- });
-
- if (G_IS_DEV && globalConfig.debug.testCulling) {
- context.clearRect(0, 0, root.gameWidth, root.gameHeight);
- }
-
- // Transform to world space
-
- if (G_IS_DEV && globalConfig.debug.testClipping) {
- params.visibleRect = params.visibleRect.expandedInAllDirections(
- -200 / this.root.camera.zoomLevel
- );
- }
-
- root.camera.transform(context);
-
- assert(context.globalAlpha === 1.0, "Global alpha not 1 on frame start");
-
- // Update hud
- root.hud.update();
-
- // Main rendering order
- // -----
-
- if (this.root.camera.getIsMapOverlayActive()) {
- // Map overview
- root.map.drawOverlay(params);
- } else {
- // Background (grid, resources, etc)
- root.map.drawBackground(params);
-
- // Belt items
- systems.belt.drawBeltItems(params);
-
- // Miner & Static map entities etc.
- root.map.drawForeground(params);
-
- // HUB Overlay
- systems.hub.draw(params);
-
- // Green wires overlay
- root.hud.parts.wiresOverlay.draw(params);
-
- if (this.root.currentLayer === "wires") {
- // Static map entities
- root.map.drawWiresForegroundLayer(params);
- }
- }
-
- if (G_IS_DEV) {
- root.map.drawStaticEntityDebugOverlays(params);
- }
-
- if (G_IS_DEV && globalConfig.debug.renderBeltPaths) {
- systems.belt.drawBeltPathDebug(params);
- }
-
- // END OF GAME CONTENT
- // -----
-
- // Finally, draw the hud. Nothing should come after that
- root.hud.draw(params);
-
- assert(context.globalAlpha === 1.0, "Global alpha not 1 on frame end before restore");
-
- // Restore to screen space
- context.restore();
-
- // Restore parameters
- params.zoomLevel = 1;
- params.desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
- params.visibleRect = new Rectangle(0, 0, this.root.gameWidth, this.root.gameHeight);
- if (G_IS_DEV && globalConfig.debug.testClipping) {
- params.visibleRect = params.visibleRect.expandedInAllDirections(-200);
- }
-
- // Draw overlays, those are screen space
- root.hud.drawOverlays(params);
-
- assert(context.globalAlpha === 1.0, "context.globalAlpha not 1 on frame end");
-
- if (G_IS_DEV && globalConfig.debug.simulateSlowRendering) {
- let sum = 0;
- for (let i = 0; i < 1e8; ++i) {
- sum += i;
- }
- if (Math.random() > 0.95) {
- console.log(sum);
- }
- }
-
- if (G_IS_DEV && globalConfig.debug.showAtlasInfo) {
- context.font = "13px GameFont";
- context.fillStyle = "blue";
- context.fillText(
- "Atlas: " +
- desiredAtlasScale +
- " / Zoom: " +
- round2Digits(zoomLevel) +
- " / Effective Zoom: " +
- round2Digits(effectiveZoomLevel),
- 20,
- 600
- );
-
- const stats = this.root.buffers.getStats();
- context.fillText(
- "Buffers: " +
- stats.rootKeys +
- " root keys, " +
- stats.subKeys +
- " sub keys / buffers / VRAM: " +
- round2Digits(stats.vramBytes / (1024 * 1024)) +
- " MB",
-
- 20,
- 620
- );
- }
-
- if (G_IS_DEV && globalConfig.debug.testClipping) {
- context.strokeStyle = "red";
- context.lineWidth = 1;
- context.beginPath();
- context.rect(200, 200, this.root.gameWidth - 400, this.root.gameHeight - 400);
- context.stroke();
- }
- }
-}
+/* typehints:start */
+import { Application } from "../application";
+/* typehints:end */
+import { BufferMaintainer } from "../core/buffer_maintainer";
+import { disableImageSmoothing, enableImageSmoothing, registerCanvas } from "../core/buffer_utils";
+import { globalConfig } from "../core/config";
+import { getDeviceDPI, resizeHighDPICanvas } from "../core/dpi_manager";
+import { DrawParameters } from "../core/draw_parameters";
+import { gMetaBuildingRegistry } from "../core/global_registries";
+import { createLogger } from "../core/logging";
+import { Rectangle } from "../core/rectangle";
+import { randomInt, round2Digits, round3Digits } from "../core/utils";
+import { Vector } from "../core/vector";
+import { Savegame } from "../savegame/savegame";
+import { SavegameSerializer } from "../savegame/savegame_serializer";
+import { AutomaticSave } from "./automatic_save";
+import { MetaHubBuilding } from "./buildings/hub";
+import { Camera } from "./camera";
+import { DynamicTickrate } from "./dynamic_tickrate";
+import { EntityManager } from "./entity_manager";
+import { GameSystemManager } from "./game_system_manager";
+import { HubGoals } from "./hub_goals";
+import { GameHUD } from "./hud/hud";
+import { KeyActionMapper } from "./key_action_mapper";
+import { GameLogic } from "./logic";
+import { MapView } from "./map_view";
+import { defaultBuildingVariant } from "./meta_building";
+import { ProductionAnalytics } from "./production_analytics";
+import { GameRoot } from "./root";
+import { ShapeDefinitionManager } from "./shape_definition_manager";
+import { SoundProxy } from "./sound_proxy";
+import { GameTime } from "./time/game_time";
+import { ORIGINAL_SPRITE_SCALE } from "../core/sprites";
+
+const logger = createLogger("ingame/core");
+
+// Store the canvas so we can reuse it later
+/** @type {HTMLCanvasElement} */
+let lastCanvas = null;
+/** @type {CanvasRenderingContext2D} */
+let lastContext = null;
+
+/**
+ * The core manages the root and represents the whole game. It wraps the root, since
+ * the root class is just a data holder.
+ */
+export class GameCore {
+ /** @param {Application} app */
+ constructor(app) {
+ this.app = app;
+
+ /** @type {GameRoot} */
+ this.root = null;
+
+ /**
+ * Set to true at the beginning of a logic update and cleared when its finished.
+ * This is to prevent doing a recursive logic update which can lead to unexpected
+ * behaviour.
+ */
+ this.duringLogicUpdate = false;
+
+ // Cached
+ this.boundInternalTick = this.updateLogic.bind(this);
+ }
+
+ /**
+ * Initializes the root object which stores all game related data. The state
+ * is required as a back reference (used sometimes)
+ * @param {import("../states/ingame").InGameState} parentState
+ * @param {Savegame} savegame
+ */
+ initializeRoot(parentState, savegame) {
+ // Construct the root element, this is the data representation of the game
+ this.root = new GameRoot(this.app);
+ this.root.gameState = parentState;
+ this.root.keyMapper = parentState.keyActionMapper;
+ this.root.savegame = savegame;
+ this.root.gameWidth = this.app.screenWidth;
+ this.root.gameHeight = this.app.screenHeight;
+
+ // Initialize canvas element & context
+ this.internalInitCanvas();
+
+ // Members
+ const root = this.root;
+
+ // This isn't nice, but we need it right here
+ root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever);
+
+ // Needs to come first
+ root.dynamicTickrate = new DynamicTickrate(root);
+
+ // Init classes
+ root.camera = new Camera(root);
+ root.map = new MapView(root);
+ root.logic = new GameLogic(root);
+ root.hud = new GameHUD(root);
+ root.time = new GameTime(root);
+ root.automaticSave = new AutomaticSave(root);
+ root.soundProxy = new SoundProxy(root);
+
+ // Init managers
+ root.entityMgr = new EntityManager(root);
+ root.systemMgr = new GameSystemManager(root);
+ root.shapeDefinitionMgr = new ShapeDefinitionManager(root);
+ root.hubGoals = new HubGoals(root);
+ root.productionAnalytics = new ProductionAnalytics(root);
+ root.buffers = new BufferMaintainer(root);
+
+ // Initialize the hud once everything is loaded
+ this.root.hud.initialize();
+
+ // Initial resize event, it might be possible that the screen
+ // resized later during init tho, which is why will emit it later
+ // again anyways
+ this.resize(this.app.screenWidth, this.app.screenHeight);
+
+ if (G_IS_DEV) {
+ // @ts-ignore
+ window.globalRoot = root;
+ }
+ }
+
+ /**
+ * Initializes a new game, this means creating a new map and centering on the
+ * playerbase
+ * */
+ initNewGame() {
+ logger.log("Initializing new game");
+ this.root.gameIsFresh = true;
+ this.root.map.seed = randomInt(0, 100000);
+
+ // Place the hub
+ const hub = gMetaBuildingRegistry.findByClass(MetaHubBuilding).createEntity({
+ root: this.root,
+ origin: new Vector(-2, -2),
+ rotation: 0,
+ originalRotation: 0,
+ rotationVariant: 0,
+ variant: defaultBuildingVariant,
+ });
+ this.root.map.placeStaticEntity(hub);
+ this.root.entityMgr.registerEntity(hub);
+ }
+
+ /**
+ * Inits an existing game by loading the raw savegame data and deserializing it.
+ * Also runs basic validity checks.
+ */
+ initExistingGame() {
+ logger.log("Initializing existing game");
+ const serializer = new SavegameSerializer();
+
+ try {
+ const status = serializer.deserialize(this.root.savegame.getCurrentDump(), this.root);
+ if (!status.isGood()) {
+ logger.error("savegame-deserialize-failed:" + status.reason);
+ return false;
+ }
+ } catch (ex) {
+ logger.error("Exception during deserialization:", ex);
+ return false;
+ }
+ this.root.gameIsFresh = false;
+ return true;
+ }
+
+ /**
+ * Initializes the render canvas
+ */
+ internalInitCanvas() {
+ let canvas, context;
+ if (!lastCanvas) {
+ logger.log("Creating new canvas");
+ canvas = document.createElement("canvas");
+ canvas.id = "ingame_Canvas";
+ canvas.setAttribute("opaque", "true");
+ canvas.setAttribute("webkitOpaque", "true");
+ canvas.setAttribute("mozOpaque", "true");
+ this.root.gameState.getDivElement().appendChild(canvas);
+ context = canvas.getContext("2d", { alpha: false });
+
+ lastCanvas = canvas;
+ lastContext = context;
+ } else {
+ logger.log("Reusing canvas");
+ if (lastCanvas.parentElement) {
+ lastCanvas.parentElement.removeChild(lastCanvas);
+ }
+ this.root.gameState.getDivElement().appendChild(lastCanvas);
+
+ canvas = lastCanvas;
+ context = lastContext;
+
+ lastContext.clearRect(0, 0, lastCanvas.width, lastCanvas.height);
+ }
+
+ // globalConfig.smoothing.smoothMainCanvas = getDeviceDPI() < 1.5;
+ // globalConfig.smoothing.smoothMainCanvas = true;
+
+ canvas.classList.toggle("smoothed", globalConfig.smoothing.smoothMainCanvas);
+
+ // Oof, use :not() instead
+ canvas.classList.toggle("unsmoothed", !globalConfig.smoothing.smoothMainCanvas);
+
+ if (globalConfig.smoothing.smoothMainCanvas) {
+ enableImageSmoothing(context);
+ } else {
+ disableImageSmoothing(context);
+ }
+
+ this.root.canvas = canvas;
+ this.root.context = context;
+
+ registerCanvas(canvas, context);
+ }
+
+ /**
+ * Destructs the root, freeing all resources
+ */
+ destruct() {
+ if (lastCanvas && lastCanvas.parentElement) {
+ lastCanvas.parentElement.removeChild(lastCanvas);
+ }
+
+ this.root.destruct();
+ delete this.root;
+ this.root = null;
+ this.app = null;
+ }
+
+ tick(deltaMs) {
+ const root = this.root;
+
+ // Extract current real time
+ root.time.updateRealtimeNow();
+
+ // Camera is always updated, no matter what
+ root.camera.update(deltaMs);
+
+ // Perform logic ticks
+ this.root.time.performTicks(deltaMs, this.boundInternalTick);
+
+ // Update analytics
+ root.productionAnalytics.update();
+
+ // Update automatic save after everything finished
+ root.automaticSave.update();
+
+ return true;
+ }
+
+ shouldRender() {
+ if (this.root.queue.requireRedraw) {
+ return true;
+ }
+ if (this.root.hud.shouldPauseRendering()) {
+ return false;
+ }
+
+ // Do not render
+ if (!this.app.isRenderable()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ updateLogic() {
+ const root = this.root;
+
+ root.dynamicTickrate.beginTick();
+
+ if (G_IS_DEV && globalConfig.debug.disableLogicTicks) {
+ root.dynamicTickrate.endTick();
+ return true;
+ }
+
+ this.duringLogicUpdate = true;
+
+ // Update entities, this removes destroyed entities
+ root.entityMgr.update();
+
+ // IMPORTANT: At this point, the game might be game over. Stop if this is the case
+ if (!this.root) {
+ logger.log("Root destructed, returning false");
+ root.dynamicTickrate.endTick();
+
+ return false;
+ }
+
+ root.systemMgr.update();
+ // root.particleMgr.update();
+
+ this.duringLogicUpdate = false;
+ root.dynamicTickrate.endTick();
+ return true;
+ }
+
+ resize(w, h) {
+ this.root.gameWidth = w;
+ this.root.gameHeight = h;
+ resizeHighDPICanvas(this.root.canvas, w, h, globalConfig.smoothing.smoothMainCanvas);
+ this.root.signals.resized.dispatch(w, h);
+ this.root.queue.requireRedraw = true;
+ }
+
+ postLoadHook() {
+ logger.log("Dispatching post load hook");
+ this.root.signals.postLoadHook.dispatch();
+
+ if (!this.root.gameIsFresh) {
+ // Also dispatch game restored hook on restored savegames
+ this.root.signals.gameRestored.dispatch();
+ }
+
+ this.root.gameInitialized = true;
+ }
+
+ draw() {
+ const root = this.root;
+ const systems = root.systemMgr.systems;
+
+ this.root.dynamicTickrate.onFrameRendered();
+
+ if (!this.shouldRender()) {
+ // Always update hud tho
+ root.hud.update();
+ return;
+ }
+
+ this.root.signals.gameFrameStarted.dispatch();
+
+ root.queue.requireRedraw = false;
+
+ // Gather context and save all state
+ const context = root.context;
+ context.save();
+ if (G_IS_DEV) {
+ context.fillStyle = "#a10000";
+ context.fillRect(0, 0, window.innerWidth * 3, window.innerHeight * 3);
+ }
+
+ // Compute optimal zoom level and atlas scale
+ const zoomLevel = root.camera.zoomLevel;
+ const lowQuality = root.app.settings.getAllSettings().lowQualityTextures;
+ const effectiveZoomLevel =
+ (zoomLevel / globalConfig.assetsDpi) * getDeviceDPI() * globalConfig.assetsSharpness;
+
+ let desiredAtlasScale = "0.25";
+ if (effectiveZoomLevel > 0.8 && !lowQuality) {
+ desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
+ } else if (effectiveZoomLevel > 0.4 && !lowQuality) {
+ desiredAtlasScale = "0.5";
+ }
+
+ // Construct parameters required for drawing
+ const params = new DrawParameters({
+ context: context,
+ visibleRect: root.camera.getVisibleRect(),
+ desiredAtlasScale,
+ zoomLevel,
+ root: root,
+ });
+
+ if (G_IS_DEV && globalConfig.debug.testCulling) {
+ context.clearRect(0, 0, root.gameWidth, root.gameHeight);
+ }
+
+ // Transform to world space
+
+ if (G_IS_DEV && globalConfig.debug.testClipping) {
+ params.visibleRect = params.visibleRect.expandedInAllDirections(
+ -200 / this.root.camera.zoomLevel
+ );
+ }
+
+ root.camera.transform(context);
+
+ assert(context.globalAlpha === 1.0, "Global alpha not 1 on frame start");
+
+ // Update hud
+ root.hud.update();
+
+ // Main rendering order
+ // -----
+
+ if (this.root.camera.getIsMapOverlayActive()) {
+ // Map overview
+ root.map.drawOverlay(params);
+ } else {
+ // Background (grid, resources, etc)
+ root.map.drawBackground(params);
+
+ // Belt items
+ systems.belt.drawBeltItems(params);
+
+ // Miner & Static map entities etc.
+ root.map.drawForeground(params);
+
+ // HUB Overlay
+ systems.hub.draw(params);
+
+ // Green wires overlay
+ root.hud.parts.wiresOverlay.draw(params);
+
+ if (this.root.currentLayer === "wires") {
+ // Static map entities
+ root.map.drawWiresForegroundLayer(params);
+ }
+ }
+
+ if (G_IS_DEV) {
+ root.map.drawStaticEntityDebugOverlays(params);
+ }
+
+ if (G_IS_DEV && globalConfig.debug.renderBeltPaths) {
+ systems.belt.drawBeltPathDebug(params);
+ }
+
+ // END OF GAME CONTENT
+ // -----
+
+ // Finally, draw the hud. Nothing should come after that
+ root.hud.draw(params);
+
+ assert(context.globalAlpha === 1.0, "Global alpha not 1 on frame end before restore");
+
+ // Restore to screen space
+ context.restore();
+
+ // Restore parameters
+ params.zoomLevel = 1;
+ params.desiredAtlasScale = ORIGINAL_SPRITE_SCALE;
+ params.visibleRect = new Rectangle(0, 0, this.root.gameWidth, this.root.gameHeight);
+ if (G_IS_DEV && globalConfig.debug.testClipping) {
+ params.visibleRect = params.visibleRect.expandedInAllDirections(-200);
+ }
+
+ // Draw overlays, those are screen space
+ root.hud.drawOverlays(params);
+
+ assert(context.globalAlpha === 1.0, "context.globalAlpha not 1 on frame end");
+
+ if (G_IS_DEV && globalConfig.debug.simulateSlowRendering) {
+ let sum = 0;
+ for (let i = 0; i < 1e8; ++i) {
+ sum += i;
+ }
+ if (Math.random() > 0.95) {
+ console.log(sum);
+ }
+ }
+
+ if (G_IS_DEV && globalConfig.debug.showAtlasInfo) {
+ context.font = "13px GameFont";
+ context.fillStyle = "blue";
+ context.fillText(
+ "Atlas: " +
+ desiredAtlasScale +
+ " / Zoom: " +
+ round2Digits(zoomLevel) +
+ " / Effective Zoom: " +
+ round2Digits(effectiveZoomLevel),
+ 20,
+ 600
+ );
+
+ const stats = this.root.buffers.getStats();
+ context.fillText(
+ "Buffers: " +
+ stats.rootKeys +
+ " root keys, " +
+ stats.subKeys +
+ " sub keys / buffers / VRAM: " +
+ round2Digits(stats.vramBytes / (1024 * 1024)) +
+ " MB",
+
+ 20,
+ 620
+ );
+ }
+
+ if (G_IS_DEV && globalConfig.debug.testClipping) {
+ context.strokeStyle = "red";
+ context.lineWidth = 1;
+ context.beginPath();
+ context.rect(200, 200, this.root.gameWidth - 400, this.root.gameHeight - 400);
+ context.stroke();
+ }
+ }
+}
diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js
index 461a3431..b86a9552 100644
--- a/src/js/game/hud/parts/building_placer.js
+++ b/src/js/game/hud/parts/building_placer.js
@@ -1,604 +1,605 @@
-import { ClickDetector } from "../../../core/click_detector";
-import { globalConfig } from "../../../core/config";
-import { DrawParameters } from "../../../core/draw_parameters";
-import { drawRotatedSprite } from "../../../core/draw_utils";
-import { Loader } from "../../../core/loader";
-import { clamp, makeDiv, removeAllChildren } from "../../../core/utils";
-import {
- enumDirectionToAngle,
- enumDirectionToVector,
- enumInvertedDirections,
- Vector,
- enumDirection,
-} from "../../../core/vector";
-import { T } from "../../../translations";
-import { KEYMAPPINGS } from "../../key_action_mapper";
-import { defaultBuildingVariant } from "../../meta_building";
-import { THEME } from "../../theme";
-import { DynamicDomAttach } from "../dynamic_dom_attach";
-import { HUDBuildingPlacerLogic } from "./building_placer_logic";
-import { makeOffscreenBuffer } from "../../../core/buffer_utils";
-import { layers } from "../../root";
-import { getCodeFromBuildingData } from "../../building_codes";
-
-export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
- /**
- * @param {HTMLElement} parent
- */
- createElements(parent) {
- this.element = makeDiv(parent, "ingame_HUD_PlacementHints", [], ``);
-
- this.buildingInfoElements = {};
- this.buildingInfoElements.label = makeDiv(this.element, null, ["buildingLabel"], "Extract");
- this.buildingInfoElements.desc = makeDiv(this.element, null, ["description"], "");
- this.buildingInfoElements.descText = makeDiv(this.buildingInfoElements.desc, null, ["text"], "");
- this.buildingInfoElements.additionalInfo = makeDiv(
- this.buildingInfoElements.desc,
- null,
- ["additionalInfo"],
- ""
- );
- this.buildingInfoElements.hotkey = makeDiv(this.buildingInfoElements.desc, null, ["hotkey"], "");
- this.buildingInfoElements.tutorialImage = makeDiv(this.element, null, ["buildingImage"]);
-
- this.variantsElement = makeDiv(parent, "ingame_HUD_PlacerVariants");
-
- const compact = this.root.app.settings.getAllSettings().compactBuildingInfo;
- this.element.classList.toggle("compact", compact);
- this.variantsElement.classList.toggle("compact", compact);
- }
-
- initialize() {
- super.initialize();
-
- // Bind to signals
- this.signals.variantChanged.add(this.rerenderVariants, this);
- this.root.hud.signals.buildingSelectedForPlacement.add(this.startSelection, this);
-
- this.domAttach = new DynamicDomAttach(this.root, this.element, {});
- this.variantsAttach = new DynamicDomAttach(this.root, this.variantsElement, {});
-
- this.currentInterpolatedCornerTile = new Vector();
-
- this.lockIndicatorSprites = {};
- layers.forEach(layer => {
- this.lockIndicatorSprites[layer] = this.makeLockIndicatorSprite(layer);
- });
-
- //
-
- /**
- * Stores the click detectors for the variants so we can clean them up later
- * @type {Array}
- */
- this.variantClickDetectors = [];
- }
-
- /**
- * Makes the lock indicator sprite for the given layer
- * @param {Layer} layer
- */
- makeLockIndicatorSprite(layer) {
- const dims = 48;
- const [canvas, context] = makeOffscreenBuffer(dims, dims, {
- smooth: true,
- reusable: false,
- label: "lock-direction-indicator",
- });
-
- context.fillStyle = THEME.map.directionLock[layer].color;
- context.strokeStyle = THEME.map.directionLock[layer].color;
- context.lineWidth = 2;
-
- const padding = 5;
- const height = dims * 0.5;
- const bottom = (dims + height) / 2;
-
- context.moveTo(padding, bottom);
- context.lineTo(dims / 2, bottom - height);
- context.lineTo(dims - padding, bottom);
- context.closePath();
- context.stroke();
- context.fill();
-
- return canvas;
- }
-
- /**
- * Rerenders the building info dialog
- */
- rerenderInfoDialog() {
- const metaBuilding = this.currentMetaBuilding.get();
-
- if (!metaBuilding) {
- return;
- }
-
- const variant = this.currentVariant.get();
-
- this.buildingInfoElements.label.innerHTML = T.buildings[metaBuilding.id][variant].name;
- this.buildingInfoElements.descText.innerHTML = T.buildings[metaBuilding.id][variant].description;
-
- const binding = this.root.keyMapper.getBinding(KEYMAPPINGS.buildings[metaBuilding.getId()]);
- this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
- "",
- "" + binding.getKeyCodeString() + "
"
- );
-
- this.buildingInfoElements.tutorialImage.setAttribute(
- "data-icon",
- "building_tutorials/" +
- metaBuilding.getId() +
- (variant === defaultBuildingVariant ? "" : "-" + variant) +
- ".png"
- );
-
- removeAllChildren(this.buildingInfoElements.additionalInfo);
- const additionalInfo = metaBuilding.getAdditionalStatistics(this.root, this.currentVariant.get());
- for (let i = 0; i < additionalInfo.length; ++i) {
- const [label, contents] = additionalInfo[i];
- this.buildingInfoElements.additionalInfo.innerHTML += `
- ${label}:
- ${contents}
- `;
- }
- }
-
- cleanup() {
- super.cleanup();
- this.cleanupVariantClickDetectors();
- }
-
- /**
- * Cleans up all variant click detectors
- */
- cleanupVariantClickDetectors() {
- for (let i = 0; i < this.variantClickDetectors.length; ++i) {
- const detector = this.variantClickDetectors[i];
- detector.cleanup();
- }
- this.variantClickDetectors = [];
- }
-
- /**
- * Rerenders the variants displayed
- */
- rerenderVariants() {
- removeAllChildren(this.variantsElement);
- this.rerenderInfoDialog();
-
- const metaBuilding = this.currentMetaBuilding.get();
-
- // First, clear up all click detectors
- this.cleanupVariantClickDetectors();
-
- if (!metaBuilding) {
- return;
- }
- const availableVariants = metaBuilding.getAvailableVariants(this.root);
- if (availableVariants.length === 1) {
- return;
- }
-
- makeDiv(
- this.variantsElement,
- null,
- ["explanation"],
- T.ingame.buildingPlacement.cycleBuildingVariants.replace(
- "",
- "" +
- this.root.keyMapper
- .getBinding(KEYMAPPINGS.placement.cycleBuildingVariants)
- .getKeyCodeString() +
- "
"
- )
- );
-
- const container = makeDiv(this.variantsElement, null, ["variants"]);
-
- for (let i = 0; i < availableVariants.length; ++i) {
- const variant = availableVariants[i];
-
- const element = makeDiv(container, null, ["variant"]);
- element.classList.toggle("active", variant === this.currentVariant.get());
- makeDiv(element, null, ["label"], variant);
-
- const iconSize = 64;
-
- const dimensions = metaBuilding.getDimensions(variant);
- const sprite = metaBuilding.getPreviewSprite(0, variant);
- const spriteWrapper = makeDiv(element, null, ["iconWrap"]);
- spriteWrapper.setAttribute("data-tile-w", dimensions.x);
- spriteWrapper.setAttribute("data-tile-h", dimensions.y);
-
- spriteWrapper.innerHTML = sprite.getAsHTML(iconSize * dimensions.x, iconSize * dimensions.y);
-
- const detector = new ClickDetector(element, {
- consumeEvents: true,
- targetOnly: true,
- });
- detector.click.add(() => this.setVariant(variant));
- }
- }
-
- /**
- *
- * @param {DrawParameters} parameters
- */
- draw(parameters) {
- if (this.root.camera.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
- // Dont allow placing in overview mode
- this.domAttach.update(false);
- this.variantsAttach.update(false);
- return;
- }
-
- this.domAttach.update(this.currentMetaBuilding.get());
- this.variantsAttach.update(this.currentMetaBuilding.get());
- const metaBuilding = this.currentMetaBuilding.get();
-
- if (!metaBuilding) {
- return;
- }
-
- // Draw direction lock
- if (this.isDirectionLockActive) {
- this.drawDirectionLock(parameters);
- } else {
- this.drawRegularPlacement(parameters);
- }
-
- if (metaBuilding.getShowWiresLayerPreview()) {
- this.drawLayerPeek(parameters);
- }
- }
-
- /**
- *
- * @param {DrawParameters} parameters
- */
- drawLayerPeek(parameters) {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return;
- }
-
- const worldPosition = this.root.camera.screenToWorld(mousePosition);
-
- // Draw peeker
- this.root.hud.parts.layerPreview.renderPreview(
- parameters,
- worldPosition,
- 1 / this.root.camera.zoomLevel
- );
- }
-
- /**
- * @param {DrawParameters} parameters
- */
- drawRegularPlacement(parameters) {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return;
- }
-
- const metaBuilding = this.currentMetaBuilding.get();
-
- const worldPos = this.root.camera.screenToWorld(mousePosition);
- const mouseTile = worldPos.toTileSpace();
-
- // Compute best rotation variant
- const {
- rotation,
- rotationVariant,
- connectedEntities,
- } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
- root: this.root,
- tile: mouseTile,
- rotation: this.currentBaseRotation,
- variant: this.currentVariant.get(),
- layer: metaBuilding.getLayer(),
- });
-
- // Check if there are connected entities
- if (connectedEntities) {
- for (let i = 0; i < connectedEntities.length; ++i) {
- const connectedEntity = connectedEntities[i];
- const connectedWsPoint = connectedEntity.components.StaticMapEntity.getTileSpaceBounds()
- .getCenter()
- .toWorldSpace();
-
- const startWsPoint = mouseTile.toWorldSpaceCenterOfTile();
-
- const startOffset = connectedWsPoint
- .sub(startWsPoint)
- .normalize()
- .multiplyScalar(globalConfig.tileSize * 0.3);
- const effectiveStartPoint = startWsPoint.add(startOffset);
- const effectiveEndPoint = connectedWsPoint.sub(startOffset);
-
- parameters.context.globalAlpha = 0.6;
-
- // parameters.context.lineCap = "round";
- parameters.context.strokeStyle = "#7f7";
- parameters.context.lineWidth = 10;
- parameters.context.beginPath();
- parameters.context.moveTo(effectiveStartPoint.x, effectiveStartPoint.y);
- parameters.context.lineTo(effectiveEndPoint.x, effectiveEndPoint.y);
- parameters.context.stroke();
- parameters.context.globalAlpha = 1;
- // parameters.context.lineCap = "square";
- }
- }
-
- // Synchronize rotation and origin
- const staticComp = this.fakeEntity.components.StaticMapEntity;
- staticComp.origin = mouseTile;
- staticComp.rotation = rotation;
- metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
- staticComp.code = getCodeFromBuildingData(
- this.currentMetaBuilding.get(),
- this.currentVariant.get(),
- rotationVariant
- );
-
- const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
-
- // Fade in / out
- parameters.context.lineWidth = 1;
-
- // Determine the bounds and visualize them
- const entityBounds = staticComp.getTileSpaceBounds();
- const drawBorder = -3;
- if (canBuild) {
- parameters.context.strokeStyle = "rgba(56, 235, 111, 0.5)";
- parameters.context.fillStyle = "rgba(56, 235, 111, 0.2)";
- } else {
- parameters.context.strokeStyle = "rgba(255, 0, 0, 0.2)";
- parameters.context.fillStyle = "rgba(255, 0, 0, 0.2)";
- }
-
- parameters.context.beginRoundedRect(
- entityBounds.x * globalConfig.tileSize - drawBorder,
- entityBounds.y * globalConfig.tileSize - drawBorder,
- entityBounds.w * globalConfig.tileSize + 2 * drawBorder,
- entityBounds.h * globalConfig.tileSize + 2 * drawBorder,
- 4
- );
- parameters.context.stroke();
- // parameters.context.fill();
- parameters.context.globalAlpha = 1;
-
- // HACK to draw the entity sprite
- const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get());
- staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
- staticComp.drawSpriteOnBoundsClipped(parameters, previewSprite);
- staticComp.origin = mouseTile;
-
- // Draw ejectors
- if (canBuild) {
- this.drawMatchingAcceptorsAndEjectors(parameters);
- }
- }
-
- /**
- * @param {DrawParameters} parameters
- */
- drawDirectionLock(parameters) {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return;
- }
-
- const mouseWorld = this.root.camera.screenToWorld(mousePosition);
- const mouseTile = mouseWorld.toTileSpace();
- parameters.context.fillStyle = THEME.map.directionLock[this.root.currentLayer].color;
- parameters.context.strokeStyle = THEME.map.directionLock[this.root.currentLayer].background;
- parameters.context.lineWidth = 10;
-
- parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
- parameters.context.fill();
-
- if (this.lastDragTile) {
- const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
- const endLine = mouseTile.toWorldSpaceCenterOfTile();
- const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
-
- parameters.context.beginCircle(startLine.x, startLine.y, 8);
- parameters.context.fill();
-
- parameters.context.beginPath();
- parameters.context.moveTo(startLine.x, startLine.y);
- parameters.context.lineTo(midLine.x, midLine.y);
- parameters.context.lineTo(endLine.x, endLine.y);
- parameters.context.stroke();
-
- parameters.context.beginCircle(endLine.x, endLine.y, 5);
- parameters.context.fill();
-
- // Draw arrow
- const arrowSprite = this.lockIndicatorSprites[this.root.currentLayer];
- const path = this.computeDirectionLockPath();
- for (let i = 0; i < path.length - 1; i += 1) {
- const { rotation, tile } = path[i];
- const worldPos = tile.toWorldSpaceCenterOfTile();
- const angle = Math.radians(rotation);
-
- parameters.context.translate(worldPos.x, worldPos.y);
- parameters.context.rotate(angle);
- parameters.context.drawImage(
- arrowSprite,
- -6,
- -globalConfig.halfTileSize -
- clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
- globalConfig.halfTileSize -
- 6,
- 12,
- 12
- );
- parameters.context.rotate(-angle);
- parameters.context.translate(-worldPos.x, -worldPos.y);
- }
- }
- }
-
- /**
- * @param {DrawParameters} parameters
- */
- drawMatchingAcceptorsAndEjectors(parameters) {
- const acceptorComp = this.fakeEntity.components.ItemAcceptor;
- const ejectorComp = this.fakeEntity.components.ItemEjector;
- const staticComp = this.fakeEntity.components.StaticMapEntity;
- const beltComp = this.fakeEntity.components.Belt;
-
- const goodArrowSprite = Loader.getSprite("sprites/misc/slot_good_arrow.png");
- const badArrowSprite = Loader.getSprite("sprites/misc/slot_bad_arrow.png");
-
- // Just ignore the following code please ... thanks!
-
- const offsetShift = 10;
-
- let acceptorSlots = [];
- let ejectorSlots = [];
-
- if (ejectorComp) {
- ejectorSlots = ejectorComp.slots.slice();
- }
-
- if (acceptorComp) {
- acceptorSlots = acceptorComp.slots.slice();
- }
-
- if (beltComp) {
- const fakeEjectorSlot = beltComp.getFakeEjectorSlot();
- const fakeAcceptorSlot = beltComp.getFakeAcceptorSlot();
- ejectorSlots.push(fakeEjectorSlot);
- acceptorSlots.push(fakeAcceptorSlot);
- }
-
- for (let acceptorSlotIndex = 0; acceptorSlotIndex < acceptorSlots.length; ++acceptorSlotIndex) {
- const slot = acceptorSlots[acceptorSlotIndex];
-
- const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
- const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
-
- // Go over all slots
- for (
- let acceptorDirectionIndex = 0;
- acceptorDirectionIndex < slot.directions.length;
- ++acceptorDirectionIndex
- ) {
- const direction = slot.directions[acceptorDirectionIndex];
- const worldDirection = staticComp.localDirectionToWorld(direction);
-
- // Figure out which tile ejects to this slot
- const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
-
- let isBlocked = false;
- let isConnected = false;
-
- // Find all entities which are on that tile
- const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
-
- // Check for every entity:
- for (let i = 0; i < sourceEntities.length; ++i) {
- const sourceEntity = sourceEntities[i];
- const sourceEjector = sourceEntity.components.ItemEjector;
- const sourceBeltComp = sourceEntity.components.Belt;
- const sourceStaticComp = sourceEntity.components.StaticMapEntity;
- const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
-
- // If this entity is on the same layer as the slot - if so, it can either be
- // connected, or it can not be connected and thus block the input
- if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
- // This one is connected, all good
- isConnected = true;
- } else if (
- sourceBeltComp &&
- sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
- enumInvertedDirections[worldDirection]
- ) {
- // Belt connected
- isConnected = true;
- } else {
- // This one is blocked
- isBlocked = true;
- }
- }
-
- const alpha = isConnected || isBlocked ? 1.0 : 0.3;
- const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
-
- parameters.context.globalAlpha = alpha;
- drawRotatedSprite({
- parameters,
- sprite,
- x: acceptorSlotWsPos.x,
- y: acceptorSlotWsPos.y,
- angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
- size: 13,
- offsetY: offsetShift + 13,
- });
- parameters.context.globalAlpha = 1;
- }
- }
-
- // Go over all slots
- for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorSlots.length; ++ejectorSlotIndex) {
- const slot = ejectorSlots[ejectorSlotIndex];
-
- const ejectorSlotLocalTile = slot.pos.add(enumDirectionToVector[slot.direction]);
- const ejectorSlotWsTile = staticComp.localTileToWorld(ejectorSlotLocalTile);
-
- const ejectorSLotWsPos = ejectorSlotWsTile.toWorldSpaceCenterOfTile();
- const ejectorSlotWsDirection = staticComp.localDirectionToWorld(slot.direction);
-
- let isBlocked = false;
- let isConnected = false;
-
- // Find all entities which are on that tile
- const destEntities = this.root.map.getLayersContentsMultipleXY(
- ejectorSlotWsTile.x,
- ejectorSlotWsTile.y
- );
-
- // Check for every entity:
- for (let i = 0; i < destEntities.length; ++i) {
- const destEntity = destEntities[i];
- const destAcceptor = destEntity.components.ItemAcceptor;
- const destStaticComp = destEntity.components.StaticMapEntity;
-
- const destLocalTile = destStaticComp.worldToLocalTile(ejectorSlotWsTile);
- const destLocalDir = destStaticComp.worldDirectionToLocal(ejectorSlotWsDirection);
- if (destAcceptor && destAcceptor.findMatchingSlot(destLocalTile, destLocalDir)) {
- // This one is connected, all good
- isConnected = true;
- } else if (destEntity.components.Belt && destLocalDir === enumDirection.top) {
- // Connected to a belt
- isConnected = true;
- } else {
- // This one is blocked
- isBlocked = true;
- }
- }
-
- const alpha = isConnected || isBlocked ? 1.0 : 0.3;
- const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
-
- parameters.context.globalAlpha = alpha;
- drawRotatedSprite({
- parameters,
- sprite,
- x: ejectorSLotWsPos.x,
- y: ejectorSLotWsPos.y,
- angle: Math.radians(enumDirectionToAngle[ejectorSlotWsDirection]),
- size: 13,
- offsetY: offsetShift,
- });
- parameters.context.globalAlpha = 1;
- }
- }
-}
+import { ClickDetector } from "../../../core/click_detector";
+import { globalConfig } from "../../../core/config";
+import { DrawParameters } from "../../../core/draw_parameters";
+import { drawRotatedSprite } from "../../../core/draw_utils";
+import { Loader } from "../../../core/loader";
+import { clamp, makeDiv, removeAllChildren } from "../../../core/utils";
+import {
+ enumDirectionToAngle,
+ enumDirectionToVector,
+ enumInvertedDirections,
+ Vector,
+ enumDirection,
+} from "../../../core/vector";
+import { T } from "../../../translations";
+import { KEYMAPPINGS } from "../../key_action_mapper";
+import { defaultBuildingVariant } from "../../meta_building";
+import { THEME } from "../../theme";
+import { DynamicDomAttach } from "../dynamic_dom_attach";
+import { HUDBuildingPlacerLogic } from "./building_placer_logic";
+import { makeOffscreenBuffer } from "../../../core/buffer_utils";
+import { layers } from "../../root";
+import { getCodeFromBuildingData } from "../../building_codes";
+
+export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
+ /**
+ * @param {HTMLElement} parent
+ */
+ createElements(parent) {
+ this.element = makeDiv(parent, "ingame_HUD_PlacementHints", [], ``);
+
+ this.buildingInfoElements = {};
+ this.buildingInfoElements.label = makeDiv(this.element, null, ["buildingLabel"], "Extract");
+ this.buildingInfoElements.desc = makeDiv(this.element, null, ["description"], "");
+ this.buildingInfoElements.descText = makeDiv(this.buildingInfoElements.desc, null, ["text"], "");
+ this.buildingInfoElements.additionalInfo = makeDiv(
+ this.buildingInfoElements.desc,
+ null,
+ ["additionalInfo"],
+ ""
+ );
+ this.buildingInfoElements.hotkey = makeDiv(this.buildingInfoElements.desc, null, ["hotkey"], "");
+ this.buildingInfoElements.tutorialImage = makeDiv(this.element, null, ["buildingImage"]);
+
+ this.variantsElement = makeDiv(parent, "ingame_HUD_PlacerVariants");
+
+ const compact = this.root.app.settings.getAllSettings().compactBuildingInfo;
+ this.element.classList.toggle("compact", compact);
+ this.variantsElement.classList.toggle("compact", compact);
+ }
+
+ initialize() {
+ super.initialize();
+
+ // Bind to signals
+ this.signals.variantChanged.add(this.rerenderVariants, this);
+ this.root.hud.signals.buildingSelectedForPlacement.add(this.startSelection, this);
+
+ this.domAttach = new DynamicDomAttach(this.root, this.element, {});
+ this.variantsAttach = new DynamicDomAttach(this.root, this.variantsElement, {});
+
+ this.currentInterpolatedCornerTile = new Vector();
+
+ this.lockIndicatorSprites = {};
+ layers.forEach(layer => {
+ this.lockIndicatorSprites[layer] = this.makeLockIndicatorSprite(layer);
+ });
+
+ //
+
+ /**
+ * Stores the click detectors for the variants so we can clean them up later
+ * @type {Array}
+ */
+ this.variantClickDetectors = [];
+ }
+
+ /**
+ * Makes the lock indicator sprite for the given layer
+ * @param {Layer} layer
+ */
+ makeLockIndicatorSprite(layer) {
+ const dims = 48;
+ const [canvas, context] = makeOffscreenBuffer(dims, dims, {
+ smooth: true,
+ reusable: false,
+ label: "lock-direction-indicator",
+ });
+
+ context.fillStyle = THEME.map.directionLock[layer].color;
+ context.strokeStyle = THEME.map.directionLock[layer].color;
+ context.lineWidth = 2;
+
+ const padding = 5;
+ const height = dims * 0.5;
+ const bottom = (dims + height) / 2;
+
+ context.moveTo(padding, bottom);
+ context.lineTo(dims / 2, bottom - height);
+ context.lineTo(dims - padding, bottom);
+ context.closePath();
+ context.stroke();
+ context.fill();
+
+ return canvas;
+ }
+
+ /**
+ * Rerenders the building info dialog
+ */
+ rerenderInfoDialog() {
+ const metaBuilding = this.currentMetaBuilding.get();
+
+ if (!metaBuilding) {
+ return;
+ }
+
+ const variant = this.currentVariant.get();
+
+ this.buildingInfoElements.label.innerHTML = T.buildings[metaBuilding.id][variant].name;
+ this.buildingInfoElements.descText.innerHTML = T.buildings[metaBuilding.id][variant].description;
+
+ const binding = this.root.keyMapper.getBinding(KEYMAPPINGS.buildings[metaBuilding.getId()]);
+ this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace(
+ "",
+ "" + binding.getKeyCodeString() + "
"
+ );
+
+ this.buildingInfoElements.tutorialImage.setAttribute(
+ "data-icon",
+ "building_tutorials/" +
+ metaBuilding.getId() +
+ (variant === defaultBuildingVariant ? "" : "-" + variant) +
+ ".png"
+ );
+
+ removeAllChildren(this.buildingInfoElements.additionalInfo);
+ const additionalInfo = metaBuilding.getAdditionalStatistics(this.root, this.currentVariant.get());
+ for (let i = 0; i < additionalInfo.length; ++i) {
+ const [label, contents] = additionalInfo[i];
+ this.buildingInfoElements.additionalInfo.innerHTML += `
+ ${label}:
+ ${contents}
+ `;
+ }
+ }
+
+ cleanup() {
+ super.cleanup();
+ this.cleanupVariantClickDetectors();
+ }
+
+ /**
+ * Cleans up all variant click detectors
+ */
+ cleanupVariantClickDetectors() {
+ for (let i = 0; i < this.variantClickDetectors.length; ++i) {
+ const detector = this.variantClickDetectors[i];
+ detector.cleanup();
+ }
+ this.variantClickDetectors = [];
+ }
+
+ /**
+ * Rerenders the variants displayed
+ */
+ rerenderVariants() {
+ removeAllChildren(this.variantsElement);
+ this.rerenderInfoDialog();
+
+ const metaBuilding = this.currentMetaBuilding.get();
+
+ // First, clear up all click detectors
+ this.cleanupVariantClickDetectors();
+
+ if (!metaBuilding) {
+ return;
+ }
+ const availableVariants = metaBuilding.getAvailableVariants(this.root);
+ if (availableVariants.length === 1) {
+ return;
+ }
+
+ makeDiv(
+ this.variantsElement,
+ null,
+ ["explanation"],
+ T.ingame.buildingPlacement.cycleBuildingVariants.replace(
+ "",
+ "" +
+ this.root.keyMapper
+ .getBinding(KEYMAPPINGS.placement.cycleBuildingVariants)
+ .getKeyCodeString() +
+ "
"
+ )
+ );
+
+ const container = makeDiv(this.variantsElement, null, ["variants"]);
+
+ for (let i = 0; i < availableVariants.length; ++i) {
+ const variant = availableVariants[i];
+
+ const element = makeDiv(container, null, ["variant"]);
+ element.classList.toggle("active", variant === this.currentVariant.get());
+ makeDiv(element, null, ["label"], variant);
+
+ const iconSize = 64;
+
+ const dimensions = metaBuilding.getDimensions(variant);
+ const sprite = metaBuilding.getPreviewSprite(0, variant);
+ const spriteWrapper = makeDiv(element, null, ["iconWrap"]);
+ spriteWrapper.setAttribute("data-tile-w", dimensions.x);
+ spriteWrapper.setAttribute("data-tile-h", dimensions.y);
+
+ spriteWrapper.innerHTML = sprite.getAsHTML(iconSize * dimensions.x, iconSize * dimensions.y);
+
+ const detector = new ClickDetector(element, {
+ consumeEvents: true,
+ targetOnly: true,
+ });
+ detector.click.add(() => this.setVariant(variant));
+ }
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ draw(parameters) {
+ if (this.root.camera.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
+ // Dont allow placing in overview mode
+ this.domAttach.update(false);
+ this.variantsAttach.update(false);
+ return;
+ }
+
+ this.domAttach.update(this.currentMetaBuilding.get());
+ this.variantsAttach.update(this.currentMetaBuilding.get());
+ const metaBuilding = this.currentMetaBuilding.get();
+
+ if (!metaBuilding) {
+ return;
+ }
+
+ // Draw direction lock
+ if (this.isDirectionLockActive) {
+ this.drawDirectionLock(parameters);
+ } else {
+ this.drawRegularPlacement(parameters);
+ }
+
+ if (metaBuilding.getShowWiresLayerPreview()) {
+ this.drawLayerPeek(parameters);
+ }
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ drawLayerPeek(parameters) {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return;
+ }
+
+ const worldPosition = this.root.camera.screenToWorld(mousePosition);
+
+ // Draw peeker
+ this.root.hud.parts.layerPreview.renderPreview(
+ parameters,
+ worldPosition,
+ 1 / this.root.camera.zoomLevel
+ );
+ }
+
+ /**
+ * @param {DrawParameters} parameters
+ */
+ drawRegularPlacement(parameters) {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return;
+ }
+
+ const metaBuilding = this.currentMetaBuilding.get();
+
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ const mouseTile = worldPos.toTileSpace();
+
+ // Compute best rotation variant
+ const {
+ rotation,
+ rotationVariant,
+ connectedEntities,
+ } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
+ root: this.root,
+ tile: mouseTile,
+ rotation: this.currentBaseRotation,
+ variant: this.currentVariant.get(),
+ layer: metaBuilding.getLayer(),
+ });
+
+ // Check if there are connected entities
+ if (connectedEntities) {
+ for (let i = 0; i < connectedEntities.length; ++i) {
+ const connectedEntity = connectedEntities[i];
+ const connectedWsPoint = connectedEntity.components.StaticMapEntity.getTileSpaceBounds()
+ .getCenter()
+ .toWorldSpace();
+
+ const startWsPoint = mouseTile.toWorldSpaceCenterOfTile();
+
+ const startOffset = connectedWsPoint
+ .sub(startWsPoint)
+ .normalize()
+ .multiplyScalar(globalConfig.tileSize * 0.3);
+ const effectiveStartPoint = startWsPoint.add(startOffset);
+ const effectiveEndPoint = connectedWsPoint.sub(startOffset);
+
+ parameters.context.globalAlpha = 0.6;
+
+ // parameters.context.lineCap = "round";
+ parameters.context.strokeStyle = "#7f7";
+ parameters.context.lineWidth = 10;
+ parameters.context.beginPath();
+ parameters.context.moveTo(effectiveStartPoint.x, effectiveStartPoint.y);
+ parameters.context.lineTo(effectiveEndPoint.x, effectiveEndPoint.y);
+ parameters.context.stroke();
+ parameters.context.globalAlpha = 1;
+ // parameters.context.lineCap = "square";
+ }
+ }
+
+ // Synchronize rotation and origin
+ this.fakeEntity.layer = metaBuilding.getLayer();
+ const staticComp = this.fakeEntity.components.StaticMapEntity;
+ staticComp.origin = mouseTile;
+ staticComp.rotation = rotation;
+ metaBuilding.updateVariants(this.fakeEntity, rotationVariant, this.currentVariant.get());
+ staticComp.code = getCodeFromBuildingData(
+ this.currentMetaBuilding.get(),
+ this.currentVariant.get(),
+ rotationVariant
+ );
+
+ const canBuild = this.root.logic.checkCanPlaceEntity(this.fakeEntity);
+
+ // Fade in / out
+ parameters.context.lineWidth = 1;
+
+ // Determine the bounds and visualize them
+ const entityBounds = staticComp.getTileSpaceBounds();
+ const drawBorder = -3;
+ if (canBuild) {
+ parameters.context.strokeStyle = "rgba(56, 235, 111, 0.5)";
+ parameters.context.fillStyle = "rgba(56, 235, 111, 0.2)";
+ } else {
+ parameters.context.strokeStyle = "rgba(255, 0, 0, 0.2)";
+ parameters.context.fillStyle = "rgba(255, 0, 0, 0.2)";
+ }
+
+ parameters.context.beginRoundedRect(
+ entityBounds.x * globalConfig.tileSize - drawBorder,
+ entityBounds.y * globalConfig.tileSize - drawBorder,
+ entityBounds.w * globalConfig.tileSize + 2 * drawBorder,
+ entityBounds.h * globalConfig.tileSize + 2 * drawBorder,
+ 4
+ );
+ parameters.context.stroke();
+ // parameters.context.fill();
+ parameters.context.globalAlpha = 1;
+
+ // HACK to draw the entity sprite
+ const previewSprite = metaBuilding.getBlueprintSprite(rotationVariant, this.currentVariant.get());
+ staticComp.origin = worldPos.divideScalar(globalConfig.tileSize).subScalars(0.5, 0.5);
+ staticComp.drawSpriteOnBoundsClipped(parameters, previewSprite);
+ staticComp.origin = mouseTile;
+
+ // Draw ejectors
+ if (canBuild) {
+ this.drawMatchingAcceptorsAndEjectors(parameters);
+ }
+ }
+
+ /**
+ * @param {DrawParameters} parameters
+ */
+ drawDirectionLock(parameters) {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return;
+ }
+
+ const mouseWorld = this.root.camera.screenToWorld(mousePosition);
+ const mouseTile = mouseWorld.toTileSpace();
+ parameters.context.fillStyle = THEME.map.directionLock[this.root.currentLayer].color;
+ parameters.context.strokeStyle = THEME.map.directionLock[this.root.currentLayer].background;
+ parameters.context.lineWidth = 10;
+
+ parameters.context.beginCircle(mouseWorld.x, mouseWorld.y, 4);
+ parameters.context.fill();
+
+ if (this.lastDragTile) {
+ const startLine = this.lastDragTile.toWorldSpaceCenterOfTile();
+ const endLine = mouseTile.toWorldSpaceCenterOfTile();
+ const midLine = this.currentDirectionLockCorner.toWorldSpaceCenterOfTile();
+
+ parameters.context.beginCircle(startLine.x, startLine.y, 8);
+ parameters.context.fill();
+
+ parameters.context.beginPath();
+ parameters.context.moveTo(startLine.x, startLine.y);
+ parameters.context.lineTo(midLine.x, midLine.y);
+ parameters.context.lineTo(endLine.x, endLine.y);
+ parameters.context.stroke();
+
+ parameters.context.beginCircle(endLine.x, endLine.y, 5);
+ parameters.context.fill();
+
+ // Draw arrow
+ const arrowSprite = this.lockIndicatorSprites[this.root.currentLayer];
+ const path = this.computeDirectionLockPath();
+ for (let i = 0; i < path.length - 1; i += 1) {
+ const { rotation, tile } = path[i];
+ const worldPos = tile.toWorldSpaceCenterOfTile();
+ const angle = Math.radians(rotation);
+
+ parameters.context.translate(worldPos.x, worldPos.y);
+ parameters.context.rotate(angle);
+ parameters.context.drawImage(
+ arrowSprite,
+ -6,
+ -globalConfig.halfTileSize -
+ clamp((this.root.time.realtimeNow() * 1.5) % 1.0, 0, 1) * 1 * globalConfig.tileSize +
+ globalConfig.halfTileSize -
+ 6,
+ 12,
+ 12
+ );
+ parameters.context.rotate(-angle);
+ parameters.context.translate(-worldPos.x, -worldPos.y);
+ }
+ }
+ }
+
+ /**
+ * @param {DrawParameters} parameters
+ */
+ drawMatchingAcceptorsAndEjectors(parameters) {
+ const acceptorComp = this.fakeEntity.components.ItemAcceptor;
+ const ejectorComp = this.fakeEntity.components.ItemEjector;
+ const staticComp = this.fakeEntity.components.StaticMapEntity;
+ const beltComp = this.fakeEntity.components.Belt;
+
+ const goodArrowSprite = Loader.getSprite("sprites/misc/slot_good_arrow.png");
+ const badArrowSprite = Loader.getSprite("sprites/misc/slot_bad_arrow.png");
+
+ // Just ignore the following code please ... thanks!
+
+ const offsetShift = 10;
+
+ let acceptorSlots = [];
+ let ejectorSlots = [];
+
+ if (ejectorComp) {
+ ejectorSlots = ejectorComp.slots.slice();
+ }
+
+ if (acceptorComp) {
+ acceptorSlots = acceptorComp.slots.slice();
+ }
+
+ if (beltComp) {
+ const fakeEjectorSlot = beltComp.getFakeEjectorSlot();
+ const fakeAcceptorSlot = beltComp.getFakeAcceptorSlot();
+ ejectorSlots.push(fakeEjectorSlot);
+ acceptorSlots.push(fakeAcceptorSlot);
+ }
+
+ for (let acceptorSlotIndex = 0; acceptorSlotIndex < acceptorSlots.length; ++acceptorSlotIndex) {
+ const slot = acceptorSlots[acceptorSlotIndex];
+
+ const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
+ const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
+
+ // Go over all slots
+ for (
+ let acceptorDirectionIndex = 0;
+ acceptorDirectionIndex < slot.directions.length;
+ ++acceptorDirectionIndex
+ ) {
+ const direction = slot.directions[acceptorDirectionIndex];
+ const worldDirection = staticComp.localDirectionToWorld(direction);
+
+ // Figure out which tile ejects to this slot
+ const sourceTile = acceptorSlotWsTile.add(enumDirectionToVector[worldDirection]);
+
+ let isBlocked = false;
+ let isConnected = false;
+
+ // Find all entities which are on that tile
+ const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
+
+ // Check for every entity:
+ for (let i = 0; i < sourceEntities.length; ++i) {
+ const sourceEntity = sourceEntities[i];
+ const sourceEjector = sourceEntity.components.ItemEjector;
+ const sourceBeltComp = sourceEntity.components.Belt;
+ const sourceStaticComp = sourceEntity.components.StaticMapEntity;
+ const ejectorAcceptLocalTile = sourceStaticComp.worldToLocalTile(acceptorSlotWsTile);
+
+ // If this entity is on the same layer as the slot - if so, it can either be
+ // connected, or it can not be connected and thus block the input
+ if (sourceEjector && sourceEjector.anySlotEjectsToLocalTile(ejectorAcceptLocalTile)) {
+ // This one is connected, all good
+ isConnected = true;
+ } else if (
+ sourceBeltComp &&
+ sourceStaticComp.localDirectionToWorld(sourceBeltComp.direction) ===
+ enumInvertedDirections[worldDirection]
+ ) {
+ // Belt connected
+ isConnected = true;
+ } else {
+ // This one is blocked
+ isBlocked = true;
+ }
+ }
+
+ const alpha = isConnected || isBlocked ? 1.0 : 0.3;
+ const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
+
+ parameters.context.globalAlpha = alpha;
+ drawRotatedSprite({
+ parameters,
+ sprite,
+ x: acceptorSlotWsPos.x,
+ y: acceptorSlotWsPos.y,
+ angle: Math.radians(enumDirectionToAngle[enumInvertedDirections[worldDirection]]),
+ size: 13,
+ offsetY: offsetShift + 13,
+ });
+ parameters.context.globalAlpha = 1;
+ }
+ }
+
+ // Go over all slots
+ for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorSlots.length; ++ejectorSlotIndex) {
+ const slot = ejectorSlots[ejectorSlotIndex];
+
+ const ejectorSlotLocalTile = slot.pos.add(enumDirectionToVector[slot.direction]);
+ const ejectorSlotWsTile = staticComp.localTileToWorld(ejectorSlotLocalTile);
+
+ const ejectorSLotWsPos = ejectorSlotWsTile.toWorldSpaceCenterOfTile();
+ const ejectorSlotWsDirection = staticComp.localDirectionToWorld(slot.direction);
+
+ let isBlocked = false;
+ let isConnected = false;
+
+ // Find all entities which are on that tile
+ const destEntities = this.root.map.getLayersContentsMultipleXY(
+ ejectorSlotWsTile.x,
+ ejectorSlotWsTile.y
+ );
+
+ // Check for every entity:
+ for (let i = 0; i < destEntities.length; ++i) {
+ const destEntity = destEntities[i];
+ const destAcceptor = destEntity.components.ItemAcceptor;
+ const destStaticComp = destEntity.components.StaticMapEntity;
+
+ const destLocalTile = destStaticComp.worldToLocalTile(ejectorSlotWsTile);
+ const destLocalDir = destStaticComp.worldDirectionToLocal(ejectorSlotWsDirection);
+ if (destAcceptor && destAcceptor.findMatchingSlot(destLocalTile, destLocalDir)) {
+ // This one is connected, all good
+ isConnected = true;
+ } else if (destEntity.components.Belt && destLocalDir === enumDirection.top) {
+ // Connected to a belt
+ isConnected = true;
+ } else {
+ // This one is blocked
+ isBlocked = true;
+ }
+ }
+
+ const alpha = isConnected || isBlocked ? 1.0 : 0.3;
+ const sprite = isBlocked ? badArrowSprite : goodArrowSprite;
+
+ parameters.context.globalAlpha = alpha;
+ drawRotatedSprite({
+ parameters,
+ sprite,
+ x: ejectorSLotWsPos.x,
+ y: ejectorSLotWsPos.y,
+ angle: Math.radians(enumDirectionToAngle[ejectorSlotWsDirection]),
+ size: 13,
+ offsetY: offsetShift,
+ });
+ parameters.context.globalAlpha = 1;
+ }
+ }
+}
diff --git a/src/js/game/hud/parts/building_placer_logic.js b/src/js/game/hud/parts/building_placer_logic.js
index 898801c0..686e6d1c 100644
--- a/src/js/game/hud/parts/building_placer_logic.js
+++ b/src/js/game/hud/parts/building_placer_logic.js
@@ -1,775 +1,778 @@
-import { globalConfig } from "../../../core/config";
-import { gMetaBuildingRegistry } from "../../../core/global_registries";
-import { Signal, STOP_PROPAGATION } from "../../../core/signal";
-import { TrackedState } from "../../../core/tracked_state";
-import { Vector } from "../../../core/vector";
-import { enumMouseButton } from "../../camera";
-import { StaticMapEntityComponent } from "../../components/static_map_entity";
-import { Entity } from "../../entity";
-import { KEYMAPPINGS } from "../../key_action_mapper";
-import { defaultBuildingVariant, MetaBuilding } from "../../meta_building";
-import { BaseHUDPart } from "../base_hud_part";
-import { SOUNDS } from "../../../platform/sound";
-import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
-import { enumHubGoalRewards } from "../../tutorial_goals";
-import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
-import { MetaHubBuilding } from "../../buildings/hub";
-
-/**
- * Contains all logic for the building placer - this doesn't include the rendering
- * of info boxes or drawing.
- */
-export class HUDBuildingPlacerLogic extends BaseHUDPart {
- /**
- * Initializes the logic
- * @see BaseHUDPart.initialize
- */
- initialize() {
- /**
- * We use a fake entity to get information about how a building will look
- * once placed
- * @type {Entity}
- */
- this.fakeEntity = null;
-
- // Signals
- this.signals = {
- variantChanged: new Signal(),
- draggingStarted: new Signal(),
- };
-
- /**
- * The current building
- * @type {TypedTrackedState}
- */
- this.currentMetaBuilding = new TrackedState(this.onSelectedMetaBuildingChanged, this);
-
- /**
- * The current rotation
- * @type {number}
- */
- this.currentBaseRotationGeneral = 0;
-
- /**
- * The current rotation preference for each building.
- * @type{Object.}
- */
- this.preferredBaseRotations = {};
-
- /**
- * Whether we are currently dragging
- * @type {boolean}
- */
- this.currentlyDragging = false;
-
- /**
- * Current building variant
- * @type {TypedTrackedState}
- */
- this.currentVariant = new TrackedState(() => this.signals.variantChanged.dispatch());
-
- /**
- * Whether we are currently drag-deleting
- * @type {boolean}
- */
- this.currentlyDeleting = false;
-
- /**
- * Stores which variants for each building we prefer, this is based on what
- * the user last selected
- * @type {Object.}
- */
- this.preferredVariants = {};
-
- /**
- * The tile we last dragged from
- * @type {Vector}
- */
- this.lastDragTile = null;
-
- /**
- * The side for direction lock
- * @type {number} (0|1)
- */
- this.currentDirectionLockSide = 0;
-
- /**
- * Whether the side for direction lock has not yet been determined.
- * @type {boolean}
- */
- this.currentDirectionLockSideIndeterminate = true;
-
- this.initializeBindings();
- }
-
- /**
- * Initializes all bindings
- */
- initializeBindings() {
- // KEYBINDINGS
- const keyActionMapper = this.root.keyMapper;
- keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
- keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
- keyActionMapper
- .getBinding(KEYMAPPINGS.placement.switchDirectionLockSide)
- .add(this.switchDirectionLockSide, this);
- keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
- keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.startPipette, this);
- this.root.gameState.inputReciever.keyup.add(this.checkForDirectionLockSwitch, this);
-
- // BINDINGS TO GAME EVENTS
- this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this);
- this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this);
- this.root.signals.storyGoalCompleted.add(() => this.signals.variantChanged.dispatch());
- this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
- this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
-
- // MOUSE BINDINGS
- this.root.camera.downPreHandler.add(this.onMouseDown, this);
- this.root.camera.movePreHandler.add(this.onMouseMove, this);
- this.root.camera.upPostHandler.add(this.onMouseUp, this);
- }
-
- /**
- * Called when the edit mode got changed
- * @param {Layer} layer
- */
- onEditModeChanged(layer) {
- const metaBuilding = this.currentMetaBuilding.get();
- if (metaBuilding) {
- if (metaBuilding.getLayer() !== layer) {
- // This layer doesn't fit the edit mode anymore
- this.currentMetaBuilding.set(null);
- }
- }
- }
-
- /**
- * Returns the current base rotation for the current meta-building.
- * @returns {number}
- */
- get currentBaseRotation() {
- if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
- return this.currentBaseRotationGeneral;
- }
- const metaBuilding = this.currentMetaBuilding.get();
- if (metaBuilding && this.preferredBaseRotations.hasOwnProperty(metaBuilding.getId())) {
- return this.preferredBaseRotations[metaBuilding.getId()];
- } else {
- return this.currentBaseRotationGeneral;
- }
- }
-
- /**
- * Sets the base rotation for the current meta-building.
- * @param {number} rotation The new rotation/angle.
- */
- set currentBaseRotation(rotation) {
- if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
- this.currentBaseRotationGeneral = rotation;
- } else {
- const metaBuilding = this.currentMetaBuilding.get();
- if (metaBuilding) {
- this.preferredBaseRotations[metaBuilding.getId()] = rotation;
- } else {
- this.currentBaseRotationGeneral = rotation;
- }
- }
- }
-
- /**
- * Returns if the direction lock is currently active
- * @returns {boolean}
- */
- get isDirectionLockActive() {
- const metaBuilding = this.currentMetaBuilding.get();
- return (
- metaBuilding &&
- metaBuilding.getHasDirectionLockAvailable() &&
- this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.lockBeltDirection).pressed
- );
- }
-
- /**
- * Returns the current direction lock corner, that is, the corner between
- * mouse and original start point
- * @returns {Vector|null}
- */
- get currentDirectionLockCorner() {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return null;
- }
-
- if (!this.lastDragTile) {
- // Haven't dragged yet
- return null;
- }
-
- // Figure which points the line visits
- const worldPos = this.root.camera.screenToWorld(mousePosition);
- const mouseTile = worldPos.toTileSpace();
-
- // Figure initial direction
- const dx = Math.abs(this.lastDragTile.x - mouseTile.x);
- const dy = Math.abs(this.lastDragTile.y - mouseTile.y);
- if (dx === 0 && dy === 0) {
- // Back at the start. Try a new direction.
- this.currentDirectionLockSideIndeterminate = true;
- } else if (this.currentDirectionLockSideIndeterminate) {
- this.currentDirectionLockSideIndeterminate = false;
- this.currentDirectionLockSide = dx <= dy ? 0 : 1;
- }
-
- if (this.currentDirectionLockSide === 0) {
- return new Vector(this.lastDragTile.x, mouseTile.y);
- } else {
- return new Vector(mouseTile.x, this.lastDragTile.y);
- }
- }
-
- /**
- * Aborts the placement
- */
- abortPlacement() {
- if (this.currentMetaBuilding.get()) {
- this.currentMetaBuilding.set(null);
- return STOP_PROPAGATION;
- }
- }
-
- /**
- * Aborts any dragging
- */
- abortDragging() {
- this.currentlyDragging = true;
- this.currentlyDeleting = false;
- this.initialPlacementVector = null;
- this.lastDragTile = null;
- }
-
- /**
- * @see BaseHUDPart.update
- */
- update() {
- // Always update since the camera might have moved
- const mousePos = this.root.app.mousePosition;
- if (mousePos) {
- this.onMouseMove(mousePos);
- }
-
- // Make sure we have nothing selected while in overview mode
- if (this.root.camera.getIsMapOverlayActive()) {
- if (this.currentMetaBuilding.get()) {
- this.currentMetaBuilding.set(null);
- }
- }
- }
-
- /**
- * Tries to rotate the current building
- */
- tryRotate() {
- const selectedBuilding = this.currentMetaBuilding.get();
- if (selectedBuilding) {
- if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
- this.currentBaseRotation = (this.currentBaseRotation + 270) % 360;
- } else {
- this.currentBaseRotation = (this.currentBaseRotation + 90) % 360;
- }
- const staticComp = this.fakeEntity.components.StaticMapEntity;
- staticComp.rotation = this.currentBaseRotation;
- }
- }
- /**
- * Tries to delete the building under the mouse
- */
- deleteBelowCursor() {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return false;
- }
-
- const worldPos = this.root.camera.screenToWorld(mousePosition);
- const tile = worldPos.toTileSpace();
- const contents = this.root.map.getTileContent(tile, this.root.currentLayer);
- if (contents) {
- if (this.root.logic.tryDeleteBuilding(contents)) {
- this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Starts the pipette function
- */
- startPipette() {
- // Disable in overview
- if (this.root.camera.getIsMapOverlayActive()) {
- return;
- }
-
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return;
- }
-
- const worldPos = this.root.camera.screenToWorld(mousePosition);
- const tile = worldPos.toTileSpace();
-
- const contents = this.root.map.getTileContent(tile, this.root.currentLayer);
- if (!contents) {
- const tileBelow = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
-
- // Check if there's a shape or color item below, if so select the miner
- if (tileBelow) {
- this.currentMetaBuilding.set(gMetaBuildingRegistry.findByClass(MetaMinerBuilding));
-
- // Select chained miner if available, since thats always desired once unlocked
- if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_miner_chainable)) {
- this.currentVariant.set(enumMinerVariants.chainable);
- }
- } else {
- this.currentMetaBuilding.set(null);
- }
- return;
- }
-
- // Try to extract the building
- const buildingCode = contents.components.StaticMapEntity.code;
- const extracted = getBuildingDataFromCode(buildingCode);
-
- // Disable pipetting the hub
- if (extracted.metaInstance.getId() === gMetaBuildingRegistry.findByClass(MetaHubBuilding).getId()) {
- this.currentMetaBuilding.set(null);
- return;
- }
-
- // If the building we are picking is the same as the one we have, clear the cursor.
- if (
- this.currentMetaBuilding.get() &&
- extracted.metaInstance.getId() === this.currentMetaBuilding.get().getId() &&
- extracted.variant === this.currentVariant.get()
- ) {
- this.currentMetaBuilding.set(null);
- return;
- }
-
- this.currentMetaBuilding.set(extracted.metaInstance);
- this.currentVariant.set(extracted.variant);
- this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
- }
-
- /**
- * Switches the side for the direction lock manually
- */
- switchDirectionLockSide() {
- this.currentDirectionLockSide = 1 - this.currentDirectionLockSide;
- }
-
- /**
- * Checks if the direction lock key got released and if such, resets the placement
- * @param {any} args
- */
- checkForDirectionLockSwitch({ keyCode }) {
- if (
- keyCode ===
- this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.lockBeltDirection).keyCode
- ) {
- this.abortDragging();
- }
- }
-
- /**
- * Tries to place the current building at the given tile
- * @param {Vector} tile
- */
- tryPlaceCurrentBuildingAt(tile) {
- if (this.root.camera.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
- // Dont allow placing in overview mode
- return;
- }
-
- const metaBuilding = this.currentMetaBuilding.get();
- const { rotation, rotationVariant } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
- root: this.root,
- tile,
- rotation: this.currentBaseRotation,
- variant: this.currentVariant.get(),
- layer: metaBuilding.getLayer(),
- });
-
- const entity = this.root.logic.tryPlaceBuilding({
- origin: tile,
- rotation,
- rotationVariant,
- originalRotation: this.currentBaseRotation,
- building: this.currentMetaBuilding.get(),
- variant: this.currentVariant.get(),
- });
-
- if (entity) {
- // Succesfully placed, find which entity we actually placed
- this.root.signals.entityManuallyPlaced.dispatch(entity);
-
- // Check if we should flip the orientation (used for tunnels)
- if (
- metaBuilding.getFlipOrientationAfterPlacement() &&
- !this.root.keyMapper.getBinding(
- KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
- ).pressed
- ) {
- this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
- }
-
- // Check if we should stop placement
- if (
- !metaBuilding.getStayInPlacementMode() &&
- !this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).pressed &&
- !this.root.app.settings.getAllSettings().alwaysMultiplace
- ) {
- // Stop placement
- this.currentMetaBuilding.set(null);
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Cycles through the variants
- */
- cycleVariants() {
- const metaBuilding = this.currentMetaBuilding.get();
- if (!metaBuilding) {
- this.currentVariant.set(defaultBuildingVariant);
- } else {
- const availableVariants = metaBuilding.getAvailableVariants(this.root);
- const index = availableVariants.indexOf(this.currentVariant.get());
- assert(
- index >= 0,
- "Current variant was invalid: " + this.currentVariant.get() + " out of " + availableVariants
- );
- const newIndex = (index + 1) % availableVariants.length;
- const newVariant = availableVariants[newIndex];
- this.setVariant(newVariant);
- }
- }
-
- /**
- * Sets the current variant to the given variant
- * @param {string} variant
- */
- setVariant(variant) {
- const metaBuilding = this.currentMetaBuilding.get();
- this.currentVariant.set(variant);
-
- this.preferredVariants[metaBuilding.getId()] = variant;
- }
-
- /**
- * Performs the direction locked placement between two points after
- * releasing the mouse
- */
- executeDirectionLockedPlacement() {
- const metaBuilding = this.currentMetaBuilding.get();
- if (!metaBuilding) {
- // No active building
- return;
- }
-
- // Get path to place
- const path = this.computeDirectionLockPath();
-
- // Store if we placed anything
- let anythingPlaced = false;
-
- // Perform this in bulk to avoid recalculations
- this.root.logic.performBulkOperation(() => {
- for (let i = 0; i < path.length; ++i) {
- const { rotation, tile } = path[i];
- this.currentBaseRotation = rotation;
- if (this.tryPlaceCurrentBuildingAt(tile)) {
- anythingPlaced = true;
- }
- }
- });
-
- if (anythingPlaced) {
- this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
- }
- }
-
- /**
- * Finds the path which the current direction lock will use
- * @returns {Array<{ tile: Vector, rotation: number }>}
- */
- computeDirectionLockPath() {
- const mousePosition = this.root.app.mousePosition;
- if (!mousePosition) {
- // Not on screen
- return [];
- }
-
- let result = [];
-
- // Figure which points the line visits
- const worldPos = this.root.camera.screenToWorld(mousePosition);
- let endTile = worldPos.toTileSpace();
- let startTile = this.lastDragTile;
-
- // if the alt key is pressed, reverse belt planner direction by switching start and end tile
- if (this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse).pressed) {
- let tmp = startTile;
- startTile = endTile;
- endTile = tmp;
- }
-
- // Place from start to corner
- const pathToCorner = this.currentDirectionLockCorner.sub(startTile);
- const deltaToCorner = pathToCorner.normalize().round();
- const lengthToCorner = Math.round(pathToCorner.length());
- let currentPos = startTile.copy();
-
- let rotation = (Math.round(Math.degrees(deltaToCorner.angle()) / 90) * 90 + 360) % 360;
-
- if (lengthToCorner > 0) {
- for (let i = 0; i < lengthToCorner; ++i) {
- result.push({
- tile: currentPos.copy(),
- rotation,
- });
- currentPos.addInplace(deltaToCorner);
- }
- }
-
- // Place from corner to end
- const pathFromCorner = endTile.sub(this.currentDirectionLockCorner);
- const deltaFromCorner = pathFromCorner.normalize().round();
- const lengthFromCorner = Math.round(pathFromCorner.length());
-
- if (lengthFromCorner > 0) {
- rotation = (Math.round(Math.degrees(deltaFromCorner.angle()) / 90) * 90 + 360) % 360;
- for (let i = 0; i < lengthFromCorner + 1; ++i) {
- result.push({
- tile: currentPos.copy(),
- rotation,
- });
- currentPos.addInplace(deltaFromCorner);
- }
- } else {
- // Finish last one
- result.push({
- tile: currentPos.copy(),
- rotation,
- });
- }
- return result;
- }
-
- /**
- * Selects a given building
- * @param {MetaBuilding} metaBuilding
- */
- startSelection(metaBuilding) {
- this.currentMetaBuilding.set(metaBuilding);
- }
-
- /**
- * Called when the selected buildings changed
- * @param {MetaBuilding} metaBuilding
- */
- onSelectedMetaBuildingChanged(metaBuilding) {
- this.abortDragging();
- this.root.hud.signals.selectedPlacementBuildingChanged.dispatch(metaBuilding);
- if (metaBuilding) {
- const variant = this.preferredVariants[metaBuilding.getId()] || defaultBuildingVariant;
- this.currentVariant.set(variant);
-
- this.fakeEntity = new Entity(null);
- metaBuilding.setupEntityComponents(this.fakeEntity, null);
-
- this.fakeEntity.addComponent(
- new StaticMapEntityComponent({
- origin: new Vector(0, 0),
- rotation: 0,
- tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
- code: getCodeFromBuildingData(metaBuilding, variant, 0),
- })
- );
- metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
- } else {
- this.fakeEntity = null;
- }
-
- // Since it depends on both, rerender twice
- this.signals.variantChanged.dispatch();
- }
-
- /**
- * mouse down pre handler
- * @param {Vector} pos
- * @param {enumMouseButton} button
- */
- onMouseDown(pos, button) {
- if (this.root.camera.getIsMapOverlayActive()) {
- // We do not allow dragging if the overlay is active
- return;
- }
-
- const metaBuilding = this.currentMetaBuilding.get();
-
- // Placement
- if (button === enumMouseButton.left && metaBuilding) {
- this.currentlyDragging = true;
- this.currentlyDeleting = false;
- this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace();
-
- // Place initial building, but only if direction lock is not active
- if (!this.isDirectionLockActive) {
- if (this.tryPlaceCurrentBuildingAt(this.lastDragTile)) {
- this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
- }
- }
- return STOP_PROPAGATION;
- }
-
- // Deletion
- if (button === enumMouseButton.right && !metaBuilding) {
- this.currentlyDragging = true;
- this.currentlyDeleting = true;
- this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace();
- if (this.deleteBelowCursor()) {
- return STOP_PROPAGATION;
- }
- }
-
- // Cancel placement
- if (button === enumMouseButton.right && metaBuilding) {
- this.currentMetaBuilding.set(null);
- }
- }
-
- /**
- * mouse move pre handler
- * @param {Vector} pos
- */
- onMouseMove(pos) {
- if (this.root.camera.getIsMapOverlayActive()) {
- return;
- }
-
- // Check for direction lock
- if (this.isDirectionLockActive) {
- return;
- }
-
- const metaBuilding = this.currentMetaBuilding.get();
- if ((metaBuilding || this.currentlyDeleting) && this.lastDragTile) {
- const oldPos = this.lastDragTile;
- let newPos = this.root.camera.screenToWorld(pos).toTileSpace();
-
- // Check if camera is moving, since then we do nothing
- if (this.root.camera.desiredCenter) {
- this.lastDragTile = newPos;
- return;
- }
-
- // Check if anything changed
- if (!oldPos.equals(newPos)) {
- // Automatic Direction
- if (
- metaBuilding &&
- metaBuilding.getRotateAutomaticallyWhilePlacing(this.currentVariant.get()) &&
- !this.root.keyMapper.getBinding(
- KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
- ).pressed
- ) {
- const delta = newPos.sub(oldPos);
- const angleDeg = Math.degrees(delta.angle());
- this.currentBaseRotation = (Math.round(angleDeg / 90) * 90 + 360) % 360;
-
- // Holding alt inverts the placement
- if (this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse).pressed) {
- this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
- }
- }
-
- // bresenham
- let x0 = oldPos.x;
- let y0 = oldPos.y;
- let x1 = newPos.x;
- let y1 = newPos.y;
-
- var dx = Math.abs(x1 - x0);
- var dy = Math.abs(y1 - y0);
- var sx = x0 < x1 ? 1 : -1;
- var sy = y0 < y1 ? 1 : -1;
- var err = dx - dy;
-
- let anythingPlaced = false;
- let anythingDeleted = false;
-
- while (this.currentlyDeleting || this.currentMetaBuilding.get()) {
- if (this.currentlyDeleting) {
- // Deletion
- const contents = this.root.map.getLayerContentXY(x0, y0, this.root.currentLayer);
- if (contents && !contents.queuedForDestroy && !contents.destroyed) {
- if (this.root.logic.tryDeleteBuilding(contents)) {
- anythingDeleted = true;
- }
- }
- } else {
- // Placement
- if (this.tryPlaceCurrentBuildingAt(new Vector(x0, y0))) {
- anythingPlaced = true;
- }
- }
-
- if (x0 === x1 && y0 === y1) break;
- var e2 = 2 * err;
- if (e2 > -dy) {
- err -= dy;
- x0 += sx;
- }
- if (e2 < dx) {
- err += dx;
- y0 += sy;
- }
- }
-
- if (anythingPlaced) {
- this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
- }
- if (anythingDeleted) {
- this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
- }
- }
-
- this.lastDragTile = newPos;
- return STOP_PROPAGATION;
- }
- }
-
- /**
- * Mouse up handler
- */
- onMouseUp() {
- if (this.root.camera.getIsMapOverlayActive()) {
- return;
- }
-
- // Check for direction lock
- if (this.lastDragTile && this.currentlyDragging && this.isDirectionLockActive) {
- this.executeDirectionLockedPlacement();
- }
-
- this.abortDragging();
- }
-}
+import { globalConfig } from "../../../core/config";
+import { gMetaBuildingRegistry } from "../../../core/global_registries";
+import { Signal, STOP_PROPAGATION } from "../../../core/signal";
+import { TrackedState } from "../../../core/tracked_state";
+import { Vector } from "../../../core/vector";
+import { enumMouseButton } from "../../camera";
+import { StaticMapEntityComponent } from "../../components/static_map_entity";
+import { Entity } from "../../entity";
+import { KEYMAPPINGS } from "../../key_action_mapper";
+import { defaultBuildingVariant, MetaBuilding } from "../../meta_building";
+import { BaseHUDPart } from "../base_hud_part";
+import { SOUNDS } from "../../../platform/sound";
+import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner";
+import { enumHubGoalRewards } from "../../tutorial_goals";
+import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes";
+import { MetaHubBuilding } from "../../buildings/hub";
+
+/**
+ * Contains all logic for the building placer - this doesn't include the rendering
+ * of info boxes or drawing.
+ */
+export class HUDBuildingPlacerLogic extends BaseHUDPart {
+ /**
+ * Initializes the logic
+ * @see BaseHUDPart.initialize
+ */
+ initialize() {
+ /**
+ * We use a fake entity to get information about how a building will look
+ * once placed
+ * @type {Entity}
+ */
+ this.fakeEntity = null;
+
+ // Signals
+ this.signals = {
+ variantChanged: new Signal(),
+ draggingStarted: new Signal(),
+ };
+
+ /**
+ * The current building
+ * @type {TypedTrackedState}
+ */
+ this.currentMetaBuilding = new TrackedState(this.onSelectedMetaBuildingChanged, this);
+
+ /**
+ * The current rotation
+ * @type {number}
+ */
+ this.currentBaseRotationGeneral = 0;
+
+ /**
+ * The current rotation preference for each building.
+ * @type{Object.}
+ */
+ this.preferredBaseRotations = {};
+
+ /**
+ * Whether we are currently dragging
+ * @type {boolean}
+ */
+ this.currentlyDragging = false;
+
+ /**
+ * Current building variant
+ * @type {TypedTrackedState}
+ */
+ this.currentVariant = new TrackedState(() => this.signals.variantChanged.dispatch());
+
+ /**
+ * Whether we are currently drag-deleting
+ * @type {boolean}
+ */
+ this.currentlyDeleting = false;
+
+ /**
+ * Stores which variants for each building we prefer, this is based on what
+ * the user last selected
+ * @type {Object.}
+ */
+ this.preferredVariants = {};
+
+ /**
+ * The tile we last dragged from
+ * @type {Vector}
+ */
+ this.lastDragTile = null;
+
+ /**
+ * The side for direction lock
+ * @type {number} (0|1)
+ */
+ this.currentDirectionLockSide = 0;
+
+ /**
+ * Whether the side for direction lock has not yet been determined.
+ * @type {boolean}
+ */
+ this.currentDirectionLockSideIndeterminate = true;
+
+ this.initializeBindings();
+ }
+
+ /**
+ * Initializes all bindings
+ */
+ initializeBindings() {
+ // KEYBINDINGS
+ const keyActionMapper = this.root.keyMapper;
+ keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this);
+ keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this);
+ keyActionMapper
+ .getBinding(KEYMAPPINGS.placement.switchDirectionLockSide)
+ .add(this.switchDirectionLockSide, this);
+ keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
+ keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.startPipette, this);
+ this.root.gameState.inputReciever.keyup.add(this.checkForDirectionLockSwitch, this);
+
+ // BINDINGS TO GAME EVENTS
+ this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this);
+ this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this);
+ this.root.signals.storyGoalCompleted.add(() => this.signals.variantChanged.dispatch());
+ this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
+ this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
+
+ // MOUSE BINDINGS
+ this.root.camera.downPreHandler.add(this.onMouseDown, this);
+ this.root.camera.movePreHandler.add(this.onMouseMove, this);
+ this.root.camera.upPostHandler.add(this.onMouseUp, this);
+ }
+
+ /**
+ * Called when the edit mode got changed
+ * @param {Layer} layer
+ */
+ onEditModeChanged(layer) {
+ const metaBuilding = this.currentMetaBuilding.get();
+ if (metaBuilding) {
+ if (metaBuilding.getLayer() !== layer) {
+ // This layer doesn't fit the edit mode anymore
+ this.currentMetaBuilding.set(null);
+ }
+ }
+ }
+
+ /**
+ * Returns the current base rotation for the current meta-building.
+ * @returns {number}
+ */
+ get currentBaseRotation() {
+ if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
+ return this.currentBaseRotationGeneral;
+ }
+ const metaBuilding = this.currentMetaBuilding.get();
+ if (metaBuilding && this.preferredBaseRotations.hasOwnProperty(metaBuilding.getId())) {
+ return this.preferredBaseRotations[metaBuilding.getId()];
+ } else {
+ return this.currentBaseRotationGeneral;
+ }
+ }
+
+ /**
+ * Sets the base rotation for the current meta-building.
+ * @param {number} rotation The new rotation/angle.
+ */
+ set currentBaseRotation(rotation) {
+ if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
+ this.currentBaseRotationGeneral = rotation;
+ } else {
+ const metaBuilding = this.currentMetaBuilding.get();
+ if (metaBuilding) {
+ this.preferredBaseRotations[metaBuilding.getId()] = rotation;
+ } else {
+ this.currentBaseRotationGeneral = rotation;
+ }
+ }
+ }
+
+ /**
+ * Returns if the direction lock is currently active
+ * @returns {boolean}
+ */
+ get isDirectionLockActive() {
+ const metaBuilding = this.currentMetaBuilding.get();
+ return (
+ metaBuilding &&
+ metaBuilding.getHasDirectionLockAvailable() &&
+ this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.lockBeltDirection).pressed
+ );
+ }
+
+ /**
+ * Returns the current direction lock corner, that is, the corner between
+ * mouse and original start point
+ * @returns {Vector|null}
+ */
+ get currentDirectionLockCorner() {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return null;
+ }
+
+ if (!this.lastDragTile) {
+ // Haven't dragged yet
+ return null;
+ }
+
+ // Figure which points the line visits
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ const mouseTile = worldPos.toTileSpace();
+
+ // Figure initial direction
+ const dx = Math.abs(this.lastDragTile.x - mouseTile.x);
+ const dy = Math.abs(this.lastDragTile.y - mouseTile.y);
+ if (dx === 0 && dy === 0) {
+ // Back at the start. Try a new direction.
+ this.currentDirectionLockSideIndeterminate = true;
+ } else if (this.currentDirectionLockSideIndeterminate) {
+ this.currentDirectionLockSideIndeterminate = false;
+ this.currentDirectionLockSide = dx <= dy ? 0 : 1;
+ }
+
+ if (this.currentDirectionLockSide === 0) {
+ return new Vector(this.lastDragTile.x, mouseTile.y);
+ } else {
+ return new Vector(mouseTile.x, this.lastDragTile.y);
+ }
+ }
+
+ /**
+ * Aborts the placement
+ */
+ abortPlacement() {
+ if (this.currentMetaBuilding.get()) {
+ this.currentMetaBuilding.set(null);
+ return STOP_PROPAGATION;
+ }
+ }
+
+ /**
+ * Aborts any dragging
+ */
+ abortDragging() {
+ this.currentlyDragging = true;
+ this.currentlyDeleting = false;
+ this.initialPlacementVector = null;
+ this.lastDragTile = null;
+ }
+
+ /**
+ * @see BaseHUDPart.update
+ */
+ update() {
+ // Always update since the camera might have moved
+ const mousePos = this.root.app.mousePosition;
+ if (mousePos) {
+ this.onMouseMove(mousePos);
+ }
+
+ // Make sure we have nothing selected while in overview mode
+ if (this.root.camera.getIsMapOverlayActive()) {
+ if (this.currentMetaBuilding.get()) {
+ this.currentMetaBuilding.set(null);
+ }
+ }
+ }
+
+ /**
+ * Tries to rotate the current building
+ */
+ tryRotate() {
+ const selectedBuilding = this.currentMetaBuilding.get();
+ if (selectedBuilding) {
+ if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
+ this.currentBaseRotation = (this.currentBaseRotation + 270) % 360;
+ } else {
+ this.currentBaseRotation = (this.currentBaseRotation + 90) % 360;
+ }
+ const staticComp = this.fakeEntity.components.StaticMapEntity;
+ staticComp.rotation = this.currentBaseRotation;
+ }
+ }
+ /**
+ * Tries to delete the building under the mouse
+ */
+ deleteBelowCursor() {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return false;
+ }
+
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ const tile = worldPos.toTileSpace();
+ const contents = this.root.map.getTileContent(tile, this.root.currentLayer);
+ if (contents) {
+ if (this.root.logic.tryDeleteBuilding(contents)) {
+ this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Starts the pipette function
+ */
+ startPipette() {
+ // Disable in overview
+ if (this.root.camera.getIsMapOverlayActive()) {
+ return;
+ }
+
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return;
+ }
+
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ const tile = worldPos.toTileSpace();
+
+ const contents = this.root.map.getTileContent(tile, this.root.currentLayer);
+ if (!contents) {
+ const tileBelow = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
+
+ // Check if there's a shape or color item below, if so select the miner
+ if (tileBelow) {
+ this.currentMetaBuilding.set(gMetaBuildingRegistry.findByClass(MetaMinerBuilding));
+
+ // Select chained miner if available, since thats always desired once unlocked
+ if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_miner_chainable)) {
+ this.currentVariant.set(enumMinerVariants.chainable);
+ }
+ } else {
+ this.currentMetaBuilding.set(null);
+ }
+ return;
+ }
+
+ // Try to extract the building
+ const buildingCode = contents.components.StaticMapEntity.code;
+ const extracted = getBuildingDataFromCode(buildingCode);
+
+ // Disable pipetting the hub
+ if (extracted.metaInstance.getId() === gMetaBuildingRegistry.findByClass(MetaHubBuilding).getId()) {
+ this.currentMetaBuilding.set(null);
+ return;
+ }
+
+ // If the building we are picking is the same as the one we have, clear the cursor.
+ if (
+ this.currentMetaBuilding.get() &&
+ extracted.metaInstance.getId() === this.currentMetaBuilding.get().getId() &&
+ extracted.variant === this.currentVariant.get()
+ ) {
+ this.currentMetaBuilding.set(null);
+ return;
+ }
+
+ this.currentMetaBuilding.set(extracted.metaInstance);
+ this.currentVariant.set(extracted.variant);
+ this.currentBaseRotation = contents.components.StaticMapEntity.rotation;
+ }
+
+ /**
+ * Switches the side for the direction lock manually
+ */
+ switchDirectionLockSide() {
+ this.currentDirectionLockSide = 1 - this.currentDirectionLockSide;
+ }
+
+ /**
+ * Checks if the direction lock key got released and if such, resets the placement
+ * @param {any} args
+ */
+ checkForDirectionLockSwitch({ keyCode }) {
+ if (
+ keyCode ===
+ this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.lockBeltDirection).keyCode
+ ) {
+ this.abortDragging();
+ }
+ }
+
+ /**
+ * Tries to place the current building at the given tile
+ * @param {Vector} tile
+ */
+ tryPlaceCurrentBuildingAt(tile) {
+ if (this.root.camera.zoomLevel < globalConfig.mapChunkOverviewMinZoom) {
+ // Dont allow placing in overview mode
+ return;
+ }
+
+ const metaBuilding = this.currentMetaBuilding.get();
+ const { rotation, rotationVariant } = metaBuilding.computeOptimalDirectionAndRotationVariantAtTile({
+ root: this.root,
+ tile,
+ rotation: this.currentBaseRotation,
+ variant: this.currentVariant.get(),
+ layer: metaBuilding.getLayer(),
+ });
+
+ const entity = this.root.logic.tryPlaceBuilding({
+ origin: tile,
+ rotation,
+ rotationVariant,
+ originalRotation: this.currentBaseRotation,
+ building: this.currentMetaBuilding.get(),
+ variant: this.currentVariant.get(),
+ });
+
+ if (entity) {
+ // Succesfully placed, find which entity we actually placed
+ this.root.signals.entityManuallyPlaced.dispatch(entity);
+
+ // Check if we should flip the orientation (used for tunnels)
+ if (
+ metaBuilding.getFlipOrientationAfterPlacement() &&
+ !this.root.keyMapper.getBinding(
+ KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
+ ).pressed
+ ) {
+ this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
+ }
+
+ // Check if we should stop placement
+ if (
+ !metaBuilding.getStayInPlacementMode() &&
+ !this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeMultiple).pressed &&
+ !this.root.app.settings.getAllSettings().alwaysMultiplace
+ ) {
+ // Stop placement
+ this.currentMetaBuilding.set(null);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Cycles through the variants
+ */
+ cycleVariants() {
+ const metaBuilding = this.currentMetaBuilding.get();
+ if (!metaBuilding) {
+ this.currentVariant.set(defaultBuildingVariant);
+ } else {
+ const availableVariants = metaBuilding.getAvailableVariants(this.root);
+ const index = availableVariants.indexOf(this.currentVariant.get());
+ assert(
+ index >= 0,
+ "Current variant was invalid: " + this.currentVariant.get() + " out of " + availableVariants
+ );
+ const newIndex = (index + 1) % availableVariants.length;
+ const newVariant = availableVariants[newIndex];
+ this.setVariant(newVariant);
+ }
+ }
+
+ /**
+ * Sets the current variant to the given variant
+ * @param {string} variant
+ */
+ setVariant(variant) {
+ const metaBuilding = this.currentMetaBuilding.get();
+ this.currentVariant.set(variant);
+
+ this.preferredVariants[metaBuilding.getId()] = variant;
+ }
+
+ /**
+ * Performs the direction locked placement between two points after
+ * releasing the mouse
+ */
+ executeDirectionLockedPlacement() {
+ const metaBuilding = this.currentMetaBuilding.get();
+ if (!metaBuilding) {
+ // No active building
+ return;
+ }
+
+ // Get path to place
+ const path = this.computeDirectionLockPath();
+
+ // Store if we placed anything
+ let anythingPlaced = false;
+
+ // Perform this in bulk to avoid recalculations
+ this.root.logic.performBulkOperation(() => {
+ for (let i = 0; i < path.length; ++i) {
+ const { rotation, tile } = path[i];
+ this.currentBaseRotation = rotation;
+ if (this.tryPlaceCurrentBuildingAt(tile)) {
+ anythingPlaced = true;
+ }
+ }
+ });
+
+ if (anythingPlaced) {
+ this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
+ }
+ }
+
+ /**
+ * Finds the path which the current direction lock will use
+ * @returns {Array<{ tile: Vector, rotation: number }>}
+ */
+ computeDirectionLockPath() {
+ const mousePosition = this.root.app.mousePosition;
+ if (!mousePosition) {
+ // Not on screen
+ return [];
+ }
+
+ let result = [];
+
+ // Figure which points the line visits
+ const worldPos = this.root.camera.screenToWorld(mousePosition);
+ let endTile = worldPos.toTileSpace();
+ let startTile = this.lastDragTile;
+
+ // if the alt key is pressed, reverse belt planner direction by switching start and end tile
+ if (this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse).pressed) {
+ let tmp = startTile;
+ startTile = endTile;
+ endTile = tmp;
+ }
+
+ // Place from start to corner
+ const pathToCorner = this.currentDirectionLockCorner.sub(startTile);
+ const deltaToCorner = pathToCorner.normalize().round();
+ const lengthToCorner = Math.round(pathToCorner.length());
+ let currentPos = startTile.copy();
+
+ let rotation = (Math.round(Math.degrees(deltaToCorner.angle()) / 90) * 90 + 360) % 360;
+
+ if (lengthToCorner > 0) {
+ for (let i = 0; i < lengthToCorner; ++i) {
+ result.push({
+ tile: currentPos.copy(),
+ rotation,
+ });
+ currentPos.addInplace(deltaToCorner);
+ }
+ }
+
+ // Place from corner to end
+ const pathFromCorner = endTile.sub(this.currentDirectionLockCorner);
+ const deltaFromCorner = pathFromCorner.normalize().round();
+ const lengthFromCorner = Math.round(pathFromCorner.length());
+
+ if (lengthFromCorner > 0) {
+ rotation = (Math.round(Math.degrees(deltaFromCorner.angle()) / 90) * 90 + 360) % 360;
+ for (let i = 0; i < lengthFromCorner + 1; ++i) {
+ result.push({
+ tile: currentPos.copy(),
+ rotation,
+ });
+ currentPos.addInplace(deltaFromCorner);
+ }
+ } else {
+ // Finish last one
+ result.push({
+ tile: currentPos.copy(),
+ rotation,
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Selects a given building
+ * @param {MetaBuilding} metaBuilding
+ */
+ startSelection(metaBuilding) {
+ this.currentMetaBuilding.set(metaBuilding);
+ }
+
+ /**
+ * Called when the selected buildings changed
+ * @param {MetaBuilding} metaBuilding
+ */
+ onSelectedMetaBuildingChanged(metaBuilding) {
+ this.abortDragging();
+ this.root.hud.signals.selectedPlacementBuildingChanged.dispatch(metaBuilding);
+ if (metaBuilding) {
+ const variant = this.preferredVariants[metaBuilding.getId()] || defaultBuildingVariant;
+ this.currentVariant.set(variant);
+
+ this.fakeEntity = new Entity(null);
+ metaBuilding.setupEntityComponents(this.fakeEntity, null);
+
+ this.fakeEntity.addComponent(
+ new StaticMapEntityComponent({
+ origin: new Vector(0, 0),
+ rotation: 0,
+ tileSize: metaBuilding.getDimensions(this.currentVariant.get()).copy(),
+ code: getCodeFromBuildingData(metaBuilding, variant, 0),
+ })
+ );
+ metaBuilding.updateVariants(this.fakeEntity, 0, this.currentVariant.get());
+ } else {
+ this.fakeEntity = null;
+ }
+
+ // Since it depends on both, rerender twice
+ this.signals.variantChanged.dispatch();
+ }
+
+ /**
+ * mouse down pre handler
+ * @param {Vector} pos
+ * @param {enumMouseButton} button
+ */
+ onMouseDown(pos, button) {
+ if (this.root.camera.getIsMapOverlayActive()) {
+ // We do not allow dragging if the overlay is active
+ return;
+ }
+
+ const metaBuilding = this.currentMetaBuilding.get();
+
+ // Placement
+ if (button === enumMouseButton.left && metaBuilding) {
+ this.currentlyDragging = true;
+ this.currentlyDeleting = false;
+ this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace();
+
+ // Place initial building, but only if direction lock is not active
+ if (!this.isDirectionLockActive) {
+ if (this.tryPlaceCurrentBuildingAt(this.lastDragTile)) {
+ this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
+ }
+ }
+ return STOP_PROPAGATION;
+ }
+
+ // Deletion
+ if (
+ button === enumMouseButton.right &&
+ (!metaBuilding || !this.root.app.settings.getAllSettings().clearCursorOnDeleteWhilePlacing)
+ ) {
+ this.currentlyDragging = true;
+ this.currentlyDeleting = true;
+ this.lastDragTile = this.root.camera.screenToWorld(pos).toTileSpace();
+ if (this.deleteBelowCursor()) {
+ return STOP_PROPAGATION;
+ }
+ }
+
+ // Cancel placement
+ if (button === enumMouseButton.right && metaBuilding) {
+ this.currentMetaBuilding.set(null);
+ }
+ }
+
+ /**
+ * mouse move pre handler
+ * @param {Vector} pos
+ */
+ onMouseMove(pos) {
+ if (this.root.camera.getIsMapOverlayActive()) {
+ return;
+ }
+
+ // Check for direction lock
+ if (this.isDirectionLockActive) {
+ return;
+ }
+
+ const metaBuilding = this.currentMetaBuilding.get();
+ if ((metaBuilding || this.currentlyDeleting) && this.lastDragTile) {
+ const oldPos = this.lastDragTile;
+ let newPos = this.root.camera.screenToWorld(pos).toTileSpace();
+
+ // Check if camera is moving, since then we do nothing
+ if (this.root.camera.desiredCenter) {
+ this.lastDragTile = newPos;
+ return;
+ }
+
+ // Check if anything changed
+ if (!oldPos.equals(newPos)) {
+ // Automatic Direction
+ if (
+ metaBuilding &&
+ metaBuilding.getRotateAutomaticallyWhilePlacing(this.currentVariant.get()) &&
+ !this.root.keyMapper.getBinding(
+ KEYMAPPINGS.placementModifiers.placementDisableAutoOrientation
+ ).pressed
+ ) {
+ const delta = newPos.sub(oldPos);
+ const angleDeg = Math.degrees(delta.angle());
+ this.currentBaseRotation = (Math.round(angleDeg / 90) * 90 + 360) % 360;
+
+ // Holding alt inverts the placement
+ if (this.root.keyMapper.getBinding(KEYMAPPINGS.placementModifiers.placeInverse).pressed) {
+ this.currentBaseRotation = (180 + this.currentBaseRotation) % 360;
+ }
+ }
+
+ // bresenham
+ let x0 = oldPos.x;
+ let y0 = oldPos.y;
+ let x1 = newPos.x;
+ let y1 = newPos.y;
+
+ var dx = Math.abs(x1 - x0);
+ var dy = Math.abs(y1 - y0);
+ var sx = x0 < x1 ? 1 : -1;
+ var sy = y0 < y1 ? 1 : -1;
+ var err = dx - dy;
+
+ let anythingPlaced = false;
+ let anythingDeleted = false;
+
+ while (this.currentlyDeleting || this.currentMetaBuilding.get()) {
+ if (this.currentlyDeleting) {
+ // Deletion
+ const contents = this.root.map.getLayerContentXY(x0, y0, this.root.currentLayer);
+ if (contents && !contents.queuedForDestroy && !contents.destroyed) {
+ if (this.root.logic.tryDeleteBuilding(contents)) {
+ anythingDeleted = true;
+ }
+ }
+ } else {
+ // Placement
+ if (this.tryPlaceCurrentBuildingAt(new Vector(x0, y0))) {
+ anythingPlaced = true;
+ }
+ }
+
+ if (x0 === x1 && y0 === y1) break;
+ var e2 = 2 * err;
+ if (e2 > -dy) {
+ err -= dy;
+ x0 += sx;
+ }
+ if (e2 < dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+
+ if (anythingPlaced) {
+ this.root.soundProxy.playUi(metaBuilding.getPlacementSound());
+ }
+ if (anythingDeleted) {
+ this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
+ }
+ }
+
+ this.lastDragTile = newPos;
+ return STOP_PROPAGATION;
+ }
+ }
+
+ /**
+ * Mouse up handler
+ */
+ onMouseUp() {
+ if (this.root.camera.getIsMapOverlayActive()) {
+ return;
+ }
+
+ // Check for direction lock
+ if (this.lastDragTile && this.currentlyDragging && this.isDirectionLockActive) {
+ this.executeDirectionLockedPlacement();
+ }
+
+ this.abortDragging();
+ }
+}
diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js
index 8d5c2e76..a8972434 100644
--- a/src/js/game/hud/parts/mass_selector.js
+++ b/src/js/game/hud/parts/mass_selector.js
@@ -1,308 +1,317 @@
-import { BaseHUDPart } from "../base_hud_part";
-import { Vector } from "../../../core/vector";
-import { STOP_PROPAGATION } from "../../../core/signal";
-import { DrawParameters } from "../../../core/draw_parameters";
-import { Entity } from "../../entity";
-import { Loader } from "../../../core/loader";
-import { globalConfig } from "../../../core/config";
-import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
-import { DynamicDomAttach } from "../dynamic_dom_attach";
-import { createLogger } from "../../../core/logging";
-import { enumMouseButton } from "../../camera";
-import { T } from "../../../translations";
-import { KEYMAPPINGS } from "../../key_action_mapper";
-import { THEME } from "../../theme";
-import { enumHubGoalRewards } from "../../tutorial_goals";
-import { Blueprint } from "../../blueprint";
-
-const logger = createLogger("hud/mass_selector");
-
-export class HUDMassSelector extends BaseHUDPart {
- createElements(parent) {}
-
- initialize() {
- this.currentSelectionStartWorld = null;
- this.currentSelectionEnd = null;
- this.selectedUids = new Set();
-
- this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this);
- this.root.hud.signals.pasteBlueprintRequested.add(this.clearSelection, this);
-
- this.root.camera.downPreHandler.add(this.onMouseDown, this);
- this.root.camera.movePreHandler.add(this.onMouseMove, this);
- this.root.camera.upPostHandler.add(this.onMouseUp, this);
-
- this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).add(this.onBack, this);
- this.root.keyMapper
- .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete)
- .add(this.confirmDelete, this);
- this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCut).add(this.confirmCut, this);
- this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this);
-
- this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this);
- this.root.signals.editModeChanged.add(this.clearSelection, this);
- }
-
- /**
- * Handles the destroy callback and makes sure we clean our list
- * @param {Entity} entity
- */
- onEntityDestroyed(entity) {
- this.selectedUids.delete(entity.uid);
- }
-
- /**
- *
- */
- onBack() {
- // Clear entities on escape
- if (this.selectedUids.size > 0) {
- this.selectedUids = new Set();
- return STOP_PROPAGATION;
- }
- }
-
- /**
- * Clears the entire selection
- */
- clearSelection() {
- this.selectedUids = new Set();
- }
-
- confirmDelete() {
- if (
- !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
- this.selectedUids.size > 100
- ) {
- const { ok } = this.root.hud.parts.dialogs.showWarning(
- T.dialogs.massDeleteConfirm.title,
- T.dialogs.massDeleteConfirm.desc.replace(
- "",
- "" + formatBigNumberFull(this.selectedUids.size)
- ),
- ["cancel:good:escape", "ok:bad:enter"]
- );
- ok.add(() => this.doDelete());
- } else {
- this.doDelete();
- }
- }
-
- doDelete() {
- const entityUids = Array.from(this.selectedUids);
- for (let i = 0; i < entityUids.length; ++i) {
- const uid = entityUids[i];
- const entity = this.root.entityMgr.findByUid(uid);
- if (!this.root.logic.tryDeleteBuilding(entity)) {
- logger.error("Error in mass delete, could not remove building");
- this.selectedUids.delete(uid);
- }
- }
- }
-
- startCopy() {
- if (this.selectedUids.size > 0) {
- if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
- this.root.hud.parts.dialogs.showInfo(
- T.dialogs.blueprintsNotUnlocked.title,
- T.dialogs.blueprintsNotUnlocked.desc
- );
- return;
- }
- this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids));
- this.selectedUids = new Set();
- this.root.soundProxy.playUiClick();
- } else {
- this.root.soundProxy.playUiError();
- }
- }
-
- confirmCut() {
- if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
- this.root.hud.parts.dialogs.showInfo(
- T.dialogs.blueprintsNotUnlocked.title,
- T.dialogs.blueprintsNotUnlocked.desc
- );
- } else if (
- !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
- this.selectedUids.size > 100
- ) {
- const { ok } = this.root.hud.parts.dialogs.showWarning(
- T.dialogs.massCutConfirm.title,
- T.dialogs.massCutConfirm.desc.replace(
- "",
- "" + formatBigNumberFull(this.selectedUids.size)
- ),
- ["cancel:good:escape", "ok:bad:enter"]
- );
- ok.add(() => this.doCut());
- } else {
- this.doCut();
- }
- }
-
- doCut() {
- if (this.selectedUids.size > 0) {
- const entityUids = Array.from(this.selectedUids);
-
- const cutAction = () => {
- // copy code relies on entities still existing, so must copy before deleting.
- this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids);
-
- for (let i = 0; i < entityUids.length; ++i) {
- const uid = entityUids[i];
- const entity = this.root.entityMgr.findByUid(uid);
- if (!this.root.logic.tryDeleteBuilding(entity)) {
- logger.error("Error in mass cut, could not remove building");
- this.selectedUids.delete(uid);
- }
- }
- };
-
- const blueprint = Blueprint.fromUids(this.root, entityUids);
- if (blueprint.canAfford(this.root)) {
- cutAction();
- } else {
- const { cancel, ok } = this.root.hud.parts.dialogs.showWarning(
- T.dialogs.massCutInsufficientConfirm.title,
- T.dialogs.massCutInsufficientConfirm.desc,
- ["cancel:good:escape", "ok:bad:enter"]
- );
- ok.add(cutAction);
- }
-
- this.root.soundProxy.playUiClick();
- } else {
- this.root.soundProxy.playUiError();
- }
- }
-
- /**
- * mouse down pre handler
- * @param {Vector} pos
- * @param {enumMouseButton} mouseButton
- */
- onMouseDown(pos, mouseButton) {
- if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).pressed) {
- return;
- }
-
- if (mouseButton !== enumMouseButton.left) {
- return;
- }
-
- if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).pressed) {
- // Start new selection
- this.selectedUids = new Set();
- }
-
- this.currentSelectionStartWorld = this.root.camera.screenToWorld(pos.copy());
- this.currentSelectionEnd = pos.copy();
- return STOP_PROPAGATION;
- }
-
- /**
- * mouse move pre handler
- * @param {Vector} pos
- */
- onMouseMove(pos) {
- if (this.currentSelectionStartWorld) {
- this.currentSelectionEnd = pos.copy();
- }
- }
-
- onMouseUp() {
- if (this.currentSelectionStartWorld) {
- const worldStart = this.currentSelectionStartWorld;
- const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
-
- const tileStart = worldStart.toTileSpace();
- const tileEnd = worldEnd.toTileSpace();
-
- const realTileStart = tileStart.min(tileEnd);
- const realTileEnd = tileStart.max(tileEnd);
-
- for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
- for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
- const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
- if (contents && this.root.logic.canDeleteBuilding(contents)) {
- this.selectedUids.add(contents.uid);
- }
- }
- }
-
- this.currentSelectionStartWorld = null;
- this.currentSelectionEnd = null;
- }
- }
-
- /**
- *
- * @param {DrawParameters} parameters
- */
- draw(parameters) {
- const boundsBorder = 2;
-
- if (this.currentSelectionStartWorld) {
- const worldStart = this.currentSelectionStartWorld;
- const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
-
- const realWorldStart = worldStart.min(worldEnd);
- const realWorldEnd = worldStart.max(worldEnd);
-
- const tileStart = worldStart.toTileSpace();
- const tileEnd = worldEnd.toTileSpace();
-
- const realTileStart = tileStart.min(tileEnd);
- const realTileEnd = tileStart.max(tileEnd);
-
- parameters.context.lineWidth = 1;
- parameters.context.fillStyle = THEME.map.selectionBackground;
- parameters.context.strokeStyle = THEME.map.selectionOutline;
- parameters.context.beginPath();
- parameters.context.rect(
- realWorldStart.x,
- realWorldStart.y,
- realWorldEnd.x - realWorldStart.x,
- realWorldEnd.y - realWorldStart.y
- );
- parameters.context.fill();
- parameters.context.stroke();
-
- parameters.context.fillStyle = THEME.map.selectionOverlay;
-
- for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
- for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
- const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
- if (contents && this.root.logic.canDeleteBuilding(contents)) {
- const staticComp = contents.components.StaticMapEntity;
- const bounds = staticComp.getTileSpaceBounds();
- parameters.context.beginRoundedRect(
- bounds.x * globalConfig.tileSize + boundsBorder,
- bounds.y * globalConfig.tileSize + boundsBorder,
- bounds.w * globalConfig.tileSize - 2 * boundsBorder,
- bounds.h * globalConfig.tileSize - 2 * boundsBorder,
- 2
- );
- parameters.context.fill();
- }
- }
- }
- }
-
- parameters.context.fillStyle = THEME.map.selectionOverlay;
- this.selectedUids.forEach(uid => {
- const entity = this.root.entityMgr.findByUid(uid);
- const staticComp = entity.components.StaticMapEntity;
- const bounds = staticComp.getTileSpaceBounds();
- parameters.context.beginRoundedRect(
- bounds.x * globalConfig.tileSize + boundsBorder,
- bounds.y * globalConfig.tileSize + boundsBorder,
- bounds.w * globalConfig.tileSize - 2 * boundsBorder,
- bounds.h * globalConfig.tileSize - 2 * boundsBorder,
- 2
- );
- parameters.context.fill();
- });
- }
-}
+import { BaseHUDPart } from "../base_hud_part";
+import { Vector } from "../../../core/vector";
+import { STOP_PROPAGATION } from "../../../core/signal";
+import { DrawParameters } from "../../../core/draw_parameters";
+import { Entity } from "../../entity";
+import { Loader } from "../../../core/loader";
+import { globalConfig } from "../../../core/config";
+import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
+import { DynamicDomAttach } from "../dynamic_dom_attach";
+import { createLogger } from "../../../core/logging";
+import { enumMouseButton } from "../../camera";
+import { T } from "../../../translations";
+import { KEYMAPPINGS } from "../../key_action_mapper";
+import { THEME } from "../../theme";
+import { enumHubGoalRewards } from "../../tutorial_goals";
+import { Blueprint } from "../../blueprint";
+
+const logger = createLogger("hud/mass_selector");
+
+export class HUDMassSelector extends BaseHUDPart {
+ createElements(parent) {}
+
+ initialize() {
+ this.currentSelectionStartWorld = null;
+ this.currentSelectionEnd = null;
+ this.selectedUids = new Set();
+
+ this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this);
+ this.root.hud.signals.pasteBlueprintRequested.add(this.clearSelection, this);
+
+ this.root.camera.downPreHandler.add(this.onMouseDown, this);
+ this.root.camera.movePreHandler.add(this.onMouseMove, this);
+ this.root.camera.upPostHandler.add(this.onMouseUp, this);
+
+ this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).add(this.onBack, this);
+ this.root.keyMapper
+ .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete)
+ .add(this.confirmDelete, this);
+ this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCut).add(this.confirmCut, this);
+ this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this);
+
+ this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this);
+ this.root.signals.editModeChanged.add(this.clearSelection, this);
+ }
+
+ /**
+ * Handles the destroy callback and makes sure we clean our list
+ * @param {Entity} entity
+ */
+ onEntityDestroyed(entity) {
+ this.selectedUids.delete(entity.uid);
+ }
+
+ /**
+ *
+ */
+ onBack() {
+ // Clear entities on escape
+ if (this.selectedUids.size > 0) {
+ this.selectedUids = new Set();
+ return STOP_PROPAGATION;
+ }
+ }
+
+ /**
+ * Clears the entire selection
+ */
+ clearSelection() {
+ this.selectedUids = new Set();
+ }
+
+ confirmDelete() {
+ if (
+ !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
+ this.selectedUids.size > 100
+ ) {
+ const { ok } = this.root.hud.parts.dialogs.showWarning(
+ T.dialogs.massDeleteConfirm.title,
+ T.dialogs.massDeleteConfirm.desc.replace(
+ "",
+ "" + formatBigNumberFull(this.selectedUids.size)
+ ),
+ ["cancel:good:escape", "ok:bad:enter"]
+ );
+ ok.add(() => this.doDelete());
+ } else {
+ this.doDelete();
+ }
+ }
+
+ doDelete() {
+ const entityUids = Array.from(this.selectedUids);
+ for (let i = 0; i < entityUids.length; ++i) {
+ const uid = entityUids[i];
+ const entity = this.root.entityMgr.findByUid(uid);
+ if (!this.root.logic.tryDeleteBuilding(entity)) {
+ logger.error("Error in mass delete, could not remove building");
+ this.selectedUids.delete(uid);
+ }
+ }
+ }
+
+ startCopy() {
+ if (this.selectedUids.size > 0) {
+ if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
+ this.root.hud.parts.dialogs.showInfo(
+ T.dialogs.blueprintsNotUnlocked.title,
+ T.dialogs.blueprintsNotUnlocked.desc
+ );
+ return;
+ }
+ this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids));
+ this.selectedUids = new Set();
+ this.root.soundProxy.playUiClick();
+ } else {
+ this.root.soundProxy.playUiError();
+ }
+ }
+
+ confirmCut() {
+ if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
+ this.root.hud.parts.dialogs.showInfo(
+ T.dialogs.blueprintsNotUnlocked.title,
+ T.dialogs.blueprintsNotUnlocked.desc
+ );
+ } else if (
+ !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
+ this.selectedUids.size > 100
+ ) {
+ const { ok } = this.root.hud.parts.dialogs.showWarning(
+ T.dialogs.massCutConfirm.title,
+ T.dialogs.massCutConfirm.desc.replace(
+ "",
+ "" + formatBigNumberFull(this.selectedUids.size)
+ ),
+ ["cancel:good:escape", "ok:bad:enter"]
+ );
+ ok.add(() => this.doCut());
+ } else {
+ this.doCut();
+ }
+ }
+
+ doCut() {
+ if (this.selectedUids.size > 0) {
+ const entityUids = Array.from(this.selectedUids);
+
+ const cutAction = () => {
+ // copy code relies on entities still existing, so must copy before deleting.
+ this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids);
+
+ for (let i = 0; i < entityUids.length; ++i) {
+ const uid = entityUids[i];
+ const entity = this.root.entityMgr.findByUid(uid);
+ if (!this.root.logic.tryDeleteBuilding(entity)) {
+ logger.error("Error in mass cut, could not remove building");
+ this.selectedUids.delete(uid);
+ }
+ }
+ };
+
+ const blueprint = Blueprint.fromUids(this.root, entityUids);
+ if (blueprint.canAfford(this.root)) {
+ cutAction();
+ } else {
+ const { cancel, ok } = this.root.hud.parts.dialogs.showWarning(
+ T.dialogs.massCutInsufficientConfirm.title,
+ T.dialogs.massCutInsufficientConfirm.desc,
+ ["cancel:good:escape", "ok:bad:enter"]
+ );
+ ok.add(cutAction);
+ }
+
+ this.root.soundProxy.playUiClick();
+ } else {
+ this.root.soundProxy.playUiError();
+ }
+ }
+
+ /**
+ * mouse down pre handler
+ * @param {Vector} pos
+ * @param {enumMouseButton} mouseButton
+ */
+ onMouseDown(pos, mouseButton) {
+ if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).pressed) {
+ return;
+ }
+
+ if (mouseButton !== enumMouseButton.left) {
+ return;
+ }
+
+ if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).pressed) {
+ // Start new selection
+ this.selectedUids = new Set();
+ }
+
+ this.currentSelectionStartWorld = this.root.camera.screenToWorld(pos.copy());
+ this.currentSelectionEnd = pos.copy();
+ return STOP_PROPAGATION;
+ }
+
+ /**
+ * mouse move pre handler
+ * @param {Vector} pos
+ */
+ onMouseMove(pos) {
+ if (this.currentSelectionStartWorld) {
+ this.currentSelectionEnd = pos.copy();
+ }
+ }
+
+ onMouseUp() {
+ if (this.currentSelectionStartWorld) {
+ const worldStart = this.currentSelectionStartWorld;
+ const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
+
+ const tileStart = worldStart.toTileSpace();
+ const tileEnd = worldEnd.toTileSpace();
+
+ const realTileStart = tileStart.min(tileEnd);
+ const realTileEnd = tileStart.max(tileEnd);
+
+ for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
+ for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
+ const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
+ if (contents && this.root.logic.canDeleteBuilding(contents)) {
+ this.selectedUids.add(contents.uid);
+ }
+ }
+ }
+
+ this.currentSelectionStartWorld = null;
+ this.currentSelectionEnd = null;
+ }
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ draw(parameters) {
+ const boundsBorder = 2;
+
+ if (this.currentSelectionStartWorld) {
+ const worldStart = this.currentSelectionStartWorld;
+ const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
+
+ const realWorldStart = worldStart.min(worldEnd);
+ const realWorldEnd = worldStart.max(worldEnd);
+
+ const tileStart = worldStart.toTileSpace();
+ const tileEnd = worldEnd.toTileSpace();
+
+ const realTileStart = tileStart.min(tileEnd);
+ const realTileEnd = tileStart.max(tileEnd);
+
+ parameters.context.lineWidth = 1;
+ parameters.context.fillStyle = THEME.map.selectionBackground;
+ parameters.context.strokeStyle = THEME.map.selectionOutline;
+ parameters.context.beginPath();
+ parameters.context.rect(
+ realWorldStart.x,
+ realWorldStart.y,
+ realWorldEnd.x - realWorldStart.x,
+ realWorldEnd.y - realWorldStart.y
+ );
+ parameters.context.fill();
+ parameters.context.stroke();
+
+ parameters.context.fillStyle = THEME.map.selectionOverlay;
+
+ const renderedUids = new Set();
+
+ for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
+ for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
+ const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
+ if (contents && this.root.logic.canDeleteBuilding(contents)) {
+ // Prevent rendering the overlay twice
+ const uid = contents.uid;
+ if (renderedUids.has(uid)) {
+ continue;
+ }
+ renderedUids.add(uid);
+
+ const staticComp = contents.components.StaticMapEntity;
+ const bounds = staticComp.getTileSpaceBounds();
+ parameters.context.beginRoundedRect(
+ bounds.x * globalConfig.tileSize + boundsBorder,
+ bounds.y * globalConfig.tileSize + boundsBorder,
+ bounds.w * globalConfig.tileSize - 2 * boundsBorder,
+ bounds.h * globalConfig.tileSize - 2 * boundsBorder,
+ 2
+ );
+ parameters.context.fill();
+ }
+ }
+ }
+ }
+
+ parameters.context.fillStyle = THEME.map.selectionOverlay;
+ this.selectedUids.forEach(uid => {
+ const entity = this.root.entityMgr.findByUid(uid);
+ const staticComp = entity.components.StaticMapEntity;
+ const bounds = staticComp.getTileSpaceBounds();
+ parameters.context.beginRoundedRect(
+ bounds.x * globalConfig.tileSize + boundsBorder,
+ bounds.y * globalConfig.tileSize + boundsBorder,
+ bounds.w * globalConfig.tileSize - 2 * boundsBorder,
+ bounds.h * globalConfig.tileSize - 2 * boundsBorder,
+ 2
+ );
+ parameters.context.fill();
+ });
+ }
+}
diff --git a/src/js/game/hud/parts/shape_viewer.js b/src/js/game/hud/parts/shape_viewer.js
index 55feb976..ea4273aa 100644
--- a/src/js/game/hud/parts/shape_viewer.js
+++ b/src/js/game/hud/parts/shape_viewer.js
@@ -1,133 +1,133 @@
-import { InputReceiver } from "../../../core/input_receiver";
-import { makeDiv, removeAllChildren } from "../../../core/utils";
-import { T } from "../../../translations";
-import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
-import { ShapeDefinition } from "../../shape_definition";
-import { BaseHUDPart } from "../base_hud_part";
-import { DynamicDomAttach } from "../dynamic_dom_attach";
-
-const copy = require("clipboard-copy");
-
-export class HUDShapeViewer extends BaseHUDPart {
- createElements(parent) {
- this.background = makeDiv(parent, "ingame_HUD_ShapeViewer", ["ingameDialog"]);
-
- // DIALOG Inner / Wrapper
- this.dialogInner = makeDiv(this.background, null, ["dialogInner"]);
- this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shapeViewer.title);
- this.closeButton = makeDiv(this.title, null, ["closeButton"]);
- this.trackClicks(this.closeButton, this.close);
- this.contentDiv = makeDiv(this.dialogInner, null, ["content"]);
-
- this.renderArea = makeDiv(this.contentDiv, null, ["renderArea"]);
- this.infoArea = makeDiv(this.contentDiv, null, ["infoArea"]);
-
- // Create button to copy the shape area
- this.copyButton = document.createElement("button");
- this.copyButton.classList.add("styledButton", "copyKey");
- this.copyButton.innerText = T.ingame.shapeViewer.copyKey;
- this.infoArea.appendChild(this.copyButton);
- }
-
- initialize() {
- this.root.hud.signals.viewShapeDetailsRequested.add(this.renderForShape, this);
-
- this.domAttach = new DynamicDomAttach(this.root, this.background, {
- attachClass: "visible",
- });
-
- this.currentShapeKey = null;
-
- this.inputReciever = new InputReceiver("shape_viewer");
- this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
-
- this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
-
- this.trackClicks(this.copyButton, this.onCopyKeyRequested);
-
- this.close();
- }
-
- /**
- * Called when the copying of a key was requested
- */
- onCopyKeyRequested() {
- if (this.currentShapeKey) {
- copy(this.currentShapeKey);
- this.close();
- }
- }
-
- /**
- * Closes the dialog
- */
- close() {
- this.visible = false;
- document.body.classList.remove("ingameDialogOpen");
- this.root.app.inputMgr.makeSureDetached(this.inputReciever);
- this.update();
- }
-
- /**
- * Shows the viewer for a given definition
- * @param {ShapeDefinition} definition
- */
- renderForShape(definition) {
- this.visible = true;
- document.body.classList.add("ingameDialogOpen");
- this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
-
- removeAllChildren(this.renderArea);
-
- this.currentShapeKey = definition.getHash();
-
- const layers = definition.layers;
- this.contentDiv.setAttribute("data-layers", layers.length);
-
- for (let i = 0; i < layers.length; ++i) {
- const layerElem = makeDiv(this.renderArea, null, ["layer", "layer-" + i]);
-
- let fakeLayers = [];
- for (let k = 0; k < i; ++k) {
- fakeLayers.push([null, null, null, null]);
- }
- fakeLayers.push(layers[i]);
-
- const thisLayerOnly = new ShapeDefinition({ layers: fakeLayers });
- const thisLayerCanvas = thisLayerOnly.generateAsCanvas(160);
- layerElem.appendChild(thisLayerCanvas);
-
- for (let quad = 0; quad < 4; ++quad) {
- const quadElem = makeDiv(layerElem, null, ["quad", "quad-" + quad]);
-
- const contents = layers[i][quad];
- if (contents) {
- const colorLabelElem = makeDiv(
- quadElem,
- null,
- ["colorLabel"],
- T.ingame.colors[contents.color]
- );
- } else {
- const emptyLabelElem = makeDiv(
- quadElem,
- null,
- ["emptyLabel"],
- T.ingame.shapeViewer.empty
- );
- }
- }
- }
- }
-
- /**
- * Cleans up everything
- */
- cleanup() {
- document.body.classList.remove("ingameDialogOpen");
- }
-
- update() {
- this.domAttach.update(this.visible);
- }
-}
+import { InputReceiver } from "../../../core/input_receiver";
+import { makeDiv, removeAllChildren } from "../../../core/utils";
+import { T } from "../../../translations";
+import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
+import { ShapeDefinition } from "../../shape_definition";
+import { BaseHUDPart } from "../base_hud_part";
+import { DynamicDomAttach } from "../dynamic_dom_attach";
+
+const copy = require("clipboard-copy");
+
+export class HUDShapeViewer extends BaseHUDPart {
+ createElements(parent) {
+ this.background = makeDiv(parent, "ingame_HUD_ShapeViewer", ["ingameDialog"]);
+
+ // DIALOG Inner / Wrapper
+ this.dialogInner = makeDiv(this.background, null, ["dialogInner"]);
+ this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shapeViewer.title);
+ this.closeButton = makeDiv(this.title, null, ["closeButton"]);
+ this.trackClicks(this.closeButton, this.close);
+ this.contentDiv = makeDiv(this.dialogInner, null, ["content"]);
+
+ this.renderArea = makeDiv(this.contentDiv, null, ["renderArea"]);
+ this.infoArea = makeDiv(this.contentDiv, null, ["infoArea"]);
+
+ // Create button to copy the shape area
+ this.copyButton = document.createElement("button");
+ this.copyButton.classList.add("styledButton", "copyKey");
+ this.copyButton.innerText = T.ingame.shapeViewer.copyKey;
+ this.infoArea.appendChild(this.copyButton);
+ }
+
+ initialize() {
+ this.root.hud.signals.viewShapeDetailsRequested.add(this.renderForShape, this);
+
+ this.domAttach = new DynamicDomAttach(this.root, this.background, {
+ attachClass: "visible",
+ });
+
+ this.currentShapeKey = null;
+
+ this.inputReciever = new InputReceiver("shape_viewer");
+ this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
+
+ this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
+
+ this.trackClicks(this.copyButton, this.onCopyKeyRequested);
+
+ this.close();
+ }
+
+ /**
+ * Called when the copying of a key was requested
+ */
+ onCopyKeyRequested() {
+ if (this.currentShapeKey) {
+ copy(this.currentShapeKey);
+ this.close();
+ }
+ }
+
+ /**
+ * Closes the dialog
+ */
+ close() {
+ this.visible = false;
+ document.body.classList.remove("ingameDialogOpen");
+ this.root.app.inputMgr.makeSureDetached(this.inputReciever);
+ this.update();
+ }
+
+ /**
+ * Shows the viewer for a given definition
+ * @param {ShapeDefinition} definition
+ */
+ renderForShape(definition) {
+ this.visible = true;
+ document.body.classList.add("ingameDialogOpen");
+ this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
+
+ removeAllChildren(this.renderArea);
+
+ this.currentShapeKey = definition.getHash();
+
+ const layers = definition.layers;
+ this.contentDiv.setAttribute("data-layers", layers.length);
+
+ for (let i = layers.length - 1; i >= 0; --i) {
+ const layerElem = makeDiv(this.renderArea, null, ["layer", "layer-" + i]);
+
+ let fakeLayers = [];
+ for (let k = 0; k < i; ++k) {
+ fakeLayers.push([null, null, null, null]);
+ }
+ fakeLayers.push(layers[i]);
+
+ const thisLayerOnly = new ShapeDefinition({ layers: fakeLayers });
+ const thisLayerCanvas = thisLayerOnly.generateAsCanvas(160);
+ layerElem.appendChild(thisLayerCanvas);
+
+ for (let quad = 0; quad < 4; ++quad) {
+ const quadElem = makeDiv(layerElem, null, ["quad", "quad-" + quad]);
+
+ const contents = layers[i][quad];
+ if (contents) {
+ const colorLabelElem = makeDiv(
+ quadElem,
+ null,
+ ["colorLabel"],
+ T.ingame.colors[contents.color]
+ );
+ } else {
+ const emptyLabelElem = makeDiv(
+ quadElem,
+ null,
+ ["emptyLabel"],
+ T.ingame.shapeViewer.empty
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Cleans up everything
+ */
+ cleanup() {
+ document.body.classList.remove("ingameDialogOpen");
+ }
+
+ update() {
+ this.domAttach.update(this.visible);
+ }
+}
diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js
index e15af4fb..c5136312 100644
--- a/src/js/game/hud/parts/statistics.js
+++ b/src/js/game/hud/parts/statistics.js
@@ -47,9 +47,11 @@ export class HUDStatistics extends BaseHUDPart {
this.trackClicks(button, () => this.setDataSource(dataSource));
}
+ const buttonDisplaySorted = makeButton(this.filtersDisplayMode, ["displaySorted"]);
const buttonDisplayDetailed = makeButton(this.filtersDisplayMode, ["displayDetailed"]);
const buttonDisplayIcons = makeButton(this.filtersDisplayMode, ["displayIcons"]);
+ this.trackClicks(buttonDisplaySorted, () => this.toggleSorted());
this.trackClicks(buttonDisplayIcons, () => this.setDisplayMode(enumDisplayMode.icons));
this.trackClicks(buttonDisplayDetailed, () => this.setDisplayMode(enumDisplayMode.detailed));
@@ -80,6 +82,21 @@ export class HUDStatistics extends BaseHUDPart {
}
}
+ /**
+ * @param {boolean} sorted
+ */
+ setSorted(sorted) {
+ this.sorted = sorted;
+ this.dialogInner.setAttribute("data-sorted", String(sorted));
+ if (this.visible) {
+ this.rerenderFull();
+ }
+ }
+
+ toggleSorted() {
+ this.setSorted(!this.sorted);
+ }
+
initialize() {
this.domAttach = new DynamicDomAttach(this.root, this.background, {
attachClass: "visible",
@@ -95,6 +112,7 @@ export class HUDStatistics extends BaseHUDPart {
/** @type {Object.} */
this.activeHandles = {};
+ this.setSorted(true);
this.setDataSource(enumAnalyticsDataSource.produced);
this.setDisplayMode(enumDisplayMode.detailed);
@@ -183,7 +201,22 @@ export class HUDStatistics extends BaseHUDPart {
}
}
- entries.sort((a, b) => b[1] - a[1]);
+ const pinnedShapes = this.root.hud.parts.pinnedShapes;
+
+ entries.sort((a, b) => {
+ const aPinned = pinnedShapes.isShapePinned(a[0]);
+ const bPinned = pinnedShapes.isShapePinned(b[0]);
+
+ if (aPinned !== bPinned) {
+ return aPinned ? -1 : 1;
+ }
+
+ // Sort by shape key for some consistency
+ if (!this.sorted || b[1] == a[1]) {
+ return b[0].localeCompare(a[0]);
+ }
+ return b[1] - a[1];
+ });
let rendered = new Set();
diff --git a/src/js/game/hud/parts/statistics_handle.js b/src/js/game/hud/parts/statistics_handle.js
index d5c60d3b..6f49f8d3 100644
--- a/src/js/game/hud/parts/statistics_handle.js
+++ b/src/js/game/hud/parts/statistics_handle.js
@@ -74,6 +74,11 @@ export class HUDShapeStatisticsHandle {
return;
}
+ this.element.classList.toggle(
+ "pinned",
+ this.root.hud.parts.pinnedShapes.isShapePinned(this.definition.getHash())
+ );
+
switch (dataSource) {
case enumAnalyticsDataSource.stored: {
this.counter.innerText = formatBigNumber(
@@ -87,15 +92,10 @@ export class HUDShapeStatisticsHandle {
(this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) /
globalConfig.analyticsSliceDurationSeconds) *
60;
- this.counter.innerText = T.ingame.statistics.shapesPerMinute.replace(
+ this.counter.innerText = T.ingame.statistics.shapesPerSecond.replace(
"",
- formatBigNumber(rate)
+ formatBigNumber(rate / 60)
);
-
- if (G_IS_DEV && globalConfig.debug.detailedStatistics) {
- this.counter.innerText = "" + round2Digits(rate / 60) + " /s";
- }
-
break;
}
}
diff --git a/src/js/game/hud/parts/wires_toolbar.js b/src/js/game/hud/parts/wires_toolbar.js
index 542218e2..af5a31dd 100644
--- a/src/js/game/hud/parts/wires_toolbar.js
+++ b/src/js/game/hud/parts/wires_toolbar.js
@@ -1,25 +1,27 @@
-import { HUDBaseToolbar } from "./base_toolbar";
-import { MetaWireBuilding } from "../../buildings/wire";
-import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
-import { MetaLogicGateBuilding } from "../../buildings/logic_gate";
-import { MetaLeverBuilding } from "../../buildings/lever";
-import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel";
-
-const supportedBuildings = [
- MetaWireBuilding,
- MetaWireTunnelBuilding,
- MetaConstantSignalBuilding,
- MetaLogicGateBuilding,
- MetaLeverBuilding,
-];
-
-export class HUDWiresToolbar extends HUDBaseToolbar {
- constructor(root) {
- super(root, {
- supportedBuildings,
- visibilityCondition: () =>
- !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
- htmlElementId: "ingame_HUD_wires_toolbar",
- });
- }
-}
+import { HUDBaseToolbar } from "./base_toolbar";
+import { MetaWireBuilding } from "../../buildings/wire";
+import { MetaConstantSignalBuilding } from "../../buildings/constant_signal";
+import { MetaLogicGateBuilding } from "../../buildings/logic_gate";
+import { MetaLeverBuilding } from "../../buildings/lever";
+import { MetaWireTunnelBuilding } from "../../buildings/wire_tunnel";
+import { MetaVirtualProcessorBuilding } from "../../buildings/virtual_processor";
+
+const supportedBuildings = [
+ MetaWireBuilding,
+ MetaWireTunnelBuilding,
+ MetaConstantSignalBuilding,
+ MetaLogicGateBuilding,
+ MetaLeverBuilding,
+ MetaVirtualProcessorBuilding,
+];
+
+export class HUDWiresToolbar extends HUDBaseToolbar {
+ constructor(root) {
+ super(root, {
+ supportedBuildings,
+ visibilityCondition: () =>
+ !this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
+ htmlElementId: "ingame_HUD_wires_toolbar",
+ });
+ }
+}
diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js
index f88e5a22..099e2b21 100644
--- a/src/js/game/key_action_mapper.js
+++ b/src/js/game/key_action_mapper.js
@@ -1,456 +1,457 @@
-/* typehints:start */
-import { GameRoot } from "./root";
-import { InputReceiver } from "../core/input_receiver";
-import { Application } from "../application";
-/* typehints:end */
-
-import { Signal, STOP_PROPAGATION } from "../core/signal";
-import { IS_MOBILE } from "../core/config";
-import { T } from "../translations";
-function key(str) {
- return str.toUpperCase().charCodeAt(0);
-}
-
-export const KEYMAPPINGS = {
- general: {
- confirm: { keyCode: 13 }, // enter
- back: { keyCode: 27, builtin: true }, // escape
- },
-
- ingame: {
- menuOpenShop: { keyCode: key("F") },
- menuOpenStats: { keyCode: key("G") },
- menuClose: { keyCode: key("Q") },
-
- toggleHud: { keyCode: 113 }, // F2
- exportScreenshot: { keyCode: 114 }, // F3PS
- toggleFPSInfo: { keyCode: 115 }, // F4
-
- switchLayers: { keyCode: key("Y") },
- },
-
- navigation: {
- mapMoveUp: { keyCode: key("W") },
- mapMoveRight: { keyCode: key("D") },
- mapMoveDown: { keyCode: key("S") },
- mapMoveLeft: { keyCode: key("A") },
- mapMoveFaster: { keyCode: 16 }, //shift
-
- centerMap: { keyCode: 32 }, // SPACE
- mapZoomIn: { keyCode: 187, repeated: true }, // "+"
- mapZoomOut: { keyCode: 189, repeated: true }, // "-"
-
- createMarker: { keyCode: key("M") },
- },
-
- buildings: {
- belt: { keyCode: key("1") },
- splitter: { keyCode: key("2") },
- underground_belt: { keyCode: key("3") },
- miner: { keyCode: key("4") },
- cutter: { keyCode: key("5") },
- rotater: { keyCode: key("6") },
- stacker: { keyCode: key("7") },
- mixer: { keyCode: key("8") },
- painter: { keyCode: key("9") },
- trash: { keyCode: key("0") },
-
- lever: { keyCode: key("L") },
- filter: { keyCode: key("B") },
- display: { keyCode: key("N") },
-
- wire: { keyCode: key("1") },
- wire_tunnel: { keyCode: key("2") },
- constant_signal: { keyCode: key("3") },
- logic_gate: { keyCode: key("4") },
- },
-
- placement: {
- pipette: { keyCode: key("Q") },
- rotateWhilePlacing: { keyCode: key("R") },
- rotateInverseModifier: { keyCode: 16 }, // SHIFT
- cycleBuildingVariants: { keyCode: key("T") },
- cycleBuildings: { keyCode: 9 }, // TAB
- switchDirectionLockSide: { keyCode: key("R") },
- },
-
- massSelect: {
- massSelectStart: { keyCode: 17 }, // CTRL
- massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
- massSelectCopy: { keyCode: key("C") },
- massSelectCut: { keyCode: key("X") },
- confirmMassDelete: { keyCode: 46 }, // DEL
- pasteLastBlueprint: { keyCode: key("V") },
- },
-
- placementModifiers: {
- lockBeltDirection: { keyCode: 16 }, // SHIFT
- placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
- placeMultiple: { keyCode: 16 }, // SHIFT
- placeInverse: { keyCode: 18 }, // ALT
- },
-};
-
-// Assign ids
-for (const categoryId in KEYMAPPINGS) {
- for (const mappingId in KEYMAPPINGS[categoryId]) {
- KEYMAPPINGS[categoryId][mappingId].id = mappingId;
- }
-}
-
-export const KEYCODE_LMB = 1;
-export const KEYCODE_MMB = 2;
-export const KEYCODE_RMB = 3;
-
-/**
- * Returns a keycode -> string
- * @param {number} code
- * @returns {string}
- */
-export function getStringForKeyCode(code) {
- switch (code) {
- case KEYCODE_LMB:
- return "LMB";
- case KEYCODE_MMB:
- return "MMB";
- case KEYCODE_RMB:
- return "RMB";
- case 4:
- return "MB4";
- case 5:
- return "MB5";
- case 8:
- return "⌫";
- case 9:
- return T.global.keys.tab;
- case 13:
- return "⏎";
- case 16:
- return "⇪";
- case 17:
- return T.global.keys.control;
- case 18:
- return T.global.keys.alt;
- case 19:
- return "PAUSE";
- case 20:
- return "CAPS";
- case 27:
- return T.global.keys.escape;
- case 32:
- return T.global.keys.space;
- case 33:
- return "PGUP";
- case 34:
- return "PGDOWN";
- case 35:
- return "END";
- case 36:
- return "HOME";
- case 37:
- return "⬅";
- case 38:
- return "⬆";
- case 39:
- return "➡";
- case 40:
- return "⬇";
- case 44:
- return "PRNT";
- case 45:
- return "INS";
- case 46:
- return "DEL";
- case 93:
- return "SEL";
- case 96:
- return "NUM 0";
- case 97:
- return "NUM 1";
- case 98:
- return "NUM 2";
- case 99:
- return "NUM 3";
- case 100:
- return "NUM 4";
- case 101:
- return "NUM 5";
- case 102:
- return "NUM 6";
- case 103:
- return "NUM 7";
- case 104:
- return "NUM 8";
- case 105:
- return "NUM 9";
- case 106:
- return "*";
- case 107:
- return "+";
- case 109:
- return "-";
- case 110:
- return ".";
- case 111:
- return "/";
- case 112:
- return "F1";
- case 113:
- return "F2";
- case 114:
- return "F3";
- case 115:
- return "F4";
- case 116:
- return "F4";
- case 117:
- return "F5";
- case 118:
- return "F6";
- case 119:
- return "F7";
- case 120:
- return "F8";
- case 121:
- return "F9";
- case 122:
- return "F10";
- case 123:
- return "F11";
- case 124:
- return "F12";
-
- case 144:
- return "NUMLOCK";
- case 145:
- return "SCRLOCK";
- case 182:
- return "COMP";
- case 183:
- return "CALC";
- case 186:
- return ";";
- case 187:
- return "+";
- case 188:
- return ",";
- case 189:
- return "-";
- case 191:
- return "/";
- case 219:
- return "[";
- case 220:
- return "\\";
- case 221:
- return "]";
- case 222:
- return "'";
- }
-
- return String.fromCharCode(code);
-}
-
-export class Keybinding {
- /**
- *
- * @param {KeyActionMapper} keyMapper
- * @param {Application} app
- * @param {object} param0
- * @param {number} param0.keyCode
- * @param {boolean=} param0.builtin
- * @param {boolean=} param0.repeated
- */
- constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
- assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
- this.keyMapper = keyMapper;
- this.app = app;
- this.keyCode = keyCode;
- this.builtin = builtin;
- this.repeated = repeated;
-
- this.signal = new Signal();
- this.toggled = new Signal();
- }
-
- /**
- * Returns whether this binding is currently pressed
- * @returns {boolean}
- */
- get pressed() {
- // Check if the key is down
- if (this.app.inputMgr.keysDown.has(this.keyCode)) {
- // Check if it is the top reciever
- const reciever = this.keyMapper.inputReceiver;
- return this.app.inputMgr.getTopReciever() === reciever;
- }
- return false;
- }
-
- /**
- * Adds an event listener
- * @param {function() : void} receiver
- * @param {object=} scope
- */
- add(receiver, scope = null) {
- this.signal.add(receiver, scope);
- }
-
- /**
- * @param {Element} elem
- * @returns {HTMLElement} the created element, or null if the keybindings are not shown
- * */
- appendLabelToElement(elem) {
- if (IS_MOBILE) {
- return null;
- }
- const spacer = document.createElement("code");
- spacer.classList.add("keybinding");
- spacer.innerHTML = getStringForKeyCode(this.keyCode);
- elem.appendChild(spacer);
- return spacer;
- }
-
- /**
- * Returns the key code as a nice string
- */
- getKeyCodeString() {
- return getStringForKeyCode(this.keyCode);
- }
-
- /**
- * Remvoes all signal receivers
- */
- clearSignalReceivers() {
- this.signal.removeAll();
- }
-}
-
-export class KeyActionMapper {
- /**
- *
- * @param {GameRoot} root
- * @param {InputReceiver} inputReciever
- */
- constructor(root, inputReciever) {
- this.root = root;
- this.inputReceiver = inputReciever;
-
- inputReciever.keydown.add(this.handleKeydown, this);
- inputReciever.keyup.add(this.handleKeyup, this);
-
- /** @type {Object.} */
- this.keybindings = {};
-
- const overrides = root.app.settings.getKeybindingOverrides();
-
- for (const category in KEYMAPPINGS) {
- for (const key in KEYMAPPINGS[category]) {
- let payload = Object.assign({}, KEYMAPPINGS[category][key]);
- if (overrides[key]) {
- payload.keyCode = overrides[key];
- }
-
- this.keybindings[key] = new Keybinding(this, this.root.app, payload);
- }
- }
-
- inputReciever.pageBlur.add(this.onPageBlur, this);
- inputReciever.destroyed.add(this.cleanup, this);
- }
-
- /**
- * Returns all keybindings starting with the given id
- * @param {string} pattern
- * @returns {Array}
- */
- getKeybindingsStartingWith(pattern) {
- let result = [];
- for (const key in this.keybindings) {
- if (key.startsWith(pattern)) {
- result.push(this.keybindings[key]);
- }
- }
- return result;
- }
-
- /**
- * Forwards the given events to the other mapper (used in tooltips)
- * @param {KeyActionMapper} receiver
- * @param {Array} bindings
- */
- forward(receiver, bindings) {
- for (let i = 0; i < bindings.length; ++i) {
- const key = bindings[i];
- this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
- }
- }
-
- cleanup() {
- for (const key in this.keybindings) {
- this.keybindings[key].signal.removeAll();
- }
- }
-
- onPageBlur() {
- // Reset all down states
- // Find mapping
- for (const key in this.keybindings) {
- /** @type {Keybinding} */
- const binding = this.keybindings[key];
- }
- }
-
- /**
- * Internal keydown handler
- * @param {object} param0
- * @param {number} param0.keyCode
- * @param {boolean} param0.shift
- * @param {boolean} param0.alt
- * @param {boolean=} param0.initial
- */
- handleKeydown({ keyCode, shift, alt, initial }) {
- let stop = false;
-
- // Find mapping
- for (const key in this.keybindings) {
- /** @type {Keybinding} */
- const binding = this.keybindings[key];
- if (binding.keyCode === keyCode && (initial || binding.repeated)) {
- /** @type {Signal} */
- const signal = this.keybindings[key].signal;
- if (signal.dispatch() === STOP_PROPAGATION) {
- return;
- }
- }
- }
-
- if (stop) {
- return STOP_PROPAGATION;
- }
- }
-
- /**
- * Internal keyup handler
- * @param {object} param0
- * @param {number} param0.keyCode
- * @param {boolean} param0.shift
- * @param {boolean} param0.alt
- */
- handleKeyup({ keyCode, shift, alt }) {
- // Empty
- }
-
- /**
- * Returns a given keybinding
- * @param {{ keyCode: number }} binding
- * @returns {Keybinding}
- */
- getBinding(binding) {
- // @ts-ignore
- const id = binding.id;
- assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
- assert(this.keybindings[id], "Keybinding " + id + " not known!");
- return this.keybindings[id];
- }
-}
+/* typehints:start */
+import { GameRoot } from "./root";
+import { InputReceiver } from "../core/input_receiver";
+import { Application } from "../application";
+/* typehints:end */
+
+import { Signal, STOP_PROPAGATION } from "../core/signal";
+import { IS_MOBILE } from "../core/config";
+import { T } from "../translations";
+function key(str) {
+ return str.toUpperCase().charCodeAt(0);
+}
+
+export const KEYMAPPINGS = {
+ general: {
+ confirm: { keyCode: 13 }, // enter
+ back: { keyCode: 27, builtin: true }, // escape
+ },
+
+ ingame: {
+ menuOpenShop: { keyCode: key("F") },
+ menuOpenStats: { keyCode: key("G") },
+ menuClose: { keyCode: key("Q") },
+
+ toggleHud: { keyCode: 113 }, // F2
+ exportScreenshot: { keyCode: 114 }, // F3PS
+ toggleFPSInfo: { keyCode: 115 }, // F4
+
+ switchLayers: { keyCode: key("Y") },
+ },
+
+ navigation: {
+ mapMoveUp: { keyCode: key("W") },
+ mapMoveRight: { keyCode: key("D") },
+ mapMoveDown: { keyCode: key("S") },
+ mapMoveLeft: { keyCode: key("A") },
+ mapMoveFaster: { keyCode: 16 }, //shift
+
+ centerMap: { keyCode: 32 }, // SPACE
+ mapZoomIn: { keyCode: 187, repeated: true }, // "+"
+ mapZoomOut: { keyCode: 189, repeated: true }, // "-"
+
+ createMarker: { keyCode: key("M") },
+ },
+
+ buildings: {
+ belt: { keyCode: key("1") },
+ splitter: { keyCode: key("2") },
+ underground_belt: { keyCode: key("3") },
+ miner: { keyCode: key("4") },
+ cutter: { keyCode: key("5") },
+ rotater: { keyCode: key("6") },
+ stacker: { keyCode: key("7") },
+ mixer: { keyCode: key("8") },
+ painter: { keyCode: key("9") },
+ trash: { keyCode: key("0") },
+
+ lever: { keyCode: key("L") },
+ filter: { keyCode: key("B") },
+ display: { keyCode: key("N") },
+
+ wire: { keyCode: key("1") },
+ wire_tunnel: { keyCode: key("2") },
+ constant_signal: { keyCode: key("3") },
+ logic_gate: { keyCode: key("4") },
+ virtual_processor: { keyCode: key("5") },
+ },
+
+ placement: {
+ pipette: { keyCode: key("Q") },
+ rotateWhilePlacing: { keyCode: key("R") },
+ rotateInverseModifier: { keyCode: 16 }, // SHIFT
+ cycleBuildingVariants: { keyCode: key("T") },
+ cycleBuildings: { keyCode: 9 }, // TAB
+ switchDirectionLockSide: { keyCode: key("R") },
+ },
+
+ massSelect: {
+ massSelectStart: { keyCode: 17 }, // CTRL
+ massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
+ massSelectCopy: { keyCode: key("C") },
+ massSelectCut: { keyCode: key("X") },
+ confirmMassDelete: { keyCode: 46 }, // DEL
+ pasteLastBlueprint: { keyCode: key("V") },
+ },
+
+ placementModifiers: {
+ lockBeltDirection: { keyCode: 16 }, // SHIFT
+ placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
+ placeMultiple: { keyCode: 16 }, // SHIFT
+ placeInverse: { keyCode: 18 }, // ALT
+ },
+};
+
+// Assign ids
+for (const categoryId in KEYMAPPINGS) {
+ for (const mappingId in KEYMAPPINGS[categoryId]) {
+ KEYMAPPINGS[categoryId][mappingId].id = mappingId;
+ }
+}
+
+export const KEYCODE_LMB = 1;
+export const KEYCODE_MMB = 2;
+export const KEYCODE_RMB = 3;
+
+/**
+ * Returns a keycode -> string
+ * @param {number} code
+ * @returns {string}
+ */
+export function getStringForKeyCode(code) {
+ switch (code) {
+ case KEYCODE_LMB:
+ return "LMB";
+ case KEYCODE_MMB:
+ return "MMB";
+ case KEYCODE_RMB:
+ return "RMB";
+ case 4:
+ return "MB4";
+ case 5:
+ return "MB5";
+ case 8:
+ return "⌫";
+ case 9:
+ return T.global.keys.tab;
+ case 13:
+ return "⏎";
+ case 16:
+ return "⇪";
+ case 17:
+ return T.global.keys.control;
+ case 18:
+ return T.global.keys.alt;
+ case 19:
+ return "PAUSE";
+ case 20:
+ return "CAPS";
+ case 27:
+ return T.global.keys.escape;
+ case 32:
+ return T.global.keys.space;
+ case 33:
+ return "PGUP";
+ case 34:
+ return "PGDOWN";
+ case 35:
+ return "END";
+ case 36:
+ return "HOME";
+ case 37:
+ return "⬅";
+ case 38:
+ return "⬆";
+ case 39:
+ return "➡";
+ case 40:
+ return "⬇";
+ case 44:
+ return "PRNT";
+ case 45:
+ return "INS";
+ case 46:
+ return "DEL";
+ case 93:
+ return "SEL";
+ case 96:
+ return "NUM 0";
+ case 97:
+ return "NUM 1";
+ case 98:
+ return "NUM 2";
+ case 99:
+ return "NUM 3";
+ case 100:
+ return "NUM 4";
+ case 101:
+ return "NUM 5";
+ case 102:
+ return "NUM 6";
+ case 103:
+ return "NUM 7";
+ case 104:
+ return "NUM 8";
+ case 105:
+ return "NUM 9";
+ case 106:
+ return "*";
+ case 107:
+ return "+";
+ case 109:
+ return "-";
+ case 110:
+ return ".";
+ case 111:
+ return "/";
+ case 112:
+ return "F1";
+ case 113:
+ return "F2";
+ case 114:
+ return "F3";
+ case 115:
+ return "F4";
+ case 116:
+ return "F4";
+ case 117:
+ return "F5";
+ case 118:
+ return "F6";
+ case 119:
+ return "F7";
+ case 120:
+ return "F8";
+ case 121:
+ return "F9";
+ case 122:
+ return "F10";
+ case 123:
+ return "F11";
+ case 124:
+ return "F12";
+
+ case 144:
+ return "NUMLOCK";
+ case 145:
+ return "SCRLOCK";
+ case 182:
+ return "COMP";
+ case 183:
+ return "CALC";
+ case 186:
+ return ";";
+ case 187:
+ return "+";
+ case 188:
+ return ",";
+ case 189:
+ return "-";
+ case 191:
+ return "/";
+ case 219:
+ return "[";
+ case 220:
+ return "\\";
+ case 221:
+ return "]";
+ case 222:
+ return "'";
+ }
+
+ return String.fromCharCode(code);
+}
+
+export class Keybinding {
+ /**
+ *
+ * @param {KeyActionMapper} keyMapper
+ * @param {Application} app
+ * @param {object} param0
+ * @param {number} param0.keyCode
+ * @param {boolean=} param0.builtin
+ * @param {boolean=} param0.repeated
+ */
+ constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
+ assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
+ this.keyMapper = keyMapper;
+ this.app = app;
+ this.keyCode = keyCode;
+ this.builtin = builtin;
+ this.repeated = repeated;
+
+ this.signal = new Signal();
+ this.toggled = new Signal();
+ }
+
+ /**
+ * Returns whether this binding is currently pressed
+ * @returns {boolean}
+ */
+ get pressed() {
+ // Check if the key is down
+ if (this.app.inputMgr.keysDown.has(this.keyCode)) {
+ // Check if it is the top reciever
+ const reciever = this.keyMapper.inputReceiver;
+ return this.app.inputMgr.getTopReciever() === reciever;
+ }
+ return false;
+ }
+
+ /**
+ * Adds an event listener
+ * @param {function() : void} receiver
+ * @param {object=} scope
+ */
+ add(receiver, scope = null) {
+ this.signal.add(receiver, scope);
+ }
+
+ /**
+ * @param {Element} elem
+ * @returns {HTMLElement} the created element, or null if the keybindings are not shown
+ * */
+ appendLabelToElement(elem) {
+ if (IS_MOBILE) {
+ return null;
+ }
+ const spacer = document.createElement("code");
+ spacer.classList.add("keybinding");
+ spacer.innerHTML = getStringForKeyCode(this.keyCode);
+ elem.appendChild(spacer);
+ return spacer;
+ }
+
+ /**
+ * Returns the key code as a nice string
+ */
+ getKeyCodeString() {
+ return getStringForKeyCode(this.keyCode);
+ }
+
+ /**
+ * Remvoes all signal receivers
+ */
+ clearSignalReceivers() {
+ this.signal.removeAll();
+ }
+}
+
+export class KeyActionMapper {
+ /**
+ *
+ * @param {GameRoot} root
+ * @param {InputReceiver} inputReciever
+ */
+ constructor(root, inputReciever) {
+ this.root = root;
+ this.inputReceiver = inputReciever;
+
+ inputReciever.keydown.add(this.handleKeydown, this);
+ inputReciever.keyup.add(this.handleKeyup, this);
+
+ /** @type {Object.} */
+ this.keybindings = {};
+
+ const overrides = root.app.settings.getKeybindingOverrides();
+
+ for (const category in KEYMAPPINGS) {
+ for (const key in KEYMAPPINGS[category]) {
+ let payload = Object.assign({}, KEYMAPPINGS[category][key]);
+ if (overrides[key]) {
+ payload.keyCode = overrides[key];
+ }
+
+ this.keybindings[key] = new Keybinding(this, this.root.app, payload);
+ }
+ }
+
+ inputReciever.pageBlur.add(this.onPageBlur, this);
+ inputReciever.destroyed.add(this.cleanup, this);
+ }
+
+ /**
+ * Returns all keybindings starting with the given id
+ * @param {string} pattern
+ * @returns {Array}
+ */
+ getKeybindingsStartingWith(pattern) {
+ let result = [];
+ for (const key in this.keybindings) {
+ if (key.startsWith(pattern)) {
+ result.push(this.keybindings[key]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Forwards the given events to the other mapper (used in tooltips)
+ * @param {KeyActionMapper} receiver
+ * @param {Array} bindings
+ */
+ forward(receiver, bindings) {
+ for (let i = 0; i < bindings.length; ++i) {
+ const key = bindings[i];
+ this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
+ }
+ }
+
+ cleanup() {
+ for (const key in this.keybindings) {
+ this.keybindings[key].signal.removeAll();
+ }
+ }
+
+ onPageBlur() {
+ // Reset all down states
+ // Find mapping
+ for (const key in this.keybindings) {
+ /** @type {Keybinding} */
+ const binding = this.keybindings[key];
+ }
+ }
+
+ /**
+ * Internal keydown handler
+ * @param {object} param0
+ * @param {number} param0.keyCode
+ * @param {boolean} param0.shift
+ * @param {boolean} param0.alt
+ * @param {boolean=} param0.initial
+ */
+ handleKeydown({ keyCode, shift, alt, initial }) {
+ let stop = false;
+
+ // Find mapping
+ for (const key in this.keybindings) {
+ /** @type {Keybinding} */
+ const binding = this.keybindings[key];
+ if (binding.keyCode === keyCode && (initial || binding.repeated)) {
+ /** @type {Signal} */
+ const signal = this.keybindings[key].signal;
+ if (signal.dispatch() === STOP_PROPAGATION) {
+ return;
+ }
+ }
+ }
+
+ if (stop) {
+ return STOP_PROPAGATION;
+ }
+ }
+
+ /**
+ * Internal keyup handler
+ * @param {object} param0
+ * @param {number} param0.keyCode
+ * @param {boolean} param0.shift
+ * @param {boolean} param0.alt
+ */
+ handleKeyup({ keyCode, shift, alt }) {
+ // Empty
+ }
+
+ /**
+ * Returns a given keybinding
+ * @param {{ keyCode: number }} binding
+ * @returns {Keybinding}
+ */
+ getBinding(binding) {
+ // @ts-ignore
+ const id = binding.id;
+ assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
+ assert(this.keybindings[id], "Keybinding " + id + " not known!");
+ return this.keybindings[id];
+ }
+}
diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js
index 0918e7af..1ea04955 100644
--- a/src/js/game/map_chunk_view.js
+++ b/src/js/game/map_chunk_view.js
@@ -1,274 +1,281 @@
-import { globalConfig } from "../core/config";
-import { DrawParameters } from "../core/draw_parameters";
-import { getBuildingDataFromCode } from "./building_codes";
-import { Entity } from "./entity";
-import { MapChunk } from "./map_chunk";
-import { GameRoot } from "./root";
-import { THEME } from "./theme";
-import { drawSpriteClipped } from "../core/draw_utils";
-
-export const CHUNK_OVERLAY_RES = 3;
-
-export class MapChunkView extends MapChunk {
- /**
- *
- * @param {GameRoot} root
- * @param {number} x
- * @param {number} y
- */
- constructor(root, x, y) {
- super(root, x, y);
-
- /**
- * Whenever something changes, we increase this number - so we know we need to redraw
- */
- this.renderIteration = 0;
-
- this.markDirty();
- }
-
- /**
- * Marks this chunk as dirty, rerendering all caches
- */
- markDirty() {
- ++this.renderIteration;
- this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
- }
-
- /**
- * Draws the background layer
- * @param {DrawParameters} parameters
- */
- drawBackgroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
- systems.mapResources.drawChunk(parameters, this);
- systems.beltUnderlays.drawChunk(parameters, this);
- systems.belt.drawChunk(parameters, this);
- }
-
- /**
- * Draws the foreground layer
- * @param {DrawParameters} parameters
- */
- drawForegroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
-
- systems.itemEjector.drawChunk(parameters, this);
- systems.itemAcceptor.drawChunk(parameters, this);
-
- systems.miner.drawChunk(parameters, this);
-
- systems.staticMapEntities.drawChunk(parameters, this);
- systems.lever.drawChunk(parameters, this);
- systems.display.drawChunk(parameters, this);
- systems.storage.drawChunk(parameters, this);
- }
-
- /**
- * Overlay
- * @param {DrawParameters} parameters
- */
- drawOverlay(parameters) {
- const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
- const sprite = this.root.buffers.getForKey({
- key: "chunk@" + this.root.currentLayer,
- subKey: this.renderKey,
- w: overlaySize,
- h: overlaySize,
- dpi: 1,
- redrawMethod: this.generateOverlayBuffer.bind(this),
- });
-
- const dims = globalConfig.mapChunkWorldSize;
-
- // Draw chunk "pixel" art
- parameters.context.imageSmoothingEnabled = false;
- drawSpriteClipped({
- parameters,
- sprite,
- x: this.x * dims,
- y: this.y * dims,
- w: dims,
- h: dims,
- originalW: overlaySize,
- originalH: overlaySize,
- });
-
- parameters.context.imageSmoothingEnabled = true;
-
- // Draw patch items
- if (this.root.currentLayer === "regular") {
- for (let i = 0; i < this.patches.length; ++i) {
- const patch = this.patches[i];
-
- const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
- const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
- const diameter = Math.min(80, 30 / parameters.zoomLevel);
-
- patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
- }
- }
- }
-
- /**
- *
- * @param {HTMLCanvasElement} canvas
- * @param {CanvasRenderingContext2D} context
- * @param {number} w
- * @param {number} h
- * @param {number} dpi
- */
- generateOverlayBuffer(canvas, context, w, h, dpi) {
- context.fillStyle =
- this.containedEntities.length > 0
- ? THEME.map.chunkOverview.filled
- : THEME.map.chunkOverview.empty;
- context.fillRect(0, 0, w, h);
-
- for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
- const lowerArray = this.lowerLayer[x];
- const upperArray = this.contents[x];
- for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
- const upperContent = upperArray[y];
- if (upperContent) {
- const staticComp = upperContent.components.StaticMapEntity;
- const data = getBuildingDataFromCode(staticComp.code);
- const metaBuilding = data.metaInstance;
-
- const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
- staticComp.rotation,
- data.rotationVariant,
- data.variant,
- upperContent
- );
-
- if (overlayMatrix) {
- // Draw lower content first since it "shines" through
- const lowerContent = lowerArray[y];
- if (lowerContent) {
- context.fillStyle = lowerContent.getBackgroundColorAsResource();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
- }
-
- context.fillStyle = metaBuilding.getSilhouetteColor();
- for (let dx = 0; dx < 3; ++dx) {
- for (let dy = 0; dy < 3; ++dy) {
- const isFilled = overlayMatrix[dx + dy * 3];
- if (isFilled) {
- context.fillRect(
- x * CHUNK_OVERLAY_RES + dx,
- y * CHUNK_OVERLAY_RES + dy,
- 1,
- 1
- );
- }
- }
- }
-
- continue;
- } else {
- context.fillStyle = metaBuilding.getSilhouetteColor();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
-
- continue;
- }
- }
-
- const lowerContent = lowerArray[y];
- if (lowerContent) {
- context.fillStyle = lowerContent.getBackgroundColorAsResource();
- context.fillRect(
- x * CHUNK_OVERLAY_RES,
- y * CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES,
- CHUNK_OVERLAY_RES
- );
- }
- }
- }
-
- if (this.root.currentLayer === "wires") {
- // Draw wires overlay
-
- context.fillStyle = THEME.map.wires.overlayColor;
- context.fillRect(0, 0, w, h);
-
- for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
- const wiresArray = this.wireContents[x];
- for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
- const content = wiresArray[y];
- if (!content) {
- continue;
- }
- MapChunkView.drawSingleWiresOverviewTile({
- context,
- x: x * CHUNK_OVERLAY_RES,
- y: y * CHUNK_OVERLAY_RES,
- entity: content,
- tileSizePixels: CHUNK_OVERLAY_RES,
- });
- }
- }
- }
- }
-
- /**
- * @param {object} param0
- * @param {CanvasRenderingContext2D} param0.context
- * @param {number} param0.x
- * @param {number} param0.y
- * @param {Entity} param0.entity
- * @param {number} param0.tileSizePixels
- * @param {string=} param0.overrideColor Optionally override the color to be rendered
- */
- static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) {
- const staticComp = entity.components.StaticMapEntity;
- const data = getBuildingDataFromCode(staticComp.code);
- const metaBuilding = data.metaInstance;
- const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
- staticComp.rotation,
- data.rotationVariant,
- data.variant,
- entity
- );
- context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
- if (overlayMatrix) {
- for (let dx = 0; dx < 3; ++dx) {
- for (let dy = 0; dy < 3; ++dy) {
- const isFilled = overlayMatrix[dx + dy * 3];
- if (isFilled) {
- context.fillRect(
- x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES,
- y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES,
- tileSizePixels / CHUNK_OVERLAY_RES,
- tileSizePixels / CHUNK_OVERLAY_RES
- );
- }
- }
- }
- } else {
- context.fillRect(x, y, tileSizePixels, tileSizePixels);
- }
- }
-
- /**
- * Draws the wires layer
- * @param {DrawParameters} parameters
- */
- drawWiresForegroundLayer(parameters) {
- const systems = this.root.systemMgr.systems;
- systems.wire.drawChunk(parameters, this);
- systems.staticMapEntities.drawWiresChunk(parameters, this);
- systems.wiredPins.drawChunk(parameters, this);
- }
-}
+import { globalConfig } from "../core/config";
+import { DrawParameters } from "../core/draw_parameters";
+import { getBuildingDataFromCode } from "./building_codes";
+import { Entity } from "./entity";
+import { MapChunk } from "./map_chunk";
+import { GameRoot } from "./root";
+import { THEME } from "./theme";
+import { drawSpriteClipped } from "../core/draw_utils";
+
+export const CHUNK_OVERLAY_RES = 3;
+
+export class MapChunkView extends MapChunk {
+ /**
+ *
+ * @param {GameRoot} root
+ * @param {number} x
+ * @param {number} y
+ */
+ constructor(root, x, y) {
+ super(root, x, y);
+
+ /**
+ * Whenever something changes, we increase this number - so we know we need to redraw
+ */
+ this.renderIteration = 0;
+
+ this.markDirty();
+ }
+
+ /**
+ * Marks this chunk as dirty, rerendering all caches
+ */
+ markDirty() {
+ ++this.renderIteration;
+ this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
+ }
+
+ /**
+ * Draws the background layer
+ * @param {DrawParameters} parameters
+ */
+ drawBackgroundLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+ systems.mapResources.drawChunk(parameters, this);
+ systems.beltUnderlays.drawChunk(parameters, this);
+ systems.belt.drawChunk(parameters, this);
+ }
+
+ /**
+ * Draws the dynamic foreground layer
+ * @param {DrawParameters} parameters
+ */
+ drawForegroundDynamicLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+
+ systems.itemEjector.drawChunk(parameters, this);
+ systems.itemAcceptor.drawChunk(parameters, this);
+ systems.miner.drawChunk(parameters, this);
+ }
+
+ /**
+ * Draws the static foreground layer
+ * @param {DrawParameters} parameters
+ */
+ drawForegroundStaticLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+
+ systems.staticMapEntities.drawChunk(parameters, this);
+ systems.lever.drawChunk(parameters, this);
+ systems.display.drawChunk(parameters, this);
+ systems.storage.drawChunk(parameters, this);
+ }
+
+ /**
+ * Overlay
+ * @param {DrawParameters} parameters
+ */
+ drawOverlay(parameters) {
+ const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
+ const sprite = this.root.buffers.getForKey({
+ key: "chunk@" + this.root.currentLayer,
+ subKey: this.renderKey,
+ w: overlaySize,
+ h: overlaySize,
+ dpi: 1,
+ redrawMethod: this.generateOverlayBuffer.bind(this),
+ });
+
+ const dims = globalConfig.mapChunkWorldSize;
+
+ // Draw chunk "pixel" art
+ parameters.context.imageSmoothingEnabled = false;
+ drawSpriteClipped({
+ parameters,
+ sprite,
+ x: this.x * dims,
+ y: this.y * dims,
+ w: dims,
+ h: dims,
+ originalW: overlaySize,
+ originalH: overlaySize,
+ });
+
+ parameters.context.imageSmoothingEnabled = true;
+
+ // Draw patch items
+ if (this.root.currentLayer === "regular") {
+ for (let i = 0; i < this.patches.length; ++i) {
+ const patch = this.patches[i];
+
+ const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
+ const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
+ const diameter = Math.min(80, 30 / parameters.zoomLevel);
+
+ patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @param {CanvasRenderingContext2D} context
+ * @param {number} w
+ * @param {number} h
+ * @param {number} dpi
+ */
+ generateOverlayBuffer(canvas, context, w, h, dpi) {
+ context.fillStyle =
+ this.containedEntities.length > 0
+ ? THEME.map.chunkOverview.filled
+ : THEME.map.chunkOverview.empty;
+ context.fillRect(0, 0, w, h);
+
+ for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
+ const lowerArray = this.lowerLayer[x];
+ const upperArray = this.contents[x];
+ for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
+ const upperContent = upperArray[y];
+ if (upperContent) {
+ const staticComp = upperContent.components.StaticMapEntity;
+ const data = getBuildingDataFromCode(staticComp.code);
+ const metaBuilding = data.metaInstance;
+
+ const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
+ staticComp.rotation,
+ data.rotationVariant,
+ data.variant,
+ upperContent
+ );
+
+ if (overlayMatrix) {
+ // Draw lower content first since it "shines" through
+ const lowerContent = lowerArray[y];
+ if (lowerContent) {
+ context.fillStyle = lowerContent.getBackgroundColorAsResource();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+ }
+
+ context.fillStyle = metaBuilding.getSilhouetteColor();
+ for (let dx = 0; dx < 3; ++dx) {
+ for (let dy = 0; dy < 3; ++dy) {
+ const isFilled = overlayMatrix[dx + dy * 3];
+ if (isFilled) {
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES + dx,
+ y * CHUNK_OVERLAY_RES + dy,
+ 1,
+ 1
+ );
+ }
+ }
+ }
+
+ continue;
+ } else {
+ context.fillStyle = metaBuilding.getSilhouetteColor();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+
+ continue;
+ }
+ }
+
+ const lowerContent = lowerArray[y];
+ if (lowerContent) {
+ context.fillStyle = lowerContent.getBackgroundColorAsResource();
+ context.fillRect(
+ x * CHUNK_OVERLAY_RES,
+ y * CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES,
+ CHUNK_OVERLAY_RES
+ );
+ }
+ }
+ }
+
+ if (this.root.currentLayer === "wires") {
+ // Draw wires overlay
+
+ context.fillStyle = THEME.map.wires.overlayColor;
+ context.fillRect(0, 0, w, h);
+
+ for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
+ const wiresArray = this.wireContents[x];
+ for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
+ const content = wiresArray[y];
+ if (!content) {
+ continue;
+ }
+ MapChunkView.drawSingleWiresOverviewTile({
+ context,
+ x: x * CHUNK_OVERLAY_RES,
+ y: y * CHUNK_OVERLAY_RES,
+ entity: content,
+ tileSizePixels: CHUNK_OVERLAY_RES,
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {object} param0
+ * @param {CanvasRenderingContext2D} param0.context
+ * @param {number} param0.x
+ * @param {number} param0.y
+ * @param {Entity} param0.entity
+ * @param {number} param0.tileSizePixels
+ * @param {string=} param0.overrideColor Optionally override the color to be rendered
+ */
+ static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) {
+ const staticComp = entity.components.StaticMapEntity;
+ const data = getBuildingDataFromCode(staticComp.code);
+ const metaBuilding = data.metaInstance;
+ const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix(
+ staticComp.rotation,
+ data.rotationVariant,
+ data.variant,
+ entity
+ );
+ context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor();
+ if (overlayMatrix) {
+ for (let dx = 0; dx < 3; ++dx) {
+ for (let dy = 0; dy < 3; ++dy) {
+ const isFilled = overlayMatrix[dx + dy * 3];
+ if (isFilled) {
+ context.fillRect(
+ x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES,
+ y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES,
+ tileSizePixels / CHUNK_OVERLAY_RES,
+ tileSizePixels / CHUNK_OVERLAY_RES
+ );
+ }
+ }
+ }
+ } else {
+ context.fillRect(x, y, tileSizePixels, tileSizePixels);
+ }
+ }
+
+ /**
+ * Draws the wires layer
+ * @param {DrawParameters} parameters
+ */
+ drawWiresForegroundLayer(parameters) {
+ const systems = this.root.systemMgr.systems;
+ systems.wire.drawChunk(parameters, this);
+ systems.staticMapEntities.drawWiresChunk(parameters, this);
+ systems.wiredPins.drawChunk(parameters, this);
+ }
+}
diff --git a/src/js/game/map_view.js b/src/js/game/map_view.js
index 178344a7..7eb864cd 100644
--- a/src/js/game/map_view.js
+++ b/src/js/game/map_view.js
@@ -1,252 +1,253 @@
-import { globalConfig } from "../core/config";
-import { DrawParameters } from "../core/draw_parameters";
-import { BaseMap } from "./map";
-import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
-import { Entity } from "./entity";
-import { THEME } from "./theme";
-import { MapChunkView } from "./map_chunk_view";
-
-/**
- * This is the view of the map, it extends the map which is the raw model and allows
- * to draw it
- */
-export class MapView extends BaseMap {
- constructor(root) {
- super(root);
-
- /**
- * DPI of the background cache images, required in some places
- */
- this.backgroundCacheDPI = 2;
-
- /**
- * The cached background sprite, containing the flat background
- * @type {HTMLCanvasElement} */
- this.cachedBackgroundCanvas = null;
-
- /** @type {CanvasRenderingContext2D} */
- this.cachedBackgroundContext = null;
- /**
- * Cached pattern of the stripes background
- * @type {CanvasPattern} */
- this.cachedBackgroundPattern = null;
-
- this.internalInitializeCachedBackgroundCanvases();
- this.root.signals.aboutToDestruct.add(this.cleanup, this);
-
- this.root.signals.entityAdded.add(this.onEntityChanged, this);
- this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
- this.root.signals.entityChanged.add(this.onEntityChanged, this);
- }
-
- cleanup() {
- freeCanvas(this.cachedBackgroundCanvas);
- this.cachedBackgroundCanvas = null;
- this.cachedBackgroundPattern = null;
- }
-
- /**
- * Called when an entity was added, removed or changed
- * @param {Entity} entity
- */
- onEntityChanged(entity) {
- const staticComp = entity.components.StaticMapEntity;
- if (staticComp) {
- const rect = staticComp.getTileSpaceBounds();
- for (let x = rect.x; x <= rect.right(); ++x) {
- for (let y = rect.y; y <= rect.bottom(); ++y) {
- this.root.map.getOrCreateChunkAtTile(x, y).markDirty();
- }
- }
- }
- }
-
- /**
- * Draws all static entities like buildings etc.
- * @param {DrawParameters} drawParameters
- */
- drawStaticEntityDebugOverlays(drawParameters) {
- const cullRange = drawParameters.visibleRect.toTileCullRectangle();
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 1;
-
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border - 1;
-
- // Render y from top down for proper blending
- for (let y = minY; y <= maxY; ++y) {
- for (let x = minX; x <= maxX; ++x) {
- // const content = this.tiles[x][y];
- const chunk = this.getChunkAtTileOrNull(x, y);
- if (!chunk) {
- continue;
- }
- const content = chunk.getTileContentFromWorldCoords(x, y);
- if (content) {
- let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
- if (!isBorder) {
- content.drawDebugOverlays(drawParameters);
- }
- }
- }
- }
- }
-
- /**
- * Initializes all canvases used for background rendering
- */
- internalInitializeCachedBackgroundCanvases() {
- // Background canvas
- const dims = globalConfig.tileSize;
- const dpi = this.backgroundCacheDPI;
- const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, {
- smooth: false,
- label: "map-cached-bg",
- });
- context.scale(dpi, dpi);
-
- context.fillStyle = THEME.map.background;
- context.fillRect(0, 0, dims, dims);
-
- const borderWidth = THEME.map.gridLineWidth;
- context.fillStyle = THEME.map.grid;
- context.fillRect(0, 0, dims, borderWidth);
- context.fillRect(0, borderWidth, borderWidth, dims);
-
- context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth);
- context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth);
-
- this.cachedBackgroundCanvas = canvas;
- this.cachedBackgroundContext = context;
- }
-
- /**
- * Draws the maps foreground
- * @param {DrawParameters} parameters
- */
- drawForeground(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundLayer);
- }
-
- /**
- * Calls a given method on all given chunks
- * @param {DrawParameters} parameters
- * @param {function} method
- */
- drawVisibleChunks(parameters, method) {
- const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 0;
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border;
-
- const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
- const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
-
- const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
- const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
-
- // Render y from top down for proper blending
- for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
- for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
- const chunk = this.root.map.getChunk(chunkX, chunkY, true);
- method.call(chunk, parameters);
- }
- }
- }
-
- /**
- * Draws the wires foreground
- * @param {DrawParameters} parameters
- */
- drawWiresForegroundLayer(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer);
- }
-
- /**
- * Draws the map overlay
- * @param {DrawParameters} parameters
- */
- drawOverlay(parameters) {
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay);
- }
-
- /**
- * Draws the map background
- * @param {DrawParameters} parameters
- */
- drawBackground(parameters) {
- if (!this.cachedBackgroundPattern) {
- this.cachedBackgroundPattern = parameters.context.createPattern(
- this.cachedBackgroundCanvas,
- "repeat"
- );
- }
-
- if (!this.root.app.settings.getAllSettings().disableTileGrid) {
- const dpi = this.backgroundCacheDPI;
- parameters.context.scale(1 / dpi, 1 / dpi);
-
- parameters.context.fillStyle = this.cachedBackgroundPattern;
- parameters.context.fillRect(
- parameters.visibleRect.x * dpi,
- parameters.visibleRect.y * dpi,
- parameters.visibleRect.w * dpi,
- parameters.visibleRect.h * dpi
- );
- parameters.context.scale(dpi, dpi);
- }
-
- this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
-
- if (G_IS_DEV && globalConfig.debug.showChunkBorders) {
- const cullRange = parameters.visibleRect.toTileCullRectangle();
- const top = cullRange.top();
- const right = cullRange.right();
- const bottom = cullRange.bottom();
- const left = cullRange.left();
-
- const border = 1;
- const minY = top - border;
- const maxY = bottom + border;
- const minX = left - border;
- const maxX = right + border - 1;
-
- const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
- const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
-
- const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
- const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
-
- for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
- for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
- parameters.context.fillStyle = "#ffaaaa";
- parameters.context.fillRect(
- chunkX * globalConfig.mapChunkWorldSize,
- chunkY * globalConfig.mapChunkWorldSize,
- globalConfig.mapChunkWorldSize,
- 3
- );
- parameters.context.fillRect(
- chunkX * globalConfig.mapChunkWorldSize,
- chunkY * globalConfig.mapChunkWorldSize,
- 3,
- globalConfig.mapChunkWorldSize
- );
- }
- }
- }
- }
-}
+import { globalConfig } from "../core/config";
+import { DrawParameters } from "../core/draw_parameters";
+import { BaseMap } from "./map";
+import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
+import { Entity } from "./entity";
+import { THEME } from "./theme";
+import { MapChunkView } from "./map_chunk_view";
+
+/**
+ * This is the view of the map, it extends the map which is the raw model and allows
+ * to draw it
+ */
+export class MapView extends BaseMap {
+ constructor(root) {
+ super(root);
+
+ /**
+ * DPI of the background cache images, required in some places
+ */
+ this.backgroundCacheDPI = 2;
+
+ /**
+ * The cached background sprite, containing the flat background
+ * @type {HTMLCanvasElement} */
+ this.cachedBackgroundCanvas = null;
+
+ /** @type {CanvasRenderingContext2D} */
+ this.cachedBackgroundContext = null;
+ /**
+ * Cached pattern of the stripes background
+ * @type {CanvasPattern} */
+ this.cachedBackgroundPattern = null;
+
+ this.internalInitializeCachedBackgroundCanvases();
+ this.root.signals.aboutToDestruct.add(this.cleanup, this);
+
+ this.root.signals.entityAdded.add(this.onEntityChanged, this);
+ this.root.signals.entityDestroyed.add(this.onEntityChanged, this);
+ this.root.signals.entityChanged.add(this.onEntityChanged, this);
+ }
+
+ cleanup() {
+ freeCanvas(this.cachedBackgroundCanvas);
+ this.cachedBackgroundCanvas = null;
+ this.cachedBackgroundPattern = null;
+ }
+
+ /**
+ * Called when an entity was added, removed or changed
+ * @param {Entity} entity
+ */
+ onEntityChanged(entity) {
+ const staticComp = entity.components.StaticMapEntity;
+ if (staticComp) {
+ const rect = staticComp.getTileSpaceBounds();
+ for (let x = rect.x; x <= rect.right(); ++x) {
+ for (let y = rect.y; y <= rect.bottom(); ++y) {
+ this.root.map.getOrCreateChunkAtTile(x, y).markDirty();
+ }
+ }
+ }
+ }
+
+ /**
+ * Draws all static entities like buildings etc.
+ * @param {DrawParameters} drawParameters
+ */
+ drawStaticEntityDebugOverlays(drawParameters) {
+ const cullRange = drawParameters.visibleRect.toTileCullRectangle();
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 1;
+
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border - 1;
+
+ // Render y from top down for proper blending
+ for (let y = minY; y <= maxY; ++y) {
+ for (let x = minX; x <= maxX; ++x) {
+ // const content = this.tiles[x][y];
+ const chunk = this.getChunkAtTileOrNull(x, y);
+ if (!chunk) {
+ continue;
+ }
+ const content = chunk.getTileContentFromWorldCoords(x, y);
+ if (content) {
+ let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
+ if (!isBorder) {
+ content.drawDebugOverlays(drawParameters);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Initializes all canvases used for background rendering
+ */
+ internalInitializeCachedBackgroundCanvases() {
+ // Background canvas
+ const dims = globalConfig.tileSize;
+ const dpi = this.backgroundCacheDPI;
+ const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, {
+ smooth: false,
+ label: "map-cached-bg",
+ });
+ context.scale(dpi, dpi);
+
+ context.fillStyle = THEME.map.background;
+ context.fillRect(0, 0, dims, dims);
+
+ const borderWidth = THEME.map.gridLineWidth;
+ context.fillStyle = THEME.map.grid;
+ context.fillRect(0, 0, dims, borderWidth);
+ context.fillRect(0, borderWidth, borderWidth, dims);
+
+ context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth);
+ context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth);
+
+ this.cachedBackgroundCanvas = canvas;
+ this.cachedBackgroundContext = context;
+ }
+
+ /**
+ * Draws the maps foreground
+ * @param {DrawParameters} parameters
+ */
+ drawForeground(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundDynamicLayer);
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundStaticLayer);
+ }
+
+ /**
+ * Calls a given method on all given chunks
+ * @param {DrawParameters} parameters
+ * @param {function} method
+ */
+ drawVisibleChunks(parameters, method) {
+ const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 0;
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border;
+
+ const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
+ const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
+
+ const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize);
+ const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize);
+
+ // Render y from top down for proper blending
+ for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
+ for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
+ const chunk = this.root.map.getChunk(chunkX, chunkY, true);
+ method.call(chunk, parameters);
+ }
+ }
+ }
+
+ /**
+ * Draws the wires foreground
+ * @param {DrawParameters} parameters
+ */
+ drawWiresForegroundLayer(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer);
+ }
+
+ /**
+ * Draws the map overlay
+ * @param {DrawParameters} parameters
+ */
+ drawOverlay(parameters) {
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay);
+ }
+
+ /**
+ * Draws the map background
+ * @param {DrawParameters} parameters
+ */
+ drawBackground(parameters) {
+ if (!this.cachedBackgroundPattern) {
+ this.cachedBackgroundPattern = parameters.context.createPattern(
+ this.cachedBackgroundCanvas,
+ "repeat"
+ );
+ }
+
+ if (!this.root.app.settings.getAllSettings().disableTileGrid) {
+ const dpi = this.backgroundCacheDPI;
+ parameters.context.scale(1 / dpi, 1 / dpi);
+
+ parameters.context.fillStyle = this.cachedBackgroundPattern;
+ parameters.context.fillRect(
+ parameters.visibleRect.x * dpi,
+ parameters.visibleRect.y * dpi,
+ parameters.visibleRect.w * dpi,
+ parameters.visibleRect.h * dpi
+ );
+ parameters.context.scale(dpi, dpi);
+ }
+
+ this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer);
+
+ if (G_IS_DEV && globalConfig.debug.showChunkBorders) {
+ const cullRange = parameters.visibleRect.toTileCullRectangle();
+ const top = cullRange.top();
+ const right = cullRange.right();
+ const bottom = cullRange.bottom();
+ const left = cullRange.left();
+
+ const border = 1;
+ const minY = top - border;
+ const maxY = bottom + border;
+ const minX = left - border;
+ const maxX = right + border - 1;
+
+ const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize);
+ const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize);
+
+ const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize);
+ const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize);
+
+ for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) {
+ for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) {
+ parameters.context.fillStyle = "#ffaaaa";
+ parameters.context.fillRect(
+ chunkX * globalConfig.mapChunkWorldSize,
+ chunkY * globalConfig.mapChunkWorldSize,
+ globalConfig.mapChunkWorldSize,
+ 3
+ );
+ parameters.context.fillRect(
+ chunkX * globalConfig.mapChunkWorldSize,
+ chunkY * globalConfig.mapChunkWorldSize,
+ 3,
+ globalConfig.mapChunkWorldSize
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js
index 7bf3b097..6763254c 100644
--- a/src/js/game/meta_building_registry.js
+++ b/src/js/game/meta_building_registry.js
@@ -1,162 +1,173 @@
-import { gMetaBuildingRegistry } from "../core/global_registries";
-import { createLogger } from "../core/logging";
-import { MetaBeltBuilding } from "./buildings/belt";
-import { MetaBeltBaseBuilding } from "./buildings/belt_base";
-import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
-import { MetaHubBuilding } from "./buildings/hub";
-import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
-import { MetaMixerBuilding } from "./buildings/mixer";
-import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
-import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
-import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
-import { MetaStackerBuilding } from "./buildings/stacker";
-import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
-import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
-import { MetaWireBuilding } from "./buildings/wire";
-import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
-import { defaultBuildingVariant } from "./meta_building";
-import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
-import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
-import { MetaLeverBuilding } from "./buildings/lever";
-import { MetaFilterBuilding } from "./buildings/filter";
-import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
-import { MetaDisplayBuilding } from "./buildings/display";
-
-const logger = createLogger("building_registry");
-
-export function initMetaBuildingRegistry() {
- gMetaBuildingRegistry.register(MetaSplitterBuilding);
- gMetaBuildingRegistry.register(MetaMinerBuilding);
- gMetaBuildingRegistry.register(MetaCutterBuilding);
- gMetaBuildingRegistry.register(MetaRotaterBuilding);
- gMetaBuildingRegistry.register(MetaStackerBuilding);
- gMetaBuildingRegistry.register(MetaMixerBuilding);
- gMetaBuildingRegistry.register(MetaPainterBuilding);
- gMetaBuildingRegistry.register(MetaTrashBuilding);
- gMetaBuildingRegistry.register(MetaBeltBuilding);
- gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
- gMetaBuildingRegistry.register(MetaHubBuilding);
- gMetaBuildingRegistry.register(MetaWireBuilding);
- gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
- gMetaBuildingRegistry.register(MetaLogicGateBuilding);
- gMetaBuildingRegistry.register(MetaLeverBuilding);
- gMetaBuildingRegistry.register(MetaFilterBuilding);
- gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
- gMetaBuildingRegistry.register(MetaDisplayBuilding);
-
- // Belt
- registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
- registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1);
- registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2);
-
- // Splitter
- registerBuildingVariant(4, MetaSplitterBuilding);
- registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
- registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
-
- // Miner
- registerBuildingVariant(7, MetaMinerBuilding);
- registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
-
- // Cutter
- registerBuildingVariant(9, MetaCutterBuilding);
- registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
-
- // Rotater
- registerBuildingVariant(11, MetaRotaterBuilding);
- registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
- registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
-
- // Stacker
- registerBuildingVariant(14, MetaStackerBuilding);
-
- // Mixer
- registerBuildingVariant(15, MetaMixerBuilding);
-
- // Painter
- registerBuildingVariant(16, MetaPainterBuilding);
- registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
- registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
- registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
-
- // Trash
- registerBuildingVariant(20, MetaTrashBuilding);
- registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage);
-
- // Underground belt
- registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
- registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
- registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
- registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
-
- // Hub
- registerBuildingVariant(26, MetaHubBuilding);
-
- // Wire
- registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
- registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
- registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
- registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
-
- // Constant signal
- registerBuildingVariant(31, MetaConstantSignalBuilding);
-
- // Logic gate
- registerBuildingVariant(32, MetaLogicGateBuilding);
- registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
- registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
- registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
- registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor);
-
- // Lever
- registerBuildingVariant(33, MetaLeverBuilding);
-
- // Filter
- registerBuildingVariant(37, MetaFilterBuilding);
-
- // Wire tunnel
- registerBuildingVariant(39, MetaWireTunnelBuilding);
- registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
-
- // Display
- registerBuildingVariant(40, MetaDisplayBuilding);
-
- // Propagate instances
- for (const key in gBuildingVariants) {
- gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
- gBuildingVariants[key].metaClass
- );
- }
-
- for (const key in gBuildingVariants) {
- const variant = gBuildingVariants[key];
- assert(variant.metaClass, "Variant has no meta: " + key);
-
- if (typeof variant.rotationVariant === "undefined") {
- variant.rotationVariant = 0;
- }
- if (typeof variant.variant === "undefined") {
- variant.variant = defaultBuildingVariant;
- }
- }
-
- logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
- logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes");
-}
-
-/**
- * Once all sprites are loaded, propagates the cache
- */
-export function initBuildingCodesAfterResourcesLoaded() {
- logger.log("Propagating sprite cache");
- for (const key in gBuildingVariants) {
- const variant = gBuildingVariants[key];
-
- variant.sprite = variant.metaInstance.getSprite(variant.rotationVariant, variant.variant);
- variant.blueprintSprite = variant.metaInstance.getBlueprintSprite(
- variant.rotationVariant,
- variant.variant
- );
- variant.silhouetteColor = variant.metaInstance.getSilhouetteColor();
- }
-}
+import { gMetaBuildingRegistry } from "../core/global_registries";
+import { createLogger } from "../core/logging";
+import { MetaBeltBuilding } from "./buildings/belt";
+import { MetaBeltBaseBuilding } from "./buildings/belt_base";
+import { enumCutterVariants, MetaCutterBuilding } from "./buildings/cutter";
+import { MetaHubBuilding } from "./buildings/hub";
+import { enumMinerVariants, MetaMinerBuilding } from "./buildings/miner";
+import { MetaMixerBuilding } from "./buildings/mixer";
+import { enumPainterVariants, MetaPainterBuilding } from "./buildings/painter";
+import { enumRotaterVariants, MetaRotaterBuilding } from "./buildings/rotater";
+import { enumSplitterVariants, MetaSplitterBuilding } from "./buildings/splitter";
+import { MetaStackerBuilding } from "./buildings/stacker";
+import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
+import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
+import { MetaWireBuilding } from "./buildings/wire";
+import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
+import { defaultBuildingVariant } from "./meta_building";
+import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
+import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
+import { MetaLeverBuilding } from "./buildings/lever";
+import { MetaFilterBuilding } from "./buildings/filter";
+import { MetaWireTunnelBuilding, enumWireTunnelVariants } from "./buildings/wire_tunnel";
+import { MetaDisplayBuilding } from "./buildings/display";
+import { MetaVirtualProcessorBuilding, enumVirtualProcessorVariants } from "./buildings/virtual_processor";
+
+const logger = createLogger("building_registry");
+
+export function initMetaBuildingRegistry() {
+ gMetaBuildingRegistry.register(MetaSplitterBuilding);
+ gMetaBuildingRegistry.register(MetaMinerBuilding);
+ gMetaBuildingRegistry.register(MetaCutterBuilding);
+ gMetaBuildingRegistry.register(MetaRotaterBuilding);
+ gMetaBuildingRegistry.register(MetaStackerBuilding);
+ gMetaBuildingRegistry.register(MetaMixerBuilding);
+ gMetaBuildingRegistry.register(MetaPainterBuilding);
+ gMetaBuildingRegistry.register(MetaTrashBuilding);
+ gMetaBuildingRegistry.register(MetaBeltBuilding);
+ gMetaBuildingRegistry.register(MetaUndergroundBeltBuilding);
+ gMetaBuildingRegistry.register(MetaHubBuilding);
+ gMetaBuildingRegistry.register(MetaWireBuilding);
+ gMetaBuildingRegistry.register(MetaConstantSignalBuilding);
+ gMetaBuildingRegistry.register(MetaLogicGateBuilding);
+ gMetaBuildingRegistry.register(MetaLeverBuilding);
+ gMetaBuildingRegistry.register(MetaFilterBuilding);
+ gMetaBuildingRegistry.register(MetaWireTunnelBuilding);
+ gMetaBuildingRegistry.register(MetaDisplayBuilding);
+ gMetaBuildingRegistry.register(MetaVirtualProcessorBuilding);
+
+ // Belt
+ registerBuildingVariant(1, MetaBeltBaseBuilding, defaultBuildingVariant, 0);
+ registerBuildingVariant(2, MetaBeltBaseBuilding, defaultBuildingVariant, 1);
+ registerBuildingVariant(3, MetaBeltBaseBuilding, defaultBuildingVariant, 2);
+
+ // Splitter
+ registerBuildingVariant(4, MetaSplitterBuilding);
+ registerBuildingVariant(5, MetaSplitterBuilding, enumSplitterVariants.compact);
+ registerBuildingVariant(6, MetaSplitterBuilding, enumSplitterVariants.compactInverse);
+ registerBuildingVariant(47, MetaSplitterBuilding, enumSplitterVariants.compactMerge);
+ registerBuildingVariant(48, MetaSplitterBuilding, enumSplitterVariants.compactMergeInverse);
+
+ // Miner
+ registerBuildingVariant(7, MetaMinerBuilding);
+ registerBuildingVariant(8, MetaMinerBuilding, enumMinerVariants.chainable);
+
+ // Cutter
+ registerBuildingVariant(9, MetaCutterBuilding);
+ registerBuildingVariant(10, MetaCutterBuilding, enumCutterVariants.quad);
+
+ // Rotater
+ registerBuildingVariant(11, MetaRotaterBuilding);
+ registerBuildingVariant(12, MetaRotaterBuilding, enumRotaterVariants.ccw);
+ registerBuildingVariant(13, MetaRotaterBuilding, enumRotaterVariants.fl);
+
+ // Stacker
+ registerBuildingVariant(14, MetaStackerBuilding);
+
+ // Mixer
+ registerBuildingVariant(15, MetaMixerBuilding);
+
+ // Painter
+ registerBuildingVariant(16, MetaPainterBuilding);
+ registerBuildingVariant(17, MetaPainterBuilding, enumPainterVariants.mirrored);
+ registerBuildingVariant(18, MetaPainterBuilding, enumPainterVariants.double);
+ registerBuildingVariant(19, MetaPainterBuilding, enumPainterVariants.quad);
+
+ // Trash
+ registerBuildingVariant(20, MetaTrashBuilding);
+ registerBuildingVariant(21, MetaTrashBuilding, enumTrashVariants.storage);
+
+ // Underground belt
+ registerBuildingVariant(22, MetaUndergroundBeltBuilding, defaultBuildingVariant, 0);
+ registerBuildingVariant(23, MetaUndergroundBeltBuilding, defaultBuildingVariant, 1);
+ registerBuildingVariant(24, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 0);
+ registerBuildingVariant(25, MetaUndergroundBeltBuilding, enumUndergroundBeltVariants.tier2, 1);
+
+ // Hub
+ registerBuildingVariant(26, MetaHubBuilding);
+
+ // Wire
+ registerBuildingVariant(27, MetaWireBuilding, defaultBuildingVariant, 0);
+ registerBuildingVariant(28, MetaWireBuilding, defaultBuildingVariant, 1);
+ registerBuildingVariant(29, MetaWireBuilding, defaultBuildingVariant, 2);
+ registerBuildingVariant(30, MetaWireBuilding, defaultBuildingVariant, 3);
+
+ // Constant signal
+ registerBuildingVariant(31, MetaConstantSignalBuilding);
+
+ // Logic gate
+ registerBuildingVariant(32, MetaLogicGateBuilding);
+ registerBuildingVariant(34, MetaLogicGateBuilding, enumLogicGateVariants.not);
+ registerBuildingVariant(35, MetaLogicGateBuilding, enumLogicGateVariants.xor);
+ registerBuildingVariant(36, MetaLogicGateBuilding, enumLogicGateVariants.or);
+ registerBuildingVariant(38, MetaLogicGateBuilding, enumLogicGateVariants.transistor);
+
+ // Lever
+ registerBuildingVariant(33, MetaLeverBuilding);
+
+ // Filter
+ registerBuildingVariant(37, MetaFilterBuilding);
+
+ // Wire tunnel
+ registerBuildingVariant(39, MetaWireTunnelBuilding);
+ registerBuildingVariant(41, MetaWireTunnelBuilding, enumWireTunnelVariants.coating);
+
+ // Display
+ registerBuildingVariant(40, MetaDisplayBuilding);
+
+ // Virtual Processor
+ registerBuildingVariant(42, MetaVirtualProcessorBuilding);
+ registerBuildingVariant(43, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.analyzer);
+ registerBuildingVariant(44, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.rotater);
+ registerBuildingVariant(45, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.unstacker);
+ registerBuildingVariant(46, MetaVirtualProcessorBuilding, enumVirtualProcessorVariants.shapecompare);
+
+ // Propagate instances
+ for (const key in gBuildingVariants) {
+ gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(
+ gBuildingVariants[key].metaClass
+ );
+ }
+
+ for (const key in gBuildingVariants) {
+ const variant = gBuildingVariants[key];
+ assert(variant.metaClass, "Variant has no meta: " + key);
+
+ if (typeof variant.rotationVariant === "undefined") {
+ variant.rotationVariant = 0;
+ }
+ if (typeof variant.variant === "undefined") {
+ variant.variant = defaultBuildingVariant;
+ }
+ }
+
+ logger.log("Registered", gMetaBuildingRegistry.getNumEntries(), "buildings");
+ logger.log("Registered", Object.keys(gBuildingVariants).length, "building codes");
+}
+
+/**
+ * Once all sprites are loaded, propagates the cache
+ */
+export function initBuildingCodesAfterResourcesLoaded() {
+ logger.log("Propagating sprite cache");
+ for (const key in gBuildingVariants) {
+ const variant = gBuildingVariants[key];
+
+ variant.sprite = variant.metaInstance.getSprite(variant.rotationVariant, variant.variant);
+ variant.blueprintSprite = variant.metaInstance.getBlueprintSprite(
+ variant.rotationVariant,
+ variant.variant
+ );
+ variant.silhouetteColor = variant.metaInstance.getSilhouetteColor();
+ }
+}
diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js
index d3f6c2d6..79ede9c1 100644
--- a/src/js/game/shape_definition.js
+++ b/src/js/game/shape_definition.js
@@ -85,7 +85,7 @@ export class ShapeDefinition extends BasicSerializableObject {
return errorCode;
}
const definition = ShapeDefinition.fromShortKey(data);
- this.layers = definition.layers;
+ this.layers = /** @type {Array} */ (definition.layers);
}
serialize() {
@@ -102,7 +102,8 @@ export class ShapeDefinition extends BasicSerializableObject {
/**
* The layers from bottom to top
- * @type {Array} */
+ * @type {Array}
+ */
this.layers = layers;
/** @type {string} */
@@ -600,7 +601,7 @@ export class ShapeDefinition extends BasicSerializableObject {
for (let quadrantIndex = 0; quadrantIndex < 4; ++quadrantIndex) {
const item = quadrants[quadrantIndex];
if (item) {
- item.color = colors[quadrantIndex] || item.color;
+ item.color = colors[quadrantIndex] || null;
}
}
}
diff --git a/src/js/game/systems/constant_signal.js b/src/js/game/systems/constant_signal.js
index f1ea9f48..93417ef5 100644
--- a/src/js/game/systems/constant_signal.js
+++ b/src/js/game/systems/constant_signal.js
@@ -1,129 +1,135 @@
-import trim from "trim";
-import { DialogWithForm } from "../../core/modal_dialog_elements";
-import { FormElementInput } from "../../core/modal_dialog_forms";
-import { BaseItem } from "../base_item";
-import { enumColors } from "../colors";
-import { ConstantSignalComponent } from "../components/constant_signal";
-import { Entity } from "../entity";
-import { GameSystemWithFilter } from "../game_system_with_filter";
-import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
-import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
-import { ShapeDefinition } from "../shape_definition";
-
-export class ConstantSignalSystem extends GameSystemWithFilter {
- constructor(root) {
- super(root, [ConstantSignalComponent]);
-
- this.root.signals.entityManuallyPlaced.add(this.querySigalValue, this);
- }
-
- update() {
- // Set signals
- for (let i = 0; i < this.allEntities.length; ++i) {
- const entity = this.allEntities[i];
- const pinsComp = entity.components.WiredPins;
- const signalComp = entity.components.ConstantSignal;
- pinsComp.slots[0].value = signalComp.signal;
- }
- }
-
- /**
- * Asks the entity to enter a valid signal code
- * @param {Entity} entity
- */
- querySigalValue(entity) {
- if (!entity.components.ConstantSignal) {
- return;
- }
-
- // Ok, query, but also save the uid because it could get stale
- const uid = entity.uid;
-
- const signalValueInput = new FormElementInput({
- id: "signalValue",
- label: null,
- placeholder: "",
- defaultValue: "",
- validator: val => this.parseSignalCode(val),
- });
- const dialog = new DialogWithForm({
- app: this.root.app,
- title: "Set Signal",
- desc: "Enter a shape code, color or '0' or '1'",
- formElements: [signalValueInput],
- buttons: ["cancel:bad:escape", "ok:good:enter"],
- });
- this.root.hud.parts.dialogs.internalShowDialog(dialog);
-
- // When confirmed, set the signal
- dialog.buttonSignals.ok.add(() => {
- if (!this.root || !this.root.entityMgr) {
- // Game got stopped
- return;
- }
-
- const entityRef = this.root.entityMgr.findByUid(uid, false);
- if (!entityRef) {
- // outdated
- return;
- }
-
- const constantComp = entityRef.components.ConstantSignal;
- if (!constantComp) {
- // no longer interesting
- return;
- }
-
- constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
- });
-
- // When cancelled, destroy the entity again
- dialog.buttonSignals.cancel.add(() => {
- if (!this.root || !this.root.entityMgr) {
- // Game got stopped
- return;
- }
-
- const entityRef = this.root.entityMgr.findByUid(uid, false);
- if (!entityRef) {
- // outdated
- return;
- }
-
- const constantComp = entityRef.components.ConstantSignal;
- if (!constantComp) {
- // no longer interesting
- return;
- }
-
- this.root.logic.tryDeleteBuilding(entityRef);
- });
- }
-
- /**
- * Tries to parse a signal code
- * @param {string} code
- * @returns {BaseItem}
- */
- parseSignalCode(code) {
- code = trim(code);
- const codeLower = code.toLowerCase();
-
- if (enumColors[codeLower]) {
- return COLOR_ITEM_SINGLETONS[codeLower];
- }
- if (code === "1" || codeLower === "true") {
- return BOOL_TRUE_SINGLETON;
- }
-
- if (code === "0" || codeLower === "false") {
- return BOOL_FALSE_SINGLETON;
- }
-
- if (ShapeDefinition.isValidShortKey(code)) {
- return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
- }
-
- return null;
- }
-}
+import trim from "trim";
+import { DialogWithForm } from "../../core/modal_dialog_elements";
+import { FormElementInput } from "../../core/modal_dialog_forms";
+import { BaseItem } from "../base_item";
+import { enumColors } from "../colors";
+import { ConstantSignalComponent } from "../components/constant_signal";
+import { Entity } from "../entity";
+import { GameSystemWithFilter } from "../game_system_with_filter";
+import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
+import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
+import { ShapeDefinition } from "../shape_definition";
+
+export class ConstantSignalSystem extends GameSystemWithFilter {
+ constructor(root) {
+ super(root, [ConstantSignalComponent]);
+
+ this.root.signals.entityManuallyPlaced.add(this.querySigalValue, this);
+ }
+
+ update() {
+ // Set signals
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+ const pinsComp = entity.components.WiredPins;
+ const signalComp = entity.components.ConstantSignal;
+ pinsComp.slots[0].value = signalComp.signal;
+ }
+ }
+
+ /**
+ * Asks the entity to enter a valid signal code
+ * @param {Entity} entity
+ */
+ querySigalValue(entity) {
+ if (!entity.components.ConstantSignal) {
+ return;
+ }
+
+ // Ok, query, but also save the uid because it could get stale
+ const uid = entity.uid;
+
+ const signalValueInput = new FormElementInput({
+ id: "signalValue",
+ label: null,
+ placeholder: "",
+ defaultValue: "",
+ validator: val => this.parseSignalCode(val),
+ });
+ const dialog = new DialogWithForm({
+ app: this.root.app,
+ title: "Set Signal",
+ desc: "Enter a shape code, color or '0' or '1'",
+ formElements: [signalValueInput],
+ buttons: ["cancel:bad:escape", "ok:good:enter"],
+ closeButton: false,
+ });
+ this.root.hud.parts.dialogs.internalShowDialog(dialog);
+
+ // When confirmed, set the signal
+ dialog.buttonSignals.ok.add(() => {
+ if (!this.root || !this.root.entityMgr) {
+ // Game got stopped
+ return;
+ }
+
+ const entityRef = this.root.entityMgr.findByUid(uid, false);
+ if (!entityRef) {
+ // outdated
+ return;
+ }
+
+ const constantComp = entityRef.components.ConstantSignal;
+ if (!constantComp) {
+ // no longer interesting
+ return;
+ }
+
+ constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
+ });
+
+ // When cancelled, destroy the entity again
+ dialog.buttonSignals.cancel.add(() => {
+ if (!this.root || !this.root.entityMgr) {
+ // Game got stopped
+ return;
+ }
+
+ const entityRef = this.root.entityMgr.findByUid(uid, false);
+ if (!entityRef) {
+ // outdated
+ return;
+ }
+
+ const constantComp = entityRef.components.ConstantSignal;
+ if (!constantComp) {
+ // no longer interesting
+ return;
+ }
+
+ this.root.logic.tryDeleteBuilding(entityRef);
+ });
+ }
+
+ /**
+ * Tries to parse a signal code
+ * @param {string} code
+ * @returns {BaseItem}
+ */
+ parseSignalCode(code) {
+ if (!this.root || !this.root.shapeDefinitionMgr) {
+ // Stale reference
+ return null;
+ }
+
+ code = trim(code);
+ const codeLower = code.toLowerCase();
+
+ if (enumColors[codeLower]) {
+ return COLOR_ITEM_SINGLETONS[codeLower];
+ }
+ if (code === "1" || codeLower === "true") {
+ return BOOL_TRUE_SINGLETON;
+ }
+
+ if (code === "0" || codeLower === "false") {
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ if (ShapeDefinition.isValidShortKey(code)) {
+ return this.root.shapeDefinitionMgr.getShapeItemFromShortKey(code);
+ }
+
+ return null;
+ }
+}
diff --git a/src/js/game/systems/lever.js b/src/js/game/systems/lever.js
index 0d538afc..75b6cf28 100644
--- a/src/js/game/systems/lever.js
+++ b/src/js/game/systems/lever.js
@@ -1,51 +1,44 @@
-import { GameSystemWithFilter } from "../game_system_with_filter";
-import { LeverComponent } from "../components/lever";
-import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
-import { MapChunkView } from "../map_chunk_view";
-import { globalConfig } from "../../core/config";
-import { Loader } from "../../core/loader";
-
-export class LeverSystem extends GameSystemWithFilter {
- constructor(root) {
- super(root, [LeverComponent]);
-
- this.spriteOn = Loader.getSprite("sprites/wires/lever_on.png");
- this.spriteOff = Loader.getSprite("sprites/buildings/lever.png");
- }
-
- update() {
- for (let i = 0; i < this.allEntities.length; ++i) {
- const entity = this.allEntities[i];
-
- const leverComp = entity.components.Lever;
- const pinsComp = entity.components.WiredPins;
-
- // Simply sync the status to the first slot
- pinsComp.slots[0].value = leverComp.toggled ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
- }
- }
-
- /**
- * Draws a given chunk
- * @param {import("../../core/draw_utils").DrawParameters} parameters
- * @param {MapChunkView} chunk
- */
- drawChunk(parameters, chunk) {
- const contents = chunk.containedEntitiesByLayer.regular;
- for (let i = 0; i < contents.length; ++i) {
- const entity = contents[i];
- const leverComp = entity.components.Lever;
- if (leverComp) {
- const sprite = leverComp.toggled ? this.spriteOn : this.spriteOff;
- const origin = entity.components.StaticMapEntity.origin;
- sprite.drawCached(
- parameters,
- origin.x * globalConfig.tileSize,
- origin.y * globalConfig.tileSize,
- globalConfig.tileSize,
- globalConfig.tileSize
- );
- }
- }
- }
-}
+import { GameSystemWithFilter } from "../game_system_with_filter";
+import { LeverComponent } from "../components/lever";
+import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
+import { MapChunkView } from "../map_chunk_view";
+import { globalConfig } from "../../core/config";
+import { Loader } from "../../core/loader";
+
+export class LeverSystem extends GameSystemWithFilter {
+ constructor(root) {
+ super(root, [LeverComponent]);
+
+ this.spriteOn = Loader.getSprite("sprites/wires/lever_on.png");
+ this.spriteOff = Loader.getSprite("sprites/buildings/lever.png");
+ }
+
+ update() {
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+
+ const leverComp = entity.components.Lever;
+ const pinsComp = entity.components.WiredPins;
+
+ // Simply sync the status to the first slot
+ pinsComp.slots[0].value = leverComp.toggled ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
+ }
+ }
+
+ /**
+ * Draws a given chunk
+ * @param {import("../../core/draw_utils").DrawParameters} parameters
+ * @param {MapChunkView} chunk
+ */
+ drawChunk(parameters, chunk) {
+ const contents = chunk.containedEntitiesByLayer.regular;
+ for (let i = 0; i < contents.length; ++i) {
+ const entity = contents[i];
+ const leverComp = entity.components.Lever;
+ if (leverComp) {
+ const sprite = leverComp.toggled ? this.spriteOn : this.spriteOff;
+ entity.components.StaticMapEntity.drawSpriteOnBoundsClipped(parameters, sprite);
+ }
+ }
+ }
+}
diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js
index 9fe41250..6914410e 100644
--- a/src/js/game/systems/logic_gate.js
+++ b/src/js/game/systems/logic_gate.js
@@ -1,180 +1,326 @@
-import { LogicGateComponent, enumLogicGateType } from "../components/logic_gate";
-import { GameSystemWithFilter } from "../game_system_with_filter";
-import { BaseItem } from "../base_item";
-import { enumPinSlotType } from "../components/wired_pins";
-import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON, BooleanItem } from "../items/boolean_item";
-import { enumItemProcessorTypes } from "../components/item_processor";
-
-export class LogicGateSystem extends GameSystemWithFilter {
- constructor(root) {
- super(root, [LogicGateComponent]);
-
- this.boundOperations = {
- [enumLogicGateType.and]: this.compute_AND.bind(this),
- [enumLogicGateType.not]: this.compute_NOT.bind(this),
- [enumLogicGateType.xor]: this.compute_XOR.bind(this),
- [enumLogicGateType.or]: this.compute_OR.bind(this),
- [enumLogicGateType.transistor]: this.compute_IF.bind(this),
- };
- }
-
- update() {
- for (let i = 0; i < this.allEntities.length; ++i) {
- const entity = this.allEntities[i];
- const logicComp = entity.components.LogicGate;
- const slotComp = entity.components.WiredPins;
-
- const slotValues = [];
-
- for (let i = 0; i < slotComp.slots.length; ++i) {
- const slot = slotComp.slots[i];
- if (slot.type !== enumPinSlotType.logicalAcceptor) {
- continue;
- }
- if (slot.linkedNetwork) {
- slotValues.push(slot.linkedNetwork.currentValue);
- } else {
- slotValues.push(null);
- }
- }
-
- const result = this.boundOperations[logicComp.type](slotValues);
-
- // @TODO: For now we hardcode the value to always be slot 0
- assert(
- slotValues.length === slotComp.slots.length - 1,
- "Bad slot config, should have N acceptor slots and 1 ejector"
- );
- assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
-
- slotComp.slots[0].value = result;
- }
- }
-
- /**
- * @param {Array} parameters
- * @returns {BaseItem}
- */
- compute_AND(parameters) {
- assert(parameters.length === 2, "bad parameter count for AND");
-
- const param1 = parameters[0];
- const param2 = parameters[1];
- if (!param1 || !param2) {
- // Not enough params
- return BOOL_FALSE_SINGLETON;
- }
-
- const itemType = param1.getItemType();
-
- if (itemType !== param2.getItemType()) {
- // Differing type
- return BOOL_FALSE_SINGLETON;
- }
-
- if (itemType === "boolean") {
- return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
- ? BOOL_TRUE_SINGLETON
- : BOOL_FALSE_SINGLETON;
- }
-
- return BOOL_FALSE_SINGLETON;
- }
-
- /**
- * @param {Array} parameters
- * @returns {BaseItem}
- */
- compute_NOT(parameters) {
- const item = parameters[0];
- if (!item) {
- return BOOL_TRUE_SINGLETON;
- }
-
- if (item.getItemType() !== "boolean") {
- // Not a boolean actually
- return BOOL_FALSE_SINGLETON;
- }
-
- const value = /** @type {BooleanItem} */ (item).value;
- return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
- }
-
- /**
- * @param {Array} parameters
- * @returns {BaseItem}
- */
- compute_XOR(parameters) {
- assert(parameters.length === 2, "bad parameter count for XOR");
-
- const param1 = parameters[0];
- const param2 = parameters[1];
- if (!param1 && !param2) {
- // Not enough params
- return BOOL_FALSE_SINGLETON;
- }
-
- // Check for the right types
- if (param1 && param1.getItemType() !== "boolean") {
- return BOOL_FALSE_SINGLETON;
- }
-
- if (param2 && param2.getItemType() !== "boolean") {
- return BOOL_FALSE_SINGLETON;
- }
-
- const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
- const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
-
- return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
- }
-
- /**
- * @param {Array} parameters
- * @returns {BaseItem}
- */
- compute_OR(parameters) {
- assert(parameters.length === 2, "bad parameter count for OR");
-
- const param1 = parameters[0];
- const param2 = parameters[1];
- if (!param1 && !param2) {
- // Not enough params
- return BOOL_FALSE_SINGLETON;
- }
-
- const valueParam1 =
- param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
- const valueParam2 =
- param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0;
-
- return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
- }
-
- /**
- * @param {Array} parameters
- * @returns {BaseItem}
- */
- compute_IF(parameters) {
- assert(parameters.length === 2, "bad parameter count for IF");
-
- const flag = parameters[0];
- const value = parameters[1];
- if (!flag || !value) {
- // Not enough params
- return null;
- }
-
- if (flag.getItemType() !== "boolean") {
- // Flag is not a boolean
- return null;
- }
-
- // pass through item
- if (/** @type {BooleanItem} */ (flag).value) {
- return value;
- }
-
- return null;
- }
-}
+import { BaseItem } from "../base_item";
+import { enumColors } from "../colors";
+import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
+import { enumPinSlotType } from "../components/wired_pins";
+import { GameSystemWithFilter } from "../game_system_with_filter";
+import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem } from "../items/boolean_item";
+import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
+import { ShapeDefinition } from "../shape_definition";
+import { ShapeItem } from "../items/shape_item";
+
+export class LogicGateSystem extends GameSystemWithFilter {
+ constructor(root) {
+ super(root, [LogicGateComponent]);
+
+ this.boundOperations = {
+ [enumLogicGateType.and]: this.compute_AND.bind(this),
+ [enumLogicGateType.not]: this.compute_NOT.bind(this),
+ [enumLogicGateType.xor]: this.compute_XOR.bind(this),
+ [enumLogicGateType.or]: this.compute_OR.bind(this),
+ [enumLogicGateType.transistor]: this.compute_IF.bind(this),
+
+ [enumLogicGateType.rotater]: this.compute_ROTATE.bind(this),
+ [enumLogicGateType.analyzer]: this.compute_ANALYZE.bind(this),
+ [enumLogicGateType.cutter]: this.compute_CUT.bind(this),
+ [enumLogicGateType.unstacker]: this.compute_UNSTACK.bind(this),
+ [enumLogicGateType.shapecompare]: this.compute_SHAPECOMPARE.bind(this),
+ };
+ }
+
+ update() {
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+ const logicComp = entity.components.LogicGate;
+ const slotComp = entity.components.WiredPins;
+
+ const slotValues = [];
+
+ for (let i = 0; i < slotComp.slots.length; ++i) {
+ const slot = slotComp.slots[i];
+ if (slot.type !== enumPinSlotType.logicalAcceptor) {
+ continue;
+ }
+ if (slot.linkedNetwork) {
+ slotValues.push(slot.linkedNetwork.currentValue);
+ } else {
+ slotValues.push(null);
+ }
+ }
+
+ const result = this.boundOperations[logicComp.type](slotValues);
+
+ if (Array.isArray(result)) {
+ let resultIndex = 0;
+ for (let i = 0; i < slotComp.slots.length; ++i) {
+ const slot = slotComp.slots[i];
+ if (slot.type !== enumPinSlotType.logicalEjector) {
+ continue;
+ }
+ slot.value = result[resultIndex++];
+ }
+ } else {
+ // @TODO: For now we hardcode the value to always be slot 0
+ assert(
+ slotValues.length === slotComp.slots.length - 1,
+ "Bad slot config, should have N acceptor slots and 1 ejector"
+ );
+ assert(slotComp.slots[0].type === enumPinSlotType.logicalEjector, "Slot 0 should be ejector");
+ slotComp.slots[0].value = result;
+ }
+ }
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_AND(parameters) {
+ assert(parameters.length === 2, "bad parameter count for AND");
+
+ const param1 = parameters[0];
+ const param2 = parameters[1];
+ if (!param1 || !param2) {
+ // Not enough params
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ const itemType = param1.getItemType();
+
+ if (itemType !== param2.getItemType()) {
+ // Differing type
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ if (itemType === "boolean") {
+ return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value
+ ? BOOL_TRUE_SINGLETON
+ : BOOL_FALSE_SINGLETON;
+ }
+
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_NOT(parameters) {
+ const item = parameters[0];
+ if (!item) {
+ return BOOL_TRUE_SINGLETON;
+ }
+
+ if (item.getItemType() !== "boolean") {
+ // Not a boolean actually
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ const value = /** @type {BooleanItem} */ (item).value;
+ return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON;
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_XOR(parameters) {
+ assert(parameters.length === 2, "bad parameter count for XOR");
+
+ const param1 = parameters[0];
+ const param2 = parameters[1];
+ if (!param1 && !param2) {
+ // Not enough params
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ // Check for the right types
+ if (param1 && param1.getItemType() !== "boolean") {
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ if (param2 && param2.getItemType() !== "boolean") {
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0;
+ const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0;
+
+ return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_OR(parameters) {
+ assert(parameters.length === 2, "bad parameter count for OR");
+
+ const param1 = parameters[0];
+ const param2 = parameters[1];
+ if (!param1 && !param2) {
+ // Not enough params
+ return BOOL_FALSE_SINGLETON;
+ }
+
+ const valueParam1 =
+ param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0;
+ const valueParam2 =
+ param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0;
+
+ return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_IF(parameters) {
+ assert(parameters.length === 2, "bad parameter count for IF");
+
+ const flag = parameters[0];
+ const value = parameters[1];
+ if (!flag || !value) {
+ // Not enough params
+ return null;
+ }
+
+ if (flag.getItemType() !== "boolean") {
+ // Flag is not a boolean
+ return null;
+ }
+
+ // pass through item
+ if (/** @type {BooleanItem} */ (flag).value) {
+ return value;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_ROTATE(parameters) {
+ const item = parameters[0];
+ if (!item || item.getItemType() !== "shape") {
+ // Not a shape
+ return null;
+ }
+
+ const definition = /** @type {ShapeItem} */ (item).definition;
+ const rotatedDefinition = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition);
+ return this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinition);
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {[BaseItem, BaseItem]}
+ */
+ compute_ANALYZE(parameters) {
+ const item = parameters[0];
+ if (!item || item.getItemType() !== "shape") {
+ // Not a shape
+ return [null, null];
+ }
+
+ const definition = /** @type {ShapeItem} */ (item).definition;
+ const lowerLayer = /** @type {import("../shape_definition").ShapeLayer} */ (definition.layers[0]);
+ if (!lowerLayer) {
+ return [null, null];
+ }
+
+ const topRightContent = lowerLayer[0];
+
+ if (!topRightContent || topRightContent.subShape === null) {
+ return [null, null];
+ }
+
+ const newDefinition = new ShapeDefinition({
+ layers: [
+ [
+ { subShape: topRightContent.subShape, color: enumColors.uncolored },
+ { subShape: topRightContent.subShape, color: enumColors.uncolored },
+ { subShape: topRightContent.subShape, color: enumColors.uncolored },
+ { subShape: topRightContent.subShape, color: enumColors.uncolored },
+ ],
+ ],
+ });
+
+ return [
+ COLOR_ITEM_SINGLETONS[topRightContent.color],
+ this.root.shapeDefinitionMgr.getShapeItemFromDefinition(newDefinition),
+ ];
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {[BaseItem, BaseItem]}
+ */
+ compute_CUT(parameters) {
+ const item = parameters[0];
+ if (!item || item.getItemType() !== "shape") {
+ // Not a shape
+ return [null, null];
+ }
+
+ const definition = /** @type {ShapeItem} */ (item).definition;
+ const result = this.root.shapeDefinitionMgr.shapeActionCutHalf(definition);
+ return [
+ result[0].isEntirelyEmpty()
+ ? null
+ : this.root.shapeDefinitionMgr.getShapeItemFromDefinition(result[0]),
+ result[1].isEntirelyEmpty()
+ ? null
+ : this.root.shapeDefinitionMgr.getShapeItemFromDefinition(result[1]),
+ ];
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {[BaseItem, BaseItem]}
+ */
+ compute_UNSTACK(parameters) {
+ const item = parameters[0];
+ if (!item || item.getItemType() !== "shape") {
+ // Not a shape
+ return [null, null];
+ }
+
+ const definition = /** @type {ShapeItem} */ (item).definition;
+ const layers = /** @type {Array} */ (definition.layers);
+
+ const upperLayerDefinition = new ShapeDefinition({
+ layers: [layers[layers.length - 1]],
+ });
+
+ const lowerLayers = layers.slice(0, layers.length - 1);
+ const lowerLayerDefinition =
+ lowerLayers.length > 0 ? new ShapeDefinition({ layers: lowerLayers }) : null;
+
+ return [
+ lowerLayerDefinition
+ ? this.root.shapeDefinitionMgr.getShapeItemFromDefinition(lowerLayerDefinition)
+ : null,
+ this.root.shapeDefinitionMgr.getShapeItemFromDefinition(upperLayerDefinition),
+ ];
+ }
+
+ /**
+ * @param {Array} parameters
+ * @returns {BaseItem}
+ */
+ compute_SHAPECOMPARE(parameters) {
+ const itemA = parameters[0];
+ const itemB = parameters[1];
+
+ return itemA &&
+ itemB &&
+ itemA.getItemType() === "shape" &&
+ itemB.getItemType() === "shape" &&
+ /** @type {ShapeItem} */ (itemA).definition.getHash() ===
+ /** @type {ShapeItem} */ (itemB).definition.getHash()
+ ? BOOL_TRUE_SINGLETON
+ : BOOL_FALSE_SINGLETON;
+ }
+}
diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js
index 72fd093f..9084f508 100644
--- a/src/js/game/tutorial_goals.js
+++ b/src/js/game/tutorial_goals.js
@@ -1,179 +1,182 @@
-import { ShapeDefinition } from "./shape_definition";
-import { finalGameShape } from "./upgrades";
-
-/**
- * Don't forget to also update tutorial_goals_mappings.js as well as the translations!
- * @enum {string}
- */
-export const enumHubGoalRewards = {
- reward_cutter_and_trash: "reward_cutter_and_trash",
- reward_rotater: "reward_rotater",
- reward_painter: "reward_painter",
- reward_mixer: "reward_mixer",
- reward_stacker: "reward_stacker",
- reward_splitter: "reward_splitter",
- reward_tunnel: "reward_tunnel",
-
- reward_rotater_ccw: "reward_rotater_ccw",
- reward_rotater_fl: "reward_rotater_fl",
- reward_miner_chainable: "reward_miner_chainable",
- reward_underground_belt_tier_2: "reward_underground_belt_tier_2",
- reward_splitter_compact: "reward_splitter_compact",
- reward_cutter_quad: "reward_cutter_quad",
- reward_painter_double: "reward_painter_double",
- reward_painter_quad: "reward_painter_quad",
- reward_storage: "reward_storage",
-
- reward_blueprints: "reward_blueprints",
- reward_freeplay: "reward_freeplay",
-
- no_reward: "no_reward",
- no_reward_freeplay: "no_reward_freeplay",
-};
-
-export const tutorialGoals = [
- // 1
- // Circle
- {
- shape: "CuCuCuCu", // belts t1
- required: 40,
- reward: enumHubGoalRewards.reward_cutter_and_trash,
- },
-
- // 2
- // Cutter
- {
- shape: "----CuCu", //
- required: 40,
- reward: enumHubGoalRewards.no_reward,
- },
-
- // 3
- // Rectangle
- {
- shape: "RuRuRuRu", // miners t1
- required: 100,
- reward: enumHubGoalRewards.reward_splitter,
- },
-
- // 4
- {
- shape: "RuRu----", // processors t2
- required: 120,
- reward: enumHubGoalRewards.reward_rotater,
- },
-
- // 5
- // Rotater
- {
- shape: "Cu----Cu", // belts t2
- required: 200,
- reward: enumHubGoalRewards.reward_tunnel,
- },
-
- // 6
- {
- shape: "Cu------", // miners t2
- required: 400,
- reward: enumHubGoalRewards.reward_painter,
- },
-
- // 7
- // Painter
- {
- shape: "CrCrCrCr", // unused
- required: 800,
- reward: enumHubGoalRewards.reward_rotater_ccw,
- },
-
- // 8
- {
- shape: "RbRb----", // painter t2
- required: 1000,
- reward: enumHubGoalRewards.reward_mixer,
- },
-
- // 9
- // Mixing (purple)
- {
- shape: "CpCpCpCp", // belts t3
- required: 1400,
- reward: enumHubGoalRewards.reward_splitter_compact,
- },
-
- // 10
- // Star shape + cyan
- {
- shape: "ScScScSc", // miners t3
- required: 1600,
- reward: enumHubGoalRewards.reward_stacker,
- },
-
- // 11
- // Stacker
- {
- shape: "CgScScCg", // processors t3
- required: 1800,
- reward: enumHubGoalRewards.reward_miner_chainable,
- },
-
- // 12
- // Blueprints
- {
- shape: "CbCbCbRb:CwCwCwCw",
- required: 2000,
- reward: enumHubGoalRewards.reward_blueprints,
- },
-
- // 13
- {
- shape: "RpRpRpRp:CwCwCwCw", // painting t3
- required: 12000,
- reward: enumHubGoalRewards.reward_underground_belt_tier_2,
- },
-
- // 14
- {
- shape: "SrSrSrSr:CyCyCyCy", // unused
- required: 16000,
- reward: enumHubGoalRewards.reward_storage,
- },
-
- // 15
- {
- shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
- required: 25000,
- reward: enumHubGoalRewards.reward_cutter_quad,
- },
-
- // 16
- {
- shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
- required: 50000,
- reward: enumHubGoalRewards.reward_painter_double,
- },
-
- // 17
- {
- shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
- required: 120000,
- reward: enumHubGoalRewards.reward_painter_quad,
- },
-
- // 18
- {
- shape: finalGameShape,
- required: 250000,
- reward: enumHubGoalRewards.reward_freeplay,
- },
-];
-
-if (G_IS_DEV) {
- tutorialGoals.forEach(({ shape }) => {
- try {
- ShapeDefinition.fromShortKey(shape);
- } catch (ex) {
- throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape);
- }
- });
-}
+import { ShapeDefinition } from "./shape_definition";
+import { finalGameShape } from "./upgrades";
+
+/**
+ * Don't forget to also update tutorial_goals_mappings.js as well as the translations!
+ * @enum {string}
+ */
+export const enumHubGoalRewards = {
+ reward_cutter_and_trash: "reward_cutter_and_trash",
+ reward_rotater: "reward_rotater",
+ reward_painter: "reward_painter",
+ reward_mixer: "reward_mixer",
+ reward_stacker: "reward_stacker",
+ reward_splitter: "reward_splitter",
+ reward_tunnel: "reward_tunnel",
+
+ reward_rotater_ccw: "reward_rotater_ccw",
+ reward_rotater_fl: "reward_rotater_fl",
+ reward_miner_chainable: "reward_miner_chainable",
+ reward_underground_belt_tier_2: "reward_underground_belt_tier_2",
+ reward_splitter_compact: "reward_splitter_compact",
+ reward_cutter_quad: "reward_cutter_quad",
+ reward_painter_double: "reward_painter_double",
+ reward_painter_quad: "reward_painter_quad",
+ reward_storage: "reward_storage",
+
+ // @todo: unlock
+ reward_merger_compact: "reward_compact_merger",
+
+ reward_blueprints: "reward_blueprints",
+ reward_freeplay: "reward_freeplay",
+
+ no_reward: "no_reward",
+ no_reward_freeplay: "no_reward_freeplay",
+};
+
+export const tutorialGoals = [
+ // 1
+ // Circle
+ {
+ shape: "CuCuCuCu", // belts t1
+ required: 40,
+ reward: enumHubGoalRewards.reward_cutter_and_trash,
+ },
+
+ // 2
+ // Cutter
+ {
+ shape: "----CuCu", //
+ required: 40,
+ reward: enumHubGoalRewards.no_reward,
+ },
+
+ // 3
+ // Rectangle
+ {
+ shape: "RuRuRuRu", // miners t1
+ required: 100,
+ reward: enumHubGoalRewards.reward_splitter,
+ },
+
+ // 4
+ {
+ shape: "RuRu----", // processors t2
+ required: 120,
+ reward: enumHubGoalRewards.reward_rotater,
+ },
+
+ // 5
+ // Rotater
+ {
+ shape: "Cu----Cu", // belts t2
+ required: 200,
+ reward: enumHubGoalRewards.reward_tunnel,
+ },
+
+ // 6
+ {
+ shape: "Cu------", // miners t2
+ required: 400,
+ reward: enumHubGoalRewards.reward_painter,
+ },
+
+ // 7
+ // Painter
+ {
+ shape: "CrCrCrCr", // unused
+ required: 800,
+ reward: enumHubGoalRewards.reward_rotater_ccw,
+ },
+
+ // 8
+ {
+ shape: "RbRb----", // painter t2
+ required: 1000,
+ reward: enumHubGoalRewards.reward_mixer,
+ },
+
+ // 9
+ // Mixing (purple)
+ {
+ shape: "CpCpCpCp", // belts t3
+ required: 1400,
+ reward: enumHubGoalRewards.reward_splitter_compact,
+ },
+
+ // 10
+ // Star shape + cyan
+ {
+ shape: "ScScScSc", // miners t3
+ required: 1600,
+ reward: enumHubGoalRewards.reward_stacker,
+ },
+
+ // 11
+ // Stacker
+ {
+ shape: "CgScScCg", // processors t3
+ required: 1800,
+ reward: enumHubGoalRewards.reward_miner_chainable,
+ },
+
+ // 12
+ // Blueprints
+ {
+ shape: "CbCbCbRb:CwCwCwCw",
+ required: 2000,
+ reward: enumHubGoalRewards.reward_blueprints,
+ },
+
+ // 13
+ {
+ shape: "RpRpRpRp:CwCwCwCw", // painting t3
+ required: 12000,
+ reward: enumHubGoalRewards.reward_underground_belt_tier_2,
+ },
+
+ // 14
+ {
+ shape: "SrSrSrSr:CyCyCyCy", // unused
+ required: 16000,
+ reward: enumHubGoalRewards.reward_storage,
+ },
+
+ // 15
+ {
+ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
+ required: 25000,
+ reward: enumHubGoalRewards.reward_cutter_quad,
+ },
+
+ // 16
+ {
+ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
+ required: 50000,
+ reward: enumHubGoalRewards.reward_painter_double,
+ },
+
+ // 17
+ {
+ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
+ required: 120000,
+ reward: enumHubGoalRewards.reward_painter_quad,
+ },
+
+ // 18
+ {
+ shape: finalGameShape,
+ required: 250000,
+ reward: enumHubGoalRewards.reward_freeplay,
+ },
+];
+
+if (G_IS_DEV) {
+ tutorialGoals.forEach(({ shape }) => {
+ try {
+ ShapeDefinition.fromShortKey(shape);
+ } catch (ex) {
+ throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape);
+ }
+ });
+}
diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js
index f3f61c4a..ba4a033e 100644
--- a/src/js/profile/application_settings.js
+++ b/src/js/profile/application_settings.js
@@ -1,593 +1,600 @@
-/* typehints:start */
-import { Application } from "../application";
-/* typehints:end */
-
-import { ReadWriteProxy } from "../core/read_write_proxy";
-import { BoolSetting, EnumSetting, BaseSetting } from "./setting_types";
-import { createLogger } from "../core/logging";
-import { ExplainedResult } from "../core/explained_result";
-import { THEMES, THEME, applyGameTheme } from "../game/theme";
-import { IS_DEMO } from "../core/config";
-import { T } from "../translations";
-import { LANGUAGES } from "../languages";
-
-const logger = createLogger("application_settings");
-
-/**
- * @enum {string}
- */
-export const enumCategories = {
- general: "general",
- userInterface: "userInterface",
- performance: "performance",
- advanced: "advanced",
-};
-
-export const uiScales = [
- {
- id: "super_small",
- size: 0.6,
- },
- {
- id: "small",
- size: 0.8,
- },
- {
- id: "regular",
- size: 1,
- },
- {
- id: "large",
- size: 1.05,
- },
- {
- id: "huge",
- size: 1.1,
- },
-];
-
-export const scrollWheelSensitivities = [
- {
- id: "super_slow",
- scale: 0.25,
- },
- {
- id: "slow",
- scale: 0.5,
- },
- {
- id: "regular",
- scale: 1,
- },
- {
- id: "fast",
- scale: 2,
- },
- {
- id: "super_fast",
- scale: 4,
- },
-];
-
-export const movementSpeeds = [
- {
- id: "super_slow",
- multiplier: 0.25,
- },
- {
- id: "slow",
- multiplier: 0.5,
- },
- {
- id: "regular",
- multiplier: 1,
- },
- {
- id: "fast",
- multiplier: 2,
- },
- {
- id: "super_fast",
- multiplier: 4,
- },
- {
- id: "extremely_fast",
- multiplier: 8,
- },
-];
-
-export const autosaveIntervals = [
- {
- id: "one_minute",
- seconds: 60,
- },
- {
- id: "two_minutes",
- seconds: 120,
- },
- {
- id: "five_minutes",
- seconds: 5 * 60,
- },
- {
- id: "ten_minutes",
- seconds: 10 * 60,
- },
- {
- id: "twenty_minutes",
- seconds: 20 * 60,
- },
- {
- id: "disabled",
- seconds: null,
- },
-];
-
-/** @type {Array} */
-export const allApplicationSettings = [
- new EnumSetting("language", {
- options: Object.keys(LANGUAGES),
- valueGetter: key => key,
- textGetter: key => LANGUAGES[key].name,
- category: enumCategories.general,
- restartRequired: true,
- changeCb: (app, id) => null,
- magicValue: "auto-detect",
- }),
-
- new EnumSetting("uiScale", {
- options: uiScales.sort((a, b) => a.size - b.size),
- valueGetter: scale => scale.id,
- textGetter: scale => T.settings.labels.uiScale.scales[scale.id],
- category: enumCategories.userInterface,
- restartRequired: false,
- changeCb:
- /**
- * @param {Application} app
- */
- (app, id) => app.updateAfterUiScaleChanged(),
- }),
-
- new BoolSetting(
- "soundsMuted",
- enumCategories.general,
- /**
- * @param {Application} app
- */
- (app, value) => app.sound.setSoundsMuted(value)
- ),
- new BoolSetting(
- "musicMuted",
- enumCategories.general,
- /**
- * @param {Application} app
- */
- (app, value) => app.sound.setMusicMuted(value)
- ),
-
- new BoolSetting(
- "fullscreen",
- enumCategories.general,
- /**
- * @param {Application} app
- */
- (app, value) => {
- if (app.platformWrapper.getSupportsFullscreen()) {
- app.platformWrapper.setFullscreen(value);
- }
- },
- !IS_DEMO
- ),
-
- new BoolSetting(
- "enableColorBlindHelper",
- enumCategories.general,
- /**
- * @param {Application} app
- */
- (app, value) => null
- ),
-
- new BoolSetting("offerHints", enumCategories.userInterface, (app, value) => {}),
-
- new EnumSetting("theme", {
- options: Object.keys(THEMES),
- valueGetter: theme => theme,
- textGetter: theme => T.settings.labels.theme.themes[theme],
- category: enumCategories.userInterface,
- restartRequired: false,
- changeCb:
- /**
- * @param {Application} app
- */
- (app, id) => {
- applyGameTheme(id);
- document.documentElement.setAttribute("data-theme", id);
- },
- enabled: !IS_DEMO,
- }),
-
- new EnumSetting("autosaveInterval", {
- options: autosaveIntervals,
- valueGetter: interval => interval.id,
- textGetter: interval => T.settings.labels.autosaveInterval.intervals[interval.id],
- category: enumCategories.advanced,
- restartRequired: false,
- changeCb:
- /**
- * @param {Application} app
- */
- (app, id) => null,
- }),
-
- new EnumSetting("scrollWheelSensitivity", {
- options: scrollWheelSensitivities.sort((a, b) => a.scale - b.scale),
- valueGetter: scale => scale.id,
- textGetter: scale => T.settings.labels.scrollWheelSensitivity.sensitivity[scale.id],
- category: enumCategories.advanced,
- restartRequired: false,
- changeCb:
- /**
- * @param {Application} app
- */
- (app, id) => app.updateAfterUiScaleChanged(),
- }),
-
- new EnumSetting("movementSpeed", {
- options: movementSpeeds.sort((a, b) => a.multiplier - b.multiplier),
- valueGetter: multiplier => multiplier.id,
- textGetter: multiplier => T.settings.labels.movementSpeed.speeds[multiplier.id],
- category: enumCategories.advanced,
- restartRequired: false,
- changeCb: (app, id) => {},
- }),
-
- new BoolSetting("alwaysMultiplace", enumCategories.advanced, (app, value) => {}),
- new BoolSetting("enableTunnelSmartplace", enumCategories.advanced, (app, value) => {}),
- new BoolSetting("vignette", enumCategories.userInterface, (app, value) => {}),
- new BoolSetting("compactBuildingInfo", enumCategories.userInterface, (app, value) => {}),
- new BoolSetting("disableCutDeleteWarnings", enumCategories.advanced, (app, value) => {}),
- new BoolSetting("rotationByBuilding", enumCategories.advanced, (app, value) => {}),
-
- new EnumSetting("refreshRate", {
- options: ["60", "75", "100", "120", "144", "165", "250", "500"],
- valueGetter: rate => rate,
- textGetter: rate => rate + " Hz",
- category: enumCategories.performance,
- restartRequired: false,
- changeCb: (app, id) => {},
- enabled: !IS_DEMO,
- }),
-
- new BoolSetting("lowQualityMapResources", enumCategories.performance, (app, value) => {}),
- new BoolSetting("disableTileGrid", enumCategories.performance, (app, value) => {}),
- new BoolSetting("lowQualityTextures", enumCategories.performance, (app, value) => {}),
-];
-
-export function getApplicationSettingById(id) {
- return allApplicationSettings.find(setting => setting.id === id);
-}
-
-class SettingsStorage {
- constructor() {
- this.uiScale = "regular";
- this.fullscreen = G_IS_STANDALONE;
-
- this.soundsMuted = false;
- this.musicMuted = false;
- this.theme = "light";
- this.refreshRate = "60";
- this.scrollWheelSensitivity = "regular";
- this.movementSpeed = "regular";
- this.language = "auto-detect";
- this.autosaveInterval = "two_minutes";
-
- this.alwaysMultiplace = false;
- this.offerHints = true;
- this.enableTunnelSmartplace = true;
- this.vignette = true;
- this.compactBuildingInfo = false;
- this.disableCutDeleteWarnings = false;
- this.rotationByBuilding = true;
-
- this.enableColorBlindHelper = false;
-
- this.lowQualityMapResources = false;
- this.disableTileGrid = false;
- this.lowQualityTextures = false;
-
- /**
- * @type {Object.}
- */
- this.keybindingOverrides = {};
- }
-}
-
-export class ApplicationSettings extends ReadWriteProxy {
- constructor(app) {
- super(app, "app_settings.bin");
- }
-
- initialize() {
- // Read and directly write latest data back
- return this.readAsync()
- .then(() => {
- // Apply default setting callbacks
- const settings = this.getAllSettings();
- for (let i = 0; i < allApplicationSettings.length; ++i) {
- const handle = allApplicationSettings[i];
- handle.apply(this.app, settings[handle.id]);
- }
- })
-
- .then(() => this.writeAsync());
- }
-
- save() {
- return this.writeAsync();
- }
-
- // Getters
-
- /**
- * @returns {SettingsStorage}
- */
- getAllSettings() {
- return this.getCurrentData().settings;
- }
-
- /**
- * @param {string} key
- */
- getSetting(key) {
- assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key);
- return this.getAllSettings()[key];
- }
-
- getInterfaceScaleId() {
- if (!this.currentData) {
- // Not initialized yet
- return "regular";
- }
- return this.getAllSettings().uiScale;
- }
-
- getDesiredFps() {
- return parseInt(this.getAllSettings().refreshRate);
- }
-
- getInterfaceScaleValue() {
- const id = this.getInterfaceScaleId();
- for (let i = 0; i < uiScales.length; ++i) {
- if (uiScales[i].id === id) {
- return uiScales[i].size;
- }
- }
- logger.error("Unknown ui scale id:", id);
- return 1;
- }
-
- getScrollWheelSensitivity() {
- const id = this.getAllSettings().scrollWheelSensitivity;
- for (let i = 0; i < scrollWheelSensitivities.length; ++i) {
- if (scrollWheelSensitivities[i].id === id) {
- return scrollWheelSensitivities[i].scale;
- }
- }
- logger.error("Unknown scroll wheel sensitivity id:", id);
- return 1;
- }
-
- getMovementSpeed() {
- const id = this.getAllSettings().movementSpeed;
- for (let i = 0; i < movementSpeeds.length; ++i) {
- if (movementSpeeds[i].id === id) {
- return movementSpeeds[i].multiplier;
- }
- }
- logger.error("Unknown movement speed id:", id);
- return 1;
- }
-
- getAutosaveIntervalSeconds() {
- const id = this.getAllSettings().autosaveInterval;
- for (let i = 0; i < autosaveIntervals.length; ++i) {
- if (autosaveIntervals[i].id === id) {
- return autosaveIntervals[i].seconds;
- }
- }
- logger.error("Unknown autosave interval id:", id);
- return 120;
- }
-
- getIsFullScreen() {
- return this.getAllSettings().fullscreen;
- }
-
- getKeybindingOverrides() {
- return this.getAllSettings().keybindingOverrides;
- }
-
- getLanguage() {
- return this.getAllSettings().language;
- }
-
- // Setters
-
- updateLanguage(id) {
- assert(LANGUAGES[id], "Language not known: " + id);
- return this.updateSetting("language", id);
- }
-
- /**
- * @param {string} key
- * @param {string|boolean} value
- */
- updateSetting(key, value) {
- for (let i = 0; i < allApplicationSettings.length; ++i) {
- const setting = allApplicationSettings[i];
- if (setting.id === key) {
- if (!setting.validate(value)) {
- assertAlways(false, "Bad setting value: " + key);
- }
- this.getAllSettings()[key] = value;
- if (setting.changeCb) {
- setting.changeCb(this.app, value);
- }
- return this.writeAsync();
- }
- }
- assertAlways(false, "Unknown setting: " + key);
- }
-
- /**
- * Sets a new keybinding override
- * @param {string} keybindingId
- * @param {number} keyCode
- */
- updateKeybindingOverride(keybindingId, keyCode) {
- assert(Number.isInteger(keyCode), "Not a valid key code: " + keyCode);
- this.getAllSettings().keybindingOverrides[keybindingId] = keyCode;
- return this.writeAsync();
- }
-
- /**
- * Resets a given keybinding override
- * @param {string} id
- */
- resetKeybindingOverride(id) {
- delete this.getAllSettings().keybindingOverrides[id];
- return this.writeAsync();
- }
- /**
- * Resets all keybinding overrides
- */
- resetKeybindingOverrides() {
- this.getAllSettings().keybindingOverrides = {};
- return this.writeAsync();
- }
-
- // RW Proxy impl
- verify(data) {
- if (!data.settings) {
- return ExplainedResult.bad("missing key 'settings'");
- }
- if (typeof data.settings !== "object") {
- return ExplainedResult.bad("Bad settings object");
- }
-
- const settings = data.settings;
- for (let i = 0; i < allApplicationSettings.length; ++i) {
- const setting = allApplicationSettings[i];
- const storedValue = settings[setting.id];
- if (!setting.validate(storedValue)) {
- return ExplainedResult.bad("Bad setting value for " + setting.id + ": " + storedValue);
- }
- }
- return ExplainedResult.good();
- }
-
- getDefaultData() {
- return {
- version: this.getCurrentVersion(),
- settings: new SettingsStorage(),
- };
- }
-
- getCurrentVersion() {
- return 21;
- }
-
- /** @param {{settings: SettingsStorage, version: number}} data */
- migrate(data) {
- // Simply reset before
- if (data.version < 5) {
- data.settings = new SettingsStorage();
- data.version = this.getCurrentVersion();
- return ExplainedResult.good();
- }
-
- if (data.version < 6) {
- data.settings.alwaysMultiplace = false;
- data.version = 6;
- }
-
- if (data.version < 7) {
- data.settings.offerHints = true;
- data.version = 7;
- }
-
- if (data.version < 8) {
- data.settings.scrollWheelSensitivity = "regular";
- data.version = 8;
- }
-
- if (data.version < 9) {
- data.settings.language = "auto-detect";
- data.version = 9;
- }
-
- if (data.version < 10) {
- data.settings.movementSpeed = "regular";
- data.version = 10;
- }
-
- if (data.version < 11) {
- data.settings.enableTunnelSmartplace = true;
- data.version = 11;
- }
-
- if (data.version < 12) {
- data.settings.vignette = true;
- data.version = 12;
- }
-
- if (data.version < 13) {
- data.settings.compactBuildingInfo = false;
- data.version = 13;
- }
-
- if (data.version < 14) {
- data.settings.disableCutDeleteWarnings = false;
- data.version = 14;
- }
-
- if (data.version < 15) {
- data.settings.autosaveInterval = "two_minutes";
- data.version = 15;
- }
-
- if (data.version < 16) {
- // RE-ENABLE this setting, it already existed
- data.settings.enableTunnelSmartplace = true;
- data.version = 16;
- }
-
- if (data.version < 17) {
- data.settings.enableColorBlindHelper = false;
- data.version = 17;
- }
-
- if (data.version < 18) {
- data.settings.rotationByBuilding = true;
- data.version = 18;
- }
-
- if (data.version < 19) {
- data.settings.lowQualityMapResources = false;
- data.version = 19;
- }
-
- if (data.version < 20) {
- data.settings.disableTileGrid = false;
- data.version = 20;
- }
-
- if (data.version < 21) {
- data.settings.lowQualityTextures = false;
- data.version = 21;
- }
-
- return ExplainedResult.good();
- }
-}
+/* typehints:start */
+import { Application } from "../application";
+/* typehints:end */
+
+import { ReadWriteProxy } from "../core/read_write_proxy";
+import { BoolSetting, EnumSetting, BaseSetting } from "./setting_types";
+import { createLogger } from "../core/logging";
+import { ExplainedResult } from "../core/explained_result";
+import { THEMES, THEME, applyGameTheme } from "../game/theme";
+import { IS_DEMO } from "../core/config";
+import { T } from "../translations";
+import { LANGUAGES } from "../languages";
+
+const logger = createLogger("application_settings");
+
+/**
+ * @enum {string}
+ */
+export const enumCategories = {
+ general: "general",
+ userInterface: "userInterface",
+ performance: "performance",
+ advanced: "advanced",
+};
+
+export const uiScales = [
+ {
+ id: "super_small",
+ size: 0.6,
+ },
+ {
+ id: "small",
+ size: 0.8,
+ },
+ {
+ id: "regular",
+ size: 1,
+ },
+ {
+ id: "large",
+ size: 1.05,
+ },
+ {
+ id: "huge",
+ size: 1.1,
+ },
+];
+
+export const scrollWheelSensitivities = [
+ {
+ id: "super_slow",
+ scale: 0.25,
+ },
+ {
+ id: "slow",
+ scale: 0.5,
+ },
+ {
+ id: "regular",
+ scale: 1,
+ },
+ {
+ id: "fast",
+ scale: 2,
+ },
+ {
+ id: "super_fast",
+ scale: 4,
+ },
+];
+
+export const movementSpeeds = [
+ {
+ id: "super_slow",
+ multiplier: 0.25,
+ },
+ {
+ id: "slow",
+ multiplier: 0.5,
+ },
+ {
+ id: "regular",
+ multiplier: 1,
+ },
+ {
+ id: "fast",
+ multiplier: 2,
+ },
+ {
+ id: "super_fast",
+ multiplier: 4,
+ },
+ {
+ id: "extremely_fast",
+ multiplier: 8,
+ },
+];
+
+export const autosaveIntervals = [
+ {
+ id: "one_minute",
+ seconds: 60,
+ },
+ {
+ id: "two_minutes",
+ seconds: 120,
+ },
+ {
+ id: "five_minutes",
+ seconds: 5 * 60,
+ },
+ {
+ id: "ten_minutes",
+ seconds: 10 * 60,
+ },
+ {
+ id: "twenty_minutes",
+ seconds: 20 * 60,
+ },
+ {
+ id: "disabled",
+ seconds: null,
+ },
+];
+
+/** @type {Array} */
+export const allApplicationSettings = [
+ new EnumSetting("language", {
+ options: Object.keys(LANGUAGES),
+ valueGetter: key => key,
+ textGetter: key => LANGUAGES[key].name,
+ category: enumCategories.general,
+ restartRequired: true,
+ changeCb: (app, id) => null,
+ magicValue: "auto-detect",
+ }),
+
+ new EnumSetting("uiScale", {
+ options: uiScales.sort((a, b) => a.size - b.size),
+ valueGetter: scale => scale.id,
+ textGetter: scale => T.settings.labels.uiScale.scales[scale.id],
+ category: enumCategories.userInterface,
+ restartRequired: false,
+ changeCb:
+ /**
+ * @param {Application} app
+ */
+ (app, id) => app.updateAfterUiScaleChanged(),
+ }),
+
+ new BoolSetting(
+ "soundsMuted",
+ enumCategories.general,
+ /**
+ * @param {Application} app
+ */
+ (app, value) => app.sound.setSoundsMuted(value)
+ ),
+ new BoolSetting(
+ "musicMuted",
+ enumCategories.general,
+ /**
+ * @param {Application} app
+ */
+ (app, value) => app.sound.setMusicMuted(value)
+ ),
+
+ new BoolSetting(
+ "fullscreen",
+ enumCategories.general,
+ /**
+ * @param {Application} app
+ */
+ (app, value) => {
+ if (app.platformWrapper.getSupportsFullscreen()) {
+ app.platformWrapper.setFullscreen(value);
+ }
+ },
+ !IS_DEMO
+ ),
+
+ new BoolSetting(
+ "enableColorBlindHelper",
+ enumCategories.general,
+ /**
+ * @param {Application} app
+ */
+ (app, value) => null
+ ),
+
+ new BoolSetting("offerHints", enumCategories.userInterface, (app, value) => {}),
+
+ new EnumSetting("theme", {
+ options: Object.keys(THEMES),
+ valueGetter: theme => theme,
+ textGetter: theme => T.settings.labels.theme.themes[theme],
+ category: enumCategories.userInterface,
+ restartRequired: false,
+ changeCb:
+ /**
+ * @param {Application} app
+ */
+ (app, id) => {
+ applyGameTheme(id);
+ document.documentElement.setAttribute("data-theme", id);
+ },
+ enabled: !IS_DEMO,
+ }),
+
+ new EnumSetting("autosaveInterval", {
+ options: autosaveIntervals,
+ valueGetter: interval => interval.id,
+ textGetter: interval => T.settings.labels.autosaveInterval.intervals[interval.id],
+ category: enumCategories.advanced,
+ restartRequired: false,
+ changeCb:
+ /**
+ * @param {Application} app
+ */
+ (app, id) => null,
+ }),
+
+ new EnumSetting("scrollWheelSensitivity", {
+ options: scrollWheelSensitivities.sort((a, b) => a.scale - b.scale),
+ valueGetter: scale => scale.id,
+ textGetter: scale => T.settings.labels.scrollWheelSensitivity.sensitivity[scale.id],
+ category: enumCategories.advanced,
+ restartRequired: false,
+ changeCb:
+ /**
+ * @param {Application} app
+ */
+ (app, id) => app.updateAfterUiScaleChanged(),
+ }),
+
+ new EnumSetting("movementSpeed", {
+ options: movementSpeeds.sort((a, b) => a.multiplier - b.multiplier),
+ valueGetter: multiplier => multiplier.id,
+ textGetter: multiplier => T.settings.labels.movementSpeed.speeds[multiplier.id],
+ category: enumCategories.advanced,
+ restartRequired: false,
+ changeCb: (app, id) => {},
+ }),
+
+ new BoolSetting("alwaysMultiplace", enumCategories.advanced, (app, value) => {}),
+ new BoolSetting("clearCursorOnDeleteWhilePlacing", enumCategories.advanced, (app, value) => {}),
+ new BoolSetting("enableTunnelSmartplace", enumCategories.advanced, (app, value) => {}),
+ new BoolSetting("vignette", enumCategories.userInterface, (app, value) => {}),
+ new BoolSetting("compactBuildingInfo", enumCategories.userInterface, (app, value) => {}),
+ new BoolSetting("disableCutDeleteWarnings", enumCategories.advanced, (app, value) => {}),
+ new BoolSetting("rotationByBuilding", enumCategories.advanced, (app, value) => {}),
+
+ new EnumSetting("refreshRate", {
+ options: ["60", "75", "100", "120", "144", "165", "250", "500"],
+ valueGetter: rate => rate,
+ textGetter: rate => rate + " Hz",
+ category: enumCategories.performance,
+ restartRequired: false,
+ changeCb: (app, id) => {},
+ enabled: !IS_DEMO,
+ }),
+
+ new BoolSetting("lowQualityMapResources", enumCategories.performance, (app, value) => {}),
+ new BoolSetting("disableTileGrid", enumCategories.performance, (app, value) => {}),
+ new BoolSetting("lowQualityTextures", enumCategories.performance, (app, value) => {}),
+];
+
+export function getApplicationSettingById(id) {
+ return allApplicationSettings.find(setting => setting.id === id);
+}
+
+class SettingsStorage {
+ constructor() {
+ this.uiScale = "regular";
+ this.fullscreen = G_IS_STANDALONE;
+
+ this.soundsMuted = false;
+ this.musicMuted = false;
+ this.theme = "light";
+ this.refreshRate = "60";
+ this.scrollWheelSensitivity = "regular";
+ this.movementSpeed = "regular";
+ this.language = "auto-detect";
+ this.autosaveInterval = "two_minutes";
+
+ this.alwaysMultiplace = false;
+ this.offerHints = true;
+ this.enableTunnelSmartplace = true;
+ this.vignette = true;
+ this.compactBuildingInfo = false;
+ this.disableCutDeleteWarnings = false;
+ this.rotationByBuilding = true;
+ this.clearCursorOnDeleteWhilePlacing = true;
+
+ this.enableColorBlindHelper = false;
+
+ this.lowQualityMapResources = false;
+ this.disableTileGrid = false;
+ this.lowQualityTextures = false;
+
+ /**
+ * @type {Object.}
+ */
+ this.keybindingOverrides = {};
+ }
+}
+
+export class ApplicationSettings extends ReadWriteProxy {
+ constructor(app) {
+ super(app, "app_settings.bin");
+ }
+
+ initialize() {
+ // Read and directly write latest data back
+ return this.readAsync()
+ .then(() => {
+ // Apply default setting callbacks
+ const settings = this.getAllSettings();
+ for (let i = 0; i < allApplicationSettings.length; ++i) {
+ const handle = allApplicationSettings[i];
+ handle.apply(this.app, settings[handle.id]);
+ }
+ })
+
+ .then(() => this.writeAsync());
+ }
+
+ save() {
+ return this.writeAsync();
+ }
+
+ // Getters
+
+ /**
+ * @returns {SettingsStorage}
+ */
+ getAllSettings() {
+ return this.getCurrentData().settings;
+ }
+
+ /**
+ * @param {string} key
+ */
+ getSetting(key) {
+ assert(this.getAllSettings().hasOwnProperty(key), "Setting not known: " + key);
+ return this.getAllSettings()[key];
+ }
+
+ getInterfaceScaleId() {
+ if (!this.currentData) {
+ // Not initialized yet
+ return "regular";
+ }
+ return this.getAllSettings().uiScale;
+ }
+
+ getDesiredFps() {
+ return parseInt(this.getAllSettings().refreshRate);
+ }
+
+ getInterfaceScaleValue() {
+ const id = this.getInterfaceScaleId();
+ for (let i = 0; i < uiScales.length; ++i) {
+ if (uiScales[i].id === id) {
+ return uiScales[i].size;
+ }
+ }
+ logger.error("Unknown ui scale id:", id);
+ return 1;
+ }
+
+ getScrollWheelSensitivity() {
+ const id = this.getAllSettings().scrollWheelSensitivity;
+ for (let i = 0; i < scrollWheelSensitivities.length; ++i) {
+ if (scrollWheelSensitivities[i].id === id) {
+ return scrollWheelSensitivities[i].scale;
+ }
+ }
+ logger.error("Unknown scroll wheel sensitivity id:", id);
+ return 1;
+ }
+
+ getMovementSpeed() {
+ const id = this.getAllSettings().movementSpeed;
+ for (let i = 0; i < movementSpeeds.length; ++i) {
+ if (movementSpeeds[i].id === id) {
+ return movementSpeeds[i].multiplier;
+ }
+ }
+ logger.error("Unknown movement speed id:", id);
+ return 1;
+ }
+
+ getAutosaveIntervalSeconds() {
+ const id = this.getAllSettings().autosaveInterval;
+ for (let i = 0; i < autosaveIntervals.length; ++i) {
+ if (autosaveIntervals[i].id === id) {
+ return autosaveIntervals[i].seconds;
+ }
+ }
+ logger.error("Unknown autosave interval id:", id);
+ return 120;
+ }
+
+ getIsFullScreen() {
+ return this.getAllSettings().fullscreen;
+ }
+
+ getKeybindingOverrides() {
+ return this.getAllSettings().keybindingOverrides;
+ }
+
+ getLanguage() {
+ return this.getAllSettings().language;
+ }
+
+ // Setters
+
+ updateLanguage(id) {
+ assert(LANGUAGES[id], "Language not known: " + id);
+ return this.updateSetting("language", id);
+ }
+
+ /**
+ * @param {string} key
+ * @param {string|boolean} value
+ */
+ updateSetting(key, value) {
+ for (let i = 0; i < allApplicationSettings.length; ++i) {
+ const setting = allApplicationSettings[i];
+ if (setting.id === key) {
+ if (!setting.validate(value)) {
+ assertAlways(false, "Bad setting value: " + key);
+ }
+ this.getAllSettings()[key] = value;
+ if (setting.changeCb) {
+ setting.changeCb(this.app, value);
+ }
+ return this.writeAsync();
+ }
+ }
+ assertAlways(false, "Unknown setting: " + key);
+ }
+
+ /**
+ * Sets a new keybinding override
+ * @param {string} keybindingId
+ * @param {number} keyCode
+ */
+ updateKeybindingOverride(keybindingId, keyCode) {
+ assert(Number.isInteger(keyCode), "Not a valid key code: " + keyCode);
+ this.getAllSettings().keybindingOverrides[keybindingId] = keyCode;
+ return this.writeAsync();
+ }
+
+ /**
+ * Resets a given keybinding override
+ * @param {string} id
+ */
+ resetKeybindingOverride(id) {
+ delete this.getAllSettings().keybindingOverrides[id];
+ return this.writeAsync();
+ }
+ /**
+ * Resets all keybinding overrides
+ */
+ resetKeybindingOverrides() {
+ this.getAllSettings().keybindingOverrides = {};
+ return this.writeAsync();
+ }
+
+ // RW Proxy impl
+ verify(data) {
+ if (!data.settings) {
+ return ExplainedResult.bad("missing key 'settings'");
+ }
+ if (typeof data.settings !== "object") {
+ return ExplainedResult.bad("Bad settings object");
+ }
+
+ const settings = data.settings;
+ for (let i = 0; i < allApplicationSettings.length; ++i) {
+ const setting = allApplicationSettings[i];
+ const storedValue = settings[setting.id];
+ if (!setting.validate(storedValue)) {
+ return ExplainedResult.bad("Bad setting value for " + setting.id + ": " + storedValue);
+ }
+ }
+ return ExplainedResult.good();
+ }
+
+ getDefaultData() {
+ return {
+ version: this.getCurrentVersion(),
+ settings: new SettingsStorage(),
+ };
+ }
+
+ getCurrentVersion() {
+ return 22;
+ }
+
+ /** @param {{settings: SettingsStorage, version: number}} data */
+ migrate(data) {
+ // Simply reset before
+ if (data.version < 5) {
+ data.settings = new SettingsStorage();
+ data.version = this.getCurrentVersion();
+ return ExplainedResult.good();
+ }
+
+ if (data.version < 6) {
+ data.settings.alwaysMultiplace = false;
+ data.version = 6;
+ }
+
+ if (data.version < 7) {
+ data.settings.offerHints = true;
+ data.version = 7;
+ }
+
+ if (data.version < 8) {
+ data.settings.scrollWheelSensitivity = "regular";
+ data.version = 8;
+ }
+
+ if (data.version < 9) {
+ data.settings.language = "auto-detect";
+ data.version = 9;
+ }
+
+ if (data.version < 10) {
+ data.settings.movementSpeed = "regular";
+ data.version = 10;
+ }
+
+ if (data.version < 11) {
+ data.settings.enableTunnelSmartplace = true;
+ data.version = 11;
+ }
+
+ if (data.version < 12) {
+ data.settings.vignette = true;
+ data.version = 12;
+ }
+
+ if (data.version < 13) {
+ data.settings.compactBuildingInfo = false;
+ data.version = 13;
+ }
+
+ if (data.version < 14) {
+ data.settings.disableCutDeleteWarnings = false;
+ data.version = 14;
+ }
+
+ if (data.version < 15) {
+ data.settings.autosaveInterval = "two_minutes";
+ data.version = 15;
+ }
+
+ if (data.version < 16) {
+ // RE-ENABLE this setting, it already existed
+ data.settings.enableTunnelSmartplace = true;
+ data.version = 16;
+ }
+
+ if (data.version < 17) {
+ data.settings.enableColorBlindHelper = false;
+ data.version = 17;
+ }
+
+ if (data.version < 18) {
+ data.settings.rotationByBuilding = true;
+ data.version = 18;
+ }
+
+ if (data.version < 19) {
+ data.settings.lowQualityMapResources = false;
+ data.version = 19;
+ }
+
+ if (data.version < 20) {
+ data.settings.disableTileGrid = false;
+ data.version = 20;
+ }
+
+ if (data.version < 21) {
+ data.settings.lowQualityTextures = false;
+ data.version = 21;
+ }
+
+ if (data.version < 22) {
+ data.settings.clearCursorOnDeleteWhilePlacing = true;
+ data.version = 22;
+ }
+
+ return ExplainedResult.good();
+ }
+}
diff --git a/src/js/savegame/savegame_manager.js b/src/js/savegame/savegame_manager.js
index 42e56734..52f9dc14 100644
--- a/src/js/savegame/savegame_manager.js
+++ b/src/js/savegame/savegame_manager.js
@@ -1,218 +1,225 @@
-import { ExplainedResult } from "../core/explained_result";
-import { createLogger } from "../core/logging";
-import { ReadWriteProxy } from "../core/read_write_proxy";
-import { globalConfig } from "../core/config";
-import { Savegame } from "./savegame";
-const logger = createLogger("savegame_manager");
-
-const Rusha = require("rusha");
-
-/**
- * @typedef {import("./savegame_typedefs").SavegamesData} SavegamesData
- * @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
- */
-
-/** @enum {string} */
-export const enumLocalSavegameStatus = {
- offline: "offline",
- synced: "synced",
-};
-
-export class SavegameManager extends ReadWriteProxy {
- constructor(app) {
- super(app, "savegames.bin");
-
- this.currentData = this.getDefaultData();
- }
-
- // RW Proxy Impl
- /**
- * @returns {SavegamesData}
- */
- getDefaultData() {
- return {
- version: this.getCurrentVersion(),
- savegames: [],
- };
- }
-
- getCurrentVersion() {
- return 1001;
- }
-
- /**
- * @returns {SavegamesData}
- */
- getCurrentData() {
- return super.getCurrentData();
- }
-
- verify(data) {
- // TODO / FIXME!!!!
- return ExplainedResult.good();
- }
-
- /**
- *
- * @param {SavegamesData} data
- */
- migrate(data) {
- if (data.version < 1001) {
- data.savegames.forEach(savegame => {
- savegame.level = 0;
- });
- data.version = 1001;
- }
-
- return ExplainedResult.good();
- }
-
- // End rw proxy
-
- /**
- * @returns {Array}
- */
- getSavegamesMetaData() {
- return this.currentData.savegames;
- }
-
- /**
- *
- * @param {string} internalId
- * @returns {Savegame}
- */
- getSavegameById(internalId) {
- const metadata = this.getGameMetaDataByInternalId(internalId);
- if (!metadata) {
- return null;
- }
- return new Savegame(this.app, { internalId, metaDataRef: metadata });
- }
-
- /**
- * Deletes a savegame
- * @param {SavegameMetadata} game
- */
- deleteSavegame(game) {
- const handle = new Savegame(this.app, {
- internalId: game.internalId,
- metaDataRef: game,
- });
-
- return handle.deleteAsync().then(() => {
- for (let i = 0; i < this.currentData.savegames.length; ++i) {
- const potentialGame = this.currentData.savegames[i];
- if (potentialGame.internalId === handle.internalId) {
- this.currentData.savegames.splice(i, 1);
- break;
- }
- }
-
- return this.writeAsync();
- });
- }
-
- /**
- * Returns a given games metadata by id
- * @param {string} id
- * @returns {SavegameMetadata}
- */
- getGameMetaDataByInternalId(id) {
- for (let i = 0; i < this.currentData.savegames.length; ++i) {
- const data = this.currentData.savegames[i];
- if (data.internalId === id) {
- return data;
- }
- }
- logger.error("Savegame internal id not found:", id);
- return null;
- }
-
- /**
- * Creates a new savegame
- * @returns {Savegame}
- */
- createNewSavegame() {
- const id = this.generateInternalId();
-
- const metaData = /** @type {SavegameMetadata} */ ({
- lastUpdate: Date.now(),
- version: Savegame.getCurrentVersion(),
- internalId: id,
- });
-
- this.currentData.savegames.push(metaData);
- this.sortSavegames();
-
- return new Savegame(this.app, {
- internalId: id,
- metaDataRef: metaData,
- });
- }
-
- importSavegame(data) {
- const savegame = this.createNewSavegame();
- const migrationResult = savegame.migrate(data);
- if (migrationResult.isBad()) {
- return Promise.reject("Failed to migrate: " + migrationResult.reason);
- }
-
- savegame.currentData = data;
- const verification = savegame.verify(data);
- if (verification.isBad()) {
- return Promise.reject("Verification failed: " + verification.result);
- }
-
- return savegame.writeSavegameAndMetadata().then(() => this.sortSavegames());
- }
-
- /**
- * Sorts all savegames by their creation time descending
- * @returns {Promise}
- */
- sortSavegames() {
- this.currentData.savegames.sort((a, b) => b.lastUpdate - a.lastUpdate);
- let promiseChain = Promise.resolve();
- while (this.currentData.savegames.length > 30) {
- const toRemove = this.currentData.savegames.pop();
-
- // Try to remove the savegame since its no longer available
- const game = new Savegame(this.app, {
- internalId: toRemove.internalId,
- metaDataRef: toRemove,
- });
- promiseChain = promiseChain
- .then(() => game.deleteAsync())
- .then(
- () => {},
- err => {
- logger.error(this, "Failed to remove old savegame:", toRemove, ":", err);
- }
- );
- }
-
- return promiseChain;
- }
-
- /**
- * Helper method to generate a new internal savegame id
- */
- generateInternalId() {
- return Rusha.createHash()
- .update(Date.now() + "/" + Math.random())
- .digest("hex");
- }
-
- // End
-
- initialize() {
- // First read, then directly write to ensure we have the latest data
- // @ts-ignore
- return this.readAsync().then(() => {
- if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
- return Promise.resolve();
- }
- return this.sortSavegames().then(() => this.writeAsync());
- });
- }
-}
+import { ExplainedResult } from "../core/explained_result";
+import { createLogger } from "../core/logging";
+import { ReadWriteProxy } from "../core/read_write_proxy";
+import { globalConfig } from "../core/config";
+import { Savegame } from "./savegame";
+const logger = createLogger("savegame_manager");
+
+const Rusha = require("rusha");
+
+/**
+ * @typedef {import("./savegame_typedefs").SavegamesData} SavegamesData
+ * @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
+ */
+
+/** @enum {string} */
+export const enumLocalSavegameStatus = {
+ offline: "offline",
+ synced: "synced",
+};
+
+export class SavegameManager extends ReadWriteProxy {
+ constructor(app) {
+ super(app, "savegames.bin");
+
+ this.currentData = this.getDefaultData();
+ }
+
+ // RW Proxy Impl
+ /**
+ * @returns {SavegamesData}
+ */
+ getDefaultData() {
+ return {
+ version: this.getCurrentVersion(),
+ savegames: [],
+ };
+ }
+
+ getCurrentVersion() {
+ return 1002;
+ }
+
+ /**
+ * @returns {SavegamesData}
+ */
+ getCurrentData() {
+ return super.getCurrentData();
+ }
+
+ verify(data) {
+ // TODO / FIXME!!!!
+ return ExplainedResult.good();
+ }
+
+ /**
+ *
+ * @param {SavegamesData} data
+ */
+ migrate(data) {
+ if (data.version < 1001) {
+ data.savegames.forEach(savegame => {
+ savegame.level = 0;
+ });
+ data.version = 1001;
+ }
+
+ if (data.version < 1002) {
+ data.savegames.forEach(savegame => {
+ savegame.name = null;
+ });
+ data.version = 1002;
+ }
+
+ return ExplainedResult.good();
+ }
+
+ // End rw proxy
+
+ /**
+ * @returns {Array}
+ */
+ getSavegamesMetaData() {
+ return this.currentData.savegames;
+ }
+
+ /**
+ *
+ * @param {string} internalId
+ * @returns {Savegame}
+ */
+ getSavegameById(internalId) {
+ const metadata = this.getGameMetaDataByInternalId(internalId);
+ if (!metadata) {
+ return null;
+ }
+ return new Savegame(this.app, { internalId, metaDataRef: metadata });
+ }
+
+ /**
+ * Deletes a savegame
+ * @param {SavegameMetadata} game
+ */
+ deleteSavegame(game) {
+ const handle = new Savegame(this.app, {
+ internalId: game.internalId,
+ metaDataRef: game,
+ });
+
+ return handle.deleteAsync().then(() => {
+ for (let i = 0; i < this.currentData.savegames.length; ++i) {
+ const potentialGame = this.currentData.savegames[i];
+ if (potentialGame.internalId === handle.internalId) {
+ this.currentData.savegames.splice(i, 1);
+ break;
+ }
+ }
+
+ return this.writeAsync();
+ });
+ }
+
+ /**
+ * Returns a given games metadata by id
+ * @param {string} id
+ * @returns {SavegameMetadata}
+ */
+ getGameMetaDataByInternalId(id) {
+ for (let i = 0; i < this.currentData.savegames.length; ++i) {
+ const data = this.currentData.savegames[i];
+ if (data.internalId === id) {
+ return data;
+ }
+ }
+ logger.error("Savegame internal id not found:", id);
+ return null;
+ }
+
+ /**
+ * Creates a new savegame
+ * @returns {Savegame}
+ */
+ createNewSavegame() {
+ const id = this.generateInternalId();
+
+ const metaData = /** @type {SavegameMetadata} */ ({
+ lastUpdate: Date.now(),
+ version: Savegame.getCurrentVersion(),
+ internalId: id,
+ });
+
+ this.currentData.savegames.push(metaData);
+ this.sortSavegames();
+
+ return new Savegame(this.app, {
+ internalId: id,
+ metaDataRef: metaData,
+ });
+ }
+
+ importSavegame(data) {
+ const savegame = this.createNewSavegame();
+ const migrationResult = savegame.migrate(data);
+ if (migrationResult.isBad()) {
+ return Promise.reject("Failed to migrate: " + migrationResult.reason);
+ }
+
+ savegame.currentData = data;
+ const verification = savegame.verify(data);
+ if (verification.isBad()) {
+ return Promise.reject("Verification failed: " + verification.result);
+ }
+
+ return savegame.writeSavegameAndMetadata().then(() => this.sortSavegames());
+ }
+
+ /**
+ * Sorts all savegames by their creation time descending
+ * @returns {Promise}
+ */
+ sortSavegames() {
+ this.currentData.savegames.sort((a, b) => b.lastUpdate - a.lastUpdate);
+ let promiseChain = Promise.resolve();
+ while (this.currentData.savegames.length > 30) {
+ const toRemove = this.currentData.savegames.pop();
+
+ // Try to remove the savegame since its no longer available
+ const game = new Savegame(this.app, {
+ internalId: toRemove.internalId,
+ metaDataRef: toRemove,
+ });
+ promiseChain = promiseChain
+ .then(() => game.deleteAsync())
+ .then(
+ () => {},
+ err => {
+ logger.error(this, "Failed to remove old savegame:", toRemove, ":", err);
+ }
+ );
+ }
+
+ return promiseChain;
+ }
+
+ /**
+ * Helper method to generate a new internal savegame id
+ */
+ generateInternalId() {
+ return Rusha.createHash()
+ .update(Date.now() + "/" + Math.random())
+ .digest("hex");
+ }
+
+ // End
+
+ initialize() {
+ // First read, then directly write to ensure we have the latest data
+ // @ts-ignore
+ return this.readAsync().then(() => {
+ if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
+ return Promise.resolve();
+ }
+ return this.sortSavegames().then(() => this.writeAsync());
+ });
+ }
+}
diff --git a/src/js/savegame/savegame_typedefs.js b/src/js/savegame/savegame_typedefs.js
index f5bb08c2..0f94cd6a 100644
--- a/src/js/savegame/savegame_typedefs.js
+++ b/src/js/savegame/savegame_typedefs.js
@@ -1,38 +1,39 @@
-/**
- * @typedef {import("../game/entity").Entity} Entity
- *
- * @typedef {{}} SavegameStats
- *
- * @typedef {{
- * camera: any,
- * time: any,
- * entityMgr: any,
- * map: any,
- * hubGoals: any,
- * pinnedShapes: any,
- * waypoints: any,
- * entities: Array,
- * beltPaths: Array
- * }} SerializedGame
- *
- * @typedef {{
- * version: number,
- * dump: SerializedGame,
- * stats: SavegameStats,
- * lastUpdate: number,
- * }} SavegameData
- *
- * @typedef {{
- * lastUpdate: number,
- * version: number,
- * internalId: string,
- * level: number
- * }} SavegameMetadata
- *
- * @typedef {{
- * version: number,
- * savegames: Array
- * }} SavegamesData
- */
-
-export default {};
+/**
+ * @typedef {import("../game/entity").Entity} Entity
+ *
+ * @typedef {{}} SavegameStats
+ *
+ * @typedef {{
+ * camera: any,
+ * time: any,
+ * entityMgr: any,
+ * map: any,
+ * hubGoals: any,
+ * pinnedShapes: any,
+ * waypoints: any,
+ * entities: Array,
+ * beltPaths: Array
+ * }} SerializedGame
+ *
+ * @typedef {{
+ * version: number,
+ * dump: SerializedGame,
+ * stats: SavegameStats,
+ * lastUpdate: number,
+ * }} SavegameData
+ *
+ * @typedef {{
+ * lastUpdate: number,
+ * version: number,
+ * internalId: string,
+ * level: number
+ * name: string|null
+ * }} SavegameMetadata
+ *
+ * @typedef {{
+ * version: number,
+ * savegames: Array
+ * }} SavegamesData
+ */
+
+export default {};
diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js
index 3d39e826..bea209a8 100644
--- a/src/js/states/main_menu.js
+++ b/src/js/states/main_menu.js
@@ -1,534 +1,579 @@
-import { GameState } from "../core/game_state";
-import { cachebust } from "../core/cachebust";
-import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
-import {
- makeDiv,
- makeButtonElement,
- formatSecondsToTimeAgo,
- waitNextFrame,
- isSupportedBrowser,
- makeButton,
- removeAllChildren,
-} from "../core/utils";
-import { ReadWriteProxy } from "../core/read_write_proxy";
-import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
-import { T } from "../translations";
-import { getApplicationSettingById } from "../profile/application_settings";
-
-/**
- * @typedef {import("../savegame/savegame_typedefs").SavegameMetadata} SavegameMetadata
- * @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
- */
-
-/**
- * Generates a file download
- * @param {string} filename
- * @param {string} text
- */
-function generateFileDownload(filename, text) {
- var element = document.createElement("a");
- element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
- element.setAttribute("download", filename);
-
- element.style.display = "none";
- document.body.appendChild(element);
-
- element.click();
- document.body.removeChild(element);
-}
-
-export class MainMenuState extends GameState {
- constructor() {
- super("MainMenuState");
- }
-
- getInnerHTML() {
- const bannerHtml = `
- ${T.demoBanners.title}
-
- ${T.demoBanners.intro}
-
- Get the shapez.io standalone!
- `;
-
- return `
-
-
-
-
-
- ${
- G_IS_STANDALONE || G_IS_DEV
- ? `
-
- `
- : ""
- }
-
-
-
-
-
-
-
-
-
-
Wires update!
-
-
-
-
-
-
- ${IS_DEMO ? `
${bannerHtml}
` : ""}
-
-
-
- ${
- isSupportedBrowser()
- ? ""
- : `
${T.mainMenu.browserWarning}
`
- }
-
-
-
-
-
-
-
- `;
- }
-
- requestImportSavegame() {
- if (
- IS_DEMO &&
- this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
- !this.app.platformWrapper.getHasUnlimitedSavegames()
- ) {
- this.app.analytics.trackUiClick("importgame_slot_limit_show");
- this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc);
- return;
- }
-
- var input = document.createElement("input");
- input.type = "file";
- input.accept = ".bin";
-
- input.onchange = e => {
- const file = input.files[0];
- if (file) {
- waitNextFrame().then(() => {
- this.app.analytics.trackUiClick("import_savegame");
- const closeLoader = this.dialogs.showLoadingDialog();
- const reader = new FileReader();
- reader.addEventListener("load", event => {
- const contents = event.target.result;
- let realContent;
-
- try {
- realContent = ReadWriteProxy.deserializeObject(contents);
- } catch (err) {
- closeLoader();
- this.dialogs.showWarning(
- T.dialogs.importSavegameError.title,
- T.dialogs.importSavegameError.text + " " + err
- );
- return;
- }
-
- this.app.savegameMgr.importSavegame(realContent).then(
- () => {
- closeLoader();
- this.dialogs.showWarning(
- T.dialogs.importSavegameSuccess.title,
- T.dialogs.importSavegameSuccess.text
- );
-
- this.renderMainMenu();
- this.renderSavegames();
- },
- err => {
- closeLoader();
- this.dialogs.showWarning(
- T.dialogs.importSavegameError.title,
- T.dialogs.importSavegameError.text + ": " + err
- );
- }
- );
- });
- reader.addEventListener("error", error => {
- this.dialogs.showWarning(
- T.dialogs.importSavegameError.title,
- T.dialogs.importSavegameError.text + ": " + error
- );
- });
- reader.readAsText(file, "utf-8");
- });
- }
- };
- input.click();
- }
-
- onBackButton() {
- this.app.platformWrapper.exitApp();
- }
-
- onEnter(payload) {
- this.dialogs = new HUDModalDialogs(null, this.app);
- const dialogsElement = document.body.querySelector(".modalDialogParent");
- this.dialogs.initializeToElement(dialogsElement);
-
- if (payload.loadError) {
- this.dialogs.showWarning(
- T.dialogs.gameLoadFailure.title,
- T.dialogs.gameLoadFailure.text + " " + payload.loadError
- );
- }
-
- const qs = this.htmlElement.querySelector.bind(this.htmlElement);
-
- if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
- const games = this.app.savegameMgr.getSavegamesMetaData();
- if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
- this.resumeGame(games[0]);
- } else {
- this.onPlayButtonClicked();
- }
- }
-
- // Initialize video
- this.videoElement = this.htmlElement.querySelector("video");
- this.videoElement.playbackRate = 0.9;
- this.videoElement.addEventListener("canplay", () => {
- if (this.videoElement) {
- this.videoElement.classList.add("loaded");
- }
- });
-
- this.trackClicks(qs(".settingsButton"), this.onSettingsButtonClicked);
- this.trackClicks(qs(".changelog"), this.onChangelogClicked);
- this.trackClicks(qs(".redditLink"), this.onRedditClicked);
- this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked);
- this.trackClicks(qs(".helpTranslate"), this.onTranslationHelpLinkClicked);
-
- if (G_IS_STANDALONE) {
- this.trackClicks(qs(".exitAppButton"), this.onExitAppButtonClicked);
- }
-
- this.renderMainMenu();
- this.renderSavegames();
-
- const steamLink = this.htmlElement.querySelector(".steamLink");
- if (steamLink) {
- this.trackClicks(steamLink, () => this.onSteamLinkClicked(), { preventClick: true });
- }
-
- const discordLink = this.htmlElement.querySelector(".discordLink");
- this.trackClicks(
- discordLink,
- () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord),
- { preventClick: true }
- );
-
- const githubLink = this.htmlElement.querySelector(".githubLink");
- this.trackClicks(
- githubLink,
- () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github),
- { preventClick: true }
- );
-
- const producerLink = this.htmlElement.querySelector(".producerLink");
- this.trackClicks(
- producerLink,
- () => this.app.platformWrapper.openExternalLink("https://tobspr.com"),
- { preventClick: true }
- );
- }
-
- renderMainMenu() {
- const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons");
- removeAllChildren(buttonContainer);
-
- // Import button
- const importButtonElement = makeButtonElement(
- ["importButton", "styledButton"],
- T.mainMenu.importSavegame
- );
- this.trackClicks(importButtonElement, this.requestImportSavegame);
-
- if (this.savedGames.length > 0) {
- // Continue game
- const continueButton = makeButton(
- buttonContainer,
- ["continueButton", "styledButton"],
- T.mainMenu.continue
- );
- this.trackClicks(continueButton, this.onContinueButtonClicked);
-
- const outerDiv = makeDiv(buttonContainer, null, ["outer"], null);
- outerDiv.appendChild(importButtonElement);
- const newGameButton = makeButton(
- this.htmlElement.querySelector(".mainContainer .outer"),
- ["newGameButton", "styledButton"],
- T.mainMenu.newGame
- );
- this.trackClicks(newGameButton, this.onPlayButtonClicked);
- } else {
- // New game
- const playBtn = makeButton(buttonContainer, ["playButton", "styledButton"], T.mainMenu.play);
- this.trackClicks(playBtn, this.onPlayButtonClicked);
- buttonContainer.appendChild(importButtonElement);
- }
- }
-
- onSteamLinkClicked() {
- this.app.analytics.trackUiClick("main_menu_steam_link_2");
- this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage);
- return false;
- }
-
- onExitAppButtonClicked() {
- this.app.platformWrapper.exitApp();
- }
-
- onChangelogClicked() {
- this.moveToState("ChangelogState");
- }
-
- onRedditClicked() {
- this.app.analytics.trackUiClick("main_menu_reddit_link");
- this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.reddit);
- }
-
- onLanguageChooseClicked() {
- this.app.analytics.trackUiClick("choose_language");
- const setting = /** @type {EnumSetting} */ (getApplicationSettingById("language"));
-
- const { optionSelected } = this.dialogs.showOptionChooser(T.settings.labels.language.title, {
- active: this.app.settings.getLanguage(),
- options: setting.options.map(option => ({
- value: setting.valueGetter(option),
- text: setting.textGetter(option),
- desc: setting.descGetter(option),
- iconPrefix: setting.iconPrefix,
- })),
- });
-
- optionSelected.add(value => {
- this.app.settings.updateLanguage(value);
- if (setting.restartRequired) {
- if (this.app.platformWrapper.getSupportsRestart()) {
- this.app.platformWrapper.performRestart();
- } else {
- this.dialogs.showInfo(T.dialogs.restartRequired.title, T.dialogs.restartRequired.text, [
- "ok:good",
- ]);
- }
- }
-
- if (setting.changeCb) {
- setting.changeCb(this.app, value);
- }
-
- // Update current icon
- this.htmlElement.querySelector("button.languageChoose").setAttribute("data-languageIcon", value);
- }, this);
- }
-
- get savedGames() {
- return this.app.savegameMgr.getSavegamesMetaData();
- }
-
- renderSavegames() {
- const oldContainer = this.htmlElement.querySelector(".mainContainer .savegames");
- if (oldContainer) {
- oldContainer.remove();
- }
- const games = this.savedGames;
- if (games.length > 0) {
- const parent = makeDiv(this.htmlElement.querySelector(".mainContainer"), null, ["savegames"]);
-
- for (let i = 0; i < games.length; ++i) {
- const elem = makeDiv(parent, null, ["savegame"]);
-
- makeDiv(
- elem,
- null,
- ["playtime"],
- formatSecondsToTimeAgo((new Date().getTime() - games[i].lastUpdate) / 1000.0)
- );
-
- makeDiv(
- elem,
- null,
- ["level"],
- games[i].level
- ? T.mainMenu.savegameLevel.replace("", "" + games[i].level)
- : T.mainMenu.savegameLevelUnknown
- );
-
- const deleteButton = document.createElement("button");
- deleteButton.classList.add("styledButton", "deleteGame");
- elem.appendChild(deleteButton);
-
- const downloadButton = document.createElement("button");
- downloadButton.classList.add("styledButton", "downloadGame");
- elem.appendChild(downloadButton);
-
- const resumeButton = document.createElement("button");
- resumeButton.classList.add("styledButton", "resumeGame");
- elem.appendChild(resumeButton);
-
- this.trackClicks(deleteButton, () => this.deleteGame(games[i]));
- this.trackClicks(downloadButton, () => this.downloadGame(games[i]));
- this.trackClicks(resumeButton, () => this.resumeGame(games[i]));
- }
- }
- }
-
- /**
- * @param {SavegameMetadata} game
- */
- resumeGame(game) {
- this.app.analytics.trackUiClick("resume_game");
-
- this.app.adProvider.showVideoAd().then(() => {
- this.app.analytics.trackUiClick("resume_game_adcomplete");
- const savegame = this.app.savegameMgr.getSavegameById(game.internalId);
- savegame
- .readAsync()
- .then(() => {
- this.moveToState("InGameState", {
- savegame,
- });
- })
- .catch(err => {
- this.dialogs.showWarning(
- T.dialogs.gameLoadFailure.title,
- T.dialogs.gameLoadFailure.text + " " + err
- );
- });
- });
- }
-
- /**
- * @param {SavegameMetadata} game
- */
- deleteGame(game) {
- this.app.analytics.trackUiClick("delete_game");
-
- const signals = this.dialogs.showWarning(
- T.dialogs.confirmSavegameDelete.title,
- T.dialogs.confirmSavegameDelete.text,
- ["delete:bad", "cancel:good"]
- );
-
- signals.delete.add(() => {
- this.app.savegameMgr.deleteSavegame(game).then(
- () => {
- this.renderSavegames();
- if (this.savedGames.length <= 0) this.renderMainMenu();
- },
- err => {
- this.dialogs.showWarning(
- T.dialogs.savegameDeletionError.title,
- T.dialogs.savegameDeletionError.text + " " + err
- );
- }
- );
- });
- }
-
- /**
- * @param {SavegameMetadata} game
- */
- downloadGame(game) {
- this.app.analytics.trackUiClick("download_game");
-
- const savegame = this.app.savegameMgr.getSavegameById(game.internalId);
- savegame.readAsync().then(() => {
- const data = ReadWriteProxy.serializeObject(savegame.currentData);
- generateFileDownload(savegame.filename, data);
- });
- }
-
- onSettingsButtonClicked() {
- this.moveToState("SettingsState");
- }
-
- onTranslationHelpLinkClicked() {
- this.app.analytics.trackUiClick("translation_help_link");
- this.app.platformWrapper.openExternalLink(
- "https://github.com/tobspr/shapez.io/blob/master/translations"
- );
- }
-
- onPlayButtonClicked() {
- if (
- IS_DEMO &&
- this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
- !this.app.platformWrapper.getHasUnlimitedSavegames()
- ) {
- this.app.analytics.trackUiClick("startgame_slot_limit_show");
- this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc);
- return;
- }
-
- this.app.analytics.trackUiClick("startgame");
- this.app.adProvider.showVideoAd().then(() => {
- const savegame = this.app.savegameMgr.createNewSavegame();
-
- this.moveToState("InGameState", {
- savegame,
- });
- this.app.analytics.trackUiClick("startgame_adcomplete");
- });
- }
-
- onContinueButtonClicked() {
- let latestLastUpdate = 0;
- let latestInternalId;
- this.app.savegameMgr.currentData.savegames.forEach(saveGame => {
- if (saveGame.lastUpdate > latestLastUpdate) {
- latestLastUpdate = saveGame.lastUpdate;
- latestInternalId = saveGame.internalId;
- }
- });
-
- const savegame = this.app.savegameMgr.getSavegameById(latestInternalId);
- savegame.readAsync().then(() => {
- this.moveToState("InGameState", {
- savegame,
- });
- });
- }
-
- onLeave() {
- this.dialogs.cleanup();
- }
-}
+import { GameState } from "../core/game_state";
+import { cachebust } from "../core/cachebust";
+import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
+import {
+ makeDiv,
+ makeButtonElement,
+ formatSecondsToTimeAgo,
+ waitNextFrame,
+ isSupportedBrowser,
+ makeButton,
+ removeAllChildren,
+} from "../core/utils";
+import { ReadWriteProxy } from "../core/read_write_proxy";
+import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
+import { T } from "../translations";
+import { getApplicationSettingById } from "../profile/application_settings";
+import { FormElementInput } from "../core/modal_dialog_forms";
+import { DialogWithForm } from "../core/modal_dialog_elements";
+
+/**
+ * @typedef {import("../savegame/savegame_typedefs").SavegameMetadata} SavegameMetadata
+ * @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
+ */
+
+/**
+ * Generates a file download
+ * @param {string} filename
+ * @param {string} text
+ */
+function generateFileDownload(filename, text) {
+ var element = document.createElement("a");
+ element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
+ element.setAttribute("download", filename);
+
+ element.style.display = "none";
+ document.body.appendChild(element);
+
+ element.click();
+ document.body.removeChild(element);
+}
+
+export class MainMenuState extends GameState {
+ constructor() {
+ super("MainMenuState");
+ }
+
+ getInnerHTML() {
+ const bannerHtml = `
+ ${T.demoBanners.title}
+
+ ${T.demoBanners.intro}
+
+ Get the shapez.io standalone!
+ `;
+
+ return `
+
+
+
+
+
+ ${
+ G_IS_STANDALONE || G_IS_DEV
+ ? `
+
+ `
+ : ""
+ }
+
+
+
+
+
+
+
+
+
+
Wires update!
+
+
+
+
+
+
+ ${IS_DEMO ? `
${bannerHtml}
` : ""}
+
+
+
+ ${
+ isSupportedBrowser()
+ ? ""
+ : `
${T.mainMenu.browserWarning}
`
+ }
+
+
+
+
+
+
+
+ `;
+ }
+
+ requestImportSavegame() {
+ if (
+ IS_DEMO &&
+ this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
+ !this.app.platformWrapper.getHasUnlimitedSavegames()
+ ) {
+ this.app.analytics.trackUiClick("importgame_slot_limit_show");
+ this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc);
+ return;
+ }
+
+ var input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".bin";
+
+ input.onchange = e => {
+ const file = input.files[0];
+ if (file) {
+ waitNextFrame().then(() => {
+ this.app.analytics.trackUiClick("import_savegame");
+ const closeLoader = this.dialogs.showLoadingDialog();
+ const reader = new FileReader();
+ reader.addEventListener("load", event => {
+ const contents = event.target.result;
+ let realContent;
+
+ try {
+ realContent = ReadWriteProxy.deserializeObject(contents);
+ } catch (err) {
+ closeLoader();
+ this.dialogs.showWarning(
+ T.dialogs.importSavegameError.title,
+ T.dialogs.importSavegameError.text + " " + err
+ );
+ return;
+ }
+
+ this.app.savegameMgr.importSavegame(realContent).then(
+ () => {
+ closeLoader();
+ this.dialogs.showWarning(
+ T.dialogs.importSavegameSuccess.title,
+ T.dialogs.importSavegameSuccess.text
+ );
+
+ this.renderMainMenu();
+ this.renderSavegames();
+ },
+ err => {
+ closeLoader();
+ this.dialogs.showWarning(
+ T.dialogs.importSavegameError.title,
+ T.dialogs.importSavegameError.text + ": " + err
+ );
+ }
+ );
+ });
+ reader.addEventListener("error", error => {
+ this.dialogs.showWarning(
+ T.dialogs.importSavegameError.title,
+ T.dialogs.importSavegameError.text + ": " + error
+ );
+ });
+ reader.readAsText(file, "utf-8");
+ });
+ }
+ };
+ input.click();
+ }
+
+ onBackButton() {
+ this.app.platformWrapper.exitApp();
+ }
+
+ onEnter(payload) {
+ this.dialogs = new HUDModalDialogs(null, this.app);
+ const dialogsElement = document.body.querySelector(".modalDialogParent");
+ this.dialogs.initializeToElement(dialogsElement);
+
+ if (payload.loadError) {
+ this.dialogs.showWarning(
+ T.dialogs.gameLoadFailure.title,
+ T.dialogs.gameLoadFailure.text + " " + payload.loadError
+ );
+ }
+
+ const qs = this.htmlElement.querySelector.bind(this.htmlElement);
+
+ if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
+ const games = this.app.savegameMgr.getSavegamesMetaData();
+ if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
+ this.resumeGame(games[0]);
+ } else {
+ this.onPlayButtonClicked();
+ }
+ }
+
+ // Initialize video
+ this.videoElement = this.htmlElement.querySelector("video");
+ this.videoElement.playbackRate = 0.9;
+ this.videoElement.addEventListener("canplay", () => {
+ if (this.videoElement) {
+ this.videoElement.classList.add("loaded");
+ }
+ });
+
+ this.trackClicks(qs(".settingsButton"), this.onSettingsButtonClicked);
+ this.trackClicks(qs(".changelog"), this.onChangelogClicked);
+ this.trackClicks(qs(".redditLink"), this.onRedditClicked);
+ this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked);
+ this.trackClicks(qs(".helpTranslate"), this.onTranslationHelpLinkClicked);
+
+ if (G_IS_STANDALONE) {
+ this.trackClicks(qs(".exitAppButton"), this.onExitAppButtonClicked);
+ }
+
+ this.renderMainMenu();
+ this.renderSavegames();
+
+ const steamLink = this.htmlElement.querySelector(".steamLink");
+ if (steamLink) {
+ this.trackClicks(steamLink, () => this.onSteamLinkClicked(), { preventClick: true });
+ }
+
+ const discordLink = this.htmlElement.querySelector(".discordLink");
+ this.trackClicks(
+ discordLink,
+ () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord),
+ { preventClick: true }
+ );
+
+ const githubLink = this.htmlElement.querySelector(".githubLink");
+ this.trackClicks(
+ githubLink,
+ () => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github),
+ { preventClick: true }
+ );
+
+ const producerLink = this.htmlElement.querySelector(".producerLink");
+ this.trackClicks(
+ producerLink,
+ () => this.app.platformWrapper.openExternalLink("https://tobspr.com"),
+ { preventClick: true }
+ );
+ }
+
+ renderMainMenu() {
+ const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons");
+ removeAllChildren(buttonContainer);
+
+ // Import button
+ const importButtonElement = makeButtonElement(
+ ["importButton", "styledButton"],
+ T.mainMenu.importSavegame
+ );
+ this.trackClicks(importButtonElement, this.requestImportSavegame);
+
+ if (this.savedGames.length > 0) {
+ // Continue game
+ const continueButton = makeButton(
+ buttonContainer,
+ ["continueButton", "styledButton"],
+ T.mainMenu.continue
+ );
+ this.trackClicks(continueButton, this.onContinueButtonClicked);
+
+ const outerDiv = makeDiv(buttonContainer, null, ["outer"], null);
+ outerDiv.appendChild(importButtonElement);
+ const newGameButton = makeButton(
+ this.htmlElement.querySelector(".mainContainer .outer"),
+ ["newGameButton", "styledButton"],
+ T.mainMenu.newGame
+ );
+ this.trackClicks(newGameButton, this.onPlayButtonClicked);
+ } else {
+ // New game
+ const playBtn = makeButton(buttonContainer, ["playButton", "styledButton"], T.mainMenu.play);
+ this.trackClicks(playBtn, this.onPlayButtonClicked);
+ buttonContainer.appendChild(importButtonElement);
+ }
+ }
+
+ onSteamLinkClicked() {
+ this.app.analytics.trackUiClick("main_menu_steam_link_2");
+ this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.standaloneStorePage);
+ return false;
+ }
+
+ onExitAppButtonClicked() {
+ this.app.platformWrapper.exitApp();
+ }
+
+ onChangelogClicked() {
+ this.moveToState("ChangelogState");
+ }
+
+ onRedditClicked() {
+ this.app.analytics.trackUiClick("main_menu_reddit_link");
+ this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.reddit);
+ }
+
+ onLanguageChooseClicked() {
+ this.app.analytics.trackUiClick("choose_language");
+ const setting = /** @type {EnumSetting} */ (getApplicationSettingById("language"));
+
+ const { optionSelected } = this.dialogs.showOptionChooser(T.settings.labels.language.title, {
+ active: this.app.settings.getLanguage(),
+ options: setting.options.map(option => ({
+ value: setting.valueGetter(option),
+ text: setting.textGetter(option),
+ desc: setting.descGetter(option),
+ iconPrefix: setting.iconPrefix,
+ })),
+ });
+
+ optionSelected.add(value => {
+ this.app.settings.updateLanguage(value);
+ if (setting.restartRequired) {
+ if (this.app.platformWrapper.getSupportsRestart()) {
+ this.app.platformWrapper.performRestart();
+ } else {
+ this.dialogs.showInfo(T.dialogs.restartRequired.title, T.dialogs.restartRequired.text, [
+ "ok:good",
+ ]);
+ }
+ }
+
+ if (setting.changeCb) {
+ setting.changeCb(this.app, value);
+ }
+
+ // Update current icon
+ this.htmlElement.querySelector("button.languageChoose").setAttribute("data-languageIcon", value);
+ }, this);
+ }
+
+ get savedGames() {
+ return this.app.savegameMgr.getSavegamesMetaData();
+ }
+
+ renderSavegames() {
+ const oldContainer = this.htmlElement.querySelector(".mainContainer .savegames");
+ if (oldContainer) {
+ oldContainer.remove();
+ }
+ const games = this.savedGames;
+ if (games.length > 0) {
+ const parent = makeDiv(this.htmlElement.querySelector(".mainContainer"), null, ["savegames"]);
+
+ for (let i = 0; i < games.length; ++i) {
+ const elem = makeDiv(parent, null, ["savegame"]);
+
+ makeDiv(
+ elem,
+ null,
+ ["playtime"],
+ formatSecondsToTimeAgo((new Date().getTime() - games[i].lastUpdate) / 1000.0)
+ );
+
+ makeDiv(
+ elem,
+ null,
+ ["level"],
+ games[i].level
+ ? T.mainMenu.savegameLevel.replace("", "" + games[i].level)
+ : T.mainMenu.savegameLevelUnknown
+ );
+
+ const name = makeDiv(
+ elem,
+ null,
+ ["name"],
+ "" + (games[i].name ? games[i].name : T.mainMenu.savegameUnnamed) + " "
+ );
+
+ const deleteButton = document.createElement("button");
+ deleteButton.classList.add("styledButton", "deleteGame");
+ elem.appendChild(deleteButton);
+
+ const downloadButton = document.createElement("button");
+ downloadButton.classList.add("styledButton", "downloadGame");
+ elem.appendChild(downloadButton);
+
+ const renameButton = document.createElement("button");
+ renameButton.classList.add("styledButton", "renameGame");
+ name.appendChild(renameButton);
+
+ const resumeButton = document.createElement("button");
+ resumeButton.classList.add("styledButton", "resumeGame");
+ elem.appendChild(resumeButton);
+
+ this.trackClicks(deleteButton, () => this.deleteGame(games[i]));
+ this.trackClicks(downloadButton, () => this.downloadGame(games[i]));
+ this.trackClicks(resumeButton, () => this.resumeGame(games[i]));
+ this.trackClicks(renameButton, () => this.requestRenameSavegame(games[i]));
+ }
+ }
+ }
+
+ /**
+ * @param {SavegameMetadata} game
+ */
+ requestRenameSavegame(game) {
+ const regex = /^[a-zA-Z0-9_\- ]{1,20}$/;
+
+ const nameInput = new FormElementInput({
+ id: "nameInput",
+ label: null,
+ placeholder: "",
+ defaultValue: game.name || "",
+ validator: val => val.match(regex),
+ });
+ const dialog = new DialogWithForm({
+ app: this.app,
+ title: T.dialogs.renameSavegame.title,
+ desc: T.dialogs.renameSavegame.desc,
+ formElements: [nameInput],
+ buttons: ["cancel:bad:escape", "ok:good:enter"],
+ });
+ this.dialogs.internalShowDialog(dialog);
+
+ // When confirmed, save the name
+ dialog.buttonSignals.ok.add(() => {
+ game.name = nameInput.getValue();
+ this.app.savegameMgr.writeAsync();
+ this.renderSavegames();
+ });
+ }
+
+ /**
+ * @param {SavegameMetadata} game
+ */
+ resumeGame(game) {
+ this.app.analytics.trackUiClick("resume_game");
+
+ this.app.adProvider.showVideoAd().then(() => {
+ this.app.analytics.trackUiClick("resume_game_adcomplete");
+ const savegame = this.app.savegameMgr.getSavegameById(game.internalId);
+ savegame
+ .readAsync()
+ .then(() => {
+ this.moveToState("InGameState", {
+ savegame,
+ });
+ })
+ .catch(err => {
+ this.dialogs.showWarning(
+ T.dialogs.gameLoadFailure.title,
+ T.dialogs.gameLoadFailure.text + " " + err
+ );
+ });
+ });
+ }
+
+ /**
+ * @param {SavegameMetadata} game
+ */
+ deleteGame(game) {
+ this.app.analytics.trackUiClick("delete_game");
+
+ const signals = this.dialogs.showWarning(
+ T.dialogs.confirmSavegameDelete.title,
+ T.dialogs.confirmSavegameDelete.text,
+ ["delete:bad", "cancel:good"]
+ );
+
+ signals.delete.add(() => {
+ this.app.savegameMgr.deleteSavegame(game).then(
+ () => {
+ this.renderSavegames();
+ if (this.savedGames.length <= 0) this.renderMainMenu();
+ },
+ err => {
+ this.dialogs.showWarning(
+ T.dialogs.savegameDeletionError.title,
+ T.dialogs.savegameDeletionError.text + " " + err
+ );
+ }
+ );
+ });
+ }
+
+ /**
+ * @param {SavegameMetadata} game
+ */
+ downloadGame(game) {
+ this.app.analytics.trackUiClick("download_game");
+
+ const savegame = this.app.savegameMgr.getSavegameById(game.internalId);
+ savegame.readAsync().then(() => {
+ const data = ReadWriteProxy.serializeObject(savegame.currentData);
+ const filename = (game.name || "unnamed") + ".bin";
+ generateFileDownload(filename, data);
+ });
+ }
+
+ onSettingsButtonClicked() {
+ this.moveToState("SettingsState");
+ }
+
+ onTranslationHelpLinkClicked() {
+ this.app.analytics.trackUiClick("translation_help_link");
+ this.app.platformWrapper.openExternalLink(
+ "https://github.com/tobspr/shapez.io/blob/master/translations"
+ );
+ }
+
+ onPlayButtonClicked() {
+ if (
+ IS_DEMO &&
+ this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
+ !this.app.platformWrapper.getHasUnlimitedSavegames()
+ ) {
+ this.app.analytics.trackUiClick("startgame_slot_limit_show");
+ this.dialogs.showWarning(T.dialogs.oneSavegameLimit.title, T.dialogs.oneSavegameLimit.desc);
+ return;
+ }
+
+ this.app.analytics.trackUiClick("startgame");
+ this.app.adProvider.showVideoAd().then(() => {
+ const savegame = this.app.savegameMgr.createNewSavegame();
+
+ this.moveToState("InGameState", {
+ savegame,
+ });
+ this.app.analytics.trackUiClick("startgame_adcomplete");
+ });
+ }
+
+ onContinueButtonClicked() {
+ let latestLastUpdate = 0;
+ let latestInternalId;
+ this.app.savegameMgr.currentData.savegames.forEach(saveGame => {
+ if (saveGame.lastUpdate > latestLastUpdate) {
+ latestLastUpdate = saveGame.lastUpdate;
+ latestInternalId = saveGame.internalId;
+ }
+ });
+
+ const savegame = this.app.savegameMgr.getSavegameById(latestInternalId);
+ savegame.readAsync().then(() => {
+ this.moveToState("InGameState", {
+ savegame,
+ });
+ });
+ }
+
+ onLeave() {
+ this.dialogs.cleanup();
+ }
+}
diff --git a/src/js/states/mobile_warning.js b/src/js/states/mobile_warning.js
index de17578a..a48a69ed 100644
--- a/src/js/states/mobile_warning.js
+++ b/src/js/states/mobile_warning.js
@@ -17,7 +17,7 @@ export class MobileWarningState extends GameState {
There is also no estimate when this will change, but feel to make a contribution! It's
open source !
- If you want to play on your computer, you can also get the standalone on steam:
+ If you want to play on your computer, you can also get the standalone on Steam:
-
- Failed to import your savegame:
+ Ha ocurrit un error intentant importar la teva partida:
importSavegameSuccess:
title: Importar
@@ -184,7 +184,7 @@ dialogs:
gameLoadFailure:
title: No es pot carregar la partida guardada
text: >-
- Failed to load your savegame:
+ Ha ocurrit un error al intentar carregar la teva partida:
confirmSavegameDelete:
title: Eliminar
@@ -194,7 +194,7 @@ dialogs:
savegameDeletionError:
title: Error en eliminar
text: >-
- Failed to delete the savegame:
+ Ha ocurrit un error al intentar eliminar la teva partida:
restartRequired:
title: Reiniciar
@@ -240,12 +240,12 @@ dialogs:
massCutConfirm:
title: Tallar edificis
desc: >-
- Estàs esborrant molts edificis de cop ( per ser exactes)! Estàs segur que vols seguir?
+ Estàs tallant molts edificis de cop ( per ser exactes)! Estàs segur que vols seguir?
massCutInsufficientConfirm:
- title: Confirm cut
+ title: Confirmar tallar
desc: >-
- You can not afford to paste this area! Are you sure you want to cut it?
+ No pots aferrar a aquesta zona! Estàs segur de que vols tallarla?
blueprintsNotUnlocked:
title: Encara no s'ha desbloquejat
@@ -271,7 +271,7 @@ dialogs:
desc: En la Demo només pots crear dos marcadors, aconsegueix la versió completa per gaudir de l'experiència completa!
exportScreenshotWarning:
- title: Export screenshot
+ title: Exportar Captura de Pantalla
desc: Has demanat exportar la teva/teua base com una captura de pantalla. Tin en conte que aquest procés pot ser molt lent i inclús crashear el teu joc!
ingame:
@@ -307,7 +307,7 @@ ingame:
purple: Morat
cyan: Cian
white: Blanc
- black: Black
+ black: Negre
uncolored: Sense color
# Everything related to placing buildings (I.e. as soon as you selected a building
@@ -315,7 +315,7 @@ ingame:
buildingPlacement:
# Buildings can have different variants which are unlocked at later levels,
# and this is the hint shown when there are multiple variants available.
- cycleBuildingVariants: Pulsa per a ciclar entre variants.
+ cycleBuildingVariants: Prem per a ciclar entre variants.
# Shows the hotkey in the ui, e.g. "Hotkey: Q"
hotkeyLabel: >-
@@ -687,139 +687,139 @@ settings:
disabled: Desactivat
scrollWheelSensitivity:
- title: Zoom sensitivity
+ title: Sensitivitat del Zoom
description: >-
- Changes how sensitive the zoom is (Either mouse wheel or trackpad).
+ Canvia la sensitivitat del zoom (Roda del ratolí o del trackpad).
sensitivity:
- super_slow: Super slow
- slow: Slow
+ super_slow: Molt lent
+ slow: Lent
regular: Regular
- fast: Fast
- super_fast: Super fast
+ fast: Ràpid
+ super_fast: Molt Ràpid
movementSpeed:
- title: Movement speed
+ title: Velocitat de Moviment
description: >-
- Changes how fast the view moves when using the keyboard.
+ Canvia la rapidesa amb la que la vista es mou mentres empres el teclat.
speeds:
- super_slow: Super slow
- slow: Slow
+ super_slow: Molt lent
+ slow: Lent
regular: Regular
- fast: Fast
- super_fast: Super Fast
- extremely_fast: Extremely Fast
+ fast: Rápid
+ super_fast: Molt Ràpid
+ extremely_fast: Extremadament Ràpid
language:
- title: Language
+ title: Idioma
description: >-
- Change the language. All translations are user-contributed and might be incomplete!
+ Canvia l'idioma. Totes les traduccions són contribucions d'usuaris i poden estar incompletes!
enableColorBlindHelper:
- title: Color Blind Mode
+ title: Mode per Daltònics
description: >-
- Enables various tools which allow you to play the game if you are color blind.
+ Habilita diverses eines que et permeten jugar si ets Daltònic.
fullscreen:
- title: Fullscreen
+ title: Pantalla Completa
description: >-
- It is recommended to play the game in fullscreen to get the best experience. Only available in the standalone.
+ Es recomana jugar en Pantalla Completa per aconseguir la millor experiència. Només disponible a la versió completa del joc.
soundsMuted:
- title: Mute Sounds
+ title: Silencia els sons
description: >-
- If enabled, mutes all sound effects.
+ Si està activat, silencia tots els sons.
musicMuted:
- title: Mute Music
+ title: Silencia la música
description: >-
- If enabled, mutes all music.
+ Si està activat, silencia la música.
theme:
- title: Game theme
+ title: Tema del joc (Visual)
description: >-
- Choose the game theme (light / dark).
+ Tria el tema visual (clar / oscur).
themes:
- dark: Dark
- light: Light
+ dark: Oscur
+ light: Clar
refreshRate:
- title: Simulation Target
+ title: Objectiu de Simulació
description: >-
- If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates. This might actually decrease the FPS if your computer is too slow.
+ Si tens un monitor de 144hz, canvia la tarifa de refresc aquí per que el joc es mostri de forma correcta a tarifes de refresc altes. Pot decrementar els FPS si el teu ordenador és massa lent.
alwaysMultiplace:
- title: Multiplace
+ title: Col·locació Múltiple
description: >-
- If enabled, all buildings will stay selected after placement until you cancel it. This is equivalent to holding SHIFT permanently.
+ Si s'activa, tots els edificis es mantindràn seleccionats després de col·locarlos fins que ho cancel·lis. Això és equivalent a mantenir SHIFT permanentment.
offerHints:
- title: Hints & Tutorials
+ title: Pistes i Tutorials
description: >-
- Whether to offer hints and tutorials while playing. Also hides certain UI elements up to a given level to make it easier to get into the game.
+ Si s'activa, es mostraràn pistes i tutorials mentres es juga. També amaga certs elements visuals fins a un nivell per que sigui més fàcil aprendre a jugar.
enableTunnelSmartplace:
- title: Smart Tunnels
+ title: Túnels Intel·ligents
description: >-
- When enabled, placing tunnels will automatically remove unnecessary belts. This also enables you to drag tunnels and excess tunnels will get removed.
+ Si s'activa, al col·locar túnels s'eliminaràn les cintes transportadores innecessaris. També et permet arrastrar túnels i els túnels sobrants s'eliminaràn.
vignette:
- title: Vignette
+ title: Vinyeta
description: >-
- Enables the vignette, which darkens the screen corners and makes text easier to read.
+ Activa la vinyeta, que obscureix els cantons de la pantalla i facilita la lectura de texte.
rotationByBuilding:
- title: Rotation by building type
+ title: Rotació segons el tipus d'edifici.
description: >-
- Each building type remembers the rotation you last set it to individually. This may be more comfortable if you frequently switch between placing different building types.
+ Cada tipus d'edifici recorda la rotació que vau definir per última vegada de manera individual. Això pot ser més còmode si canvies freqüentment entre edificis.
compactBuildingInfo:
- title: Compact Building Infos
+ title: Informació sobre Edificis Compactes
description: >-
- Shortens info boxes for buildings by only showing their ratios. Otherwise a description and image is shown.
+ Escurça els quadres d’informació dels edificis només mostrant les seves velocitats. En cas contrari, es mostra una descripció i una imatge.
disableCutDeleteWarnings:
- title: Disable Cut/Delete Warnings
+ title: Desactiva els diàlegs de Talla/Borra
description: >-
- Disables the warning dialogs brought up when cutting/deleting more than 100 entities.
+ Desactiva els diàlegs d'advertència que es mostren en tallar / suprimir més de 100 entitats.
keybindings:
- title: Keybindings
+ title: Combinacions de tecles
hint: >-
- Tip: Be sure to make use of CTRL, SHIFT and ALT! They enable different placement options.
+ Tip: Assegura't d'emprar CTRL, SHIFT i ALT! Et permeten col·locar objectes de formes diferents.
- resetKeybindings: Reset Keybindings
+ resetKeybindings: Resetejar les Combinacions de tecles
categoryLabels:
- general: Application
- ingame: Game
- navigation: Navigating
- placement: Placement
- massSelect: Mass Select
- buildings: Building Shortcuts
- placementModifiers: Placement Modifiers
+ general: Aplicació
+ ingame: Joc
+ navigation: Navegació
+ placement: Col·locació
+ massSelect: Sel·lecció Massiva
+ buildings: Dreceres d'Edificis
+ placementModifiers: Modificadors de col·locació
mappings:
- confirm: Confirm
- back: Back
- mapMoveUp: Move Up
- mapMoveRight: Move Right
- mapMoveDown: Move Down
- mapMoveLeft: Move Left
- mapMoveFaster: Move Faster
- centerMap: Center Map
+ confirm: Confirmar
+ back: Enrere
+ mapMoveUp: Moure Amunt
+ mapMoveRight: Moure Dreta
+ mapMoveDown: Moure Avall
+ mapMoveLeft: Moure Esquerra
+ mapMoveFaster: Moure més Ràpid
+ centerMap: Centrar Mapa
- mapZoomIn: Zoom in
- mapZoomOut: Zoom out
- createMarker: Create Marker
+ mapZoomIn: Apropar
+ mapZoomOut: Allunyar
+ createMarker: Crea un Marcador
- menuOpenShop: Upgrades
- menuOpenStats: Statistics
- menuClose: Close Menu
+ menuOpenShop: Millores
+ menuOpenStats: Estadístiques
+ menuClose: Tancar Menú
- toggleHud: Toggle HUD
- toggleFPSInfo: Toggle FPS and Debug Info
- switchLayers: Switch layers
- exportScreenshot: Export whole Base as Image
+ toggleHud: Commutar HUD
+ toggleFPSInfo: Commutar FPS i Informació de Depuració
+ switchLayers: Canviar capes
+ exportScreenshot: Exportar la Base com a Imatge
belt: *belt
splitter: *splitter
underground_belt: *underground_belt
@@ -834,49 +834,49 @@ keybindings:
trash: *trash
wire: *wire
- pipette: Pipette
- rotateWhilePlacing: Rotate
+ pipette: Pipeta
+ rotateWhilePlacing: Rotar
rotateInverseModifier: >-
- Modifier: Rotate CCW instead
- cycleBuildingVariants: Cycle Variants
- confirmMassDelete: Delete area
- pasteLastBlueprint: Paste last blueprint
- cycleBuildings: Cycle Buildings
- lockBeltDirection: Enable belt planner
+ Modifier: Rotar en sentit antihorari
+ cycleBuildingVariants: Rotar les Variants
+ confirmMassDelete: Eliminar àrea
+ pasteLastBlueprint: Afferar el darrer pla
+ cycleBuildings: Rotar els Buildings
+ lockBeltDirection: Habilitar el planificador de cintes transportadores
switchDirectionLockSide: >-
- Planner: Switch side
+ Planner: Canviar costat
- massSelectStart: Hold and drag to start
- massSelectSelectMultiple: Select multiple areas
- massSelectCopy: Copy area
- massSelectCut: Cut area
+ massSelectStart: Manteniu premut i arrossegueu per començar
+ massSelectSelectMultiple: Seleccionar múltiples àrees
+ massSelectCopy: Copiar àrea
+ massSelectCut: Tallar àrea
- placementDisableAutoOrientation: Disable automatic orientation
- placeMultiple: Stay in placement mode
- placeInverse: Invert automatic belt orientation
+ placementDisableAutoOrientation: Desactivar orientació automàtica
+ placeMultiple: Mantenir-se en mode de col·locació
+ placeInverse: Invertir orientació automàtica de les cintes transportadores
about:
- title: About this Game
+ title: Sobre aquest Joc
body: >-
- This game is open source and developed by Tobias Springer (this is me).
+ Aquest joc és de codi obert i desenvolupat per Tobias Springer (sóc jo).
- If you want to contribute, check out shapez.io on GitHub .
+ Si vols contribuir, visita shapez.io a GitHub .
- This game wouldn't have been possible without the great Discord community around my games - You should really join the Discord server !
+ Aquest joc no hauria estat possible sense la gran comunitat de Discord al voltant dels meus jocs. Recoman unir-se al servidor de Discord !
- The soundtrack was made by Peppsen - He's awesome.
+ Banda sonora creada perPeppsen - És increïble.
- Finally, huge thanks to my best friend Niklas - Without our Factorio sessions, this game would never have existed.
+ Finalment, gràcies al meu millor amic Niklas . Sense les nostres sessions de Factorio, aquest joc mai hauria existit.
changelog:
- title: Changelog
+ title: Registre de Canvis
demo:
features:
- restoringGames: Restoring savegames
- importingGames: Importing savegames
- oneGameLimit: Limited to one savegame
- customizeKeybindings: Customizing Keybindings
- exportingBase: Exporting whole Base as Image
+ restoringGames: Restaurar partides guardats
+ importingGames: Importar partides guardats
+ oneGameLimit: Limitat a una partida guardada.
+ customizeKeybindings: Personalitzar teclats
+ exportingBase: Exportar la base com a Imatge
- settingNotAvailable: Not available in the demo.
+ settingNotAvailable: No disponible en la versió de demostració.
diff --git a/translations/base-cz.yaml b/translations/base-cz.yaml
index 51d6096c..2ee35618 100644
--- a/translations/base-cz.yaml
+++ b/translations/base-cz.yaml
@@ -234,8 +234,8 @@ dialogs:
createMarker:
title: Nová značka
- desc: Give it a meaningful name, you can also include a short key of a shape (Which you can generate here )
- titleEdit: Edit Marker
+ desc: Pojmenuj jí nějak výstižně, též ji můžeš doplnit zkratkou pro tvar (Kterou si můžete vytvořit zde )
+ titleEdit: Upravit značku
markerDemoLimit:
desc: V ukázce můžete vytvořit pouze dvě značky. Získejte plnou verzi pro neomezený počet značek!
@@ -252,8 +252,8 @@ dialogs:
může zejména u větších základen dlouho trvat, nebo dokonce shodit hru!
massCutInsufficientConfirm:
- title: Confirm cut
- desc: You can not afford to paste this area! Are you sure you want to cut it?
+ title: Potvrdit vyjmutí
+ desc: Nemůžeš si dovolit vložení této oblasti! Skutečně ji chceš vyjmout?
ingame:
# This is shown in the top left corner and displays useful keybindings in
@@ -510,25 +510,25 @@ buildings:
description: Skladuje věci navíc až do naplnění kapacity. Může být použit na skladování surovin navíc.
wire:
default:
- name: Energy Wire
- description: Allows you to transport energy.
+ name: Kabel
+ description: Dovoluje přenos energie.
advanced_processor:
default:
- name: Color Inverter
- description: Accepts a color or shape and inverts it.
+ name: Invertor barev
+ description: Přijme barvu či tvar a převrátí jej.
energy_generator:
- deliver: Deliver
- toGenerateEnergy: For
+ deliver: Dodej
+ toGenerateEnergy: Pro
default:
- name: Energy Generator
- description: Generates energy by consuming shapes.
+ name: Generátor energie
+ description: Vyrábí energii zpracováním tvarů.
wire_crossings:
default:
- name: Wire Splitter
- description: Splits a energy wire into two.
+ name: Dělič kabelů
+ description: Rozdělí kabel na dva.
merger:
- name: Wire Merger
- description: Merges two energy wires into one.
+ name: Slučovač kabelů
+ description: Spojí dva kabely do jednoho.
storyRewards:
# Those are the rewards gained from completing the store
@@ -543,7 +543,7 @@ storyRewards:
reward_painter:
title: Barvení
desc: >-
- The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them! PS: If you are colorblind, there is a color blind mode in the settings!
+ Barvič byl právě odemčen - Vytěžte nějaká ložiska barev (podobně jako těžíte tvary) a spojte je s tvarem v barviči pro jeho obarvení! PS: Pokud jste barvoslepý, v nastavení naleznete režim pro barvoslepé !
reward_mixer:
title: Míchání barev
@@ -587,7 +587,7 @@ storyRewards:
desc: Odemknuli jste variantu barviče - Funguje stejně jako normální, ale nabarví dva tvary naráz pomocí jedné barvy!
reward_painter_quad:
- title: Quad Painting
+ title: Čtyřstranné barvení
desc: Odemknuli jste variantu painter - Umožní vám nabarvit každou čtvrtinu tvaru jinou barvou!
reward_storage:
@@ -616,9 +616,9 @@ storyRewards:
settings:
title: Nastavení
categories:
- general: General
- userInterface: User Interface
- advanced: Advanced
+ general: Obecné
+ userInterface: Uživatelské rozhraní
+ advanced: Rozšířené
versionBadges:
dev: Vývojová verze
diff --git a/translations/base-de.yaml b/translations/base-de.yaml
index 29713620..f74ac50f 100644
--- a/translations/base-de.yaml
+++ b/translations/base-de.yaml
@@ -44,37 +44,38 @@ steamPage:
Nutze dein gesammeltes Wissen über die Maschinen und lasse deine Fabriken die gewünschten Formen der 18 verschiedenen Level abliefern. Schalte mit jedem Level neue Arbeitsschritte oder Gebäude frei. Das sollte dich schon für Stunden beschäftigt halten! Danach werden im Freispielmodus zufällige Formen generiert, die du ebenfalls abliefern kannst. Ich füge regelmäßig neue Funktionen hinzu und davon sind eine ganze Menge geplant!
+
Wenn du das Spiel erwirbst, erhälst du Zugriff auf die zusätzlichen Features der Standalone-Version. Das bedeutet, du kannst unter anderem die neuesten Updates zuerst spielen!
[b]Vorteile der Standalone[/b]
[list]
- [*] Dark-Mode
- [*] Unbegrenzte Wegpunkte
- [*] Unbegrenzte Anzahl von Spielständen
- [*] Weitere Einstellungen
- [*] Es kommen: Mehr Levels
- [*] Es kommen: Kabel & Energie! Voraussichtlich gegen Ende Juli 2020.
- [*] Unterstütze die Entwicklung von shapez.io ❤️
+ [*] Dark Mode
+ [*] unbegrenzte Anzahl an Wegpunkten
+ [*] unbegrenzte Anzahl an Speicherständen
+ [*] zusätzliche Einstellungen
+ [*] Bald: Strom & Kabel! (Ungefähr) geplant für ende Juli 2020.
+ [*] Bald: mehr Level
+ [*] Erlaubt es mir shapez.io weiter zu entwickeln ❤️
[/list]
- [b]Geplante Funktionen[/b]
+ [b]Zukünftige Updates:[/b]
- Ich bin aktiv mit der Entwicklung beschäftigt und versuche jede Woche ein Update oder den aktuellen Stand der Entwicklung zu veröffentlichen.
+ Ich update das Spiel sehr oft, und versuche wöchentlich ein Update zu veröffentlichen!
[list]
[*] Verschiedene Karten und Herausforderungen (z.B. Karten mit Hindernissen)
- [*] Puzzle (Liefere die gewünschten Formen mit begrenztem Platz / limitierten Gebäuden)
- [*] Story-Modus mit Gebäudekosten
- [*] Einstellbare Kartengenerierung (Ändere die Grösse/Anzahl/Dichte der Ressourcenflecken, den Seed und mehr)
- [*] Mehr Formentypen
- [*] Performanceverbesserungen (Das Spiel läuft bereits ganz gut!)
+ [*] Herausforderungen (liefere die geforderte Form mit einer beschränkten Karte / anzahl an Gebäuden ab)
+ [*] Eine Kampange, in der die Gebäude einen Preis haben.
+ [*] einen konfigurierbaren Kartengenerator (bestimme Ressourcen / Größe / Dichte, den Seed und viel mehr)
+ [*] zusätzliche Formen
+ [*] Performanceverbesserungen (Das Spiel läuft bereits sehr gut!)
[*] Und vieles mehr!
[/list]
- [b] Dieses Spiel ist Open Source[/b]
+ [b]Das Spiel ist open source![/b]
- Jeder kann dazu beitragen! Ich bin aktiv in die Community involviert, versuche alle Vorschläge zu lesen und beziehe so viel Feedback wie möglich mit in die Entwicklung ein.
+ Jeder kann dazu beitragen. Ich bin aktiv in der Communtiy involviert und versuche alle Vorschläge zu lesen und beziehe so viel Feedback wie möglich mit in die Entwicklung ein.
Die komplette Roadmap gibt es auf dem Trello-Board zum Nachlesen!
[b]Links [/b]
@@ -83,6 +84,8 @@ steamPage:
[*] [url=https://discord.com/invite/HN7EVzV]Offizieller Discord[/url]
[*] [url=https://trello.com/b/ISQncpJP/shapezio]Roadmap[/url]
[*] [url=https://www.reddit.com/r/shapezio]Subreddit[/url]
+ [*] [url=https://github.com/tobspr/shapez.io]Source code (GitHub)[/url]
+ [*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Hilf zu übersetzen[/url]
[*] [url=https://github.com/tobspr/shapez.io]Quelltext (GitHub)[/url]
[*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Hilf beim Übersetzen[/url]
[/list]
@@ -105,7 +108,7 @@ global:
trillions: T
# Shown for infinitely big numbers
- infinite: unend
+ infinite: ∞
time:
# Used for formatting past time dates
@@ -141,9 +144,9 @@ demoBanners:
mainMenu:
play: Spielen
+ changelog: Änderungsprotokoll
continue: Fortsetzen
newGame: Neues Spiel
- changelog: Änderungshistorie
subreddit: Reddit
importSavegame: Importieren
openSourceHint: Dieses Spiel ist Open Source!
@@ -153,7 +156,7 @@ mainMenu:
# This is shown when using firefox and other browsers which are not supported.
browserWarning: >-
- Sorry, aber das Spiel wird in deinem Browser langsam laufen! Erwerbe die Standalone-Version oder downloade Chrome für die beste Erfahrung!
+ Sorry, aber das Spiel wird in deinem Browser langsam laufen! Kaufe die Standalone-Version oder downloade Chrome für die beste Erfahrung!
savegameLevel: Level
savegameLevelUnknown: Unbekanntes Level
@@ -167,7 +170,7 @@ dialogs:
restart: Neustart
reset: Zurücksetzen
getStandalone: Standalone Ansehen
- deleteGame: Ich weiß, was ich tue
+ deleteGame: Ich weiß, was ich tue!
viewUpdate: Update anzeigen
showUpgrades: Upgrades anzeigen
showKeybindings: Kürzel anzeigen
@@ -200,15 +203,15 @@ dialogs:
restartRequired:
title: Neustart benötigt
text: >-
- Du muss das Spiel neu starten, um die Einstellungen anzuwenden.
+ Du musst das Spiel neu starten, um die Einstellungen anzuwenden.
editKeybinding:
title: Tastenbelegung ändern
- desc: Drücke die (Maus-)Taste, die du vergeben willst, oder ESC um abzubrechen.
+ desc: Drücke die (Maus-)Taste, die du belegen möchtest, oder ESC um abzubrechen.
resetKeybindingsConfirmation:
title: Tastenbelegung zurücksetzen
- desc: Das wird alle deine Tastenbelegungen auf den Standard zurücksetzen. Bist du dir sicher?
+ desc: Dies wird alle deine Tastenbelegungen auf den Standard zurücksetzen. Bist du dir sicher?
keybindingsResetOk:
title: Tastenbelegung zurückgesetzt
@@ -225,13 +228,13 @@ dialogs:
updateSummary:
title: Neues Update!
desc: >-
- Hier sind die Änderungen, seit dem du das letzte Mal gespielt hast:
+ Hier sind die Änderungen, seitdem du das letzte Mal gespielt hast:
upgradesIntroduction:
title: Upgrades Freischalten
desc: >-
Viele deiner Formen können noch benutzt werden, um Upgrades freizuschalten - Zerstöre deine alten Fabriken nicht!
- Den Upgrade-Tab kannst du oben rechts im Bildschirm finden.
+ Den Upgrade-Tab findest du oben rechts im Bildschirm.
massDeleteConfirm:
title: Löschen bestätigen
@@ -245,13 +248,12 @@ dialogs:
massCutInsufficientConfirm:
title: Ausschneiden bestätigen
- desc: >-
- Du hast aktuell nicht genug Blaupausenformen, um die Auswahl einzufügen! Möchtest du sie trotzdem ausschneiden?
+ desc: Du kannst dir das Einfügen nicht leisten! Bist du sicher, dass du trotzdem Ausschneiden möchtest?
blueprintsNotUnlocked:
title: Noch nicht freigeschaltet
desc: >-
- Blueprints werden erst in Level 12 freigeschaltet!
+ Blaupausen werden erst in Level 12 freigeschaltet!
keybindingsIntroduction:
title: Nützliche Hotkeys
@@ -264,15 +266,16 @@ dialogs:
createMarker:
title: Neuer Marker
- titleEdit: Edit Marker
desc: Gib ihm einen griffigen Namen. Du kannst sogar die Abkürzung einer Form eingeben (Diese kann hier generiert werden).
+ titleEdit: Marker bearbeiten
markerDemoLimit:
- desc: Du kannst nur 2 benutzerdefinierte Marker in der Demo benutzen. Hol dir die Standalone, um unendlich viele Marker zu erstellen!
+ desc: Du kannst nur 2 Marker in der Demo benutzen. Hol dir die Standalone, um unendlich viele Marker zu erstellen!
exportScreenshotWarning:
title: Bildschirmfoto exportieren
- desc: Hier kannst du ein Bildschirmfoto von deiner ganzen Fabrik erstellen. Für extrem große Fabriken kann das jedoch sehr lange dauern und ggf. zum Spielabsturz führen!
+ desc: >-
+ Hier kannst du ein Bildschirmfoto von deiner ganzen Fabrik erstellen. Für extrem große Fabriken kann das jedoch sehr lange dauern und ggf. zum Spielabsturz führen!
ingame:
# This is shown in the top left corner and displays useful keybindings in
@@ -363,7 +366,7 @@ ingame:
dataSources:
stored:
title: Gelagert
- description: Zeigt die Menge an Formen, die im zentralen Gebäude gelagert sind.
+ description: Zeigt die Menge an Formen, die im Hub gelagert sind.
produced:
title: Produziert
description: Zeigt die Menge an Formen, die deine gesamte Fabrik produziert (inkl. Zwischenprodukte).
@@ -400,7 +403,7 @@ ingame:
# Map markers
waypoints:
waypoints: Markierungen
- hub: HUB
+ hub: Hub
description: Linksklick auf einen Marker, um dort hinzugelangen, Rechtsklick, um ihn zu löschen. Drücke um einen Marker aus deinem Blickwinkel, oder rechtsklicke , um einen Marker auf der ausgewählten Position zu erschaffen.
creationSuccessNotification: Marker wurde erstellt.
@@ -416,7 +419,7 @@ ingame:
hints:
1_1_extractor: Platziere einen Extrahierer auf der Kreisform um sie zu extrahieren!
1_2_conveyor: >-
- Verbinde den Extrahierer mit einem Förderband und schließe ihn am zentralen Gebäude an! Tipp: Drück und ziehe das Förderband mit der Maus!
+ Verbinde den Extrahierer mit einem Förderband und schließe ihn am Hub an! Tipp: Drück und ziehe das Förderband mit der Maus!
1_3_expand: >-
Dies ist KEIN Idle-Game! Baue mehr Extrahierer und Förderbänder, um das Ziel schneller zu erreichen. Tipp: Halte UMSCH , um mehrere Gebäude zu platzieren und nutze R um sie zu rotieren.
@@ -493,14 +496,14 @@ buildings:
rotater:
default:
- name: &rotater Rotierer
+ name: &rotater Rotierer (-90°)
description: Rotiert Formen im Uhrzeigersinn um 90 Grad.
ccw:
- name: Rotierer (CCW)
+ name: Rotierer (+90°)
description: Rotiert Formen gegen den Uhrzeigersinn um 90 Grad.
fl:
- name: Rotierer (180)
- description: Rotiert Formen um 180 Grad.
+ name: Rotierer (+180°)
+ description: Rotiert die Formen um 180 Grad.
stacker:
default:
@@ -540,25 +543,25 @@ buildings:
wire:
default:
- name: Energiekabel
- description: Transportiert Energie.
+ name: Stromkabel
+ description: Erlaubt dir Strom zu transportieren.
advanced_processor:
default:
name: Farbinvertierer
- description: Akzeptiert Farben und Formen und invertiert sie.
+ description: Invertiert die Farbe. Geht auch bei Formen.
energy_generator:
deliver: Liefere
toGenerateEnergy: für
default:
- name: Energiegenerator
- description: Generiert Energie, indem die Formen verbraucht werden.
+ name: Stromgenerator
+ description: Erzeugt Strom, indem er Formen verbraucht.
wire_crossings:
default:
name: Kabelverteiler
- description: Teilt ein Energiekabel in zwei Ausgänge.
+ description: Teilt ein Stromkabel in zwei auf.
merger:
name: Kabelverbinder
- description: Verbindet zwei Energiekabel zu einem.
+ description: Verbindet zwei Stromkabel zu einem.
storyRewards:
# Those are the rewards gained from completing the store
@@ -573,7 +576,7 @@ storyRewards:
reward_painter:
title: Färben
desc: >-
- The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them! PS: If you are colorblind, there is a color blind mode in the settings!
+ Der Färber wurde freigeschaltet. Extrahiere ein paar Farben (genauso wie bei Formen) und färbe damit eine Form im Färber! PS: Falls du Farbenblind bist, es gibt einen Modus für Farbenblinde in den Einstellungen!
reward_mixer:
title: Farben mischen
@@ -626,7 +629,7 @@ storyRewards:
reward_freeplay:
title: Freies Spiel
- desc: Du hast es geschafft! Du hast den Freispielmodus freigeschaltet! Das heißt, dass abzuliefernde Formen jetzt zufällig generiert werden! (Keine Sorge, für die Standaloneversion ist noch mehr geplant!)
+ desc: Du hast es geschafft! Du bist im freien Spiel angekommen! Das heißt, dass abzuliefernde Formen jetzt zufällig generiert werden! (Keine Sorge, für die Standaloneversion ist noch mehr geplant!)
reward_blueprints:
title: Blaupause
@@ -636,7 +639,7 @@ storyRewards:
no_reward:
title: Nächstes Level
desc: >-
- Dieses Level hat dir keine Belohnung gegeben, aber das nächste schon! PS: Denk daran, deine alten Fabriken nicht zu zerstören - Du wirst sie später alle noch brauchen, um Upgrades freizuschalten !
+ Dieses Level hat dir keine Belohnung gegeben, aber dafür das Nächste schon! PS: Denk daran, deine alten Fabriken nicht zu zerstören - Du wirst sie später alle noch brauchen, um Upgrades freizuschalten !
no_reward_freeplay:
title: Nächstes Level
@@ -684,7 +687,7 @@ settings:
scrollWheelSensitivity:
title: Zoomempfindlichkeit
description: >-
- Ändert die Sensitivität des Zooms (Sowohl Mausrad, als auch Trackpad).
+ Ändert die Empfindlichkeit des Zooms (Sowohl Mausrad, als auch Trackpad).
sensitivity:
super_slow: Sehr langsam
slow: Langsam
@@ -722,30 +725,30 @@ settings:
soundsMuted:
title: Geräusche stummschalten
description: >-
- Bei Aktivierung werden alle Geräusche stummgeschaltet.
+ Bei der Aktivierung werden alle Geräusche stummgeschaltet.
musicMuted:
title: Musik stummschalten
description: >-
- Bei Aktivierung wird die Musik stummgeschaltet.
+ Bei der Aktivierung wird die Musik stummgeschaltet.
theme:
title: Farbmodus
description: >-
- Wähle zwischen dunklem und hellem Farbmodus.
+ Wähle zwischen dem dunklen und dem hellen Farbmodus.
themes:
dark: Dunkel
light: Hell
refreshRate:
- title: Zielbildwiederholrate
+ title: Tickrate
description: >-
- Für z.B. einen 144-Hz-Monitor kann die Bildwiederholrate hier korrekt eingestellt werden. Bei einem zu langsamen Computer kann dies die Leistung beeinträchtigen.
+ Das Spiel passt die Tickrate automatisch so an, dass sie immer zwischen diesem Wert und der hälfte bleibt. Zum Beispiel bei einer Tickrate von 60 Hz versucht das Spiel, diese zu halten. Falls dies zu viel ist, regelt der Computer diese runter bis zu einer Untergrenze von 30Hz.
alwaysMultiplace:
title: Mehrfachplatzierung
description: >-
- Bei Aktivierung wird das platzierte Gebäude nicht abgewählt. Das hat den gleichen Effekt wie beim Platzieren permanent UMSCH gedrückt zu halten.
+ Bei Aktivierung wird das platzierte Gebäude nicht abgewählt. Das hat den gleichen Effekt wie beim Platzieren UMSCH gedrückt zu halten.
offerHints:
title: Hinweise & Tutorials
@@ -762,11 +765,6 @@ settings:
description: >-
Aktiviert den Vignetteneffekt, der den Rand des Bildschirms zunehmend verdunkelt und das Lesen der Textfelder vereinfacht.
- rotationByBuilding:
- title: Rotation pro Gebäudetyp
- description: >-
- Jeder Gebäudetyp merkt sich einzeln, welche Rotation ausgewählt ist. Das fühlt sich möglicherweise besser an, wenn du häufig zwischen verschiedenen Gebäudetypen wechselst.
-
compactBuildingInfo:
title: Kompakte Gebäudeinformationen
description: >-
@@ -777,6 +775,13 @@ settings:
description: >-
Deaktiviert die Warnung, die beim Löschen und Ausschneiden von mehr als 100 Feldern angezeigt wird.
+ rotationByBuilding:
+ title: Rotation pro Gebäudetyp
+ description: >-
+ Jeder Gebäudetyp merkt sich einzeln, in welche Richtung er zeigt.
+ Das fühlt sich möglicherweise besser an, wenn du häufig zwischen verschiedenen
+ Gebäudetypen wechselst.
+
keybindings:
title: Tastenbelegung
hint: >-
@@ -785,7 +790,7 @@ keybindings:
resetKeybindings: Tastenbelegung zurücksetzen.
categoryLabels:
- general: Applikation
+ general: Anwendung
ingame: Spiel
navigation: Navigation
placement: Platzierung
@@ -803,8 +808,8 @@ keybindings:
mapMoveFaster: Schneller bewegen
centerMap: Karte zentrieren
- mapZoomIn: Hineinzoomen
- mapZoomOut: Herauszoomen
+ mapZoomIn: Reinzoomen
+ mapZoomOut: Rauszoomen
createMarker: Markierung erstellen
menuOpenShop: Upgrades
@@ -832,7 +837,6 @@ keybindings:
Modifikator: stattdessen gegen den UZS rotieren
cycleBuildingVariants: Variante wählen
confirmMassDelete: Massenlöschung bestätigen
- pasteLastBlueprint: Letzte Blaupause einfügen
cycleBuildings: Gebäude rotieren
lockBeltDirection: Bandplaner aktivieren
switchDirectionLockSide: >-
@@ -846,8 +850,10 @@ keybindings:
placementDisableAutoOrientation: Automatische Orientierung deaktivieren
placeMultiple: Im Platziermodus bleiben
placeInverse: Automatische Förderbandorientierung invertieren
- advanced_processor: Farbnivertierer
- energy_generator: Energiegenerator
+ pasteLastBlueprint: Letzte Blaupause einfügen
+ advanced_processor: Farbinvertierer
+ energy_generator: Stromgenerator
+ wire: Stromkabel
about:
title: Über dieses Spiel
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index edc304b9..f9695ba5 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -1,932 +1,971 @@
-#
-# GAME TRANSLATIONS
-#
-# Contributing:
-#
-# If you want to contribute, please make a pull request on this respository
-# and I will have a look.
-#
-# Placeholders:
-#
-# Do *not* replace placeholders! Placeholders have a special syntax like
-# `Hotkey: `. They are encapsulated within angle brackets. The correct
-# translation for this one in German for example would be: `Taste: ` (notice
-# how the placeholder stayed '' and was not replaced!)
-#
-# Adding a new language:
-#
-# If you want to add a new language, ask me in the Discord and I will setup
-# the basic structure so the game also detects it.
-#
-
----
-steamPage:
- # This is the short text appearing on the steam page
- shortText: shapez.io is a game about building factories to automate the creation and processing of increasingly complex shapes across an infinitely expanding map.
-
- # This is the text shown above the Discord link
- discordLink: Official Discord - Chat with me!
-
- # This is the long description for the steam page - It is contained here so you can help to translate it, and I will regulary update the store page.
- # NOTICE:
- # - Do not translate the first line (This is the gif image at the start of the store)
- # - Please keep the markup (Stuff like [b], [list] etc) in the same format
- longText: >-
- [img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img]
-
- shapez.io is a game about building factories to automate the creation and processing of increasingly complex shapes across an infinitely expanding map.
-
- Upon delivering the requested shapes you'll progress within the game and unlock upgrades to speed up your factory.
-
- As the demand for shapes increases, you'll have to scale up your factory to meet the demand - Don't forget about resources though, you'll have to expand across the [b]infinite map[/b]!
-
- Soon you'll have to mix colors and paint your shapes with them - Combine red, green and blue color resources to produce different colors and paint shapes with them to satisfy the demand.
-
- This game features 18 progressive levels (Which should already keep you busy for hours!) but I'm constantly adding new content - There's a lot planned!
-
- Purchasing the game gives you access to the standalone version which has additional features, and you'll also receive access to newly developed features.
-
- [b]Standalone Advantages[/b]
-
- [list]
- [*] Dark Mode
- [*] Unlimited Waypoints
- [*] Unlimited Savegames
- [*] Additional settings
- [*] Coming soon: Wires & Energy! Aiming for (roughly) end of July 2020.
- [*] Coming soon: More Levels
- [*] Allows me to further develop shapez.io ❤️
- [/list]
-
- [b]Future Updates[/b]
-
- I am updating the game often and trying to push an update at least once every week!
-
- [list]
- [*] Different maps and challenges (e.g. maps with obstacles)
- [*] Puzzles (Deliver the requested shape with a restricted area / set of buildings)
- [*] A story mode where buildings have a cost
- [*] Configurable map generator (Configure resource/shape size/density, seed and more)
- [*] Additional types of shapes
- [*] Performance improvements (The game already runs pretty well!)
- [*] And much more!
- [/list]
-
- [b]This game is open source![/b]
-
- Anybody can contribute, I'm actively involved in the community and attempt to review all suggestions and take feedback into consideration where possible.
- Be sure to check out my trello board for the full roadmap!
-
- [b]Links[/b]
-
- [list]
- [*] [url=https://discord.com/invite/HN7EVzV]Official Discord[/url]
- [*] [url=https://trello.com/b/ISQncpJP/shapezio]Roadmap[/url]
- [*] [url=https://www.reddit.com/r/shapezio]Subreddit[/url]
- [*] [url=https://github.com/tobspr/shapez.io]Source code (GitHub)[/url]
- [*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Help translate[/url]
- [/list]
-
-global:
- loading: Loading
- error: Error
-
- # How big numbers are rendered, e.g. "10,000"
- thousandsDivider: ","
-
- # What symbol to use to seperate the integer part from the fractional part of a number, e.g. "0.4"
- decimalSeparator: "."
-
- # The suffix for large numbers, e.g. 1.3k, 400.2M, etc.
- suffix:
- thousands: k
- millions: M
- billions: B
- trillions: T
-
- # Shown for infinitely big numbers
- infinite: inf
-
- time:
- # Used for formatting past time dates
- oneSecondAgo: one second ago
- xSecondsAgo: seconds ago
- oneMinuteAgo: one minute ago
- xMinutesAgo: minutes ago
- oneHourAgo: one hour ago
- xHoursAgo: hours ago
- oneDayAgo: one day ago
- xDaysAgo: days ago
-
- # Short formats for times, e.g. '5h 23m'
- secondsShort: s
- minutesAndSecondsShort: m s
- hoursAndMinutesShort: h m
-
- xMinutes: minutes
-
- keys:
- tab: TAB
- control: CTRL
- alt: ALT
- escape: ESC
- shift: SHIFT
- space: SPACE
-
-demoBanners:
- # This is the "advertisement" shown in the main menu and other various places
- title: Demo Version
- intro: >-
- Get the standalone to unlock all features!
-
-mainMenu:
- play: Play
- continue: Continue
- newGame: New Game
- changelog: Changelog
- subreddit: Reddit
- importSavegame: Import
- openSourceHint: This game is open source!
- discordLink: Official Discord Server
- helpTranslate: Help translate!
- madeBy: Made by
-
- # This is shown when using firefox and other browsers which are not supported.
- browserWarning: >-
- Sorry, but the game is known to run slow on your browser! Get the standalone version or download chrome for the full experience.
-
- savegameLevel: Level
- savegameLevelUnknown: Unknown Level
-
-dialogs:
- buttons:
- ok: OK
- delete: Delete
- cancel: Cancel
- later: Later
- restart: Restart
- reset: Reset
- getStandalone: Get Standalone
- deleteGame: I know what I am doing
- viewUpdate: View Update
- showUpgrades: Show Upgrades
- showKeybindings: Show Keybindings
-
- importSavegameError:
- title: Import Error
- text: >-
- Failed to import your savegame:
-
- importSavegameSuccess:
- title: Savegame Imported
- text: >-
- Your savegame has been successfully imported.
-
- gameLoadFailure:
- title: Game is broken
- text: >-
- Failed to load your savegame:
-
- confirmSavegameDelete:
- title: Confirm deletion
- text: >-
- Are you sure you want to delete the game?
-
- savegameDeletionError:
- title: Failed to delete
- text: >-
- Failed to delete the savegame:
-
- restartRequired:
- title: Restart required
- text: >-
- You need to restart the game to apply the settings.
-
- editKeybinding:
- title: Change Keybinding
- desc: Press the key or mouse button you want to assign, or escape to cancel.
-
- resetKeybindingsConfirmation:
- title: Reset keybindings
- desc: This will reset all keybindings to their default values. Please confirm.
-
- keybindingsResetOk:
- title: Keybindings reset
- desc: The keybindings have been reset to their respective defaults!
-
- featureRestriction:
- title: Demo Version
- desc: You tried to access a feature () which is not available in the demo. Consider getting the standalone version for the full experience!
-
- oneSavegameLimit:
- title: Limited savegames
- desc: You can only have one savegame at a time in the demo version. Please remove the existing one or get the standalone version!
-
- updateSummary:
- title: New update!
- desc: >-
- Here are the changes since you last played:
-
- upgradesIntroduction:
- title: Unlock Upgrades
- desc: >-
- All shapes you produce can be used to unlock upgrades - Don't destroy your old factories!
- The upgrades tab can be found on the top right corner of the screen.
-
- massDeleteConfirm:
- title: Confirm delete
- desc: >-
- You are deleting a lot of buildings ( to be exact)! Are you sure you want to do this?
-
- massCutConfirm:
- title: Confirm cut
- desc: >-
- You are cutting a lot of buildings ( to be exact)! Are you sure you want to do this?
-
- massCutInsufficientConfirm:
- title: Confirm cut
- desc: >-
- You can not afford to paste this area! Are you sure you want to cut it?
-
- blueprintsNotUnlocked:
- title: Not unlocked yet
- desc: >-
- Complete level 12 to unlock Blueprints!
-
- keybindingsIntroduction:
- title: Useful keybindings
- desc: >-
- This game has a lot of keybindings which make it easier to build big factories.
- Here are a few, but be sure to check out the keybindings !
- CTRL
+ Drag: Select an area.
- SHIFT
: Hold to place multiple of one building.
- ALT
: Invert orientation of placed belts.
-
- createMarker:
- title: New Marker
- titleEdit: Edit Marker
- desc: Give it a meaningful name, you can also include a short key of a shape (Which you can generate here )
-
- markerDemoLimit:
- desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers!
-
- exportScreenshotWarning:
- title: Export screenshot
- desc: You requested to export your base as a screenshot. Please note that this can be quite slow for a big base and even crash your game!
-
-ingame:
- # This is shown in the top left corner and displays useful keybindings in
- # every situation
- keybindingsOverlay:
- moveMap: Move
- selectBuildings: Select area
- stopPlacement: Stop placement
- rotateBuilding: Rotate building
- placeMultiple: Place multiple
- reverseOrientation: Reverse orientation
- disableAutoOrientation: Disable auto-orientation
- toggleHud: Toggle HUD
- placeBuilding: Place building
- createMarker: Create marker
- delete: Delete
- pasteLastBlueprint: Paste last blueprint
- lockBeltDirection: Enable belt planner
- plannerSwitchSide: Flip planner side
- cutSelection: Cut
- copySelection: Copy
- clearSelection: Clear selection
- pipette: Pipette
- switchLayers: Switch layers
-
- # Names of the colors, used for the color blind mode
- colors:
- red: Red
- green: Green
- blue: Blue
- yellow: Yellow
- purple: Purple
- cyan: Cyan
- white: White
- black: Black
- uncolored: Gray
-
- # Everything related to placing buildings (I.e. as soon as you selected a building
- # from the toolbar)
- buildingPlacement:
- # Buildings can have different variants which are unlocked at later levels,
- # and this is the hint shown when there are multiple variants available.
- cycleBuildingVariants: Press to cycle variants.
-
- # Shows the hotkey in the ui, e.g. "Hotkey: Q"
- hotkeyLabel: >-
- Hotkey:
-
- infoTexts:
- speed: Speed
- range: Range
- storage: Storage
- oneItemPerSecond: 1 item / second
- itemsPerSecond: items / s
- itemsPerSecondDouble: (x2)
-
- tiles: tiles
-
- # The notification when completing a level
- levelCompleteNotification:
- # is replaced by the actual level, so this gets 'Level 03' for example.
- levelTitle: Level
- completed: Completed
- unlockText: Unlocked !
- buttonNextLevel: Next Level
-
- # Notifications on the lower right
- notifications:
- newUpgrade: A new upgrade is available!
- gameSaved: Your game has been saved.
-
- # The "Upgrades" window
- shop:
- title: Upgrades
- buttonUnlock: Upgrade
-
- # Gets replaced to e.g. "Tier IX"
- tier: Tier
-
- # The roman number for each tier
- tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X]
-
- maximumLevel: MAXIMUM LEVEL (Speed x)
-
- # The "Statistics" window
- statistics:
- title: Statistics
- dataSources:
- stored:
- title: Stored
- description: Displaying amount of stored shapes in your central building.
- produced:
- title: Produced
- description: Displaying all shapes your whole factory produces, including intermediate products.
- delivered:
- title: Delivered
- description: Displaying shapes which are delivered to your central building.
- noShapesProduced: No shapes have been produced so far.
-
- # Displays the shapes per minute, e.g. '523 / m'
- shapesPerMinute: / m
-
- # Settings menu, when you press "ESC"
- settingsMenu:
- playtime: Playtime
-
- buildingsPlaced: Buildings
- beltsPlaced: Belts
-
- buttons:
- continue: Continue
- settings: Settings
- menu: Return to menu
-
- # Bottom left tutorial hints
- tutorialHints:
- title: Need help?
- showHint: Show hint
- hideHint: Close
-
- # When placing a blueprint
- blueprintPlacer:
- cost: Cost
-
- # Map markers
- waypoints:
- waypoints: Markers
- hub: HUB
- description: Left-click a marker to jump to it, right-click to delete it. Press to create a marker from the current view, or right-click to create a marker at the selected location.
- creationSuccessNotification: Marker has been created.
-
- # Shape viewer
- shapeViewer:
- title: Layers
- empty: Empty
- copyKey: Copy Key
-
- # Interactive tutorial
- interactiveTutorial:
- title: Tutorial
- hints:
- 1_1_extractor: Place an extractor on top of a circle shape to extract it!
- 1_2_conveyor: >-
- Connect the extractor with a conveyor belt to your hub! Tip: Click and drag the belt with your mouse!
-
- 1_3_expand: >-
- This is NOT an idle game! Build more extractors and belts to finish the goal quicker. Tip: Hold SHIFT to place multiple extractors, and use R to rotate them.
-
-# All shop upgrades
-shopUpgrades:
- belt:
- name: Belts, Distributor & Tunnels
- description: Speed x → x
- miner:
- name: Extraction
- description: Speed x → x
- processors:
- name: Cutting, Rotating & Stacking
- description: Speed x → x
- painting:
- name: Mixing & Painting
- description: Speed x → x
-
-# Buildings and their name / description
-buildings:
- hub:
- deliver: Deliver
- toUnlock: to unlock
- levelShortcut: LVL
-
- belt:
- default:
- name: &belt Conveyor Belt
- description: Transports items, hold and drag to place multiple.
-
- # Internal name for the Extractor
- miner:
- default:
- name: &miner Extractor
- description: Place over a shape or color to extract it.
-
- chainable:
- name: Extractor (Chain)
- description: Place over a shape or color to extract it. Can be chained.
-
- # Internal name for the Tunnel
- underground_belt:
- default:
- name: &underground_belt Tunnel
- description: Allows you to tunnel resources under buildings and belts.
-
- tier2:
- name: Tunnel Tier II
- description: Allows you to tunnel resources under buildings and belts.
-
- # Internal name for the Balancer
- splitter:
- default:
- name: &splitter Balancer
- description: Multifunctional - Evenly distributes all inputs onto all outputs.
-
- compact:
- name: Merger (compact)
- description: Merges two conveyor belts into one.
-
- compact-inverse:
- name: Merger (compact)
- description: Merges two conveyor belts into one.
-
- cutter:
- default:
- name: &cutter Cutter
- description: Cuts shapes from top to bottom and outputs both halves. If you use only one part, be sure to destroy the other part or it will stall!
- quad:
- name: Cutter (Quad)
- description: Cuts shapes into four parts. If you use only one part, be sure to destroy the other parts or it will stall!
-
- rotater:
- default:
- name: &rotater Rotate
- description: Rotates shapes clockwise by 90 degrees.
- ccw:
- name: Rotate (CCW)
- description: Rotates shapes counter-clockwise by 90 degrees.
- fl:
- name: Rotate (180)
- description: Rotates shapes by 180 degrees.
-
- stacker:
- default:
- name: &stacker Stacker
- description: Stacks both items. If they can not be merged, the right item is placed above the left item.
-
- mixer:
- default:
- name: &mixer Color Mixer
- description: Mixes two colors using additive blending.
-
- painter:
- default:
- name: &painter Painter
- description: &painter_desc Colors the whole shape on the left input with the color from the top input.
-
- mirrored:
- name: *painter
- description: *painter_desc
-
- double:
- name: Painter (Double)
- description: Colors the shapes on the left inputs with the color from the top input.
-
- quad:
- name: Painter (Quad)
- description: Allows you to color each quadrant of the shape with a different color.
-
- trash:
- default:
- name: &trash Trash
- description: Accepts inputs from all sides and destroys them. Forever.
-
- storage:
- name: Storage
- description: Stores excess items, up to a given capacity. Can be used as an overflow gate.
-
- wire:
- default:
- name: &wire Wire
- description: &wire_desc Allows to connect logical components and can transfer items, colors or boolean signals.
-
- wire_tunnel:
- default:
- name: &wire_tunnel Wire Tunnel
- description: Allows to cross two wires without connecting them.
-
- coating:
- name: Wire Insulation
- description: Allows to pass through signals without connecting to other wires on the sides.
-
- constant_signal:
- default:
- name: &constant_signal Constant Signal
- description: Emits a constant signal (shape, color or boolean).
-
- lever:
- default:
- name: &lever Button
- description: Can be toggled to emit 1 / 0
-
- logic_gate:
- default:
- name: &logic_gate AND Gate
- description: Emits a truthy boolean signal if both inputs are truthy.
- not:
- name: NOT
- description: Inverts the given signal.
- xor:
- name: XOR
- description: Emits a truthy signal if one of the inputs is truthy, but not both.
- or:
- name: OR
- description: Emits a truthy signal if one of the inputs is truthy.
-
- transistor:
- name: Gate
- description: Only forwards the bottom input if the left input is true.
-
- filter:
- default:
- name: &filter Filter
- # TEMP
- description: Only leaves through items who match exactly the provided shape / color. If you put in a boolean 1, it leaves everything through, if you put in a 0 it will leave nothing through.
-
- display:
- default:
- name: &display Display
- # TEMP
- description: Can be connected on the wires layer to show a color or shape. When inputting a boolean item, the display will be white if the value is 1.
-
-storyRewards:
- # Those are the rewards gained from completing the store
- reward_cutter_and_trash:
- title: Cutting Shapes
- desc: You just unlocked the cutter - it cuts shapes half from top to bottom regardless of its orientation! Be sure to get rid of the waste, or otherwise it will stall - For this purpose I gave you a trash, which destroys everything you put into it!
-
- reward_rotater:
- title: Rotating
- desc: The rotater has been unlocked! It rotates shapes clockwise by 90 degrees.
-
- reward_painter:
- title: Painting
- desc: >-
- The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them! PS: If you are colorblind, there is a colorblind mode in the settings!
-
- reward_mixer:
- title: Color Mixing
- desc: The mixer has been unlocked - Combine two colors using additive blending with this building!
-
- reward_stacker:
- title: Combiner
- desc: You can now combine shapes with the combiner ! Both inputs are combined, and if they can be put next to each other, they will be fused . If not, the right input is stacked on top of the left input!
-
- reward_splitter:
- title: Splitter/Merger
- desc: The multifunctional balancer has been unlocked - It can be used to build bigger factories by splitting and merging items onto multiple belts!
-
- reward_tunnel:
- title: Tunnel
- desc: The tunnel has been unlocked - You can now tunnel items through belts and buildings with it!
-
- reward_rotater_ccw:
- title: CCW Rotating
- desc: You have unlocked a variant of the rotater - It allows you to rotate shapes counter-clockwise! To build it, select the rotater and press 'T' to cycle through its variants !
-
- reward_miner_chainable:
- title: Chaining Extractor
- desc: You have unlocked the chaining extractor ! It can forward its resources to other extractors so you can more efficiently extract resources!
-
- reward_underground_belt_tier_2:
- title: Tunnel Tier II
- desc: You have unlocked a new variant of the tunnel - It has a bigger range , and you can also mix-n-match those tunnels now!
-
- reward_splitter_compact:
- title: Compact Balancer
- desc: >-
- You have unlocked a compact variant of the balancer - It accepts two inputs and merges them into one belt!
-
- reward_cutter_quad:
- title: Quad Cutting
- desc: You have unlocked a variant of the cutter - It allows you to cut shapes in four parts instead of just two!
-
- reward_painter_double:
- title: Double Painting
- desc: You have unlocked a variant of the painter - It works as the regular painter but processes two shapes at once consuming just one color instead of two!
-
- reward_painter_quad:
- title: Quad Painting
- desc: You have unlocked a variant of the painter - It allows you to paint each part of the shape individually!
-
- reward_storage:
- title: Storage Buffer
- desc: You have unlocked a variant of the trash - It allows you to store items up to a given capacity!
-
- reward_freeplay:
- title: Freeplay
- desc: You did it! You unlocked the free-play mode ! This means that shapes are now randomly generated! (No worries, more content is planned for the standalone!)
-
- reward_blueprints:
- title: Blueprints
- desc: You can now copy and paste parts of your factory! Select an area (Hold CTRL, then drag with your mouse), and press 'C' to copy it. Pasting it is not free , you need to produce blueprint shapes to afford it! (Those you just delivered).
-
- # Special reward, which is shown when there is no reward actually
- no_reward:
- title: Next level
- desc: >-
- This level gave you no reward, but the next one will! PS: Better not destroy your existing factory - You'll need all those shapes later to unlock upgrades !
-
- no_reward_freeplay:
- title: Next level
- desc: >-
- Congratulations! By the way, more content is planned for the standalone!
-
-settings:
- title: Settings
- categories:
- general: General
- userInterface: User Interface
- advanced: Advanced
- performance: Performance
-
- versionBadges:
- dev: Development
- staging: Staging
- prod: Production
- buildDate: Built
-
- labels:
- uiScale:
- title: Interface scale
- description: >-
- Changes the size of the user interface. The interface will still scale based on your device's resolution, but this setting controls the amount of scaling.
- scales:
- super_small: Super small
- small: Small
- regular: Regular
- large: Large
- huge: Huge
-
- autosaveInterval:
- title: Autosave Interval
- description: >-
- Controls how often the game saves automatically. You can also disable it entirely here.
-
- intervals:
- one_minute: 1 Minute
- two_minutes: 2 Minutes
- five_minutes: 5 Minutes
- ten_minutes: 10 Minutes
- twenty_minutes: 20 Minutes
- disabled: Disabled
-
- scrollWheelSensitivity:
- title: Zoom sensitivity
- description: >-
- Changes how sensitive the zoom is (Either mouse wheel or trackpad).
- sensitivity:
- super_slow: Super slow
- slow: Slow
- regular: Regular
- fast: Fast
- super_fast: Super fast
-
- movementSpeed:
- title: Movement speed
- description: >-
- Changes how fast the view moves when using the keyboard.
- speeds:
- super_slow: Super slow
- slow: Slow
- regular: Regular
- fast: Fast
- super_fast: Super Fast
- extremely_fast: Extremely Fast
-
- language:
- title: Language
- description: >-
- Change the language. All translations are user-contributed and might be incomplete!
-
- enableColorBlindHelper:
- title: Color Blind Mode
- description: >-
- Enables various tools which allow you to play the game if you are color blind.
-
- fullscreen:
- title: Fullscreen
- description: >-
- It is recommended to play the game in fullscreen to get the best experience. Only available in the standalone.
-
- soundsMuted:
- title: Mute Sounds
- description: >-
- If enabled, mutes all sound effects.
-
- musicMuted:
- title: Mute Music
- description: >-
- If enabled, mutes all music.
-
- theme:
- title: Game theme
- description: >-
- Choose the game theme (light / dark).
- themes:
- dark: Dark
- light: Light
-
- refreshRate:
- title: Tick Rate
- description: >-
- The game will automatically adjust the tickrate to be between this target tickrate and half of it. For example, with a tickrate of 60hz, the game will try to stay at 60hz, and if your computer can't handle it it will go down until it eventually reaches 30hz.
-
- alwaysMultiplace:
- title: Multiplace
- description: >-
- If enabled, all buildings will stay selected after placement until you cancel it. This is equivalent to holding SHIFT permanently.
-
- offerHints:
- title: Hints & Tutorials
- description: >-
- Whether to offer hints and tutorials while playing. Also hides certain UI elements up to a given level to make it easier to get into the game.
-
- enableTunnelSmartplace:
- title: Smart Tunnels
- description: >-
- When enabled, placing tunnels will automatically remove unnecessary belts. This also enables you to drag tunnels and excess tunnels will get removed.
-
- vignette:
- title: Vignette
- description: >-
- Enables the vignette, which darkens the screen corners and makes text easier to read.
-
- rotationByBuilding:
- title: Rotation by building type
- description: >-
- Each building type remembers the rotation you last set it to individually. This may be more comfortable if you frequently switch between placing different building types.
-
- compactBuildingInfo:
- title: Compact Building Infos
- description: >-
- Shortens info boxes for buildings by only showing their ratios. Otherwise a description and image is shown.
-
- disableCutDeleteWarnings:
- title: Disable Cut/Delete Warnings
- description: >-
- Disables the warning dialogs brought up when cutting/deleting more than 100 entities.
-
- lowQualityMapResources:
- title: Low Quality Map Resources
- description: >-
- Simplifies the rendering of resources on the map when zoomed in to improve performance.
- It even looks cleaner, so be sure to try it out!
-
- disableTileGrid:
- title: Disable Grid
- description: >-
- Disabling the tile grid can help with the performance. This also makes the game look cleaner!
-
- lowQualityTextures:
- title: Low quality textures (Ugly)
- description: >-
- Uses low quality textures to save performance. This will make the game look very ugly!
-
-keybindings:
- title: Keybindings
- hint: >-
- Tip: Be sure to make use of CTRL, SHIFT and ALT! They enable different placement options.
-
- resetKeybindings: Reset Keybindings
-
- categoryLabels:
- general: Application
- ingame: Game
- navigation: Navigating
- placement: Placement
- massSelect: Mass Select
- buildings: Building Shortcuts
- placementModifiers: Placement Modifiers
-
- mappings:
- confirm: Confirm
- back: Back
- mapMoveUp: Move Up
- mapMoveRight: Move Right
- mapMoveDown: Move Down
- mapMoveLeft: Move Left
- mapMoveFaster: Move Faster
- centerMap: Center Map
-
- mapZoomIn: Zoom in
- mapZoomOut: Zoom out
- createMarker: Create Marker
-
- menuOpenShop: Upgrades
- menuOpenStats: Statistics
- menuClose: Close Menu
-
- toggleHud: Toggle HUD
- toggleFPSInfo: Toggle FPS and Debug Info
- switchLayers: Switch layers
- exportScreenshot: Export whole Base as Image
-
- # --- Do not translate the values in this section
- belt: *belt
- splitter: *splitter
- underground_belt: *underground_belt
- miner: *miner
- cutter: *cutter
- rotater: *rotater
- stacker: *stacker
- mixer: *mixer
- painter: *painter
- trash: *trash
- wire: *wire
- constant_signal: *constant_signal
- logic_gate: *logic_gate
- lever: *lever
- filter: *filter
- wire_tunnel: *wire_tunnel
- display: *display
- # ---
-
- pipette: Pipette
- rotateWhilePlacing: Rotate
- rotateInverseModifier: >-
- Modifier: Rotate CCW instead
- cycleBuildingVariants: Cycle Variants
- confirmMassDelete: Delete area
- pasteLastBlueprint: Paste last blueprint
- cycleBuildings: Cycle Buildings
- lockBeltDirection: Enable belt planner
- switchDirectionLockSide: >-
- Planner: Switch side
-
- massSelectStart: Hold and drag to start
- massSelectSelectMultiple: Select multiple areas
- massSelectCopy: Copy area
- massSelectCut: Cut area
-
- placementDisableAutoOrientation: Disable automatic orientation
- placeMultiple: Stay in placement mode
- placeInverse: Invert automatic belt orientation
-
-about:
- title: About this Game
- body: >-
- This game is open source and developed by Tobias Springer (this is me).
-
- If you want to contribute, check out shapez.io on GitHub .
-
- This game wouldn't have been possible without the great Discord community around my games - You should really join the Discord server !
-
- The soundtrack was made by Peppsen - He's awesome.
-
- Finally, huge thanks to my best friend Niklas - Without our Factorio sessions, this game would never have existed.
-
-changelog:
- title: Changelog
-
-demo:
- features:
- restoringGames: Restoring savegames
- importingGames: Importing savegames
- oneGameLimit: Limited to one savegame
- customizeKeybindings: Customizing Keybindings
- exportingBase: Exporting whole Base as Image
-
- settingNotAvailable: Not available in the demo.
+#
+# GAME TRANSLATIONS
+#
+# Contributing:
+#
+# If you want to contribute, please make a pull request on this respository
+# and I will have a look.
+#
+# Placeholders:
+#
+# Do *not* replace placeholders! Placeholders have a special syntax like
+# `Hotkey: `. They are encapsulated within angle brackets. The correct
+# translation for this one in German for example would be: `Taste: ` (notice
+# how the placeholder stayed '' and was not replaced!)
+#
+# Adding a new language:
+#
+# If you want to add a new language, ask me in the Discord and I will setup
+# the basic structure so the game also detects it.
+#
+
+---
+steamPage:
+ # This is the short text appearing on the steam page
+ shortText: shapez.io is a game about building factories to automate the creation and processing of increasingly complex shapes across an infinitely expanding map.
+
+ # This is the text shown above the Discord link
+ discordLink: Official Discord - Chat with me!
+
+ # This is the long description for the steam page - It is contained here so you can help to translate it, and I will regulary update the store page.
+ # NOTICE:
+ # - Do not translate the first line (This is the gif image at the start of the store)
+ # - Please keep the markup (Stuff like [b], [list] etc) in the same format
+ longText: >-
+ [img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img]
+
+ shapez.io is a game about building factories to automate the creation and processing of increasingly complex shapes across an infinitely expanding map.
+
+ Upon delivering the requested shapes you'll progress within the game and unlock upgrades to speed up your factory.
+
+ As the demand for shapes increases, you'll have to scale up your factory to meet the demand - Don't forget about resources though, you'll have to expand across the [b]infinite map[/b]!
+
+ Soon you'll have to mix colors and paint your shapes with them - Combine red, green and blue color resources to produce different colors and paint shapes with them to satisfy the demand.
+
+ This game features 18 progressive levels (Which should already keep you busy for hours!) but I'm constantly adding new content - There's a lot planned!
+
+ Purchasing the game gives you access to the standalone version which has additional features, and you'll also receive access to newly developed features.
+
+ [b]Standalone Advantages[/b]
+
+ [list]
+ [*] Dark Mode
+ [*] Unlimited Waypoints
+ [*] Unlimited Savegames
+ [*] Additional settings
+ [*] Coming soon: Wires & Energy! Aiming for (roughly) end of July 2020.
+ [*] Coming soon: More Levels
+ [*] Allows me to further develop shapez.io ❤️
+ [/list]
+
+ [b]Future Updates[/b]
+
+ I am updating the game often and trying to push an update at least once every week!
+
+ [list]
+ [*] Different maps and challenges (e.g. maps with obstacles)
+ [*] Puzzles (Deliver the requested shape with a restricted area / set of buildings)
+ [*] A story mode where buildings have a cost
+ [*] Configurable map generator (Configure resource/shape size/density, seed and more)
+ [*] Additional types of shapes
+ [*] Performance improvements (The game already runs pretty well!)
+ [*] And much more!
+ [/list]
+
+ [b]This game is open source![/b]
+
+ Anybody can contribute, I'm actively involved in the community and attempt to review all suggestions and take feedback into consideration where possible.
+ Be sure to check out my trello board for the full roadmap!
+
+ [b]Links[/b]
+
+ [list]
+ [*] [url=https://discord.com/invite/HN7EVzV]Official Discord[/url]
+ [*] [url=https://trello.com/b/ISQncpJP/shapezio]Roadmap[/url]
+ [*] [url=https://www.reddit.com/r/shapezio]Subreddit[/url]
+ [*] [url=https://github.com/tobspr/shapez.io]Source code (GitHub)[/url]
+ [*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Help translate[/url]
+ [/list]
+
+global:
+ loading: Loading
+ error: Error
+
+ # How big numbers are rendered, e.g. "10,000"
+ thousandsDivider: ","
+
+ # What symbol to use to seperate the integer part from the fractional part of a number, e.g. "0.4"
+ decimalSeparator: "."
+
+ # The suffix for large numbers, e.g. 1.3k, 400.2M, etc.
+ suffix:
+ thousands: k
+ millions: M
+ billions: B
+ trillions: T
+
+ # Shown for infinitely big numbers
+ infinite: inf
+
+ time:
+ # Used for formatting past time dates
+ oneSecondAgo: one second ago
+ xSecondsAgo: seconds ago
+ oneMinuteAgo: one minute ago
+ xMinutesAgo: minutes ago
+ oneHourAgo: one hour ago
+ xHoursAgo: hours ago
+ oneDayAgo: one day ago
+ xDaysAgo: days ago
+
+ # Short formats for times, e.g. '5h 23m'
+ secondsShort: s
+ minutesAndSecondsShort: m s
+ hoursAndMinutesShort: h m
+
+ xMinutes: minutes
+
+ keys:
+ tab: TAB
+ control: CTRL
+ alt: ALT
+ escape: ESC
+ shift: SHIFT
+ space: SPACE
+
+demoBanners:
+ # This is the "advertisement" shown in the main menu and other various places
+ title: Demo Version
+ intro: >-
+ Get the standalone to unlock all features!
+
+mainMenu:
+ play: Play
+ continue: Continue
+ newGame: New Game
+ changelog: Changelog
+ subreddit: Reddit
+ importSavegame: Import
+ openSourceHint: This game is open source!
+ discordLink: Official Discord Server
+ helpTranslate: Help translate!
+ madeBy: Made by
+
+ # This is shown when using firefox and other browsers which are not supported.
+ browserWarning: >-
+ Sorry, but the game is known to run slow on your browser! Get the standalone version or download Google Chrome for the full experience.
+
+ savegameLevel: Level
+ savegameLevelUnknown: Unknown Level
+ savegameUnnamed: Unnamed
+
+dialogs:
+ buttons:
+ ok: OK
+ delete: Delete
+ cancel: Cancel
+ later: Later
+ restart: Restart
+ reset: Reset
+ getStandalone: Get Standalone
+ deleteGame: I know what I am doing
+ viewUpdate: View Update
+ showUpgrades: Show Upgrades
+ showKeybindings: Show Keybindings
+
+ importSavegameError:
+ title: Import Error
+ text: >-
+ Failed to import your savegame:
+
+ importSavegameSuccess:
+ title: Savegame Imported
+ text: >-
+ Your savegame has been successfully imported.
+
+ gameLoadFailure:
+ title: Game is broken
+ text: >-
+ Failed to load your savegame:
+
+ confirmSavegameDelete:
+ title: Confirm deletion
+ text: >-
+ Are you sure you want to delete the game?
+
+ savegameDeletionError:
+ title: Failed to delete
+ text: >-
+ Failed to delete the savegame:
+
+ restartRequired:
+ title: Restart required
+ text: >-
+ You need to restart the game to apply the settings.
+
+ editKeybinding:
+ title: Change Keybinding
+ desc: Press the key or mouse button you want to assign, or escape to cancel.
+
+ resetKeybindingsConfirmation:
+ title: Reset keybindings
+ desc: This will reset all keybindings to their default values. Please confirm.
+
+ keybindingsResetOk:
+ title: Keybindings reset
+ desc: The keybindings have been reset to their respective defaults!
+
+ featureRestriction:
+ title: Demo Version
+ desc: You tried to access a feature () which is not available in the demo. Consider getting the standalone version for the full experience!
+
+ oneSavegameLimit:
+ title: Limited savegames
+ desc: You can only have one savegame at a time in the demo version. Please remove the existing one or get the standalone version!
+
+ updateSummary:
+ title: New update!
+ desc: >-
+ Here are the changes since you last played:
+
+ upgradesIntroduction:
+ title: Unlock Upgrades
+ desc: >-
+ All shapes you produce can be used to unlock upgrades - Don't destroy your old factories!
+ The upgrades tab can be found on the top right corner of the screen.
+
+ massDeleteConfirm:
+ title: Confirm delete
+ desc: >-
+ You are deleting a lot of buildings ( to be exact)! Are you sure you want to do this?
+
+ massCutConfirm:
+ title: Confirm cut
+ desc: >-
+ You are cutting a lot of buildings ( to be exact)! Are you sure you want to do this?
+
+ massCutInsufficientConfirm:
+ title: Confirm cut
+ desc: >-
+ You can not afford to paste this area! Are you sure you want to cut it?
+
+ blueprintsNotUnlocked:
+ title: Not unlocked yet
+ desc: >-
+ Complete level 12 to unlock Blueprints!
+
+ keybindingsIntroduction:
+ title: Useful keybindings
+ desc: >-
+ This game has a lot of keybindings which make it easier to build big factories.
+ Here are a few, but be sure to check out the keybindings !
+ CTRL
+ Drag: Select an area.
+ SHIFT
: Hold to place multiple of one building.
+ ALT
: Invert orientation of placed belts.
+
+ createMarker:
+ title: New Marker
+ titleEdit: Edit Marker
+ desc: Give it a meaningful name, you can also include a short key of a shape (Which you can generate here )
+
+ markerDemoLimit:
+ desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers!
+
+ exportScreenshotWarning:
+ title: Export screenshot
+ desc: You requested to export your base as a screenshot. Please note that this can be quite slow for a big base and even crash your game!
+
+ renameSavegame:
+ title: Rename Savegame
+ desc: You can rename your savegame here.
+
+ingame:
+ # This is shown in the top left corner and displays useful keybindings in
+ # every situation
+ keybindingsOverlay:
+ moveMap: Move
+ selectBuildings: Select area
+ stopPlacement: Stop placement
+ rotateBuilding: Rotate building
+ placeMultiple: Place multiple
+ reverseOrientation: Reverse orientation
+ disableAutoOrientation: Disable auto-orientation
+ toggleHud: Toggle HUD
+ placeBuilding: Place building
+ createMarker: Create marker
+ delete: Delete
+ pasteLastBlueprint: Paste last blueprint
+ lockBeltDirection: Enable belt planner
+ plannerSwitchSide: Flip planner side
+ cutSelection: Cut
+ copySelection: Copy
+ clearSelection: Clear selection
+ pipette: Pipette
+ switchLayers: Switch layers
+
+ # Names of the colors, used for the color blind mode
+ colors:
+ red: Red
+ green: Green
+ blue: Blue
+ yellow: Yellow
+ purple: Magenta
+ cyan: Cyan
+ white: White
+ black: Black
+ uncolored: Gray
+
+ # Everything related to placing buildings (I.e. as soon as you selected a building
+ # from the toolbar)
+ buildingPlacement:
+ # Buildings can have different variants which are unlocked at later levels,
+ # and this is the hint shown when there are multiple variants available.
+ cycleBuildingVariants: Press to cycle variants.
+
+ # Shows the hotkey in the ui, e.g. "Hotkey: Q"
+ hotkeyLabel: >-
+ Hotkey:
+
+ infoTexts:
+ speed: Speed
+ range: Range
+ storage: Storage
+ oneItemPerSecond: 1 item / second
+ itemsPerSecond: items / s
+ itemsPerSecondDouble: (x2)
+
+ tiles: tiles
+
+ # The notification when completing a level
+ levelCompleteNotification:
+ # is replaced by the actual level, so this gets 'Level 03' for example.
+ levelTitle: Level
+ completed: Completed
+ unlockText: Unlocked !
+ buttonNextLevel: Next Level
+
+ # Notifications on the lower right
+ notifications:
+ newUpgrade: A new upgrade is available!
+ gameSaved: Your game has been saved.
+
+ # The "Upgrades" window
+ shop:
+ title: Upgrades
+ buttonUnlock: Upgrade
+
+ # Gets replaced to e.g. "Tier IX"
+ tier: Tier
+
+ # The roman number for each tier
+ tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X]
+
+ maximumLevel: MAXIMUM LEVEL (Speed x)
+
+ # The "Statistics" window
+ statistics:
+ title: Statistics
+ dataSources:
+ stored:
+ title: Stored
+ description: Displaying amount of stored shapes in your central building.
+ produced:
+ title: Produced
+ description: Displaying all shapes your whole factory produces, including intermediate products.
+ delivered:
+ title: Delivered
+ description: Displaying shapes which are delivered to your central building.
+ noShapesProduced: No shapes have been produced so far.
+
+ # Displays the shapes per second, e.g. '523 / s'
+ shapesPerSecond: / s
+
+ # Settings menu, when you press "ESC"
+ settingsMenu:
+ playtime: Playtime
+
+ buildingsPlaced: Buildings
+ beltsPlaced: Belts
+
+ buttons:
+ continue: Continue
+ settings: Settings
+ menu: Return to menu
+
+ # Bottom left tutorial hints
+ tutorialHints:
+ title: Need help?
+ showHint: Show hint
+ hideHint: Close
+
+ # When placing a blueprint
+ blueprintPlacer:
+ cost: Cost
+
+ # Map markers
+ waypoints:
+ waypoints: Markers
+ hub: HUB
+ description: Left-click a marker to jump to it, right-click to delete it. Press to create a marker from the current view, or right-click to create a marker at the selected location.
+ creationSuccessNotification: Marker has been created.
+
+ # Shape viewer
+ shapeViewer:
+ title: Layers
+ empty: Empty
+ copyKey: Copy Key
+
+ # Interactive tutorial
+ interactiveTutorial:
+ title: Tutorial
+ hints:
+ 1_1_extractor: Place an extractor on top of a circle shape to extract it!
+ 1_2_conveyor: >-
+ Connect the extractor with a conveyor belt to your hub! Tip: Click and drag the belt with your mouse!
+
+ 1_3_expand: >-
+ This is NOT an idle game! Build more extractors and belts to finish the goal quicker. Tip: Hold SHIFT to place multiple extractors, and use R to rotate them.
+
+# All shop upgrades
+shopUpgrades:
+ belt:
+ name: Belts, Distributor & Tunnels
+ description: Speed x → x
+ miner:
+ name: Extraction
+ description: Speed x → x
+ processors:
+ name: Cutting, Rotating & Stacking
+ description: Speed x → x
+ painting:
+ name: Mixing & Painting
+ description: Speed x → x
+
+# Buildings and their name / description
+buildings:
+ hub:
+ deliver: Deliver
+ toUnlock: to unlock
+ levelShortcut: LVL
+
+ belt:
+ default:
+ name: &belt Conveyor Belt
+ description: Transports items, hold and drag to place multiple.
+
+ # Internal name for the Extractor
+ miner:
+ default:
+ name: &miner Extractor
+ description: Place over a shape or color to extract it.
+
+ chainable:
+ name: Extractor (Chain)
+ description: Place over a shape or color to extract it. Can be chained.
+
+ # Internal name for the Tunnel
+ underground_belt:
+ default:
+ name: &underground_belt Tunnel
+ description: Allows you to tunnel resources under buildings and belts.
+
+ tier2:
+ name: Tunnel Tier II
+ description: Allows you to tunnel resources under buildings and belts.
+
+ # Internal name for the Balancer
+ splitter:
+ default:
+ name: &splitter Balancer
+ description: Multifunctional - Evenly distributes all inputs onto all outputs.
+
+ compact:
+ name: Merger (compact)
+ description: Merges two conveyor belts into one.
+
+ compact-inverse:
+ name: Merger (compact)
+ description: Merges two conveyor belts into one.
+
+ compact-merge:
+ name: Splitter (compact)
+ description: Splits one conveyor belt into two.
+
+ compact-merge-inverse:
+ name: Splitter (compact)
+ description: Splits one conveyor belt into two.
+
+ cutter:
+ default:
+ name: &cutter Cutter
+ description: Cuts shapes from top to bottom and outputs both halves. If you use only one part, be sure to destroy the other part or it will stall!
+ quad:
+ name: Cutter (Quad)
+ description: Cuts shapes into four parts. If you use only one part, be sure to destroy the other parts or it will stall!
+
+ rotater:
+ default:
+ name: &rotater Rotate
+ description: Rotates shapes clockwise by 90 degrees.
+ ccw:
+ name: Rotate (CCW)
+ description: Rotates shapes counter-clockwise by 90 degrees.
+ fl:
+ name: Rotate (180)
+ description: Rotates shapes by 180 degrees.
+
+ stacker:
+ default:
+ name: &stacker Stacker
+ description: Stacks both items. If they can not be merged, the right item is placed above the left item.
+
+ mixer:
+ default:
+ name: &mixer Color Mixer
+ description: Mixes two colors using additive blending.
+
+ painter:
+ default:
+ name: &painter Painter
+ description: &painter_desc Colors the whole shape on the left input with the color from the top input.
+
+ mirrored:
+ name: *painter
+ description: *painter_desc
+
+ double:
+ name: Painter (Double)
+ description: Colors the shapes on the left inputs with the color from the top input.
+
+ quad:
+ name: Painter (Quad)
+ description: Allows you to color each quadrant of the shape with a different color.
+
+ trash:
+ default:
+ name: &trash Trash
+ description: Accepts inputs from all sides and destroys them. Forever.
+
+ storage:
+ name: Storage
+ description: Stores excess items, up to a given capacity. Can be used as an overflow gate.
+
+ wire:
+ default:
+ name: &wire Wire
+ description: &wire_desc Allows to connect logical components and can transfer items, colors or boolean signals.
+
+ wire_tunnel:
+ default:
+ name: &wire_tunnel Wire Tunnel
+ description: Allows to cross two wires without connecting them.
+
+ coating:
+ name: Wire Insulation
+ description: Allows to pass through signals without connecting to other wires on the sides.
+
+ constant_signal:
+ default:
+ name: &constant_signal Constant Signal
+ description: Emits a constant signal (shape, color or boolean).
+
+ lever:
+ default:
+ name: &lever Button
+ description: Can be toggled to emit 1 / 0
+
+ logic_gate:
+ default:
+ name: &logic_gate AND Gate
+ description: Emits a truthy boolean signal if both inputs are truthy.
+ not:
+ name: NOT
+ description: Inverts the given signal.
+ xor:
+ name: XOR
+ description: Emits a truthy signal if one of the inputs is truthy, but not both.
+ or:
+ name: OR
+ description: Emits a truthy signal if one of the inputs is truthy.
+
+ transistor:
+ name: Gate
+ description: Only forwards the bottom input if the left input is true.
+
+ filter:
+ default:
+ name: &filter Filter
+ # TEMP
+ description: Only leaves through items who match exactly the provided shape / color. If you put in a boolean 1, it leaves everything through, if you put in a 0 it will leave nothing through.
+
+ display:
+ default:
+ name: &display Display
+ # TEMP
+ description: Can be connected on the wires layer to show a color or shape. When inputting a boolean item, the display will be white if the value is 1.
+
+ virtual_processor:
+ default:
+ name: &virtual_processor Virtual Cutter
+ description: Virtually cuts the shape input from top to bottom and returns both halfs.
+
+ analyzer:
+ name: Shape Analyzer
+ description: Analyzes the top right quadrant of the lowest layer of the shape and returns its shape and color
+
+ rotater:
+ name: Virtual Rotater
+ description: Virtually rotates the shape by 90 degrees clockwise.
+
+ unstacker:
+ name: Virtual Unstacker
+ description: Returns the topmost layer to the right, and the remaining ones on the left.
+
+ shapecompare:
+ name: Compare Shapes
+ description: Returns true if both shapes are exactly equal
+
+storyRewards:
+ # Those are the rewards gained from completing the store
+ reward_cutter_and_trash:
+ title: Cutting Shapes
+ desc: You just unlocked the cutter - it cuts shapes in half from top to bottom regardless of its orientation! Be sure to get rid of the waste, or otherwise it will stall - For this purpose I have given you the trash can, which destroys everything you put into it!
+
+ reward_rotater:
+ title: Rotating
+ desc: The rotater has been unlocked! It rotates shapes clockwise by 90 degrees.
+
+ reward_painter:
+ title: Painting
+ desc: >-
+ The painter has been unlocked - Extract some color veins (just as you do with shapes) and combine it with a shape in the painter to color them! PS: If you are colorblind, there is a colorblind mode in the settings!
+
+ reward_mixer:
+ title: Color Mixing
+ desc: The mixer has been unlocked - Combine two colors using additive blending with this building!
+
+ reward_stacker:
+ title: Combiner
+ desc: You can now combine shapes with the combiner ! Both inputs are combined, and if they can be put next to each other, they will be fused . If not, the right input is stacked on top of the left input!
+
+ reward_splitter:
+ title: Splitter/Merger
+ desc: The multifunctional balancer has been unlocked - It can be used to build bigger factories by splitting and merging items onto multiple belts!
+
+ reward_tunnel:
+ title: Tunnel
+ desc: The tunnel has been unlocked - You can now tunnel items through belts and buildings with it!
+
+ reward_rotater_ccw:
+ title: CCW Rotating
+ desc: You have unlocked a variant of the rotater - It allows you to rotate shapes counter-clockwise! To build it, select the rotater and press 'T' to cycle through its variants !
+
+ reward_miner_chainable:
+ title: Chaining Extractor
+ desc: You have unlocked the chaining extractor ! It can forward its resources to other extractors so you can more efficiently extract resources!
+
+ reward_underground_belt_tier_2:
+ title: Tunnel Tier II
+ desc: You have unlocked a new variant of the tunnel - It has a bigger range , and you can also mix-n-match those tunnels now!
+
+ reward_splitter_compact:
+ title: Compact Balancer
+ desc: >-
+ You have unlocked a compact variant of the balancer - It accepts two inputs and merges them into one belt!
+
+ reward_cutter_quad:
+ title: Quad Cutting
+ desc: You have unlocked a variant of the cutter - It allows you to cut shapes in four parts instead of just two!
+
+ reward_painter_double:
+ title: Double Painting
+ desc: You have unlocked a variant of the painter - It works as the regular painter but processes two shapes at once consuming just one color instead of two!
+
+ reward_painter_quad:
+ title: Quad Painting
+ desc: You have unlocked a variant of the painter - It allows you to paint each part of the shape individually!
+
+ reward_storage:
+ title: Storage Buffer
+ desc: You have unlocked a variant of the trash - It allows you to store items up to a given capacity!
+
+ reward_freeplay:
+ title: Freeplay
+ desc: You did it! You unlocked the free-play mode ! This means that shapes are now randomly generated! (No worries, more content is planned for the standalone!)
+
+ reward_blueprints:
+ title: Blueprints
+ desc: You can now copy and paste parts of your factory! Select an area (Hold CTRL, then drag with your mouse), and press 'C' to copy it. Pasting it is not free , you need to produce blueprint shapes to afford it! (Those you just delivered).
+
+ # Special reward, which is shown when there is no reward actually
+ no_reward:
+ title: Next level
+ desc: >-
+ This level gave you no reward, but the next one will! PS: Better not destroy your existing factory - You'll need all those shapes later to unlock upgrades !
+
+ no_reward_freeplay:
+ title: Next level
+ desc: >-
+ Congratulations! By the way, more content is planned for the standalone!
+
+settings:
+ title: Settings
+ categories:
+ general: General
+ userInterface: User Interface
+ advanced: Advanced
+ performance: Performance
+
+ versionBadges:
+ dev: Development
+ staging: Staging
+ prod: Production
+ buildDate: Built
+
+ labels:
+ uiScale:
+ title: Interface scale
+ description: >-
+ Changes the size of the user interface. The interface will still scale based on your device's resolution, but this setting controls the amount of scaling.
+ scales:
+ super_small: Super small
+ small: Small
+ regular: Regular
+ large: Large
+ huge: Huge
+
+ autosaveInterval:
+ title: Autosave Interval
+ description: >-
+ Controls how often the game saves automatically. You can also disable it entirely here.
+
+ intervals:
+ one_minute: 1 Minute
+ two_minutes: 2 Minutes
+ five_minutes: 5 Minutes
+ ten_minutes: 10 Minutes
+ twenty_minutes: 20 Minutes
+ disabled: Disabled
+
+ scrollWheelSensitivity:
+ title: Zoom sensitivity
+ description: >-
+ Changes how sensitive the zoom is (Either mouse wheel or trackpad).
+ sensitivity:
+ super_slow: Super slow
+ slow: Slow
+ regular: Regular
+ fast: Fast
+ super_fast: Super fast
+
+ movementSpeed:
+ title: Movement speed
+ description: >-
+ Changes how fast the view moves when using the keyboard.
+ speeds:
+ super_slow: Super slow
+ slow: Slow
+ regular: Regular
+ fast: Fast
+ super_fast: Super Fast
+ extremely_fast: Extremely Fast
+
+ language:
+ title: Language
+ description: >-
+ Change the language. All translations are user-contributed and might be incomplete!
+
+ enableColorBlindHelper:
+ title: Color Blind Mode
+ description: >-
+ Enables various tools which allow you to play the game if you are color blind.
+
+ fullscreen:
+ title: Fullscreen
+ description: >-
+ It is recommended to play the game in fullscreen to get the best experience. Only available in the standalone.
+
+ soundsMuted:
+ title: Mute Sounds
+ description: >-
+ If enabled, mutes all sound effects.
+
+ musicMuted:
+ title: Mute Music
+ description: >-
+ If enabled, mutes all music.
+
+ theme:
+ title: Game theme
+ description: >-
+ Choose the game theme (light / dark).
+ themes:
+ dark: Dark
+ light: Light
+
+ refreshRate:
+ title: Tick Rate
+ description: >-
+ The game will automatically adjust the tickrate to be between this target tickrate and half of it. For example, with a tickrate of 60hz, the game will try to stay at 60hz, and if your computer can't handle it it will go down until it eventually reaches 30hz.
+
+ alwaysMultiplace:
+ title: Multiplace
+ description: >-
+ If enabled, all buildings will stay selected after placement until you cancel it. This is equivalent to holding SHIFT permanently.
+
+ offerHints:
+ title: Hints & Tutorials
+ description: >-
+ Whether to offer hints and tutorials while playing. Also hides certain UI elements up to a given level to make it easier to get into the game.
+
+ enableTunnelSmartplace:
+ title: Smart Tunnels
+ description: >-
+ When enabled, placing tunnels will automatically remove unnecessary belts. This also enables you to drag tunnels and excess tunnels will get removed.
+
+ vignette:
+ title: Vignette
+ description: >-
+ Enables the vignette, which darkens the screen corners and makes text easier to read.
+
+ rotationByBuilding:
+ title: Rotation by building type
+ description: >-
+ Each building type remembers the rotation you last set it to individually. This may be more comfortable if you frequently switch between placing different building types.
+
+ compactBuildingInfo:
+ title: Compact Building Infos
+ description: >-
+ Shortens info boxes for buildings by only showing their ratios. Otherwise a description and image is shown.
+
+ disableCutDeleteWarnings:
+ title: Disable Cut/Delete Warnings
+ description: >-
+ Disables the warning dialogs brought up when cutting/deleting more than 100 entities.
+
+ lowQualityMapResources:
+ title: Low Quality Map Resources
+ description: >-
+ Simplifies the rendering of resources on the map when zoomed in to improve performance.
+ It even looks cleaner, so be sure to try it out!
+
+ disableTileGrid:
+ title: Disable Grid
+ description: >-
+ Disabling the tile grid can help with the performance. This also makes the game look cleaner!
+
+ clearCursorOnDeleteWhilePlacing:
+ title: Clear Cursor on Right Click
+ description: >-
+ Enabled by default, clears the cursor whenever you right click while you have a building selected for placement. If disabled, you can delete buildings by right-clicking while placing a building.
+
+ lowQualityTextures:
+ title: Low quality textures (Ugly)
+ description: >-
+ Uses low quality textures to save performance. This will make the game look very ugly!
+
+keybindings:
+ title: Keybindings
+ hint: >-
+ Tip: Be sure to make use of CTRL, SHIFT and ALT! They enable different placement options.
+
+ resetKeybindings: Reset Keybindings
+
+ categoryLabels:
+ general: Application
+ ingame: Game
+ navigation: Navigating
+ placement: Placement
+ massSelect: Mass Select
+ buildings: Building Shortcuts
+ placementModifiers: Placement Modifiers
+
+ mappings:
+ confirm: Confirm
+ back: Back
+ mapMoveUp: Move Up
+ mapMoveRight: Move Right
+ mapMoveDown: Move Down
+ mapMoveLeft: Move Left
+ mapMoveFaster: Move Faster
+ centerMap: Center Map
+
+ mapZoomIn: Zoom in
+ mapZoomOut: Zoom out
+ createMarker: Create Marker
+
+ menuOpenShop: Upgrades
+ menuOpenStats: Statistics
+ menuClose: Close Menu
+
+ toggleHud: Toggle HUD
+ toggleFPSInfo: Toggle FPS and Debug Info
+ switchLayers: Switch layers
+ exportScreenshot: Export whole Base as Image
+
+ # --- Do not translate the values in this section
+ belt: *belt
+ splitter: *splitter
+ underground_belt: *underground_belt
+ miner: *miner
+ cutter: *cutter
+ rotater: *rotater
+ stacker: *stacker
+ mixer: *mixer
+ painter: *painter
+ trash: *trash
+ wire: *wire
+ constant_signal: *constant_signal
+ logic_gate: *logic_gate
+ lever: *lever
+ filter: *filter
+ wire_tunnel: *wire_tunnel
+ display: *display
+ # ---
+
+ pipette: Pipette
+ rotateWhilePlacing: Rotate
+ rotateInverseModifier: >-
+ Modifier: Rotate CCW instead
+ cycleBuildingVariants: Cycle Variants
+ confirmMassDelete: Delete area
+ pasteLastBlueprint: Paste last blueprint
+ cycleBuildings: Cycle Buildings
+ lockBeltDirection: Enable belt planner
+ switchDirectionLockSide: >-
+ Planner: Switch side
+
+ massSelectStart: Hold and drag to start
+ massSelectSelectMultiple: Select multiple areas
+ massSelectCopy: Copy area
+ massSelectCut: Cut area
+
+ placementDisableAutoOrientation: Disable automatic orientation
+ placeMultiple: Stay in placement mode
+ placeInverse: Invert automatic belt orientation
+
+about:
+ title: About this Game
+ body: >-
+ This game is open source and developed by Tobias Springer (this is me).
+
+ If you want to contribute, check out shapez.io on GitHub .
+
+ This game wouldn't have been possible without the great Discord community around my games - You should really join the Discord server !
+
+ The soundtrack was made by Peppsen - He's awesome.
+
+ Finally, huge thanks to my best friend Niklas - Without our Factorio sessions, this game would never have existed.
+
+changelog:
+ title: Changelog
+
+demo:
+ features:
+ restoringGames: Restoring savegames
+ importingGames: Importing savegames
+ oneGameLimit: Limited to one savegame
+ customizeKeybindings: Customizing Keybindings
+ exportingBase: Exporting whole Base as Image
+
+ settingNotAvailable: Not available in the demo.
diff --git a/translations/base-fr.yaml b/translations/base-fr.yaml
index 8eb4f0ef..fe460760 100644
--- a/translations/base-fr.yaml
+++ b/translations/base-fr.yaml
@@ -488,8 +488,8 @@ buildings:
name: Pivoteur inversé
description: Fait pivoter une forme de 90 degrés vers la gauche.
fl:
- name: Pivoteur (180)
- description: Fait pivoter les formes de 90 degrés.
+ name: Retourneur
+ description: Tourne la forme de 180 degrés.
stacker:
default:
@@ -635,9 +635,9 @@ storyRewards:
settings:
title: Options
categories:
- general: General
- userInterface: User Interface
- advanced: Advanced
+ general: Général
+ userInterface: Interface Utilisateur
+ advanced: Avancé
versionBadges:
dev: Développement
diff --git a/translations/base-tr.yaml b/translations/base-tr.yaml
index b0b7d095..eff8521f 100644
--- a/translations/base-tr.yaml
+++ b/translations/base-tr.yaml
@@ -22,7 +22,7 @@
---
steamPage:
# This is the short text appearing on the steam page
- shortText: shapez.io is a game about building factories to automate the creation and combination of increasingly complex shapes within an infinite map.
+ shortText: shapez.io giderek karmaşıklaşan şekillerin sonsuz bir harita üzerinde üretimi ve birleştirilmesi hakında bir oyundur.
# This is the long description for the steam page - It is contained here so you can help to translate it, and I will regulary update the store page.
# NOTICE:
@@ -31,56 +31,57 @@ steamPage:
longText: >-
[img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img]
- shapez.io is a game about building factories to automate the creation and processing of increasingly complex shapes across an infinitely expanding map.
- Upon delivering the requested shapes you will progress within the game and unlock upgrades to Hız up your factory.
+ shapez.io giderek karmaşıklaşan şekillerin sonsuz bir harita üzerinde üretimi ve birleştirilmesi hakında bir oyundur.
+ Talep edilen şekilleri verdikten sonra oyundaki geliştirmeleri açıp fabrikanızı hızlandırabilirsiniz.
- As the demand for shapes increases, you will have to scale up your factory to meet the demand - Don't forget about resources though, you will have to expand across the [b]infinite map[/b]!
+ Şekiller için talep artınca fabrikanı büyütüp talebi karşılamalısın - Kaynakları unutma! Sonsuz [b]sonsuz haritada[/b] genişlemen gerekecek!
- Soon you will have to mix colors and paint your shapes with them - Combine red, green and blue color resources to produce different colors and paint shapes with it to satisfy the demand.
+ Yakında renkleri karıştırman ve şekileri boyaman gerekecek - Talebi karşılamak için kırmızı, mavi ve yeşili karıştırıp şekilleri boyacaksın
- This game features 18 progressive levels (Which should keep you busy for hours already!) but I'm constantly adding new content - There is a lot planned!
+ Bu oyun 18 kademeli seviye içerir (Seni saatlerce meşgul tutumalı) ama sürekli yeni şeyler ekliyorum - Ekleyecek çok şey var!
- Purchasing the game gives you access to the standalone version which has additional features and you'll also receive access to newly developed features.
+ Bu oyunu satın almak indirilebilir versiyonuna (Ek özellikler var) ve yeni özeliklerine erişebileceksiniz.
- [b]Standalone Advantages[/b]
+ [b]İndirebilir versiyonun avantajları[/b]
[list]
- [*] Dark Mode
- [*] Unlimited Waypoints
- [*] Unlimited Savegames
- [*] Additional settings
- [*] Coming soon: Wires & Energy! Aiming for (roughly) end of July 2020.
- [*] Coming soon: More Levels
- [*] Allows me to further develop shapez.io ❤️
+ [*] Karanlık mod
+ [*] Sonsuz işaret
+ [*] Sonsuz kayıt alanı
+ [*] Ek ayarlar
+ [*] Yakında geliyor: Kablo ve enerji! Temmuz sonunu hedefliyorum.
+ [*] Yakında geliyor: Daha fazla seviyeler
+ [*] Shapez.io'yu geliştirmeme izin veriyor ❤️
[/list]
- [b]Future Updates[/b]
+ [b]Gelecek güncellemeler[/b]
- I am updating the game very often and trying to push an update at least every week!
+ Oyunu sık sık güncelliyorum ve en az haftada bir güncellemeye çalışıyorum!
[list]
- [*] Different maps and challenges (e.g. maps with obstacles)
- [*] Puzzles (Deliver the requested shape with a restricted area / set of buildings)
- [*] A story mode where buildings have a cost
- [*] Configurable map generator (Configure resource/shape size/density, seed and more)
- [*] Additional types of shapes
- [*] Performance improvements (The game already runs pretty well!)
- [*] And much more!
+ [*] Farklı haritalar ve görevler (Örneğin engelli haritalar)
+ [*] Yapbozlar (İstenen şekilleri kısıtlı bir alanda / belli yapılar ile)
+ [*] Hikaye modu ile yapıların maliyeti olacak.
+ [*] Ayarlanabilir harita yapımı(Ayarlanabilir kaynak/şekil boyut/sıklığı, seedler ve daha fazlası)
+ [*] Ek şekiller
+ [*] Performans optimizasyonları (oyun zaten iyi çalışıyor)
+ [*] Ve daha fazlası
[/list]
- [b]This game is open source![/b]
+ [b]Bu oyun açık kaynaklı![/b]
- Anybody can contribute, I'm actively involved in the community and attempt to review all suggestions and take feedback into consideration where possible.
- Be sure to check out my trello board for the full roadmap!
+ Bu oyuna herkes katkıda bulunabilir! Aktif olarak toplulukğa katkıda bulunuyorum ve bütün önerileri gözden geçirmeye çalışıyorum.
+ Yol planıma Trello'dan bakmayı unutmayın!
+
[b]Links[/b]
[list]
- [*] [url=https://discord.com/invite/HN7EVzV]Official Discord[/url]
- [*] [url=https://trello.com/b/ISQncpJP/shapezio]Roadmap[/url]
+ [*] [url=https://discord.com/invite/HN7EVzV]Dİscord'umuz[/url]
+ [*] [url=https://trello.com/b/ISQncpJP/shapezio]yol planı[/url]
[*] [url=https://www.reddit.com/r/shapezio]Subreddit[/url]
- [*] [url=https://github.com/tobspr/shapez.io]Source code (GitHub)[/url]
- [*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Help translate[/url]
+ [*] [url=https://github.com/tobspr/shapez.io]KAynak kodu (GitHub)[/url]
+ [*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Çevirmeye yardımcı olun[/url]
[/list]
discordLink: Official Discord - Chat with me!