diff --git a/res_built/atlas/atlas0_hq.json b/res_built/atlas/atlas0_hq.json
index 1c0cb736..9edcc70f 100644
--- a/res_built/atlas/atlas0_hq.json
+++ b/res_built/atlas/atlas0_hq.json
@@ -18,7 +18,7 @@
},
"sprites/belt/built/forward_2.png":
{
- "frame": {"x":1802,"y":1366,"w":116,"h":144},
+ "frame": {"x":1804,"y":1366,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -26,7 +26,7 @@
},
"sprites/belt/built/forward_3.png":
{
- "frame": {"x":1922,"y":1405,"w":116,"h":144},
+ "frame": {"x":1924,"y":1405,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -34,7 +34,7 @@
},
"sprites/belt/built/forward_4.png":
{
- "frame": {"x":1796,"y":1514,"w":116,"h":144},
+ "frame": {"x":439,"y":1038,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -42,7 +42,7 @@
},
"sprites/belt/built/forward_5.png":
{
- "frame": {"x":1796,"y":1662,"w":116,"h":144},
+ "frame": {"x":1801,"y":1514,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -50,7 +50,7 @@
},
"sprites/belt/built/forward_6.png":
{
- "frame": {"x":1916,"y":1693,"w":116,"h":144},
+ "frame": {"x":1921,"y":1553,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -58,7 +58,7 @@
},
"sprites/belt/built/forward_7.png":
{
- "frame": {"x":1795,"y":1810,"w":116,"h":144},
+ "frame": {"x":1801,"y":1662,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -66,7 +66,7 @@
},
"sprites/belt/built/forward_8.png":
{
- "frame": {"x":145,"y":1887,"w":116,"h":144},
+ "frame": {"x":1921,"y":1701,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -74,7 +74,7 @@
},
"sprites/belt/built/forward_9.png":
{
- "frame": {"x":265,"y":1887,"w":116,"h":144},
+ "frame": {"x":137,"y":1849,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -82,7 +82,7 @@
},
"sprites/belt/built/forward_10.png":
{
- "frame": {"x":1537,"y":1139,"w":116,"h":144},
+ "frame": {"x":1539,"y":1139,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -90,7 +90,7 @@
},
"sprites/belt/built/forward_11.png":
{
- "frame": {"x":1804,"y":1218,"w":116,"h":144},
+ "frame": {"x":1538,"y":1287,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -98,7 +98,7 @@
},
"sprites/belt/built/forward_12.png":
{
- "frame": {"x":1924,"y":1257,"w":116,"h":144},
+ "frame": {"x":1806,"y":1218,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -106,7 +106,7 @@
},
"sprites/belt/built/forward_13.png":
{
- "frame": {"x":439,"y":1038,"w":116,"h":144},
+ "frame": {"x":1926,"y":1257,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -114,7 +114,7 @@
},
"sprites/belt/built/left_0.png":
{
- "frame": {"x":961,"y":1033,"w":130,"h":130},
+ "frame": {"x":3,"y":1182,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -122,7 +122,7 @@
},
"sprites/belt/built/left_1.png":
{
- "frame": {"x":1523,"y":1287,"w":130,"h":130},
+ "frame": {"x":137,"y":1182,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -130,7 +130,7 @@
},
"sprites/belt/built/left_2.png":
{
- "frame": {"x":137,"y":1700,"w":130,"h":130},
+ "frame": {"x":283,"y":1703,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -138,7 +138,7 @@
},
"sprites/belt/built/left_3.png":
{
- "frame": {"x":271,"y":1700,"w":130,"h":130},
+ "frame": {"x":389,"y":1849,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -146,7 +146,7 @@
},
"sprites/belt/built/left_4.png":
{
- "frame": {"x":545,"y":1736,"w":130,"h":130},
+ "frame": {"x":417,"y":1703,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -154,7 +154,7 @@
},
"sprites/belt/built/left_5.png":
{
- "frame": {"x":927,"y":1578,"w":130,"h":130},
+ "frame": {"x":523,"y":1849,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -162,7 +162,7 @@
},
"sprites/belt/built/left_6.png":
{
- "frame": {"x":786,"y":1649,"w":130,"h":130},
+ "frame": {"x":551,"y":1703,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -170,7 +170,7 @@
},
"sprites/belt/built/left_7.png":
{
- "frame": {"x":645,"y":1887,"w":130,"h":130},
+ "frame": {"x":657,"y":1849,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -178,7 +178,7 @@
},
"sprites/belt/built/left_8.png":
{
- "frame": {"x":779,"y":1887,"w":130,"h":130},
+ "frame": {"x":791,"y":1408,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -186,7 +186,7 @@
},
"sprites/belt/built/left_9.png":
{
- "frame": {"x":913,"y":1887,"w":130,"h":130},
+ "frame": {"x":685,"y":1703,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -194,7 +194,7 @@
},
"sprites/belt/built/left_10.png":
{
- "frame": {"x":1519,"y":1421,"w":130,"h":130},
+ "frame": {"x":425,"y":1186,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -202,7 +202,7 @@
},
"sprites/belt/built/left_11.png":
{
- "frame": {"x":1519,"y":1555,"w":130,"h":130},
+ "frame": {"x":425,"y":1320,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -210,7 +210,7 @@
},
"sprites/belt/built/left_12.png":
{
- "frame": {"x":1915,"y":1841,"w":130,"h":130},
+ "frame": {"x":424,"y":1454,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -218,7 +218,7 @@
},
"sprites/belt/built/left_13.png":
{
- "frame": {"x":3,"y":1700,"w":130,"h":130},
+ "frame": {"x":144,"y":1465,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -226,7 +226,7 @@
},
"sprites/belt/built/right_0.png":
{
- "frame": {"x":1047,"y":1887,"w":130,"h":130},
+ "frame": {"x":925,"y":1476,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -234,7 +234,7 @@
},
"sprites/belt/built/right_1.png":
{
- "frame": {"x":1181,"y":1887,"w":130,"h":130},
+ "frame": {"x":565,"y":1542,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -242,7 +242,7 @@
},
"sprites/belt/built/right_2.png":
{
- "frame": {"x":920,"y":1712,"w":130,"h":130},
+ "frame": {"x":1461,"y":1703,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -250,7 +250,7 @@
},
"sprites/belt/built/right_3.png":
{
- "frame": {"x":1054,"y":1735,"w":130,"h":130},
+ "frame": {"x":791,"y":1849,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -258,7 +258,7 @@
},
"sprites/belt/built/right_4.png":
{
- "frame": {"x":1188,"y":1735,"w":130,"h":130},
+ "frame": {"x":925,"y":1610,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -266,7 +266,7 @@
},
"sprites/belt/built/right_5.png":
{
- "frame": {"x":1322,"y":1703,"w":130,"h":130},
+ "frame": {"x":1059,"y":1740,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -274,7 +274,7 @@
},
"sprites/belt/built/right_6.png":
{
- "frame": {"x":405,"y":1315,"w":130,"h":130},
+ "frame": {"x":1193,"y":1805,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -282,7 +282,7 @@
},
"sprites/belt/built/right_7.png":
{
- "frame": {"x":405,"y":1449,"w":130,"h":130},
+ "frame": {"x":1327,"y":1827,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -290,7 +290,7 @@
},
"sprites/belt/built/right_8.png":
{
- "frame": {"x":405,"y":1583,"w":130,"h":130},
+ "frame": {"x":925,"y":1744,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -298,7 +298,7 @@
},
"sprites/belt/built/right_9.png":
{
- "frame": {"x":3,"y":1182,"w":130,"h":130},
+ "frame": {"x":1059,"y":1874,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -306,7 +306,7 @@
},
"sprites/belt/built/right_10.png":
{
- "frame": {"x":1315,"y":1887,"w":130,"h":130},
+ "frame": {"x":699,"y":1542,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -314,7 +314,7 @@
},
"sprites/belt/built/right_11.png":
{
- "frame": {"x":1449,"y":1887,"w":130,"h":130},
+ "frame": {"x":1059,"y":1606,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -322,7 +322,7 @@
},
"sprites/belt/built/right_12.png":
{
- "frame": {"x":1583,"y":1887,"w":130,"h":130},
+ "frame": {"x":1193,"y":1671,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -330,7 +330,7 @@
},
"sprites/belt/built/right_13.png":
{
- "frame": {"x":1061,"y":1601,"w":130,"h":130},
+ "frame": {"x":1327,"y":1693,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -338,7 +338,7 @@
},
"sprites/blueprints/belt_left.png":
{
- "frame": {"x":137,"y":1182,"w":130,"h":130},
+ "frame": {"x":925,"y":1878,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -346,7 +346,7 @@
},
"sprites/blueprints/belt_right.png":
{
- "frame": {"x":271,"y":1182,"w":130,"h":130},
+ "frame": {"x":1461,"y":1849,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -354,7 +354,7 @@
},
"sprites/blueprints/belt_top.png":
{
- "frame": {"x":385,"y":1887,"w":116,"h":144},
+ "frame": {"x":3,"y":1318,"w":116,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":0,"w":116,"h":144},
@@ -386,7 +386,7 @@
},
"sprites/blueprints/display.png":
{
- "frame": {"x":1916,"y":1553,"w":128,"h":136},
+ "frame": {"x":257,"y":1849,"w":128,"h":136},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":8,"y":8,"w":128,"h":136},
@@ -458,7 +458,7 @@
},
"sprites/blueprints/miner.png":
{
- "frame": {"x":1513,"y":1689,"w":136,"h":143},
+ "frame": {"x":123,"y":1318,"w":136,"h":143},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":136,"h":143},
@@ -506,7 +506,7 @@
},
"sprites/blueprints/reader.png":
{
- "frame": {"x":1086,"y":1313,"w":141,"h":144},
+ "frame": {"x":938,"y":1181,"w":141,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":141,"h":144},
@@ -514,7 +514,7 @@
},
"sprites/blueprints/rotater-ccw.png":
{
- "frame": {"x":1243,"y":1123,"w":143,"h":144},
+ "frame": {"x":1245,"y":1123,"w":143,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
@@ -522,7 +522,7 @@
},
"sprites/blueprints/rotater-fl.png":
{
- "frame": {"x":940,"y":1167,"w":142,"h":144},
+ "frame": {"x":1658,"y":1325,"w":142,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":142,"h":144},
@@ -530,7 +530,7 @@
},
"sprites/blueprints/rotater.png":
{
- "frame": {"x":1390,"y":1126,"w":143,"h":144},
+ "frame": {"x":1097,"y":1171,"w":143,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":143,"h":144},
@@ -538,7 +538,7 @@
},
"sprites/blueprints/splitter-compact-inverse.png":
{
- "frame": {"x":791,"y":1254,"w":142,"h":138},
+ "frame": {"x":560,"y":1036,"w":142,"h":138},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":2,"w":142,"h":138},
@@ -546,7 +546,7 @@
},
"sprites/blueprints/splitter-compact-merge-inverse.png":
{
- "frame": {"x":1086,"y":1171,"w":142,"h":138},
+ "frame": {"x":792,"y":1118,"w":142,"h":138},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":2,"w":142,"h":138},
@@ -554,7 +554,7 @@
},
"sprites/blueprints/splitter-compact-merge.png":
{
- "frame": {"x":1376,"y":1421,"w":139,"h":138},
+ "frame": {"x":1373,"y":1409,"w":139,"h":138},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":2,"w":139,"h":138},
@@ -562,7 +562,7 @@
},
"sprites/blueprints/splitter-compact.png":
{
- "frame": {"x":1227,"y":1558,"w":139,"h":138},
+ "frame": {"x":1373,"y":1551,"w":139,"h":138},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":2,"w":139,"h":138},
@@ -602,7 +602,7 @@
},
"sprites/blueprints/underground_belt_entry-tier2.png":
{
- "frame": {"x":405,"y":1186,"w":138,"h":125},
+ "frame": {"x":1516,"y":1574,"w":138,"h":125},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":19,"w":138,"h":125},
@@ -610,7 +610,7 @@
},
"sprites/blueprints/underground_belt_entry.png":
{
- "frame": {"x":3,"y":1836,"w":138,"h":112},
+ "frame": {"x":283,"y":1182,"w":138,"h":112},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":32,"w":138,"h":112},
@@ -618,7 +618,7 @@
},
"sprites/blueprints/underground_belt_exit-tier2.png":
{
- "frame": {"x":1653,"y":1632,"w":139,"h":112},
+ "frame": {"x":1225,"y":1555,"w":139,"h":112},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":139,"h":112},
@@ -626,7 +626,7 @@
},
"sprites/blueprints/underground_belt_exit.png":
{
- "frame": {"x":645,"y":1377,"w":138,"h":112},
+ "frame": {"x":283,"y":1298,"w":138,"h":112},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":138,"h":112},
@@ -656,6 +656,14 @@
"spriteSourceSize": {"x":0,"y":0,"w":144,"h":133},
"sourceSize": {"w":144,"h":144}
},
+"sprites/blueprints/virtual_processor-stacker.png":
+{
+ "frame": {"x":961,"y":1033,"w":132,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":132,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
"sprites/blueprints/virtual_processor-unstacker.png":
{
"frame": {"x":1101,"y":704,"w":144,"h":144},
@@ -690,7 +698,7 @@
},
"sprites/blueprints/wire-turn.png":
{
- "frame": {"x":707,"y":1036,"w":82,"h":82},
+ "frame": {"x":706,"y":1036,"w":82,"h":82},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":62,"y":62,"w":82,"h":82},
@@ -714,7 +722,7 @@
},
"sprites/blueprints/wire_tunnel.png":
{
- "frame": {"x":1653,"y":1748,"w":138,"h":135},
+ "frame": {"x":1516,"y":1435,"w":138,"h":135},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":138,"h":135},
@@ -722,7 +730,7 @@
},
"sprites/buildings/belt_left.png":
{
- "frame": {"x":961,"y":1033,"w":130,"h":130},
+ "frame": {"x":3,"y":1182,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":14,"w":130,"h":130},
@@ -730,7 +738,7 @@
},
"sprites/buildings/belt_right.png":
{
- "frame": {"x":1047,"y":1887,"w":130,"h":130},
+ "frame": {"x":925,"y":1476,"w":130,"h":130},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":14,"w":130,"h":130},
@@ -810,7 +818,7 @@
},
"sprites/buildings/logic_gate-or.png":
{
- "frame": {"x":559,"y":1173,"w":143,"h":123},
+ "frame": {"x":1659,"y":1198,"w":143,"h":123},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":143,"h":123},
@@ -826,7 +834,7 @@
},
"sprites/buildings/logic_gate-xor.png":
{
- "frame": {"x":1657,"y":1198,"w":143,"h":143},
+ "frame": {"x":1392,"y":1126,"w":143,"h":143},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":143,"h":143},
@@ -834,7 +842,7 @@
},
"sprites/buildings/logic_gate.png":
{
- "frame": {"x":793,"y":1118,"w":143,"h":132},
+ "frame": {"x":1391,"y":1273,"w":143,"h":132},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":143,"h":132},
@@ -842,7 +850,7 @@
},
"sprites/buildings/miner-chainable.png":
{
- "frame": {"x":505,"y":1887,"w":136,"h":142},
+ "frame": {"x":3,"y":1703,"w":136,"h":142},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
@@ -850,7 +858,7 @@
},
"sprites/buildings/miner.png":
{
- "frame": {"x":405,"y":1736,"w":136,"h":142},
+ "frame": {"x":143,"y":1703,"w":136,"h":142},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":136,"h":142},
@@ -898,7 +906,7 @@
},
"sprites/buildings/reader.png":
{
- "frame": {"x":937,"y":1315,"w":141,"h":144},
+ "frame": {"x":790,"y":1260,"w":141,"h":144},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":141,"h":144},
@@ -906,7 +914,7 @@
},
"sprites/buildings/rotater-ccw.png":
{
- "frame": {"x":1378,"y":1274,"w":141,"h":143},
+ "frame": {"x":1083,"y":1319,"w":141,"h":143},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
@@ -914,7 +922,7 @@
},
"sprites/buildings/rotater-fl.png":
{
- "frame": {"x":1231,"y":1411,"w":141,"h":143},
+ "frame": {"x":935,"y":1329,"w":141,"h":143},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":141,"h":143},
@@ -922,7 +930,7 @@
},
"sprites/buildings/rotater.png":
{
- "frame": {"x":1657,"y":1345,"w":141,"h":143},
+ "frame": {"x":1228,"y":1408,"w":141,"h":143},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":141,"h":143},
@@ -930,7 +938,7 @@
},
"sprites/buildings/splitter-compact-inverse.png":
{
- "frame": {"x":1082,"y":1461,"w":141,"h":136},
+ "frame": {"x":1080,"y":1466,"w":141,"h":136},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":3,"w":141,"h":136},
@@ -938,7 +946,7 @@
},
"sprites/buildings/splitter-compact-merge-inverse.png":
{
- "frame": {"x":1232,"y":1271,"w":142,"h":136},
+ "frame": {"x":559,"y":1178,"w":142,"h":136},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":3,"w":142,"h":136},
@@ -946,7 +954,7 @@
},
"sprites/buildings/splitter-compact-merge.png":
{
- "frame": {"x":1653,"y":1492,"w":139,"h":136},
+ "frame": {"x":1658,"y":1473,"w":139,"h":136},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":3,"w":139,"h":136},
@@ -954,7 +962,7 @@
},
"sprites/buildings/splitter-compact.png":
{
- "frame": {"x":1370,"y":1563,"w":139,"h":136},
+ "frame": {"x":1658,"y":1613,"w":139,"h":136},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":3,"w":139,"h":136},
@@ -994,7 +1002,7 @@
},
"sprites/buildings/underground_belt_entry-tier2.png":
{
- "frame": {"x":645,"y":1493,"w":137,"h":124},
+ "frame": {"x":3,"y":1476,"w":137,"h":124},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":20,"w":137,"h":124},
@@ -1002,7 +1010,7 @@
},
"sprites/buildings/underground_belt_entry.png":
{
- "frame": {"x":928,"y":1463,"w":137,"h":111},
+ "frame": {"x":283,"y":1414,"w":137,"h":111},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":33,"w":137,"h":111},
@@ -1010,7 +1018,7 @@
},
"sprites/buildings/underground_belt_exit-tier2.png":
{
- "frame": {"x":786,"y":1534,"w":137,"h":111},
+ "frame": {"x":283,"y":1529,"w":137,"h":111},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
@@ -1018,7 +1026,7 @@
},
"sprites/buildings/underground_belt_exit.png":
{
- "frame": {"x":645,"y":1621,"w":137,"h":111},
+ "frame": {"x":424,"y":1588,"w":137,"h":111},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":137,"h":111},
@@ -1042,12 +1050,20 @@
},
"sprites/buildings/virtual_processor-shapecompare.png":
{
- "frame": {"x":560,"y":1036,"w":143,"h":133},
+ "frame": {"x":1244,"y":1271,"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-stacker.png":
+{
+ "frame": {"x":3,"y":1849,"w":130,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":130,"h":144},
+ "sourceSize": {"w":144,"h":144}
+},
"sprites/buildings/virtual_processor-unstacker.png":
{
"frame": {"x":3,"y":1035,"w":144,"h":143},
@@ -1106,7 +1122,7 @@
},
"sprites/buildings/wire_tunnel.png":
{
- "frame": {"x":787,"y":1396,"w":137,"h":134},
+ "frame": {"x":559,"y":1318,"w":137,"h":134},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":5,"w":137,"h":134},
@@ -1138,7 +1154,7 @@
},
"sprites/misc/processor_disabled.png":
{
- "frame": {"x":3,"y":1958,"w":78,"h":81},
+ "frame": {"x":3,"y":1606,"w":78,"h":81},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":10,"y":10,"w":78,"h":81},
@@ -1186,7 +1202,7 @@
},
"sprites/misc/waypoint.png":
{
- "frame": {"x":292,"y":1834,"w":38,"h":48},
+ "frame": {"x":48,"y":1997,"w":38,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":38,"h":48},
@@ -1250,7 +1266,7 @@
},
"sprites/wires/display/white.png":
{
- "frame": {"x":145,"y":1836,"w":47,"h":47},
+ "frame": {"x":90,"y":1997,"w":47,"h":47},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
@@ -1258,7 +1274,7 @@
},
"sprites/wires/display/yellow.png":
{
- "frame": {"x":196,"y":1836,"w":47,"h":47},
+ "frame": {"x":141,"y":1997,"w":47,"h":47},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":1,"w":47,"h":47},
@@ -1290,7 +1306,7 @@
},
"sprites/wires/network_conflict.png":
{
- "frame": {"x":85,"y":1952,"w":47,"h":44},
+ "frame": {"x":192,"y":1997,"w":47,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":2,"w":47,"h":44},
@@ -1298,7 +1314,7 @@
},
"sprites/wires/network_empty.png":
{
- "frame": {"x":247,"y":1834,"w":41,"h":48},
+ "frame": {"x":3,"y":1997,"w":41,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":0,"w":41,"h":48},
@@ -1338,7 +1354,7 @@
},
"sprites/wires/sets/color_turn.png":
{
- "frame": {"x":707,"y":1122,"w":81,"h":81},
+ "frame": {"x":706,"y":1122,"w":81,"h":81},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
@@ -1370,7 +1386,7 @@
},
"sprites/wires/sets/conflict_turn.png":
{
- "frame": {"x":706,"y":1207,"w":81,"h":81},
+ "frame": {"x":705,"y":1207,"w":81,"h":81},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
@@ -1426,7 +1442,7 @@
},
"sprites/wires/sets/shape_split.png":
{
- "frame": {"x":1095,"y":1086,"w":144,"h":81},
+ "frame": {"x":1097,"y":1086,"w":144,"h":81},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":63,"w":144,"h":81},
@@ -1434,7 +1450,7 @@
},
"sprites/wires/sets/shape_turn.png":
{
- "frame": {"x":706,"y":1292,"w":81,"h":81},
+ "frame": {"x":705,"y":1292,"w":81,"h":81},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":63,"y":63,"w":81,"h":81},
@@ -1455,6 +1471,6 @@
"format": "RGBA8888",
"size": {"w":2048,"h":2048},
"scale": "0.75",
- "smartupdate": "$TexturePacker:SmartUpdate:26af492934beffa75e19b73e6d9eba56:d408ec7454e52a9384f80985bf14db9c:908b89f5ca8ff73e331a35a3b14d0604$"
+ "smartupdate": "$TexturePacker:SmartUpdate:d21082eda6f288e04b0739186004794d:0912211652d1c400e2846013f9de057b:908b89f5ca8ff73e331a35a3b14d0604$"
}
}
diff --git a/res_built/atlas/atlas0_hq.png b/res_built/atlas/atlas0_hq.png
index c7e5c979..f56d35d0 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 c4702db9..c5cdf2ad 100644
--- a/res_built/atlas/atlas0_lq.json
+++ b/res_built/atlas/atlas0_lq.json
@@ -10,7 +10,7 @@
},
"sprites/belt/built/forward_1.png":
{
- "frame": {"x":3,"y":939,"w":40,"h":48},
+ "frame": {"x":51,"y":961,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -18,7 +18,7 @@
},
"sprites/belt/built/forward_2.png":
{
- "frame": {"x":179,"y":969,"w":40,"h":48},
+ "frame": {"x":352,"y":869,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -26,7 +26,7 @@
},
"sprites/belt/built/forward_3.png":
{
- "frame": {"x":407,"y":864,"w":40,"h":48},
+ "frame": {"x":300,"y":891,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -34,7 +34,7 @@
},
"sprites/belt/built/forward_4.png":
{
- "frame": {"x":352,"y":869,"w":40,"h":48},
+ "frame": {"x":249,"y":893,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -42,7 +42,7 @@
},
"sprites/belt/built/forward_5.png":
{
- "frame": {"x":300,"y":883,"w":40,"h":48},
+ "frame": {"x":455,"y":864,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -50,7 +50,7 @@
},
"sprites/belt/built/forward_6.png":
{
- "frame": {"x":249,"y":893,"w":40,"h":48},
+ "frame": {"x":396,"y":912,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -58,7 +58,7 @@
},
"sprites/belt/built/forward_7.png":
{
- "frame": {"x":451,"y":864,"w":40,"h":48},
+ "frame": {"x":344,"y":921,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -66,7 +66,7 @@
},
"sprites/belt/built/forward_8.png":
{
- "frame": {"x":223,"y":945,"w":40,"h":48},
+ "frame": {"x":227,"y":973,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -74,7 +74,7 @@
},
"sprites/belt/built/forward_9.png":
{
- "frame": {"x":267,"y":945,"w":40,"h":48},
+ "frame": {"x":271,"y":973,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -82,7 +82,7 @@
},
"sprites/belt/built/forward_10.png":
{
- "frame": {"x":47,"y":961,"w":40,"h":48},
+ "frame": {"x":95,"y":961,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -90,7 +90,7 @@
},
"sprites/belt/built/forward_11.png":
{
- "frame": {"x":91,"y":961,"w":40,"h":48},
+ "frame": {"x":139,"y":941,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -98,7 +98,7 @@
},
"sprites/belt/built/forward_12.png":
{
- "frame": {"x":135,"y":941,"w":40,"h":48},
+ "frame": {"x":183,"y":917,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -106,7 +106,7 @@
},
"sprites/belt/built/forward_13.png":
{
- "frame": {"x":179,"y":917,"w":40,"h":48},
+ "frame": {"x":183,"y":969,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -130,7 +130,7 @@
},
"sprites/belt/built/left_2.png":
{
- "frame": {"x":223,"y":611,"w":44,"h":44},
+ "frame": {"x":323,"y":699,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -138,7 +138,7 @@
},
"sprites/belt/built/left_3.png":
{
- "frame": {"x":275,"y":659,"w":44,"h":44},
+ "frame": {"x":275,"y":707,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -146,7 +146,7 @@
},
"sprites/belt/built/left_4.png":
{
- "frame": {"x":323,"y":691,"w":44,"h":44},
+ "frame": {"x":323,"y":747,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -154,7 +154,7 @@
},
"sprites/belt/built/left_5.png":
{
- "frame": {"x":275,"y":707,"w":44,"h":44},
+ "frame": {"x":208,"y":759,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -162,7 +162,7 @@
},
"sprites/belt/built/left_6.png":
{
- "frame": {"x":323,"y":739,"w":44,"h":44},
+ "frame": {"x":156,"y":773,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -170,7 +170,7 @@
},
"sprites/belt/built/left_7.png":
{
- "frame": {"x":208,"y":759,"w":44,"h":44},
+ "frame": {"x":105,"y":797,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -178,7 +178,7 @@
},
"sprites/belt/built/left_8.png":
{
- "frame": {"x":156,"y":773,"w":44,"h":44},
+ "frame": {"x":54,"y":817,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -186,7 +186,7 @@
},
"sprites/belt/built/left_9.png":
{
- "frame": {"x":105,"y":797,"w":44,"h":44},
+ "frame": {"x":3,"y":843,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -194,7 +194,7 @@
},
"sprites/belt/built/left_10.png":
{
- "frame": {"x":325,"y":547,"w":44,"h":44},
+ "frame": {"x":465,"y":619,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -202,7 +202,7 @@
},
"sprites/belt/built/left_11.png":
{
- "frame": {"x":325,"y":595,"w":44,"h":44},
+ "frame": {"x":324,"y":651,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -210,7 +210,7 @@
},
"sprites/belt/built/left_12.png":
{
- "frame": {"x":464,"y":619,"w":44,"h":44},
+ "frame": {"x":223,"y":611,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -218,7 +218,7 @@
},
"sprites/belt/built/left_13.png":
{
- "frame": {"x":324,"y":643,"w":44,"h":44},
+ "frame": {"x":275,"y":659,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -226,7 +226,7 @@
},
"sprites/belt/built/right_0.png":
{
- "frame": {"x":54,"y":817,"w":44,"h":44},
+ "frame": {"x":311,"y":795,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -234,7 +234,7 @@
},
"sprites/belt/built/right_1.png":
{
- "frame": {"x":3,"y":843,"w":44,"h":44},
+ "frame": {"x":256,"y":797,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -242,7 +242,7 @@
},
"sprites/belt/built/right_2.png":
{
- "frame": {"x":102,"y":845,"w":44,"h":44},
+ "frame": {"x":3,"y":891,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -250,7 +250,7 @@
},
"sprites/belt/built/right_3.png":
{
- "frame": {"x":51,"y":865,"w":44,"h":44},
+ "frame": {"x":359,"y":821,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -258,7 +258,7 @@
},
"sprites/belt/built/right_4.png":
{
- "frame": {"x":3,"y":891,"w":44,"h":44},
+ "frame": {"x":304,"y":843,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -266,7 +266,7 @@
},
"sprites/belt/built/right_5.png":
{
- "frame": {"x":359,"y":821,"w":44,"h":44},
+ "frame": {"x":252,"y":845,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -274,7 +274,7 @@
},
"sprites/belt/built/right_6.png":
{
- "frame": {"x":304,"y":835,"w":44,"h":44},
+ "frame": {"x":201,"y":855,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -282,7 +282,7 @@
},
"sprites/belt/built/right_7.png":
{
- "frame": {"x":252,"y":845,"w":44,"h":44},
+ "frame": {"x":150,"y":869,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -290,7 +290,7 @@
},
"sprites/belt/built/right_8.png":
{
- "frame": {"x":201,"y":855,"w":44,"h":44},
+ "frame": {"x":99,"y":893,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -298,7 +298,7 @@
},
"sprites/belt/built/right_9.png":
{
- "frame": {"x":150,"y":869,"w":44,"h":44},
+ "frame": {"x":51,"y":913,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -306,7 +306,7 @@
},
"sprites/belt/built/right_10.png":
{
- "frame": {"x":311,"y":787,"w":44,"h":44},
+ "frame": {"x":204,"y":807,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -314,7 +314,7 @@
},
"sprites/belt/built/right_11.png":
{
- "frame": {"x":256,"y":797,"w":44,"h":44},
+ "frame": {"x":153,"y":821,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -322,7 +322,7 @@
},
"sprites/belt/built/right_12.png":
{
- "frame": {"x":204,"y":807,"w":44,"h":44},
+ "frame": {"x":102,"y":845,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -330,7 +330,7 @@
},
"sprites/belt/built/right_13.png":
{
- "frame": {"x":153,"y":821,"w":44,"h":44},
+ "frame": {"x":51,"y":865,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -338,7 +338,7 @@
},
"sprites/blueprints/belt_left.png":
{
- "frame": {"x":99,"y":893,"w":44,"h":44},
+ "frame": {"x":3,"y":939,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":4,"w":44,"h":44},
@@ -346,7 +346,7 @@
},
"sprites/blueprints/belt_right.png":
{
- "frame": {"x":51,"y":913,"w":44,"h":44},
+ "frame": {"x":407,"y":864,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -354,7 +354,7 @@
},
"sprites/blueprints/belt_top.png":
{
- "frame": {"x":311,"y":935,"w":40,"h":48},
+ "frame": {"x":315,"y":973,"w":40,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":40,"h":48},
@@ -656,6 +656,14 @@
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
"sourceSize": {"w":48,"h":48}
},
+"sprites/blueprints/virtual_processor-stacker.png":
+{
+ "frame": {"x":325,"y":547,"w":45,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":45,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
"sprites/blueprints/virtual_processor-unstacker.png":
{
"frame": {"x":107,"y":400,"w":48,"h":48},
@@ -706,7 +714,7 @@
},
"sprites/blueprints/wire_tunnel-coating.png":
{
- "frame": {"x":425,"y":655,"w":12,"h":46},
+ "frame": {"x":426,"y":655,"w":12,"h":46},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":18,"y":1,"w":12,"h":46},
@@ -730,7 +738,7 @@
},
"sprites/buildings/belt_right.png":
{
- "frame": {"x":54,"y":817,"w":44,"h":44},
+ "frame": {"x":311,"y":795,"w":44,"h":44},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":44,"h":44},
@@ -818,7 +826,7 @@
},
"sprites/buildings/logic_gate-transistor.png":
{
- "frame": {"x":425,"y":586,"w":35,"h":48},
+ "frame": {"x":426,"y":586,"w":35,"h":48},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":35,"h":48},
@@ -914,7 +922,7 @@
},
"sprites/buildings/rotater-fl.png":
{
- "frame": {"x":273,"y":555,"w":48,"h":48},
+ "frame": {"x":374,"y":567,"w":48,"h":48},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
@@ -922,7 +930,7 @@
},
"sprites/buildings/rotater.png":
{
- "frame": {"x":373,"y":567,"w":48,"h":48},
+ "frame": {"x":273,"y":555,"w":48,"h":48},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
@@ -986,7 +994,7 @@
},
"sprites/buildings/trash.png":
{
- "frame": {"x":373,"y":619,"w":48,"h":48},
+ "frame": {"x":374,"y":619,"w":48,"h":48},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
@@ -1048,6 +1056,14 @@
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":45},
"sourceSize": {"w":48,"h":48}
},
+"sprites/buildings/virtual_processor-stacker.png":
+{
+ "frame": {"x":325,"y":599,"w":45,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":45,"h":48},
+ "sourceSize": {"w":48,"h":48}
+},
"sprites/buildings/virtual_processor-unstacker.png":
{
"frame": {"x":272,"y":607,"w":48,"h":48},
@@ -1066,7 +1082,7 @@
},
"sprites/buildings/wire-cross.png":
{
- "frame": {"x":457,"y":667,"w":48,"h":48},
+ "frame": {"x":458,"y":667,"w":48,"h":48},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
@@ -1098,7 +1114,7 @@
},
"sprites/buildings/wire_tunnel-coating.png":
{
- "frame": {"x":441,"y":655,"w":12,"h":46},
+ "frame": {"x":442,"y":655,"w":12,"h":46},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":18,"y":1,"w":12,"h":46},
@@ -1162,7 +1178,7 @@
},
"sprites/misc/slot_bad_arrow.png":
{
- "frame": {"x":425,"y":638,"w":13,"h":13},
+ "frame": {"x":426,"y":638,"w":13,"h":13},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
@@ -1170,7 +1186,7 @@
},
"sprites/misc/slot_good_arrow.png":
{
- "frame": {"x":442,"y":638,"w":13,"h":13},
+ "frame": {"x":443,"y":638,"w":13,"h":13},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":13},
@@ -1378,7 +1394,7 @@
},
"sprites/wires/sets/regular_cross.png":
{
- "frame": {"x":457,"y":667,"w":48,"h":48},
+ "frame": {"x":458,"y":667,"w":48,"h":48},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
@@ -1455,6 +1471,6 @@
"format": "RGBA8888",
"size": {"w":512,"h":1024},
"scale": "0.25",
- "smartupdate": "$TexturePacker:SmartUpdate:26af492934beffa75e19b73e6d9eba56:d408ec7454e52a9384f80985bf14db9c:908b89f5ca8ff73e331a35a3b14d0604$"
+ "smartupdate": "$TexturePacker:SmartUpdate:d21082eda6f288e04b0739186004794d:0912211652d1c400e2846013f9de057b:908b89f5ca8ff73e331a35a3b14d0604$"
}
}
diff --git a/res_built/atlas/atlas0_lq.png b/res_built/atlas/atlas0_lq.png
index 7a18ee17..1c60156c 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 72e36985..c5d7d311 100644
--- a/res_built/atlas/atlas0_mq.json
+++ b/res_built/atlas/atlas0_mq.json
@@ -10,7 +10,7 @@
},
"sprites/belt/built/forward_1.png":
{
- "frame": {"x":891,"y":1749,"w":78,"h":96},
+ "frame": {"x":94,"y":1746,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -18,7 +18,7 @@
},
"sprites/belt/built/forward_2.png":
{
- "frame": {"x":331,"y":1550,"w":78,"h":96},
+ "frame": {"x":754,"y":1555,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -26,7 +26,7 @@
},
"sprites/belt/built/forward_3.png":
{
- "frame": {"x":413,"y":1639,"w":78,"h":96},
+ "frame": {"x":653,"y":1564,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -34,7 +34,7 @@
},
"sprites/belt/built/forward_4.png":
{
- "frame": {"x":495,"y":1695,"w":78,"h":96},
+ "frame": {"x":556,"y":1601,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -42,7 +42,7 @@
},
"sprites/belt/built/forward_5.png":
{
- "frame": {"x":577,"y":1756,"w":78,"h":96},
+ "frame": {"x":458,"y":1663,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -50,7 +50,7 @@
},
"sprites/belt/built/forward_6.png":
{
- "frame": {"x":659,"y":1834,"w":78,"h":96},
+ "frame": {"x":359,"y":1711,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -58,7 +58,7 @@
},
"sprites/belt/built/forward_7.png":
{
- "frame": {"x":3,"y":1934,"w":78,"h":96},
+ "frame": {"x":268,"y":1771,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -66,7 +66,7 @@
},
"sprites/belt/built/forward_8.png":
{
- "frame": {"x":85,"y":1538,"w":78,"h":96},
+ "frame": {"x":176,"y":1835,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -74,7 +74,7 @@
},
"sprites/belt/built/forward_9.png":
{
- "frame": {"x":3,"y":1538,"w":78,"h":96},
+ "frame": {"x":85,"y":1846,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -82,7 +82,7 @@
},
"sprites/belt/built/forward_10.png":
{
- "frame": {"x":3,"y":1438,"w":78,"h":96},
+ "frame": {"x":3,"y":1789,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -90,7 +90,7 @@
},
"sprites/belt/built/forward_11.png":
{
- "frame": {"x":85,"y":1438,"w":78,"h":96},
+ "frame": {"x":3,"y":1889,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -98,7 +98,7 @@
},
"sprites/belt/built/forward_12.png":
{
- "frame": {"x":167,"y":1502,"w":78,"h":96},
+ "frame": {"x":856,"y":1469,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -106,7 +106,7 @@
},
"sprites/belt/built/forward_13.png":
{
- "frame": {"x":249,"y":1530,"w":78,"h":96},
+ "frame": {"x":938,"y":1469,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -114,7 +114,7 @@
},
"sprites/belt/built/left_0.png":
{
- "frame": {"x":931,"y":903,"w":87,"h":87},
+ "frame": {"x":403,"y":911,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -122,7 +122,7 @@
},
"sprites/belt/built/left_1.png":
{
- "frame": {"x":403,"y":911,"w":87,"h":87},
+ "frame": {"x":403,"y":1002,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -130,7 +130,7 @@
},
"sprites/belt/built/left_2.png":
{
- "frame": {"x":663,"y":1383,"w":87,"h":87},
+ "frame": {"x":3,"y":1516,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -138,7 +138,7 @@
},
"sprites/belt/built/left_3.png":
{
- "frame": {"x":572,"y":1418,"w":87,"h":87},
+ "frame": {"x":785,"y":1191,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -146,7 +146,7 @@
},
"sprites/belt/built/left_4.png":
{
- "frame": {"x":754,"y":1461,"w":87,"h":87},
+ "frame": {"x":876,"y":1196,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -154,7 +154,7 @@
},
"sprites/belt/built/left_5.png":
{
- "frame": {"x":663,"y":1474,"w":87,"h":87},
+ "frame": {"x":785,"y":1282,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -162,7 +162,7 @@
},
"sprites/belt/built/left_6.png":
{
- "frame": {"x":845,"y":1464,"w":87,"h":87},
+ "frame": {"x":685,"y":1287,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -170,7 +170,7 @@
},
"sprites/belt/built/left_7.png":
{
- "frame": {"x":754,"y":1552,"w":87,"h":87},
+ "frame": {"x":583,"y":1325,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -178,7 +178,7 @@
},
"sprites/belt/built/left_8.png":
{
- "frame": {"x":845,"y":1555,"w":87,"h":87},
+ "frame": {"x":482,"y":1381,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -186,7 +186,7 @@
},
"sprites/belt/built/left_9.png":
{
- "frame": {"x":3,"y":1256,"w":87,"h":87},
+ "frame": {"x":383,"y":1429,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -194,7 +194,7 @@
},
"sprites/belt/built/left_10.png":
{
- "frame": {"x":401,"y":1002,"w":87,"h":87},
+ "frame": {"x":391,"y":1338,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -202,7 +202,7 @@
},
"sprites/belt/built/left_11.png":
{
- "frame": {"x":481,"y":1275,"w":87,"h":87},
+ "frame": {"x":292,"y":1398,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -210,7 +210,7 @@
},
"sprites/belt/built/left_12.png":
{
- "frame": {"x":572,"y":1327,"w":87,"h":87},
+ "frame": {"x":196,"y":1462,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -218,7 +218,7 @@
},
"sprites/belt/built/left_13.png":
{
- "frame": {"x":481,"y":1366,"w":87,"h":87},
+ "frame": {"x":99,"y":1473,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -226,7 +226,7 @@
},
"sprites/belt/built/right_0.png":
{
- "frame": {"x":94,"y":1256,"w":87,"h":87},
+ "frame": {"x":287,"y":1489,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -234,7 +234,7 @@
},
"sprites/belt/built/right_1.png":
{
- "frame": {"x":185,"y":1320,"w":87,"h":87},
+ "frame": {"x":190,"y":1553,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -242,7 +242,7 @@
},
"sprites/belt/built/right_2.png":
{
- "frame": {"x":640,"y":1565,"w":87,"h":87},
+ "frame": {"x":674,"y":1378,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -250,7 +250,7 @@
},
"sprites/belt/built/right_3.png":
{
- "frame": {"x":731,"y":1643,"w":87,"h":87},
+ "frame": {"x":573,"y":1416,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -258,7 +258,7 @@
},
"sprites/belt/built/right_4.png":
{
- "frame": {"x":822,"y":1646,"w":87,"h":87},
+ "frame": {"x":474,"y":1472,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -266,7 +266,7 @@
},
"sprites/belt/built/right_5.png":
{
- "frame": {"x":913,"y":1658,"w":87,"h":87},
+ "frame": {"x":378,"y":1520,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -274,7 +274,7 @@
},
"sprites/belt/built/right_6.png":
{
- "frame": {"x":3,"y":1347,"w":87,"h":87},
+ "frame": {"x":281,"y":1580,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -282,7 +282,7 @@
},
"sprites/belt/built/right_7.png":
{
- "frame": {"x":94,"y":1347,"w":87,"h":87},
+ "frame": {"x":185,"y":1644,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -290,7 +290,7 @@
},
"sprites/belt/built/right_8.png":
{
- "frame": {"x":185,"y":1411,"w":87,"h":87},
+ "frame": {"x":94,"y":1655,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -298,7 +298,7 @@
},
"sprites/belt/built/right_9.png":
{
- "frame": {"x":276,"y":1439,"w":87,"h":87},
+ "frame": {"x":3,"y":1698,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -306,7 +306,7 @@
},
"sprites/belt/built/right_10.png":
{
- "frame": {"x":276,"y":1348,"w":87,"h":87},
+ "frame": {"x":94,"y":1564,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -314,7 +314,7 @@
},
"sprites/belt/built/right_11.png":
{
- "frame": {"x":367,"y":1368,"w":87,"h":87},
+ "frame": {"x":3,"y":1607,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -322,7 +322,7 @@
},
"sprites/belt/built/right_12.png":
{
- "frame": {"x":458,"y":1457,"w":87,"h":87},
+ "frame": {"x":876,"y":1287,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -330,7 +330,7 @@
},
"sprites/belt/built/right_13.png":
{
- "frame": {"x":549,"y":1509,"w":87,"h":87},
+ "frame": {"x":776,"y":1373,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -338,7 +338,7 @@
},
"sprites/blueprints/belt_left.png":
{
- "frame": {"x":367,"y":1459,"w":87,"h":87},
+ "frame": {"x":867,"y":1378,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -346,7 +346,7 @@
},
"sprites/blueprints/belt_right.png":
{
- "frame": {"x":458,"y":1548,"w":87,"h":87},
+ "frame": {"x":765,"y":1464,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -354,7 +354,7 @@
},
"sprites/blueprints/belt_top.png":
{
- "frame": {"x":167,"y":1602,"w":78,"h":96},
+ "frame": {"x":85,"y":1946,"w":78,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":78,"h":96},
@@ -386,7 +386,7 @@
},
"sprites/blueprints/display.png":
{
- "frame": {"x":549,"y":1600,"w":86,"h":91},
+ "frame": {"x":664,"y":1469,"w":86,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":5,"y":5,"w":86,"h":91},
@@ -402,7 +402,7 @@
},
"sprites/blueprints/lever.png":
{
- "frame": {"x":85,"y":1638,"w":75,"h":86},
+ "frame": {"x":167,"y":1935,"w":75,"h":86},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":11,"y":3,"w":75,"h":86},
@@ -410,7 +410,7 @@
},
"sprites/blueprints/logic_gate-not.png":
{
- "frame": {"x":936,"y":1558,"w":83,"h":96},
+ "frame": {"x":469,"y":1563,"w":83,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":7,"y":0,"w":83,"h":96},
@@ -418,7 +418,7 @@
},
"sprites/blueprints/logic_gate-or.png":
{
- "frame": {"x":203,"y":861,"w":96,"h":82},
+ "frame": {"x":303,"y":903,"w":96,"h":82},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":82},
@@ -442,7 +442,7 @@
},
"sprites/blueprints/logic_gate.png":
{
- "frame": {"x":910,"y":1094,"w":96,"h":89},
+ "frame": {"x":910,"y":1103,"w":96,"h":89},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":89},
@@ -450,7 +450,7 @@
},
"sprites/blueprints/miner-chainable.png":
{
- "frame": {"x":585,"y":1227,"w":92,"h":96},
+ "frame": {"x":100,"y":1373,"w":92,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
@@ -458,7 +458,7 @@
},
"sprites/blueprints/miner.png":
{
- "frame": {"x":681,"y":1283,"w":92,"h":96},
+ "frame": {"x":3,"y":1416,"w":92,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":92,"h":96},
@@ -506,7 +506,7 @@
},
"sprites/blueprints/reader.png":
{
- "frame": {"x":203,"y":947,"w":95,"h":96},
+ "frame": {"x":3,"y":926,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
@@ -522,7 +522,7 @@
},
"sprites/blueprints/rotater-fl.png":
{
- "frame": {"x":302,"y":961,"w":95,"h":96},
+ "frame": {"x":102,"y":983,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
@@ -538,7 +538,7 @@
},
"sprites/blueprints/splitter-compact-inverse.png":
{
- "frame": {"x":399,"y":1093,"w":95,"h":93},
+ "frame": {"x":300,"y":1047,"w":95,"h":93},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":1,"w":95,"h":93},
@@ -546,7 +546,7 @@
},
"sprites/blueprints/splitter-compact-merge-inverse.png":
{
- "frame": {"x":498,"y":1099,"w":95,"h":93},
+ "frame": {"x":201,"y":1111,"w":95,"h":93},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":1,"w":95,"h":93},
@@ -554,7 +554,7 @@
},
"sprites/blueprints/splitter-compact-merge.png":
{
- "frame": {"x":794,"y":1270,"w":93,"h":93},
+ "frame": {"x":300,"y":1144,"w":93,"h":93},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":1,"w":93,"h":93},
@@ -562,7 +562,7 @@
},
"sprites/blueprints/splitter-compact.png":
{
- "frame": {"x":891,"y":1280,"w":93,"h":93},
+ "frame": {"x":201,"y":1208,"w":93,"h":93},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":1,"w":93,"h":93},
@@ -602,7 +602,7 @@
},
"sprites/blueprints/underground_belt_entry-tier2.png":
{
- "frame": {"x":294,"y":1161,"w":93,"h":84},
+ "frame": {"x":397,"y":1172,"w":93,"h":84},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":12,"w":93,"h":84},
@@ -610,7 +610,7 @@
},
"sprites/blueprints/underground_belt_entry.png":
{
- "frame": {"x":391,"y":1190,"w":93,"h":75},
+ "frame": {"x":298,"y":1241,"w":93,"h":75},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":21,"w":93,"h":75},
@@ -618,7 +618,7 @@
},
"sprites/blueprints/underground_belt_exit-tier2.png":
{
- "frame": {"x":794,"y":1191,"w":94,"h":75},
+ "frame": {"x":399,"y":1093,"w":94,"h":75},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":94,"h":75},
@@ -626,7 +626,7 @@
},
"sprites/blueprints/underground_belt_exit.png":
{
- "frame": {"x":488,"y":1196,"w":93,"h":75},
+ "frame": {"x":198,"y":1305,"w":93,"h":75},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":0,"w":93,"h":75},
@@ -642,7 +642,7 @@
},
"sprites/blueprints/virtual_processor-rotater.png":
{
- "frame": {"x":725,"y":1734,"w":79,"h":96},
+ "frame": {"x":276,"y":1671,"w":79,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":79,"h":96},
@@ -650,12 +650,20 @@
},
"sprites/blueprints/virtual_processor-shapecompare.png":
{
- "frame": {"x":910,"y":1187,"w":96,"h":89},
+ "frame": {"x":3,"y":774,"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-stacker.png":
+{
+ "frame": {"x":931,"y":903,"w":88,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":88,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
"sprites/blueprints/virtual_processor-unstacker.png":
{
"frame": {"x":349,"y":803,"w":96,"h":96},
@@ -682,7 +690,7 @@
},
"sprites/blueprints/wire-split.png":
{
- "frame": {"x":103,"y":866,"w":96,"h":55},
+ "frame": {"x":3,"y":867,"w":96,"h":55},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":41,"w":96,"h":55},
@@ -714,7 +722,7 @@
},
"sprites/blueprints/wire_tunnel.png":
{
- "frame": {"x":3,"y":1083,"w":93,"h":91},
+ "frame": {"x":101,"y":1278,"w":93,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":2,"w":93,"h":91},
@@ -722,7 +730,7 @@
},
"sprites/buildings/belt_left.png":
{
- "frame": {"x":931,"y":903,"w":87,"h":87},
+ "frame": {"x":403,"y":911,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":9,"w":87,"h":87},
@@ -730,7 +738,7 @@
},
"sprites/buildings/belt_right.png":
{
- "frame": {"x":94,"y":1256,"w":87,"h":87},
+ "frame": {"x":287,"y":1489,"w":87,"h":87},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":9,"w":87,"h":87},
@@ -770,7 +778,7 @@
},
"sprites/buildings/display.png":
{
- "frame": {"x":936,"y":1464,"w":84,"h":90},
+ "frame": {"x":565,"y":1507,"w":84,"h":90},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":6,"y":6,"w":84,"h":90},
@@ -802,7 +810,7 @@
},
"sprites/buildings/logic_gate-not.png":
{
- "frame": {"x":639,"y":1656,"w":82,"h":96},
+ "frame": {"x":372,"y":1611,"w":82,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":8,"y":0,"w":82,"h":96},
@@ -810,7 +818,7 @@
},
"sprites/buildings/logic_gate-or.png":
{
- "frame": {"x":203,"y":774,"w":96,"h":83},
+ "frame": {"x":203,"y":866,"w":96,"h":83},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":83},
@@ -834,7 +842,7 @@
},
"sprites/buildings/logic_gate.png":
{
- "frame": {"x":103,"y":774,"w":96,"h":88},
+ "frame": {"x":203,"y":774,"w":96,"h":88},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":88},
@@ -842,7 +850,7 @@
},
"sprites/buildings/miner-chainable.png":
{
- "frame": {"x":291,"y":1249,"w":91,"h":95},
+ "frame": {"x":690,"y":1188,"w":91,"h":95},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
@@ -850,7 +858,7 @@
},
"sprites/buildings/miner.png":
{
- "frame": {"x":386,"y":1269,"w":91,"h":95},
+ "frame": {"x":590,"y":1226,"w":91,"h":95},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":91,"h":95},
@@ -898,7 +906,7 @@
},
"sprites/buildings/reader.png":
{
- "frame": {"x":3,"y":983,"w":95,"h":96},
+ "frame": {"x":3,"y":1026,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
@@ -906,7 +914,7 @@
},
"sprites/buildings/rotater-ccw.png":
{
- "frame": {"x":102,"y":983,"w":95,"h":96},
+ "frame": {"x":201,"y":1011,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
@@ -914,7 +922,7 @@
},
"sprites/buildings/rotater-fl.png":
{
- "frame": {"x":201,"y":1047,"w":95,"h":96},
+ "frame": {"x":102,"y":1083,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":95,"h":96},
@@ -922,7 +930,7 @@
},
"sprites/buildings/rotater.png":
{
- "frame": {"x":300,"y":1061,"w":95,"h":96},
+ "frame": {"x":3,"y":1126,"w":95,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":0,"w":95,"h":96},
@@ -930,7 +938,7 @@
},
"sprites/buildings/splitter-compact-inverse.png":
{
- "frame": {"x":696,"y":1188,"w":94,"h":91},
+ "frame": {"x":3,"y":1226,"w":94,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":2,"w":94,"h":91},
@@ -938,7 +946,7 @@
},
"sprites/buildings/splitter-compact-merge-inverse.png":
{
- "frame": {"x":597,"y":1132,"w":95,"h":91},
+ "frame": {"x":102,"y":1183,"w":95,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":2,"w":95,"h":91},
@@ -946,7 +954,7 @@
},
"sprites/buildings/splitter-compact-merge.png":
{
- "frame": {"x":100,"y":1083,"w":93,"h":91},
+ "frame": {"x":3,"y":1321,"w":93,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":2,"w":93,"h":91},
@@ -954,7 +962,7 @@
},
"sprites/buildings/splitter-compact.png":
{
- "frame": {"x":197,"y":1147,"w":93,"h":91},
+ "frame": {"x":497,"y":1099,"w":93,"h":91},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":2,"w":93,"h":91},
@@ -994,7 +1002,7 @@
},
"sprites/buildings/underground_belt_entry-tier2.png":
{
- "frame": {"x":873,"y":1377,"w":92,"h":83},
+ "frame": {"x":494,"y":1194,"w":92,"h":83},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":13,"w":92,"h":83},
@@ -1002,7 +1010,7 @@
},
"sprites/buildings/underground_belt_entry.png":
{
- "frame": {"x":3,"y":1178,"w":92,"h":74},
+ "frame": {"x":395,"y":1260,"w":92,"h":74},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":22,"w":92,"h":74},
@@ -1010,7 +1018,7 @@
},
"sprites/buildings/underground_belt_exit-tier2.png":
{
- "frame": {"x":99,"y":1178,"w":92,"h":74},
+ "frame": {"x":295,"y":1320,"w":92,"h":74},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
@@ -1018,7 +1026,7 @@
},
"sprites/buildings/underground_belt_exit.png":
{
- "frame": {"x":195,"y":1242,"w":92,"h":74},
+ "frame": {"x":196,"y":1384,"w":92,"h":74},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":0,"w":92,"h":74},
@@ -1034,7 +1042,7 @@
},
"sprites/buildings/virtual_processor-rotater.png":
{
- "frame": {"x":808,"y":1737,"w":79,"h":96},
+ "frame": {"x":185,"y":1735,"w":79,"h":96},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":9,"y":0,"w":79,"h":96},
@@ -1042,12 +1050,20 @@
},
"sprites/buildings/virtual_processor-shapecompare.png":
{
- "frame": {"x":3,"y":774,"w":96,"h":89},
+ "frame": {"x":103,"y":774,"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-stacker.png":
+{
+ "frame": {"x":491,"y":1281,"w":88,"h":96},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":88,"h":96},
+ "sourceSize": {"w":96,"h":96}
+},
"sprites/buildings/virtual_processor-unstacker.png":
{
"frame": {"x":510,"y":999,"w":96,"h":96},
@@ -1074,7 +1090,7 @@
},
"sprites/buildings/wire-split.png":
{
- "frame": {"x":3,"y":867,"w":96,"h":54},
+ "frame": {"x":103,"y":867,"w":96,"h":54},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
@@ -1106,7 +1122,7 @@
},
"sprites/buildings/wire_tunnel.png":
{
- "frame": {"x":777,"y":1367,"w":92,"h":90},
+ "frame": {"x":594,"y":1132,"w":92,"h":90},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":3,"y":3,"w":92,"h":90},
@@ -1330,7 +1346,7 @@
},
"sprites/wires/sets/color_split.png":
{
- "frame": {"x":303,"y":903,"w":96,"h":54},
+ "frame": {"x":103,"y":925,"w":96,"h":54},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
@@ -1362,7 +1378,7 @@
},
"sprites/wires/sets/conflict_split.png":
{
- "frame": {"x":3,"y":925,"w":96,"h":54},
+ "frame": {"x":203,"y":953,"w":96,"h":54},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
@@ -1394,7 +1410,7 @@
},
"sprites/wires/sets/regular_split.png":
{
- "frame": {"x":3,"y":867,"w":96,"h":54},
+ "frame": {"x":103,"y":867,"w":96,"h":54},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
@@ -1410,7 +1426,7 @@
},
"sprites/wires/sets/shape_cross.png":
{
- "frame": {"x":922,"y":994,"w":96,"h":96},
+ "frame": {"x":922,"y":1003,"w":96,"h":96},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":96},
@@ -1426,7 +1442,7 @@
},
"sprites/wires/sets/shape_split.png":
{
- "frame": {"x":103,"y":925,"w":96,"h":54},
+ "frame": {"x":303,"y":989,"w":96,"h":54},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":42,"w":96,"h":54},
@@ -1455,6 +1471,6 @@
"format": "RGBA8888",
"size": {"w":1024,"h":2048},
"scale": "0.5",
- "smartupdate": "$TexturePacker:SmartUpdate:26af492934beffa75e19b73e6d9eba56:d408ec7454e52a9384f80985bf14db9c:908b89f5ca8ff73e331a35a3b14d0604$"
+ "smartupdate": "$TexturePacker:SmartUpdate:d21082eda6f288e04b0739186004794d:0912211652d1c400e2846013f9de057b:908b89f5ca8ff73e331a35a3b14d0604$"
}
}
diff --git a/res_built/atlas/atlas0_mq.png b/res_built/atlas/atlas0_mq.png
index 909d3b8b..9e9b97c6 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 730b130c..947bdb04 100644
--- a/res_raw/atlas.tps
+++ b/res_raw/atlas.tps
@@ -267,6 +267,7 @@
sprites/blueprints/logic_gate.png
sprites/blueprints/miner-chainable.png
sprites/blueprints/miner.png
+ sprites/blueprints/reader.png
sprites/blueprints/rotater-ccw.png
sprites/blueprints/rotater-fl.png
sprites/blueprints/rotater.png
@@ -295,6 +296,7 @@
sprites/buildings/logic_gate-xor.png
sprites/buildings/logic_gate.png
sprites/buildings/miner-chainable.png
+ sprites/buildings/reader.png
sprites/buildings/rotater-ccw.png
sprites/buildings/rotater-fl.png
sprites/buildings/splitter-compact-inverse.png
@@ -312,6 +314,7 @@
sprites/buildings/virtual_processor.png
sprites/buildings/wire_tunnel-coating.png
sprites/buildings/wire_tunnel.png
+ sprites/misc/reader_overlay.png
sprites/wires/lever_on.png
sprites/wires/sets/color_cross.png
sprites/wires/sets/color_forward.png
@@ -466,6 +469,8 @@
sprites/buildings/miner.png
sprites/buildings/rotater.png
sprites/buildings/trash.png
+ sprites/misc/processor_disabled.png
+ sprites/misc/processor_disconnected.png
sprites/wires/logical_acceptor.png
sprites/wires/logical_ejector.png
sprites/wires/overlay_tile.png
diff --git a/res_raw/sprites/blueprints/virtual_processor-stacker.png b/res_raw/sprites/blueprints/virtual_processor-stacker.png
new file mode 100644
index 00000000..1d691491
Binary files /dev/null and b/res_raw/sprites/blueprints/virtual_processor-stacker.png differ
diff --git a/res_raw/sprites/buildings/virtual_processor-stacker.png b/res_raw/sprites/buildings/virtual_processor-stacker.png
new file mode 100644
index 00000000..15882e63
Binary files /dev/null and b/res_raw/sprites/buildings/virtual_processor-stacker.png differ
diff --git a/src/js/core/config.local.js b/src/js/core/config.local.js
index 92d0c577..4f9735cd 100644
--- a/src/js/core/config.local.js
+++ b/src/js/core/config.local.js
@@ -104,8 +104,12 @@ export default {
// Renders information about wire networks
// renderWireNetworkInfos: true,
// -----------------------------------------------------------------------------------
- // Disables ejector animations and processing
- // disableEjectorProcessing: true,
+ // Disables ejector animations and processing
+ // disableEjectorProcessing: true,
+ // -----------------------------------------------------------------------------------
+ // Allows manual ticking
+ // manualTickOnly: true,
// -----------------------------------------------------------------------------------
+
/* dev:end */
};
diff --git a/src/js/game/belt_path.js b/src/js/game/belt_path.js
index 0e4b7b79..b999e0ee 100644
--- a/src/js/game/belt_path.js
+++ b/src/js/game/belt_path.js
@@ -1,1218 +1,1241 @@
-import { globalConfig } from "../core/config";
-import { DrawParameters } from "../core/draw_parameters";
-import { createLogger } from "../core/logging";
-import { Rectangle } from "../core/rectangle";
-import { epsilonCompare, round4Digits } from "../core/utils";
-import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
-import { BasicSerializableObject, types } from "../savegame/serialization";
-import { BaseItem } from "./base_item";
-import { Entity } from "./entity";
-import { typeItemSingleton } from "./item_resolver";
-import { GameRoot } from "./root";
-
-const logger = createLogger("belt_path");
-
-// Helpers for more semantic access into interleaved arrays
-const _nextDistance = 0;
-const _item = 1;
-
-const DEBUG = G_IS_DEV && false;
-
-/**
- * Stores a path of belts, used for optimizing performance
- */
-export class BeltPath extends BasicSerializableObject {
- static getId() {
- return "BeltPath";
- }
-
- static getSchema() {
- return {
- entityPath: types.array(types.entity),
- items: types.array(types.pair(types.ufloat, typeItemSingleton)),
- spacingToFirstItem: types.ufloat,
- };
- }
-
- /**
- * Creates a path from a serialized object
- * @param {GameRoot} root
- * @param {Object} data
- * @returns {BeltPath|string}
- */
- static fromSerialized(root, data) {
- // Create fake object which looks like a belt path but skips the constructor
- const fakeObject = /** @type {BeltPath} */ (Object.create(BeltPath.prototype));
- fakeObject.root = root;
-
- // Deserialize the data
- const errorCodeDeserialize = fakeObject.deserialize(data);
- if (errorCodeDeserialize) {
- return errorCodeDeserialize;
- }
-
- // Compute other properties
- fakeObject.init(false);
-
- return fakeObject;
- }
-
- /**
- * @param {GameRoot} root
- * @param {Array} entityPath
- */
- constructor(root, entityPath) {
- super();
- this.root = root;
-
- assert(entityPath.length > 0, "invalid entity path");
- this.entityPath = entityPath;
-
- /**
- * Stores the items sorted, and their distance to the previous item (or start)
- * Layout: [distanceToNext, item]
- * @type {Array<[number, BaseItem]>}
- */
- this.items = [];
-
- /**
- * Stores the spacing to the first item
- */
-
- this.init();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("constructor");
- }
- }
- /**
- * Initializes the path by computing the properties which are not saved
- * @param {boolean} computeSpacing Whether to also compute the spacing
- */
- init(computeSpacing = true) {
- this.onPathChanged();
-
- this.totalLength = this.computeTotalLength();
-
- if (computeSpacing) {
- this.spacingToFirstItem = this.totalLength;
- }
-
- /**
- * Current bounds of this path
- * @type {Rectangle}
- */
- this.worldBounds = this.computeBounds();
-
- // Connect the belts
- for (let i = 0; i < this.entityPath.length; ++i) {
- this.entityPath[i].components.Belt.assignedPath = this;
- }
- }
-
- /**
- * Returns whether this path can accept a new item
- * @returns {boolean}
- */
- canAcceptItem() {
- return this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts;
- }
-
- /**
- * Tries to accept the item
- * @param {BaseItem} item
- */
- tryAcceptItem(item) {
- if (this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts) {
- // So, since we already need one tick to accept this item we will add this directly.
- const beltProgressPerTick =
- this.root.hubGoals.getBeltBaseSpeed() *
- this.root.dynamicTickrate.deltaSeconds *
- globalConfig.itemSpacingOnBelts;
-
- // First, compute how much progress we can make *at max*
- const maxProgress = Math.max(0, this.spacingToFirstItem - globalConfig.itemSpacingOnBelts);
- const initialProgress = Math.min(maxProgress, beltProgressPerTick);
-
- this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
- this.spacingToFirstItem = initialProgress;
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("accept-item");
- }
-
- return true;
- }
- return false;
- }
-
- /**
- * SLOW / Tries to find the item closest to the given tile
- * @param {Vector} tile
- * @returns {BaseItem|null}
- */
- findItemAtTile(tile) {
- // @TODO: This breaks color blind mode otherwise
- return null;
- }
-
- /**
- * Computes the tile bounds of the path
- * @returns {Rectangle}
- */
- computeBounds() {
- let bounds = this.entityPath[0].components.StaticMapEntity.getTileSpaceBounds();
- for (let i = 1; i < this.entityPath.length; ++i) {
- const staticComp = this.entityPath[i].components.StaticMapEntity;
- const otherBounds = staticComp.getTileSpaceBounds();
- bounds = bounds.getUnion(otherBounds);
- }
- return bounds.allScaled(globalConfig.tileSize);
- }
-
- /**
- * Recomputes cache variables once the path was changed
- */
- onPathChanged() {
- this.acceptorTarget = this.computeAcceptingEntityAndSlot();
- }
-
- /**
- * Called by the belt system when the surroundings changed
- */
- onSurroundingsChanged() {
- this.onPathChanged();
- }
-
- /**
- * Finds the entity which accepts our items
- * @return {{ entity: Entity, slot: number, direction?: enumDirection }}
- */
- computeAcceptingEntityAndSlot() {
- const lastEntity = this.entityPath[this.entityPath.length - 1];
- const lastStatic = lastEntity.components.StaticMapEntity;
- const lastBeltComp = lastEntity.components.Belt;
-
- // Figure out where and into which direction we eject items
- const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
- const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
- const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
- const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
-
- // Try to find the given acceptor component to take the item
- const targetEntity = this.root.map.getLayerContentXY(
- ejectSlotTargetWsTile.x,
- ejectSlotTargetWsTile.y,
- "regular"
- );
-
- if (targetEntity) {
- const targetStaticComp = targetEntity.components.StaticMapEntity;
- const targetBeltComp = targetEntity.components.Belt;
-
- // Check for belts (special case)
- if (targetBeltComp) {
- const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
- if (ejectSlotWsDirection === beltAcceptingDirection) {
- return {
- entity: targetEntity,
- direction: null,
- slot: 0,
- };
- }
- }
-
- // Check for item acceptors
- const targetAcceptorComp = targetEntity.components.ItemAcceptor;
- if (!targetAcceptorComp) {
- // Entity doesn't accept items
- return;
- }
-
- const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
- const matchingSlot = targetAcceptorComp.findMatchingSlot(
- targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
- ejectingDirection
- );
-
- if (!matchingSlot) {
- // No matching slot found
- return;
- }
-
- return {
- entity: targetEntity,
- slot: matchingSlot.index,
- direction: enumInvertedDirections[ejectingDirection],
- };
- }
- }
-
- // Following code will be compiled out outside of dev versions
- /* dev:start */
-
- /**
- * Helper to throw an error on mismatch
- * @param {string} change
- * @param {Array} reason
- */
- debug_failIntegrity(change, ...reason) {
- throw new Error("belt path invalid (" + change + "): " + reason.map(i => "" + i).join(" "));
- }
-
- /**
- * Checks if this path is valid
- */
- debug_checkIntegrity(currentChange = "change") {
- const fail = (...args) => this.debug_failIntegrity(currentChange, ...args);
-
- // Check for empty path
- if (this.entityPath.length === 0) {
- return fail("Belt path is empty");
- }
-
- // Check for mismatching length
- const totalLength = this.computeTotalLength();
- if (!epsilonCompare(this.totalLength, totalLength, 0.01)) {
- return this.debug_failIntegrity(
- currentChange,
- "Total length mismatch, stored =",
- this.totalLength,
- "but correct is",
- totalLength
- );
- }
-
- // Check for misconnected entities
- for (let i = 0; i < this.entityPath.length - 1; ++i) {
- const entity = this.entityPath[i];
- if (entity.destroyed) {
- return fail("Reference to destroyed entity " + entity.uid);
- }
-
- const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
- if (!followUp) {
- return fail(
- "Follow up entity for the",
- i,
- "-th entity (total length",
- this.entityPath.length,
- ") was null!"
- );
- }
- if (followUp !== this.entityPath[i + 1]) {
- return fail(
- "Follow up entity mismatch, stored is",
- this.entityPath[i + 1].uid,
- "but real one is",
- followUp.uid
- );
- }
- if (entity.components.Belt.assignedPath !== this) {
- return fail(
- "Entity with uid",
- entity.uid,
- "doesn't have this path assigned, but this path contains the entity."
- );
- }
- }
-
- // Check spacing
- if (this.spacingToFirstItem > this.totalLength + 0.005) {
- return fail(
- currentChange,
- "spacing to first item (",
- this.spacingToFirstItem,
- ") is greater than total length (",
- this.totalLength,
- ")"
- );
- }
-
- // Check distance if empty
- if (this.items.length === 0 && !epsilonCompare(this.spacingToFirstItem, this.totalLength, 0.01)) {
- return fail(
- currentChange,
- "Path is empty but spacing to first item (",
- this.spacingToFirstItem,
- ") does not equal total length (",
- this.totalLength,
- ")"
- );
- }
-
- // Check items etc
- let currentPos = this.spacingToFirstItem;
- for (let i = 0; i < this.items.length; ++i) {
- const item = this.items[i];
-
- if (item[_nextDistance] < 0 || item[_nextDistance] > this.totalLength + 0.02) {
- return fail(
- "Item has invalid offset to next item: ",
- item[_nextDistance],
- "(total length:",
- this.totalLength,
- ")"
- );
- }
-
- currentPos += item[_nextDistance];
- }
-
- // Check the total sum matches
- if (!epsilonCompare(currentPos, this.totalLength, 0.01)) {
- return fail(
- "total sum (",
- currentPos,
- ") of first item spacing (",
- this.spacingToFirstItem,
- ") and items does not match total length (",
- this.totalLength,
- ") -> items: " + this.items.map(i => i[_nextDistance]).join("|")
- );
- }
-
- // Check bounds
- const actualBounds = this.computeBounds();
- if (!actualBounds.equalsEpsilon(this.worldBounds, 0.01)) {
- return fail("Bounds are stale");
- }
- }
-
- /* dev:end */
-
- /**
- * Extends the belt path by the given belt
- * @param {Entity} entity
- */
- extendOnEnd(entity) {
- DEBUG && logger.log("Extending belt path by entity at", entity.components.StaticMapEntity.origin);
-
- const beltComp = entity.components.Belt;
-
- // Append the entity
- this.entityPath.push(entity);
- this.onPathChanged();
-
- // Extend the path length
- const additionalLength = beltComp.getEffectiveLengthTiles();
- this.totalLength += additionalLength;
- DEBUG && logger.log(" Extended total length by", additionalLength, "to", this.totalLength);
-
- // If we have no item, just update the distance to the first item
- if (this.items.length === 0) {
- this.spacingToFirstItem = this.totalLength;
- DEBUG && logger.log(" Extended spacing to first to", this.totalLength, "(= total length)");
- } else {
- // Otherwise, update the next-distance of the last item
- const lastItem = this.items[this.items.length - 1];
- DEBUG &&
- logger.log(
- " Extended spacing of last item from",
- lastItem[_nextDistance],
- "to",
- lastItem[_nextDistance] + additionalLength
- );
- lastItem[_nextDistance] += additionalLength;
- }
-
- // Assign reference
- beltComp.assignedPath = this;
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("extend-on-end");
- }
- }
-
- /**
- * Extends the path with the given entity on the beginning
- * @param {Entity} entity
- */
- extendOnBeginning(entity) {
- const beltComp = entity.components.Belt;
-
- DEBUG && logger.log("Extending the path on the beginning");
-
- // All items on that belt are simply lost (for now)
-
- const length = beltComp.getEffectiveLengthTiles();
-
- // Extend the length of this path
- this.totalLength += length;
-
- // Simply adjust the first item spacing cuz we have no items contained
- this.spacingToFirstItem += length;
-
- // Set handles and append entity
- beltComp.assignedPath = this;
- this.entityPath.unshift(entity);
- this.onPathChanged();
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("extend-on-begin");
- }
- }
-
- /**
- * Returns if the given entity is the end entity of the path
- * @param {Entity} entity
- * @returns {boolean}
- */
- isEndEntity(entity) {
- return this.entityPath[this.entityPath.length - 1] === entity;
- }
-
- /**
- * Returns if the given entity is the start entity of the path
- * @param {Entity} entity
- * @returns {boolean}
- */
- isStartEntity(entity) {
- return this.entityPath[0] === entity;
- }
-
- /**
- * Splits this path at the given entity by removing it, and
- * returning the new secondary paht
- * @param {Entity} entity
- * @returns {BeltPath}
- */
- deleteEntityOnPathSplitIntoTwo(entity) {
- DEBUG && logger.log("Splitting path at entity", entity.components.StaticMapEntity.origin);
-
- // First, find where the current path ends
- const beltComp = entity.components.Belt;
- beltComp.assignedPath = null;
-
- const entityLength = beltComp.getEffectiveLengthTiles();
- assert(this.entityPath.indexOf(entity) >= 0, "Entity not contained for split");
- assert(this.entityPath.indexOf(entity) !== 0, "Entity is first");
- assert(this.entityPath.indexOf(entity) !== this.entityPath.length - 1, "Entity is last");
-
- let firstPathEntityCount = 0;
- let firstPathLength = 0;
- let firstPathEndEntity = null;
-
- for (let i = 0; i < this.entityPath.length; ++i) {
- const otherEntity = this.entityPath[i];
- if (otherEntity === entity) {
- DEBUG && logger.log("Found entity at", i, "of length", firstPathLength);
- break;
- }
-
- ++firstPathEntityCount;
- firstPathEndEntity = otherEntity;
- firstPathLength += otherEntity.components.Belt.getEffectiveLengthTiles();
- }
-
- DEBUG &&
- logger.log(
- "First path ends at",
- firstPathLength,
- "and entity",
- firstPathEndEntity.components.StaticMapEntity.origin,
- "and has",
- firstPathEntityCount,
- "entities"
- );
-
- // Compute length of second path
- const secondPathLength = this.totalLength - firstPathLength - entityLength;
- const secondPathStart = firstPathLength + entityLength;
- const secondEntities = this.entityPath.splice(firstPathEntityCount + 1);
- DEBUG &&
- logger.log(
- "Second path starts at",
- secondPathStart,
- "and has a length of ",
- secondPathLength,
- "with",
- secondEntities.length,
- "entities"
- );
-
- // Remove the last item
- this.entityPath.pop();
-
- DEBUG && logger.log("Splitting", this.items.length, "items");
- DEBUG &&
- logger.log(
- "Old items are",
- this.items.map(i => i[_nextDistance])
- );
-
- // Create second path
- const secondPath = new BeltPath(this.root, secondEntities);
-
- // Remove all items which are no longer relevant and transfer them to the second path
- let itemPos = this.spacingToFirstItem;
- for (let i = 0; i < this.items.length; ++i) {
- const item = this.items[i];
- const distanceToNext = item[_nextDistance];
-
- DEBUG && logger.log(" Checking item at", itemPos, "with distance of", distanceToNext, "to next");
-
- // Check if this item is past the first path
- if (itemPos >= firstPathLength) {
- // Remove it from the first path
- this.items.splice(i, 1);
- i -= 1;
- DEBUG &&
- logger.log(" Removed item from first path since its no longer contained @", itemPos);
-
- // Check if its on the second path (otherwise its on the removed belt and simply lost)
- if (itemPos >= secondPathStart) {
- // Put item on second path
- secondPath.items.push([distanceToNext, item[_item]]);
- DEBUG &&
- logger.log(
- " Put item to second path @",
- itemPos,
- "with distance to next =",
- distanceToNext
- );
-
- // If it was the first item, adjust the distance to the first item
- if (secondPath.items.length === 1) {
- DEBUG && logger.log(" Sinc it was the first, set sapcing of first to", itemPos);
- secondPath.spacingToFirstItem = itemPos - secondPathStart;
- }
- } else {
- DEBUG && logger.log(" Item was on the removed belt, so its gone - forever!");
- }
- } else {
- // Seems this item is on the first path (so all good), so just make sure it doesn't
- // have a nextDistance which is bigger than the total path length
- const clampedDistanceToNext = Math.min(itemPos + distanceToNext, firstPathLength) - itemPos;
- if (clampedDistanceToNext < distanceToNext) {
- DEBUG &&
- logger.log(
- "Correcting next distance (first path) from",
- distanceToNext,
- "to",
- clampedDistanceToNext
- );
- item[_nextDistance] = clampedDistanceToNext;
- }
- }
-
- // Advance items
- itemPos += distanceToNext;
- }
-
- DEBUG &&
- logger.log(
- "New items are",
- this.items.map(i => i[_nextDistance])
- );
-
- DEBUG &&
- logger.log(
- "And second path items are",
- secondPath.items.map(i => i[_nextDistance])
- );
-
- // Adjust our total length
- this.totalLength = firstPathLength;
-
- // Make sure that if we are empty, we set our first distance properly
- if (this.items.length === 0) {
- this.spacingToFirstItem = this.totalLength;
- }
-
- this.onPathChanged();
- secondPath.onPathChanged();
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("split-two-first");
- secondPath.debug_checkIntegrity("split-two-second");
- }
-
- return secondPath;
- }
-
- /**
- * Deletes the last entity
- * @param {Entity} entity
- */
- deleteEntityOnEnd(entity) {
- assert(
- this.entityPath[this.entityPath.length - 1] === entity,
- "Not actually the last entity (instead " + this.entityPath.indexOf(entity) + ")"
- );
-
- // Ok, first remove the entity
- const beltComp = entity.components.Belt;
- const beltLength = beltComp.getEffectiveLengthTiles();
-
- DEBUG &&
- logger.log(
- "Deleting last entity on path with length",
- this.entityPath.length,
- "(reducing",
- this.totalLength,
- " by",
- beltLength,
- ")"
- );
- this.totalLength -= beltLength;
- this.entityPath.pop();
- this.onPathChanged();
-
- DEBUG &&
- logger.log(
- " New path has length of",
- this.totalLength,
- "with",
- this.entityPath.length,
- "entities"
- );
-
- // This is just for sanity
- beltComp.assignedPath = null;
-
- // Clean up items
- if (this.items.length === 0) {
- // Simple case with no items, just update the first item spacing
- this.spacingToFirstItem = this.totalLength;
- } else {
- // Ok, make sure we simply drop all items which are no longer contained
- let itemOffset = this.spacingToFirstItem;
- let lastItemOffset = itemOffset;
-
- DEBUG && logger.log(" Adjusting", this.items.length, "items");
-
- for (let i = 0; i < this.items.length; ++i) {
- const item = this.items[i];
-
- // Get rid of items past this path
- if (itemOffset >= this.totalLength) {
- DEBUG && logger.log("Dropping item (current index=", i, ")");
- this.items.splice(i, 1);
- i -= 1;
- continue;
- }
-
- DEBUG && logger.log("Item", i, "is at", itemOffset, "with next offset", item[_nextDistance]);
- lastItemOffset = itemOffset;
- itemOffset += item[_nextDistance];
- }
-
- // If we still have an item, make sure the last item matches
- if (this.items.length > 0) {
- // We can easily compute the next distance since we know where the last item is now
- const lastDistance = this.totalLength - lastItemOffset;
- assert(
- lastDistance >= 0.0,
- "Last item distance mismatch: " +
- lastDistance +
- " -> Total length was " +
- this.totalLength +
- " and lastItemOffset was " +
- lastItemOffset
- );
-
- DEBUG &&
- logger.log(
- "Adjusted distance of last item: it is at",
- lastItemOffset,
- "so it has a distance of",
- lastDistance,
- "to the end (",
- this.totalLength,
- ")"
- );
- this.items[this.items.length - 1][_nextDistance] = lastDistance;
- } else {
- DEBUG && logger.log(" Removed all items so we'll update spacing to total length");
-
- // We removed all items so update our spacing
- this.spacingToFirstItem = this.totalLength;
- }
- }
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("delete-on-end");
- }
- }
-
- /**
- * Deletes the entity of the start of the path
- * @see deleteEntityOnEnd
- * @param {Entity} entity
- */
- deleteEntityOnStart(entity) {
- assert(
- entity === this.entityPath[0],
- "Not actually the start entity (instead " + this.entityPath.indexOf(entity) + ")"
- );
-
- // Ok, first remove the entity
- const beltComp = entity.components.Belt;
- const beltLength = beltComp.getEffectiveLengthTiles();
-
- DEBUG &&
- logger.log(
- "Deleting first entity on path with length",
- this.entityPath.length,
- "(reducing",
- this.totalLength,
- " by",
- beltLength,
- ")"
- );
- this.totalLength -= beltLength;
- this.entityPath.shift();
- this.onPathChanged();
-
- DEBUG &&
- logger.log(
- " New path has length of",
- this.totalLength,
- "with",
- this.entityPath.length,
- "entities"
- );
-
- // This is just for sanity
- beltComp.assignedPath = null;
-
- // Clean up items
- if (this.items.length === 0) {
- // Simple case with no items, just update the first item spacing
- this.spacingToFirstItem = this.totalLength;
- } else {
- // Simple case, we had no item on the beginning -> all good
- if (this.spacingToFirstItem >= beltLength) {
- DEBUG &&
- logger.log(
- " No item on the first place, so we can just adjust the spacing (spacing=",
- this.spacingToFirstItem,
- ") removed =",
- beltLength
- );
- this.spacingToFirstItem -= beltLength;
- } else {
- // Welp, okay we need to drop all items which are < beltLength and adjust
- // the other item offsets as well
-
- DEBUG &&
- logger.log(
- " We have at least one item in the beginning, drop those and adjust spacing (first item @",
- this.spacingToFirstItem,
- ") since we removed",
- beltLength,
- "length from path"
- );
- DEBUG &&
- logger.log(
- " Items:",
- this.items.map(i => i[_nextDistance])
- );
-
- // Find offset to first item
- let itemOffset = this.spacingToFirstItem;
- for (let i = 0; i < this.items.length; ++i) {
- const item = this.items[i];
- if (itemOffset <= beltLength) {
- DEBUG &&
- logger.log(
- " -> Dropping item with index",
- i,
- "at",
- itemOffset,
- "since it was on the removed belt"
- );
- // This item must be dropped
- this.items.splice(i, 1);
- i -= 1;
- itemOffset += item[_nextDistance];
- continue;
- } else {
- // This item can be kept, thus its the first we know
- break;
- }
- }
-
- if (this.items.length > 0) {
- DEBUG &&
- logger.log(
- " Offset of first non-dropped item was at:",
- itemOffset,
- "-> setting spacing to it (total length=",
- this.totalLength,
- ")"
- );
-
- this.spacingToFirstItem = itemOffset - beltLength;
- assert(
- this.spacingToFirstItem >= 0.0,
- "Invalid spacing after delete on start: " + this.spacingToFirstItem
- );
- } else {
- DEBUG && logger.log(" We dropped all items, simply set spacing to total length");
- // We dropped all items, simple one
- this.spacingToFirstItem = this.totalLength;
- }
- }
- }
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("delete-on-start");
- }
- }
-
- /**
- * Extends the path by the given other path
- * @param {BeltPath} otherPath
- */
- extendByPath(otherPath) {
- assert(otherPath !== this, "Circular path dependency");
-
- const entities = otherPath.entityPath;
- DEBUG && logger.log("Extending path by other path, starting to add entities");
-
- const oldLength = this.totalLength;
-
- DEBUG && logger.log(" Adding", entities.length, "new entities, current length =", this.totalLength);
-
- // First, append entities
- for (let i = 0; i < entities.length; ++i) {
- const entity = entities[i];
- const beltComp = entity.components.Belt;
-
- // Add to path and update references
- this.entityPath.push(entity);
- beltComp.assignedPath = this;
-
- // Update our length
- const additionalLength = beltComp.getEffectiveLengthTiles();
- this.totalLength += additionalLength;
- }
-
- DEBUG &&
- logger.log(
- " Path is now",
- this.entityPath.length,
- "entities and has a length of",
- this.totalLength
- );
-
- // Now, update the distance of our last item
- if (this.items.length !== 0) {
- const lastItem = this.items[this.items.length - 1];
- lastItem[_nextDistance] += otherPath.spacingToFirstItem;
- DEBUG &&
- logger.log(" Add distance to last item, effectively being", lastItem[_nextDistance], "now");
- } else {
- // Seems we have no items, update our first item distance
- this.spacingToFirstItem = oldLength + otherPath.spacingToFirstItem;
- DEBUG &&
- logger.log(
- " We had no items, so our new spacing to first is old length (",
- oldLength,
- ") plus others spacing to first (",
- otherPath.spacingToFirstItem,
- ") =",
- this.spacingToFirstItem
- );
- }
-
- DEBUG && logger.log(" Pushing", otherPath.items.length, "items from other path");
-
- // Aaand push the other paths items
- for (let i = 0; i < otherPath.items.length; ++i) {
- const item = otherPath.items[i];
- this.items.push([item[_nextDistance], item[_item]]);
- }
-
- // Update bounds
- this.worldBounds = this.computeBounds();
-
- this.onPathChanged();
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("extend-by-path");
- }
- }
-
- /**
- * Computes the total length of the path
- * @returns {number}
- */
- computeTotalLength() {
- let length = 0;
- for (let i = 0; i < this.entityPath.length; ++i) {
- const entity = this.entityPath[i];
- length += entity.components.Belt.getEffectiveLengthTiles();
- }
- return length;
- }
-
- /**
- * Performs one tick
- */
- update() {
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("pre-update");
- }
-
- // Divide by item spacing on belts since we use throughput and not speed
- let beltSpeed =
- this.root.hubGoals.getBeltBaseSpeed() *
- this.root.dynamicTickrate.deltaSeconds *
- globalConfig.itemSpacingOnBelts;
-
- if (G_IS_DEV && globalConfig.debug.instantBelts) {
- beltSpeed *= 100;
- }
-
- let minimumDistance = 0;
-
- // Try to reduce spacing
- let remainingAmount = beltSpeed;
- for (let i = this.items.length - 1; i >= 0; --i) {
- const nextDistanceAndItem = this.items[i];
- const minimumSpacing = minimumDistance;
-
- const takeAway = Math.max(
- 0,
- Math.min(remainingAmount, nextDistanceAndItem[_nextDistance] - minimumSpacing)
- );
-
- remainingAmount -= takeAway;
- nextDistanceAndItem[_nextDistance] -= takeAway;
-
- this.spacingToFirstItem += takeAway;
- if (remainingAmount < 0.01) {
- break;
- }
-
- minimumDistance = globalConfig.itemSpacingOnBelts;
- }
-
- // Check if we have an item which is ready to be emitted
- const lastItem = this.items[this.items.length - 1];
- if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
- if (this.tryHandOverItem(lastItem[_item])) {
- this.items.pop();
- }
- }
-
- if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
- this.debug_checkIntegrity("post-update");
- }
- }
-
- /**
- * Tries to hand over the item to the end entity
- * @param {BaseItem} item
- */
- tryHandOverItem(item) {
- if (!this.acceptorTarget) {
- return;
- }
-
- const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
-
- // Check if the acceptor has a filter for example
- if (targetAcceptorComp && !targetAcceptorComp.canAcceptItem(this.acceptorTarget.slot, item)) {
- // Well, this item is not accepted
- return false;
- }
-
- // Try to pass over
- if (
- this.root.systemMgr.systems.itemEjector.tryPassOverItem(
- item,
- this.acceptorTarget.entity,
- this.acceptorTarget.slot
- )
- ) {
- // Trigger animation on the acceptor comp
- const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
- if (targetAcceptorComp) {
- targetAcceptorComp.onItemAccepted(
- this.acceptorTarget.slot,
- this.acceptorTarget.direction,
- item
- );
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Computes a world space position from the given progress
- * @param {number} progress
- * @returns {Vector}
- */
- computePositionFromProgress(progress) {
- let currentLength = 0;
-
- // floating point issuses ..
- assert(progress <= this.totalLength + 0.02, "Progress too big: " + progress);
-
- for (let i = 0; i < this.entityPath.length; ++i) {
- const beltComp = this.entityPath[i].components.Belt;
- const localLength = beltComp.getEffectiveLengthTiles();
-
- if (currentLength + localLength >= progress || i === this.entityPath.length - 1) {
- // Min required here due to floating point issues
- const localProgress = Math.min(1.0, progress - currentLength);
-
- assert(localProgress >= 0.0, "Invalid local progress: " + localProgress);
- const localSpace = beltComp.transformBeltToLocalSpace(localProgress);
- return this.entityPath[i].components.StaticMapEntity.localTileToWorld(localSpace);
- }
- currentLength += localLength;
- }
-
- assert(false, "invalid progress: " + progress + " (max: " + this.totalLength + ")");
- }
-
- /**
- *
- * @param {DrawParameters} parameters
- */
- drawDebug(parameters) {
- if (!parameters.visibleRect.containsRect(this.worldBounds)) {
- return;
- }
-
- parameters.context.fillStyle = "#d79a25";
- parameters.context.strokeStyle = "#d79a25";
- parameters.context.beginPath();
-
- for (let i = 0; i < this.entityPath.length; ++i) {
- const entity = this.entityPath[i];
- const pos = entity.components.StaticMapEntity;
- const worldPos = pos.origin.toWorldSpaceCenterOfTile();
-
- if (i === 0) {
- parameters.context.moveTo(worldPos.x, worldPos.y);
- } else {
- parameters.context.lineTo(worldPos.x, worldPos.y);
- }
- }
- parameters.context.stroke();
-
- // Items
- let progress = this.spacingToFirstItem;
- for (let i = 0; i < this.items.length; ++i) {
- const nextDistanceAndItem = this.items[i];
- const worldPos = this.computePositionFromProgress(progress).toWorldSpaceCenterOfTile();
- parameters.context.fillStyle = "#268e4d";
- parameters.context.beginRoundedRect(worldPos.x - 5, worldPos.y - 5, 10, 10, 3);
- parameters.context.fill();
- parameters.context.font = "6px GameFont";
- parameters.context.fillStyle = "#111";
- parameters.context.fillText(
- "" + round4Digits(nextDistanceAndItem[_nextDistance]),
- worldPos.x + 5,
- worldPos.y + 2
- );
- progress += nextDistanceAndItem[_nextDistance];
- }
-
- for (let i = 0; i < this.entityPath.length; ++i) {
- const entity = this.entityPath[i];
- parameters.context.fillStyle = "#d79a25";
- const pos = entity.components.StaticMapEntity;
- const worldPos = pos.origin.toWorldSpaceCenterOfTile();
- parameters.context.beginCircle(worldPos.x, worldPos.y, i === 0 ? 5 : 3);
- parameters.context.fill();
- }
-
- for (let progress = 0; progress <= this.totalLength + 0.01; progress += 0.2) {
- const worldPos = this.computePositionFromProgress(progress).toWorldSpaceCenterOfTile();
- parameters.context.fillStyle = "red";
- parameters.context.beginCircle(worldPos.x, worldPos.y, 1);
- parameters.context.fill();
- }
-
- const firstItemIndicator = this.computePositionFromProgress(
- this.spacingToFirstItem
- ).toWorldSpaceCenterOfTile();
- parameters.context.fillStyle = "purple";
- parameters.context.fillRect(firstItemIndicator.x - 3, firstItemIndicator.y - 1, 6, 2);
- }
-
- /**
- * Draws the path
- * @param {DrawParameters} parameters
- */
- draw(parameters) {
- if (!parameters.visibleRect.containsRect(this.worldBounds)) {
- return;
- }
-
- if (this.items.length === 0) {
- // Early out
- return;
- }
-
- let currentItemPos = this.spacingToFirstItem;
- let currentItemIndex = 0;
-
- let trackPos = 0.0;
-
- // Iterate whole track and check items
- for (let i = 0; i < this.entityPath.length; ++i) {
- const entity = this.entityPath[i];
- const beltComp = entity.components.Belt;
- const beltLength = beltComp.getEffectiveLengthTiles();
-
- // Check if the current items are on the belt
- while (trackPos + beltLength >= currentItemPos - 1e-5) {
- // Its on the belt, render it now
- const staticComp = entity.components.StaticMapEntity;
- assert(
- currentItemPos - trackPos >= 0,
- "invalid track pos: " + currentItemPos + " vs " + trackPos + " (l =" + beltLength + ")"
- );
-
- const localPos = beltComp.transformBeltToLocalSpace(currentItemPos - trackPos);
- const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
-
- const distanceAndItem = this.items[currentItemIndex];
-
- distanceAndItem[_item].drawItemCenteredClipped(
- worldPos.x,
- worldPos.y,
- parameters,
- globalConfig.defaultItemDiameter
- );
-
- // Check for the next item
- currentItemPos += distanceAndItem[_nextDistance];
- ++currentItemIndex;
-
- if (currentItemIndex >= this.items.length) {
- // We rendered all items
- return;
- }
- }
-
- trackPos += beltLength;
- }
- }
-}
+import { globalConfig } from "../core/config";
+import { DrawParameters } from "../core/draw_parameters";
+import { createLogger } from "../core/logging";
+import { Rectangle } from "../core/rectangle";
+import { epsilonCompare, round4Digits, clamp } from "../core/utils";
+import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
+import { BasicSerializableObject, types } from "../savegame/serialization";
+import { BaseItem } from "./base_item";
+import { Entity } from "./entity";
+import { typeItemSingleton } from "./item_resolver";
+import { GameRoot } from "./root";
+
+const logger = createLogger("belt_path");
+
+// Helpers for more semantic access into interleaved arrays
+const _nextDistance = 0;
+const _item = 1;
+
+const DEBUG = G_IS_DEV && false;
+
+/**
+ * Stores a path of belts, used for optimizing performance
+ */
+export class BeltPath extends BasicSerializableObject {
+ static getId() {
+ return "BeltPath";
+ }
+
+ static getSchema() {
+ return {
+ entityPath: types.array(types.entity),
+ items: types.array(types.pair(types.ufloat, typeItemSingleton)),
+ spacingToFirstItem: types.ufloat,
+ };
+ }
+
+ /**
+ * Creates a path from a serialized object
+ * @param {GameRoot} root
+ * @param {Object} data
+ * @returns {BeltPath|string}
+ */
+ static fromSerialized(root, data) {
+ // Create fake object which looks like a belt path but skips the constructor
+ const fakeObject = /** @type {BeltPath} */ (Object.create(BeltPath.prototype));
+ fakeObject.root = root;
+
+ // Deserialize the data
+ const errorCodeDeserialize = fakeObject.deserialize(data);
+ if (errorCodeDeserialize) {
+ return errorCodeDeserialize;
+ }
+
+ // Compute other properties
+ fakeObject.init(false);
+
+ return fakeObject;
+ }
+
+ /**
+ * @param {GameRoot} root
+ * @param {Array} entityPath
+ */
+ constructor(root, entityPath) {
+ super();
+ this.root = root;
+
+ assert(entityPath.length > 0, "invalid entity path");
+ this.entityPath = entityPath;
+
+ /**
+ * Stores the items sorted, and their distance to the previous item (or start)
+ * Layout: [distanceToNext, item]
+ * @type {Array<[number, BaseItem]>}
+ */
+ this.items = [];
+
+ /**
+ * Stores the spacing to the first item
+ */
+
+ this.init();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("constructor");
+ }
+ }
+ /**
+ * Initializes the path by computing the properties which are not saved
+ * @param {boolean} computeSpacing Whether to also compute the spacing
+ */
+ init(computeSpacing = true) {
+ this.onPathChanged();
+
+ this.totalLength = this.computeTotalLength();
+
+ if (computeSpacing) {
+ this.spacingToFirstItem = this.totalLength;
+ }
+
+ /**
+ * Current bounds of this path
+ * @type {Rectangle}
+ */
+ this.worldBounds = this.computeBounds();
+
+ // Connect the belts
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ this.entityPath[i].components.Belt.assignedPath = this;
+ }
+ }
+
+ /**
+ * Returns whether this path can accept a new item
+ * @returns {boolean}
+ */
+ canAcceptItem() {
+ return this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts;
+ }
+
+ /**
+ * Tries to accept the item
+ * @param {BaseItem} item
+ */
+ tryAcceptItem(item) {
+ if (this.spacingToFirstItem >= globalConfig.itemSpacingOnBelts) {
+ // So, since we already need one tick to accept this item we will add this directly.
+ const beltProgressPerTick =
+ this.root.hubGoals.getBeltBaseSpeed() *
+ this.root.dynamicTickrate.deltaSeconds *
+ globalConfig.itemSpacingOnBelts;
+
+ // First, compute how much progress we can make *at max*
+ const maxProgress = Math.max(0, this.spacingToFirstItem - globalConfig.itemSpacingOnBelts);
+ const initialProgress = Math.min(maxProgress, beltProgressPerTick);
+
+ this.items.unshift([this.spacingToFirstItem - initialProgress, item]);
+ this.spacingToFirstItem = initialProgress;
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("accept-item");
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * SLOW / Tries to find the item closest to the given tile
+ * @param {Vector} tile
+ * @returns {BaseItem|null}
+ */
+ findItemAtTile(tile) {
+ // @TODO: This breaks color blind mode otherwise
+ return null;
+ }
+
+ /**
+ * Computes the tile bounds of the path
+ * @returns {Rectangle}
+ */
+ computeBounds() {
+ let bounds = this.entityPath[0].components.StaticMapEntity.getTileSpaceBounds();
+ for (let i = 1; i < this.entityPath.length; ++i) {
+ const staticComp = this.entityPath[i].components.StaticMapEntity;
+ const otherBounds = staticComp.getTileSpaceBounds();
+ bounds = bounds.getUnion(otherBounds);
+ }
+ return bounds.allScaled(globalConfig.tileSize);
+ }
+
+ /**
+ * Recomputes cache variables once the path was changed
+ */
+ onPathChanged() {
+ this.acceptorTarget = this.computeAcceptingEntityAndSlot();
+ }
+
+ /**
+ * Called by the belt system when the surroundings changed
+ */
+ onSurroundingsChanged() {
+ this.onPathChanged();
+ }
+
+ /**
+ * Finds the entity which accepts our items
+ * @return {{ entity: Entity, slot: number, direction?: enumDirection }}
+ */
+ computeAcceptingEntityAndSlot() {
+ const lastEntity = this.entityPath[this.entityPath.length - 1];
+ const lastStatic = lastEntity.components.StaticMapEntity;
+ const lastBeltComp = lastEntity.components.Belt;
+
+ // Figure out where and into which direction we eject items
+ const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
+ const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
+ const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
+ const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
+
+ // Try to find the given acceptor component to take the item
+ const targetEntity = this.root.map.getLayerContentXY(
+ ejectSlotTargetWsTile.x,
+ ejectSlotTargetWsTile.y,
+ "regular"
+ );
+
+ if (targetEntity) {
+ const targetStaticComp = targetEntity.components.StaticMapEntity;
+ const targetBeltComp = targetEntity.components.Belt;
+
+ // Check for belts (special case)
+ if (targetBeltComp) {
+ const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
+ if (ejectSlotWsDirection === beltAcceptingDirection) {
+ return {
+ entity: targetEntity,
+ direction: null,
+ slot: 0,
+ };
+ }
+ }
+
+ // Check for item acceptors
+ const targetAcceptorComp = targetEntity.components.ItemAcceptor;
+ if (!targetAcceptorComp) {
+ // Entity doesn't accept items
+ return;
+ }
+
+ const ejectingDirection = targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection);
+ const matchingSlot = targetAcceptorComp.findMatchingSlot(
+ targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
+ ejectingDirection
+ );
+
+ if (!matchingSlot) {
+ // No matching slot found
+ return;
+ }
+
+ return {
+ entity: targetEntity,
+ slot: matchingSlot.index,
+ direction: enumInvertedDirections[ejectingDirection],
+ };
+ }
+ }
+
+ // Following code will be compiled out outside of dev versions
+ /* dev:start */
+
+ /**
+ * Helper to throw an error on mismatch
+ * @param {string} change
+ * @param {Array} reason
+ */
+ debug_failIntegrity(change, ...reason) {
+ throw new Error("belt path invalid (" + change + "): " + reason.map(i => "" + i).join(" "));
+ }
+
+ /**
+ * Checks if this path is valid
+ */
+ debug_checkIntegrity(currentChange = "change") {
+ const fail = (...args) => this.debug_failIntegrity(currentChange, ...args);
+
+ // Check for empty path
+ if (this.entityPath.length === 0) {
+ return fail("Belt path is empty");
+ }
+
+ // Check for mismatching length
+ const totalLength = this.computeTotalLength();
+ if (!epsilonCompare(this.totalLength, totalLength, 0.01)) {
+ return this.debug_failIntegrity(
+ currentChange,
+ "Total length mismatch, stored =",
+ this.totalLength,
+ "but correct is",
+ totalLength
+ );
+ }
+
+ // Check for misconnected entities
+ for (let i = 0; i < this.entityPath.length - 1; ++i) {
+ const entity = this.entityPath[i];
+ if (entity.destroyed) {
+ return fail("Reference to destroyed entity " + entity.uid);
+ }
+
+ const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
+ if (!followUp) {
+ return fail(
+ "Follow up entity for the",
+ i,
+ "-th entity (total length",
+ this.entityPath.length,
+ ") was null!"
+ );
+ }
+ if (followUp !== this.entityPath[i + 1]) {
+ return fail(
+ "Follow up entity mismatch, stored is",
+ this.entityPath[i + 1].uid,
+ "but real one is",
+ followUp.uid
+ );
+ }
+ if (entity.components.Belt.assignedPath !== this) {
+ return fail(
+ "Entity with uid",
+ entity.uid,
+ "doesn't have this path assigned, but this path contains the entity."
+ );
+ }
+ }
+
+ // Check spacing
+ if (this.spacingToFirstItem > this.totalLength + 0.005) {
+ return fail(
+ currentChange,
+ "spacing to first item (",
+ this.spacingToFirstItem,
+ ") is greater than total length (",
+ this.totalLength,
+ ")"
+ );
+ }
+
+ // Check distance if empty
+ if (this.items.length === 0 && !epsilonCompare(this.spacingToFirstItem, this.totalLength, 0.01)) {
+ return fail(
+ currentChange,
+ "Path is empty but spacing to first item (",
+ this.spacingToFirstItem,
+ ") does not equal total length (",
+ this.totalLength,
+ ")"
+ );
+ }
+
+ // Check items etc
+ let currentPos = this.spacingToFirstItem;
+ for (let i = 0; i < this.items.length; ++i) {
+ const item = this.items[i];
+
+ if (item[_nextDistance] < 0 || item[_nextDistance] > this.totalLength + 0.02) {
+ return fail(
+ "Item has invalid offset to next item: ",
+ item[_nextDistance],
+ "(total length:",
+ this.totalLength,
+ ")"
+ );
+ }
+
+ currentPos += item[_nextDistance];
+ }
+
+ // Check the total sum matches
+ if (!epsilonCompare(currentPos, this.totalLength, 0.01)) {
+ return fail(
+ "total sum (",
+ currentPos,
+ ") of first item spacing (",
+ this.spacingToFirstItem,
+ ") and items does not match total length (",
+ this.totalLength,
+ ") -> items: " + this.items.map(i => i[_nextDistance]).join("|")
+ );
+ }
+
+ // Check bounds
+ const actualBounds = this.computeBounds();
+ if (!actualBounds.equalsEpsilon(this.worldBounds, 0.01)) {
+ return fail("Bounds are stale");
+ }
+ }
+
+ /* dev:end */
+
+ /**
+ * Extends the belt path by the given belt
+ * @param {Entity} entity
+ */
+ extendOnEnd(entity) {
+ DEBUG && logger.log("Extending belt path by entity at", entity.components.StaticMapEntity.origin);
+
+ const beltComp = entity.components.Belt;
+
+ // Append the entity
+ this.entityPath.push(entity);
+ this.onPathChanged();
+
+ // Extend the path length
+ const additionalLength = beltComp.getEffectiveLengthTiles();
+ this.totalLength += additionalLength;
+ DEBUG && logger.log(" Extended total length by", additionalLength, "to", this.totalLength);
+
+ // If we have no item, just update the distance to the first item
+ if (this.items.length === 0) {
+ this.spacingToFirstItem = this.totalLength;
+ DEBUG && logger.log(" Extended spacing to first to", this.totalLength, "(= total length)");
+ } else {
+ // Otherwise, update the next-distance of the last item
+ const lastItem = this.items[this.items.length - 1];
+ DEBUG &&
+ logger.log(
+ " Extended spacing of last item from",
+ lastItem[_nextDistance],
+ "to",
+ lastItem[_nextDistance] + additionalLength
+ );
+ lastItem[_nextDistance] += additionalLength;
+ }
+
+ // Assign reference
+ beltComp.assignedPath = this;
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("extend-on-end");
+ }
+ }
+
+ /**
+ * Extends the path with the given entity on the beginning
+ * @param {Entity} entity
+ */
+ extendOnBeginning(entity) {
+ const beltComp = entity.components.Belt;
+
+ DEBUG && logger.log("Extending the path on the beginning");
+
+ // All items on that belt are simply lost (for now)
+
+ const length = beltComp.getEffectiveLengthTiles();
+
+ // Extend the length of this path
+ this.totalLength += length;
+
+ // Simply adjust the first item spacing cuz we have no items contained
+ this.spacingToFirstItem += length;
+
+ // Set handles and append entity
+ beltComp.assignedPath = this;
+ this.entityPath.unshift(entity);
+ this.onPathChanged();
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("extend-on-begin");
+ }
+ }
+
+ /**
+ * Returns if the given entity is the end entity of the path
+ * @param {Entity} entity
+ * @returns {boolean}
+ */
+ isEndEntity(entity) {
+ return this.entityPath[this.entityPath.length - 1] === entity;
+ }
+
+ /**
+ * Returns if the given entity is the start entity of the path
+ * @param {Entity} entity
+ * @returns {boolean}
+ */
+ isStartEntity(entity) {
+ return this.entityPath[0] === entity;
+ }
+
+ /**
+ * Splits this path at the given entity by removing it, and
+ * returning the new secondary paht
+ * @param {Entity} entity
+ * @returns {BeltPath}
+ */
+ deleteEntityOnPathSplitIntoTwo(entity) {
+ DEBUG && logger.log("Splitting path at entity", entity.components.StaticMapEntity.origin);
+
+ // First, find where the current path ends
+ const beltComp = entity.components.Belt;
+ beltComp.assignedPath = null;
+
+ const entityLength = beltComp.getEffectiveLengthTiles();
+ assert(this.entityPath.indexOf(entity) >= 0, "Entity not contained for split");
+ assert(this.entityPath.indexOf(entity) !== 0, "Entity is first");
+ assert(this.entityPath.indexOf(entity) !== this.entityPath.length - 1, "Entity is last");
+
+ let firstPathEntityCount = 0;
+ let firstPathLength = 0;
+ let firstPathEndEntity = null;
+
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const otherEntity = this.entityPath[i];
+ if (otherEntity === entity) {
+ DEBUG && logger.log("Found entity at", i, "of length", firstPathLength);
+ break;
+ }
+
+ ++firstPathEntityCount;
+ firstPathEndEntity = otherEntity;
+ firstPathLength += otherEntity.components.Belt.getEffectiveLengthTiles();
+ }
+
+ DEBUG &&
+ logger.log(
+ "First path ends at",
+ firstPathLength,
+ "and entity",
+ firstPathEndEntity.components.StaticMapEntity.origin,
+ "and has",
+ firstPathEntityCount,
+ "entities"
+ );
+
+ // Compute length of second path
+ const secondPathLength = this.totalLength - firstPathLength - entityLength;
+ const secondPathStart = firstPathLength + entityLength;
+ const secondEntities = this.entityPath.splice(firstPathEntityCount + 1);
+ DEBUG &&
+ logger.log(
+ "Second path starts at",
+ secondPathStart,
+ "and has a length of ",
+ secondPathLength,
+ "with",
+ secondEntities.length,
+ "entities"
+ );
+
+ // Remove the last item
+ this.entityPath.pop();
+
+ DEBUG && logger.log("Splitting", this.items.length, "items");
+ DEBUG &&
+ logger.log(
+ "Old items are",
+ this.items.map(i => i[_nextDistance])
+ );
+
+ // Create second path
+ const secondPath = new BeltPath(this.root, secondEntities);
+
+ // Remove all items which are no longer relevant and transfer them to the second path
+ let itemPos = this.spacingToFirstItem;
+ for (let i = 0; i < this.items.length; ++i) {
+ const item = this.items[i];
+ const distanceToNext = item[_nextDistance];
+
+ DEBUG && logger.log(" Checking item at", itemPos, "with distance of", distanceToNext, "to next");
+
+ // Check if this item is past the first path
+ if (itemPos >= firstPathLength) {
+ // Remove it from the first path
+ this.items.splice(i, 1);
+ i -= 1;
+ DEBUG &&
+ logger.log(" Removed item from first path since its no longer contained @", itemPos);
+
+ // Check if its on the second path (otherwise its on the removed belt and simply lost)
+ if (itemPos >= secondPathStart) {
+ // Put item on second path
+ secondPath.items.push([distanceToNext, item[_item]]);
+ DEBUG &&
+ logger.log(
+ " Put item to second path @",
+ itemPos,
+ "with distance to next =",
+ distanceToNext
+ );
+
+ // If it was the first item, adjust the distance to the first item
+ if (secondPath.items.length === 1) {
+ DEBUG && logger.log(" Sinc it was the first, set sapcing of first to", itemPos);
+ secondPath.spacingToFirstItem = itemPos - secondPathStart;
+ }
+ } else {
+ DEBUG && logger.log(" Item was on the removed belt, so its gone - forever!");
+ }
+ } else {
+ // Seems this item is on the first path (so all good), so just make sure it doesn't
+ // have a nextDistance which is bigger than the total path length
+ const clampedDistanceToNext = Math.min(itemPos + distanceToNext, firstPathLength) - itemPos;
+ if (clampedDistanceToNext < distanceToNext) {
+ DEBUG &&
+ logger.log(
+ "Correcting next distance (first path) from",
+ distanceToNext,
+ "to",
+ clampedDistanceToNext
+ );
+ item[_nextDistance] = clampedDistanceToNext;
+ }
+ }
+
+ // Advance items
+ itemPos += distanceToNext;
+ }
+
+ DEBUG &&
+ logger.log(
+ "New items are",
+ this.items.map(i => i[_nextDistance])
+ );
+
+ DEBUG &&
+ logger.log(
+ "And second path items are",
+ secondPath.items.map(i => i[_nextDistance])
+ );
+
+ // Adjust our total length
+ this.totalLength = firstPathLength;
+
+ // Make sure that if we are empty, we set our first distance properly
+ if (this.items.length === 0) {
+ this.spacingToFirstItem = this.totalLength;
+ }
+
+ this.onPathChanged();
+ secondPath.onPathChanged();
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("split-two-first");
+ secondPath.debug_checkIntegrity("split-two-second");
+ }
+
+ return secondPath;
+ }
+
+ /**
+ * Deletes the last entity
+ * @param {Entity} entity
+ */
+ deleteEntityOnEnd(entity) {
+ assert(
+ this.entityPath[this.entityPath.length - 1] === entity,
+ "Not actually the last entity (instead " + this.entityPath.indexOf(entity) + ")"
+ );
+
+ // Ok, first remove the entity
+ const beltComp = entity.components.Belt;
+ const beltLength = beltComp.getEffectiveLengthTiles();
+
+ DEBUG &&
+ logger.log(
+ "Deleting last entity on path with length",
+ this.entityPath.length,
+ "(reducing",
+ this.totalLength,
+ " by",
+ beltLength,
+ ")"
+ );
+ this.totalLength -= beltLength;
+ this.entityPath.pop();
+ this.onPathChanged();
+
+ DEBUG &&
+ logger.log(
+ " New path has length of",
+ this.totalLength,
+ "with",
+ this.entityPath.length,
+ "entities"
+ );
+
+ // This is just for sanity
+ beltComp.assignedPath = null;
+
+ // Clean up items
+ if (this.items.length === 0) {
+ // Simple case with no items, just update the first item spacing
+ this.spacingToFirstItem = this.totalLength;
+ } else {
+ // Ok, make sure we simply drop all items which are no longer contained
+ let itemOffset = this.spacingToFirstItem;
+ let lastItemOffset = itemOffset;
+
+ DEBUG && logger.log(" Adjusting", this.items.length, "items");
+
+ for (let i = 0; i < this.items.length; ++i) {
+ const item = this.items[i];
+
+ // Get rid of items past this path
+ if (itemOffset >= this.totalLength) {
+ DEBUG && logger.log("Dropping item (current index=", i, ")");
+ this.items.splice(i, 1);
+ i -= 1;
+ continue;
+ }
+
+ DEBUG && logger.log("Item", i, "is at", itemOffset, "with next offset", item[_nextDistance]);
+ lastItemOffset = itemOffset;
+ itemOffset += item[_nextDistance];
+ }
+
+ // If we still have an item, make sure the last item matches
+ if (this.items.length > 0) {
+ // We can easily compute the next distance since we know where the last item is now
+ const lastDistance = this.totalLength - lastItemOffset;
+ assert(
+ lastDistance >= 0.0,
+ "Last item distance mismatch: " +
+ lastDistance +
+ " -> Total length was " +
+ this.totalLength +
+ " and lastItemOffset was " +
+ lastItemOffset
+ );
+
+ DEBUG &&
+ logger.log(
+ "Adjusted distance of last item: it is at",
+ lastItemOffset,
+ "so it has a distance of",
+ lastDistance,
+ "to the end (",
+ this.totalLength,
+ ")"
+ );
+ this.items[this.items.length - 1][_nextDistance] = lastDistance;
+ } else {
+ DEBUG && logger.log(" Removed all items so we'll update spacing to total length");
+
+ // We removed all items so update our spacing
+ this.spacingToFirstItem = this.totalLength;
+ }
+ }
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("delete-on-end");
+ }
+ }
+
+ /**
+ * Deletes the entity of the start of the path
+ * @see deleteEntityOnEnd
+ * @param {Entity} entity
+ */
+ deleteEntityOnStart(entity) {
+ assert(
+ entity === this.entityPath[0],
+ "Not actually the start entity (instead " + this.entityPath.indexOf(entity) + ")"
+ );
+
+ // Ok, first remove the entity
+ const beltComp = entity.components.Belt;
+ const beltLength = beltComp.getEffectiveLengthTiles();
+
+ DEBUG &&
+ logger.log(
+ "Deleting first entity on path with length",
+ this.entityPath.length,
+ "(reducing",
+ this.totalLength,
+ " by",
+ beltLength,
+ ")"
+ );
+ this.totalLength -= beltLength;
+ this.entityPath.shift();
+ this.onPathChanged();
+
+ DEBUG &&
+ logger.log(
+ " New path has length of",
+ this.totalLength,
+ "with",
+ this.entityPath.length,
+ "entities"
+ );
+
+ // This is just for sanity
+ beltComp.assignedPath = null;
+
+ // Clean up items
+ if (this.items.length === 0) {
+ // Simple case with no items, just update the first item spacing
+ this.spacingToFirstItem = this.totalLength;
+ } else {
+ // Simple case, we had no item on the beginning -> all good
+ if (this.spacingToFirstItem >= beltLength) {
+ DEBUG &&
+ logger.log(
+ " No item on the first place, so we can just adjust the spacing (spacing=",
+ this.spacingToFirstItem,
+ ") removed =",
+ beltLength
+ );
+ this.spacingToFirstItem -= beltLength;
+ } else {
+ // Welp, okay we need to drop all items which are < beltLength and adjust
+ // the other item offsets as well
+
+ DEBUG &&
+ logger.log(
+ " We have at least one item in the beginning, drop those and adjust spacing (first item @",
+ this.spacingToFirstItem,
+ ") since we removed",
+ beltLength,
+ "length from path"
+ );
+ DEBUG &&
+ logger.log(
+ " Items:",
+ this.items.map(i => i[_nextDistance])
+ );
+
+ // Find offset to first item
+ let itemOffset = this.spacingToFirstItem;
+ for (let i = 0; i < this.items.length; ++i) {
+ const item = this.items[i];
+ if (itemOffset <= beltLength) {
+ DEBUG &&
+ logger.log(
+ " -> Dropping item with index",
+ i,
+ "at",
+ itemOffset,
+ "since it was on the removed belt"
+ );
+ // This item must be dropped
+ this.items.splice(i, 1);
+ i -= 1;
+ itemOffset += item[_nextDistance];
+ continue;
+ } else {
+ // This item can be kept, thus its the first we know
+ break;
+ }
+ }
+
+ if (this.items.length > 0) {
+ DEBUG &&
+ logger.log(
+ " Offset of first non-dropped item was at:",
+ itemOffset,
+ "-> setting spacing to it (total length=",
+ this.totalLength,
+ ")"
+ );
+
+ this.spacingToFirstItem = itemOffset - beltLength;
+ assert(
+ this.spacingToFirstItem >= 0.0,
+ "Invalid spacing after delete on start: " + this.spacingToFirstItem
+ );
+ } else {
+ DEBUG && logger.log(" We dropped all items, simply set spacing to total length");
+ // We dropped all items, simple one
+ this.spacingToFirstItem = this.totalLength;
+ }
+ }
+ }
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("delete-on-start");
+ }
+ }
+
+ /**
+ * Extends the path by the given other path
+ * @param {BeltPath} otherPath
+ */
+ extendByPath(otherPath) {
+ assert(otherPath !== this, "Circular path dependency");
+
+ const entities = otherPath.entityPath;
+ DEBUG && logger.log("Extending path by other path, starting to add entities");
+
+ const oldLength = this.totalLength;
+
+ DEBUG && logger.log(" Adding", entities.length, "new entities, current length =", this.totalLength);
+
+ // First, append entities
+ for (let i = 0; i < entities.length; ++i) {
+ const entity = entities[i];
+ const beltComp = entity.components.Belt;
+
+ // Add to path and update references
+ this.entityPath.push(entity);
+ beltComp.assignedPath = this;
+
+ // Update our length
+ const additionalLength = beltComp.getEffectiveLengthTiles();
+ this.totalLength += additionalLength;
+ }
+
+ DEBUG &&
+ logger.log(
+ " Path is now",
+ this.entityPath.length,
+ "entities and has a length of",
+ this.totalLength
+ );
+
+ // Now, update the distance of our last item
+ if (this.items.length !== 0) {
+ const lastItem = this.items[this.items.length - 1];
+ lastItem[_nextDistance] += otherPath.spacingToFirstItem;
+ DEBUG &&
+ logger.log(" Add distance to last item, effectively being", lastItem[_nextDistance], "now");
+ } else {
+ // Seems we have no items, update our first item distance
+ this.spacingToFirstItem = oldLength + otherPath.spacingToFirstItem;
+ DEBUG &&
+ logger.log(
+ " We had no items, so our new spacing to first is old length (",
+ oldLength,
+ ") plus others spacing to first (",
+ otherPath.spacingToFirstItem,
+ ") =",
+ this.spacingToFirstItem
+ );
+ }
+
+ DEBUG && logger.log(" Pushing", otherPath.items.length, "items from other path");
+
+ // Aaand push the other paths items
+ for (let i = 0; i < otherPath.items.length; ++i) {
+ const item = otherPath.items[i];
+ this.items.push([item[_nextDistance], item[_item]]);
+ }
+
+ // Update bounds
+ this.worldBounds = this.computeBounds();
+
+ this.onPathChanged();
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("extend-by-path");
+ }
+ }
+
+ /**
+ * Computes the total length of the path
+ * @returns {number}
+ */
+ computeTotalLength() {
+ let length = 0;
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const entity = this.entityPath[i];
+ length += entity.components.Belt.getEffectiveLengthTiles();
+ }
+ return length;
+ }
+
+ /**
+ * Performs one tick
+ */
+ update() {
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("pre-update");
+ }
+
+ // Divide by item spacing on belts since we use throughput and not speed
+ let beltSpeed =
+ this.root.hubGoals.getBeltBaseSpeed() *
+ this.root.dynamicTickrate.deltaSeconds *
+ globalConfig.itemSpacingOnBelts;
+
+ if (G_IS_DEV && globalConfig.debug.instantBelts) {
+ beltSpeed *= 100;
+ }
+
+ // Store whether this is the first item we processed, so premature
+ // item ejection is available
+ let isFirstItemProcessed = true;
+
+ // Store how much velocity (strictly its distance, not velocity) we have to distribute over all items
+ let remainingVelocity = beltSpeed;
+
+ for (let i = this.items.length - 1; i >= 0; --i) {
+ const nextDistanceAndItem = this.items[i];
+
+ // Compute how much spacing we need at least
+ const minimumSpacing = i === this.items.length - 1 ? 0 : globalConfig.itemSpacingOnBelts;
+
+ // Compute how much we can advance
+ const clampedProgress = Math.max(
+ 0,
+ Math.min(remainingVelocity, nextDistanceAndItem[_nextDistance] - minimumSpacing)
+ );
+
+ // Reduce our velocity by the amount we consumed
+ remainingVelocity -= clampedProgress;
+
+ // Reduce the spacing
+ nextDistanceAndItem[_nextDistance] -= clampedProgress;
+
+ // If the last item can be ejected, eject it and reduce the spacing, because otherwise
+ // we lose velocity
+ if (isFirstItemProcessed && nextDistanceAndItem[_nextDistance] < 1e-7) {
+ // Store how much velocity we "lost" because we bumped the item to the end of the
+ // belt but couldn't move it any farther. We need this to tell the item acceptor
+ // animation to start a tad later, so everything matches up. Yes I'm a perfectionist.
+ const excessVelocity = beltSpeed - clampedProgress;
+
+ // Try to directly get rid of the item
+ if (this.tryHandOverItem(nextDistanceAndItem[_item], excessVelocity)) {
+ this.items.pop();
+ }
+ }
+
+ isFirstItemProcessed = false;
+ this.spacingToFirstItem += clampedProgress;
+ if (remainingVelocity < 0.01) {
+ break;
+ }
+ }
+
+ // Check if we have an item which is ready to be emitted
+ const lastItem = this.items[this.items.length - 1];
+ if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
+ if (this.tryHandOverItem(lastItem[_item])) {
+ this.items.pop();
+ }
+ }
+
+ if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
+ this.debug_checkIntegrity("post-update");
+ }
+ }
+
+ /**
+ * Tries to hand over the item to the end entity
+ * @param {BaseItem} item
+ */
+ tryHandOverItem(item, remainingProgress = 0.0) {
+ if (!this.acceptorTarget) {
+ return;
+ }
+
+ const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
+
+ // Check if the acceptor has a filter for example
+ if (targetAcceptorComp && !targetAcceptorComp.canAcceptItem(this.acceptorTarget.slot, item)) {
+ // Well, this item is not accepted
+ return false;
+ }
+
+ // Try to pass over
+ if (
+ this.root.systemMgr.systems.itemEjector.tryPassOverItem(
+ item,
+ this.acceptorTarget.entity,
+ this.acceptorTarget.slot
+ )
+ ) {
+ // Trigger animation on the acceptor comp
+ const targetAcceptorComp = this.acceptorTarget.entity.components.ItemAcceptor;
+ if (targetAcceptorComp) {
+ targetAcceptorComp.onItemAccepted(
+ this.acceptorTarget.slot,
+ this.acceptorTarget.direction,
+ item,
+ remainingProgress
+ );
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes a world space position from the given progress
+ * @param {number} progress
+ * @returns {Vector}
+ */
+ computePositionFromProgress(progress) {
+ let currentLength = 0;
+
+ // floating point issuses ..
+ assert(progress <= this.totalLength + 0.02, "Progress too big: " + progress);
+
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const beltComp = this.entityPath[i].components.Belt;
+ const localLength = beltComp.getEffectiveLengthTiles();
+
+ if (currentLength + localLength >= progress || i === this.entityPath.length - 1) {
+ // Min required here due to floating point issues
+ const localProgress = Math.min(1.0, progress - currentLength);
+
+ assert(localProgress >= 0.0, "Invalid local progress: " + localProgress);
+ const localSpace = beltComp.transformBeltToLocalSpace(localProgress);
+ return this.entityPath[i].components.StaticMapEntity.localTileToWorld(localSpace);
+ }
+ currentLength += localLength;
+ }
+
+ assert(false, "invalid progress: " + progress + " (max: " + this.totalLength + ")");
+ }
+
+ /**
+ *
+ * @param {DrawParameters} parameters
+ */
+ drawDebug(parameters) {
+ if (!parameters.visibleRect.containsRect(this.worldBounds)) {
+ return;
+ }
+
+ parameters.context.fillStyle = "#d79a25";
+ parameters.context.strokeStyle = "#d79a25";
+ parameters.context.beginPath();
+
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const entity = this.entityPath[i];
+ const pos = entity.components.StaticMapEntity;
+ const worldPos = pos.origin.toWorldSpaceCenterOfTile();
+
+ if (i === 0) {
+ parameters.context.moveTo(worldPos.x, worldPos.y);
+ } else {
+ parameters.context.lineTo(worldPos.x, worldPos.y);
+ }
+ }
+ parameters.context.stroke();
+
+ // Items
+ let progress = this.spacingToFirstItem;
+ for (let i = 0; i < this.items.length; ++i) {
+ const nextDistanceAndItem = this.items[i];
+ const worldPos = this.computePositionFromProgress(progress).toWorldSpaceCenterOfTile();
+ parameters.context.fillStyle = "#268e4d";
+ parameters.context.beginRoundedRect(worldPos.x - 5, worldPos.y - 5, 10, 10, 3);
+ parameters.context.fill();
+ parameters.context.font = "6px GameFont";
+ parameters.context.fillStyle = "#111";
+ parameters.context.fillText(
+ "" + round4Digits(nextDistanceAndItem[_nextDistance]),
+ worldPos.x + 5,
+ worldPos.y + 2
+ );
+ progress += nextDistanceAndItem[_nextDistance];
+ }
+
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const entity = this.entityPath[i];
+ parameters.context.fillStyle = "#d79a25";
+ const pos = entity.components.StaticMapEntity;
+ const worldPos = pos.origin.toWorldSpaceCenterOfTile();
+ parameters.context.beginCircle(worldPos.x, worldPos.y, i === 0 ? 5 : 3);
+ parameters.context.fill();
+ }
+
+ for (let progress = 0; progress <= this.totalLength + 0.01; progress += 0.2) {
+ const worldPos = this.computePositionFromProgress(progress).toWorldSpaceCenterOfTile();
+ parameters.context.fillStyle = "red";
+ parameters.context.beginCircle(worldPos.x, worldPos.y, 1);
+ parameters.context.fill();
+ }
+
+ const firstItemIndicator = this.computePositionFromProgress(
+ this.spacingToFirstItem
+ ).toWorldSpaceCenterOfTile();
+ parameters.context.fillStyle = "purple";
+ parameters.context.fillRect(firstItemIndicator.x - 3, firstItemIndicator.y - 1, 6, 2);
+ }
+
+ /**
+ * Draws the path
+ * @param {DrawParameters} parameters
+ */
+ draw(parameters) {
+ if (!parameters.visibleRect.containsRect(this.worldBounds)) {
+ return;
+ }
+
+ if (this.items.length === 0) {
+ // Early out
+ return;
+ }
+
+ let currentItemPos = this.spacingToFirstItem;
+ let currentItemIndex = 0;
+
+ let trackPos = 0.0;
+
+ // Iterate whole track and check items
+ for (let i = 0; i < this.entityPath.length; ++i) {
+ const entity = this.entityPath[i];
+ const beltComp = entity.components.Belt;
+ const beltLength = beltComp.getEffectiveLengthTiles();
+
+ // Check if the current items are on the belt
+ while (trackPos + beltLength >= currentItemPos - 1e-5) {
+ // Its on the belt, render it now
+ const staticComp = entity.components.StaticMapEntity;
+ assert(
+ currentItemPos - trackPos >= 0,
+ "invalid track pos: " + currentItemPos + " vs " + trackPos + " (l =" + beltLength + ")"
+ );
+
+ const localPos = beltComp.transformBeltToLocalSpace(currentItemPos - trackPos);
+ const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
+
+ const distanceAndItem = this.items[currentItemIndex];
+
+ distanceAndItem[_item].drawItemCenteredClipped(
+ worldPos.x,
+ worldPos.y,
+ parameters,
+ globalConfig.defaultItemDiameter
+ );
+
+ // Check for the next item
+ currentItemPos += distanceAndItem[_nextDistance];
+ ++currentItemIndex;
+
+ if (currentItemIndex >= this.items.length) {
+ // We rendered all items
+ return;
+ }
+ }
+
+ trackPos += beltLength;
+ }
+ }
+}
diff --git a/src/js/game/components/item_acceptor.js b/src/js/game/components/item_acceptor.js
index ebd7ae5f..3885eb1f 100644
--- a/src/js/game/components/item_acceptor.js
+++ b/src/js/game/components/item_acceptor.js
@@ -1,144 +1,150 @@
-import { enumDirection, enumInvertedDirections, Vector } from "../../core/vector";
-import { types } from "../../savegame/serialization";
-import { BaseItem } from "../base_item";
-import { Component } from "../component";
-
-/** @typedef {{
- * pos: Vector,
- * directions: enumDirection[],
- * filter?: ItemType
- * }} ItemAcceptorSlot */
-
-/**
- * Contains information about a slot plus its location
- * @typedef {{
- * slot: ItemAcceptorSlot,
- * index: number,
- * acceptedDirection: enumDirection
- * }} ItemAcceptorLocatedSlot */
-
-/** @typedef {{
- * pos: Vector,
- * directions: enumDirection[],
- * filter?: ItemType
- * }} ItemAcceptorSlotConfig */
-
-export class ItemAcceptorComponent extends Component {
- static getId() {
- return "ItemAcceptor";
- }
-
- duplicateWithoutContents() {
- const slotsCopy = [];
- for (let i = 0; i < this.slots.length; ++i) {
- const slot = this.slots[i];
- slotsCopy.push({
- pos: slot.pos.copy(),
- directions: slot.directions.slice(),
- filter: slot.filter,
- });
- }
-
- return new ItemAcceptorComponent({
- slots: slotsCopy,
- });
- }
-
- /**
- *
- * @param {object} param0
- * @param {Array} param0.slots The slots from which we accept items
- */
- constructor({ slots = [] }) {
- super();
-
- /**
- * Fixes belt animations
- * @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
- */
- this.itemConsumptionAnimations = [];
-
- this.setSlots(slots);
- }
-
- /**
- *
- * @param {Array} slots
- */
- setSlots(slots) {
- /** @type {Array} */
- this.slots = [];
- for (let i = 0; i < slots.length; ++i) {
- const slot = slots[i];
- this.slots.push({
- pos: slot.pos,
- directions: slot.directions,
-
- // Which type of item to accept (shape | color | all) @see ItemType
- filter: slot.filter,
- });
- }
- }
-
- /**
- * Returns if this acceptor can accept a new item at slot N
- * @param {number} slotIndex
- * @param {BaseItem=} item
- */
- canAcceptItem(slotIndex, item) {
- const slot = this.slots[slotIndex];
- return !slot.filter || slot.filter === item.getItemType();
- }
-
- /**
- * Called when an item has been accepted so that
- * @param {number} slotIndex
- * @param {enumDirection} direction
- * @param {BaseItem} item
- */
- onItemAccepted(slotIndex, direction, item) {
- this.itemConsumptionAnimations.push({
- item,
- slotIndex,
- direction,
- animProgress: 0.0,
- });
- }
-
- /**
- * Tries to find a slot which accepts the current item
- * @param {Vector} targetLocalTile
- * @param {enumDirection} fromLocalDirection
- * @returns {ItemAcceptorLocatedSlot|null}
- */
- findMatchingSlot(targetLocalTile, fromLocalDirection) {
- // We need to invert our direction since the acceptor specifies *from* which direction
- // it accepts items, but the ejector specifies *into* which direction it ejects items.
- // E.g.: Ejector ejects into "right" direction but acceptor accepts from "left" direction.
- const desiredDirection = enumInvertedDirections[fromLocalDirection];
-
- // Go over all slots and try to find a target slot
- for (let slotIndex = 0; slotIndex < this.slots.length; ++slotIndex) {
- const slot = this.slots[slotIndex];
-
- // Make sure the acceptor slot is on the right position
- if (!slot.pos.equals(targetLocalTile)) {
- continue;
- }
-
- // Check if the acceptor slot accepts items from our direction
- for (let i = 0; i < slot.directions.length; ++i) {
- // const localDirection = targetStaticComp.localDirectionToWorld(slot.directions[l]);
- if (desiredDirection === slot.directions[i]) {
- return {
- slot,
- index: slotIndex,
- acceptedDirection: desiredDirection,
- };
- }
- }
- }
-
- return null;
- }
-}
+import { enumDirection, enumInvertedDirections, Vector } from "../../core/vector";
+import { types } from "../../savegame/serialization";
+import { BaseItem } from "../base_item";
+import { Component } from "../component";
+
+/** @typedef {{
+ * pos: Vector,
+ * directions: enumDirection[],
+ * filter?: ItemType
+ * }} ItemAcceptorSlot */
+
+/**
+ * Contains information about a slot plus its location
+ * @typedef {{
+ * slot: ItemAcceptorSlot,
+ * index: number,
+ * acceptedDirection: enumDirection
+ * }} ItemAcceptorLocatedSlot */
+
+/** @typedef {{
+ * pos: Vector,
+ * directions: enumDirection[],
+ * filter?: ItemType
+ * }} ItemAcceptorSlotConfig */
+
+export class ItemAcceptorComponent extends Component {
+ static getId() {
+ return "ItemAcceptor";
+ }
+
+ duplicateWithoutContents() {
+ const slotsCopy = [];
+ for (let i = 0; i < this.slots.length; ++i) {
+ const slot = this.slots[i];
+ slotsCopy.push({
+ pos: slot.pos.copy(),
+ directions: slot.directions.slice(),
+ filter: slot.filter,
+ });
+ }
+
+ return new ItemAcceptorComponent({
+ slots: slotsCopy,
+ });
+ }
+
+ /**
+ *
+ * @param {object} param0
+ * @param {Array} param0.slots The slots from which we accept items
+ */
+ constructor({ slots = [] }) {
+ super();
+
+ /**
+ * Fixes belt animations
+ * @type {Array<{
+ * item: BaseItem,
+ * slotIndex: number,
+ * animProgress: number,
+ * direction: enumDirection
+ * }>}
+ */
+ this.itemConsumptionAnimations = [];
+
+ this.setSlots(slots);
+ }
+
+ /**
+ *
+ * @param {Array} slots
+ */
+ setSlots(slots) {
+ /** @type {Array} */
+ this.slots = [];
+ for (let i = 0; i < slots.length; ++i) {
+ const slot = slots[i];
+ this.slots.push({
+ pos: slot.pos,
+ directions: slot.directions,
+
+ // Which type of item to accept (shape | color | all) @see ItemType
+ filter: slot.filter,
+ });
+ }
+ }
+
+ /**
+ * Returns if this acceptor can accept a new item at slot N
+ * @param {number} slotIndex
+ * @param {BaseItem=} item
+ */
+ canAcceptItem(slotIndex, item) {
+ const slot = this.slots[slotIndex];
+ return !slot.filter || slot.filter === item.getItemType();
+ }
+
+ /**
+ * Called when an item has been accepted so that
+ * @param {number} slotIndex
+ * @param {enumDirection} direction
+ * @param {BaseItem} item
+ * @param {number} remainingProgress World space remaining progress, can be set to set the start position of the item
+ */
+ onItemAccepted(slotIndex, direction, item, remainingProgress = 0.0) {
+ this.itemConsumptionAnimations.push({
+ item,
+ slotIndex,
+ direction,
+ animProgress: Math.min(1, remainingProgress * 2),
+ });
+ }
+
+ /**
+ * Tries to find a slot which accepts the current item
+ * @param {Vector} targetLocalTile
+ * @param {enumDirection} fromLocalDirection
+ * @returns {ItemAcceptorLocatedSlot|null}
+ */
+ findMatchingSlot(targetLocalTile, fromLocalDirection) {
+ // We need to invert our direction since the acceptor specifies *from* which direction
+ // it accepts items, but the ejector specifies *into* which direction it ejects items.
+ // E.g.: Ejector ejects into "right" direction but acceptor accepts from "left" direction.
+ const desiredDirection = enumInvertedDirections[fromLocalDirection];
+
+ // Go over all slots and try to find a target slot
+ for (let slotIndex = 0; slotIndex < this.slots.length; ++slotIndex) {
+ const slot = this.slots[slotIndex];
+
+ // Make sure the acceptor slot is on the right position
+ if (!slot.pos.equals(targetLocalTile)) {
+ continue;
+ }
+
+ // Check if the acceptor slot accepts items from our direction
+ for (let i = 0; i < slot.directions.length; ++i) {
+ // const localDirection = targetStaticComp.localDirectionToWorld(slot.directions[l]);
+ if (desiredDirection === slot.directions[i]) {
+ return {
+ slot,
+ index: slotIndex,
+ acceptedDirection: desiredDirection,
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js
index c413fd8e..5d51b4a3 100644
--- a/src/js/game/components/item_processor.js
+++ b/src/js/game/components/item_processor.js
@@ -29,6 +29,17 @@ export const enumItemProcessorRequirements = {
filter: "filter",
};
+/** @typedef {{
+ * item: BaseItem,
+ * requiredSlot?: number,
+ * preferredSlot?: number
+ * }} EjectorItemToEject */
+
+/** @typedef {{
+ * remainingTime: number,
+ * items: Array,
+ * }} EjectorCharge */
+
export class ItemProcessorComponent extends Component {
static getId() {
return "ItemProcessor";
@@ -37,20 +48,6 @@ export class ItemProcessorComponent extends Component {
static getSchema() {
return {
nextOutputSlot: types.uint,
- inputSlots: types.array(
- types.structured({
- item: typeItemSingleton,
- sourceSlot: types.uint,
- })
- ),
- itemsToEject: types.array(
- types.structured({
- item: typeItemSingleton,
- requiredSlot: types.nullable(types.uint),
- preferredSlot: types.nullable(types.uint),
- })
- ),
- secondsUntilEject: types.float,
};
}
@@ -101,21 +98,15 @@ export class ItemProcessorComponent extends Component {
* What we are currently processing, empty if we don't produce anything rn
* requiredSlot: Item *must* be ejected on this slot
* preferredSlot: Item *can* be ejected on this slot, but others are fine too if the one is not usable
- * @type {Array<{item: BaseItem, requiredSlot?: number, preferredSlot?: number}>}
+ * @type {Array}
*/
- this.itemsToEject = [];
-
- /**
- * How long it takes until we are done with the current items
- * @type {number}
- */
- this.secondsUntilEject = 0;
+ this.ongoingCharges = [];
/**
* How much processing time we have left from the last tick
* @type {number}
*/
- this.bonusFromLastTick = 0;
+ this.bonusTime = 0;
}
/**
diff --git a/src/js/game/core.js b/src/js/game/core.js
index 54c0f3e9..642d8d9d 100644
--- a/src/js/game/core.js
+++ b/src/js/game/core.js
@@ -125,6 +125,24 @@ export class GameCore {
// @ts-ignore
window.globalRoot = root;
}
+
+ // @todo Find better place
+ if (G_IS_DEV && globalConfig.debug.manualTickOnly) {
+ this.root.gameState.inputReciever.keydown.add(key => {
+ if (key.keyCode === 84) {
+ // 'T'
+
+ // Extract current real time
+ this.root.time.updateRealtimeNow();
+
+ // Perform logic ticks
+ this.root.time.performTicks(this.root.dynamicTickrate.deltaMs, this.boundInternalTick);
+
+ // Update analytics
+ root.productionAnalytics.update();
+ }
+ });
+ }
}
/**
@@ -244,11 +262,13 @@ export class GameCore {
// Camera is always updated, no matter what
root.camera.update(deltaMs);
- // Perform logic ticks
- this.root.time.performTicks(deltaMs, this.boundInternalTick);
+ if (!(G_IS_DEV && globalConfig.debug.manualTickOnly)) {
+ // Perform logic ticks
+ this.root.time.performTicks(deltaMs, this.boundInternalTick);
- // Update analytics
- root.productionAnalytics.update();
+ // Update analytics
+ root.productionAnalytics.update();
+ }
// Update automatic save after everything finished
root.automaticSave.update();
diff --git a/src/js/game/dynamic_tickrate.js b/src/js/game/dynamic_tickrate.js
index a5033acf..3e29aba3 100644
--- a/src/js/game/dynamic_tickrate.js
+++ b/src/js/game/dynamic_tickrate.js
@@ -1,124 +1,125 @@
-import { GameRoot } from "./root";
-import { createLogger } from "../core/logging";
-import { globalConfig } from "../core/config";
-
-const logger = createLogger("dynamic_tickrate");
-
-const fpsAccumulationTime = 1000;
-
-export class DynamicTickrate {
- /**
- *
- * @param {GameRoot} root
- */
- constructor(root) {
- this.root = root;
-
- this.currentTickStart = null;
- this.capturedTicks = [];
- this.averageTickDuration = 0;
-
- this.accumulatedFps = 0;
- this.accumulatedFpsLastUpdate = 0;
-
- this.averageFps = 60;
-
- this.setTickRate(60);
-
- if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
- this.setTickRate(300);
- }
- }
-
- onFrameRendered() {
- ++this.accumulatedFps;
-
- const now = performance.now();
- const timeDuration = now - this.accumulatedFpsLastUpdate;
- if (timeDuration > fpsAccumulationTime) {
- const avgFps = (this.accumulatedFps / fpsAccumulationTime) * 1000;
- this.averageFps = avgFps;
- this.accumulatedFps = 0;
- this.accumulatedFpsLastUpdate = now;
- }
- }
-
- /**
- * Sets the tick rate to N updates per second
- * @param {number} rate
- */
- setTickRate(rate) {
- logger.log("Applying tick-rate of", rate);
- this.currentTickRate = rate;
- this.deltaMs = 1000.0 / this.currentTickRate;
- this.deltaSeconds = 1.0 / this.currentTickRate;
- }
-
- /**
- * Increases the tick rate marginally
- */
- increaseTickRate() {
- if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
- return;
- }
-
- const desiredFps = this.root.app.settings.getDesiredFps();
- this.setTickRate(Math.round(Math.min(desiredFps, this.currentTickRate * 1.2)));
- }
-
- /**
- * Decreases the tick rate marginally
- */
- decreaseTickRate() {
- if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
- return;
- }
-
- const desiredFps = this.root.app.settings.getDesiredFps();
- this.setTickRate(Math.round(Math.max(desiredFps / 2, this.currentTickRate * 0.8)));
- }
-
- /**
- * Call whenever a tick began
- */
- beginTick() {
- assert(this.currentTickStart === null, "BeginTick called twice");
- this.currentTickStart = performance.now();
-
- if (this.capturedTicks.length > this.currentTickRate * 2) {
- // Take only a portion of the ticks
- this.capturedTicks.sort();
- this.capturedTicks.splice(0, 10);
- this.capturedTicks.splice(this.capturedTicks.length - 11, 10);
-
- let average = 0;
- for (let i = 0; i < this.capturedTicks.length; ++i) {
- average += this.capturedTicks[i];
- }
- average /= this.capturedTicks.length;
-
- this.averageTickDuration = average;
-
- const desiredFps = this.root.app.settings.getDesiredFps();
-
- if (this.averageFps > desiredFps * 0.9) {
- // if (average < maxTickDuration) {
- this.increaseTickRate();
- } else if (this.averageFps < desiredFps * 0.7) {
- this.decreaseTickRate();
- }
-
- this.capturedTicks = [];
- }
- }
-
- /**
- * Call whenever a tick ended
- */
- endTick() {
- assert(this.currentTickStart !== null, "EndTick called without BeginTick");
- const duration = performance.now() - this.currentTickStart;
- this.capturedTicks.push(duration);
- this.currentTickStart = null;
- }
-}
+import { GameRoot } from "./root";
+import { createLogger } from "../core/logging";
+import { globalConfig } from "../core/config";
+
+const logger = createLogger("dynamic_tickrate");
+
+const fpsAccumulationTime = 1000;
+
+export class DynamicTickrate {
+ /**
+ *
+ * @param {GameRoot} root
+ */
+ constructor(root) {
+ this.root = root;
+
+ this.currentTickStart = null;
+ this.capturedTicks = [];
+ this.averageTickDuration = 0;
+
+ this.accumulatedFps = 0;
+ this.accumulatedFpsLastUpdate = 0;
+
+ this.averageFps = 60;
+
+ this.setTickRate(this.root.app.settings.getDesiredFps());
+
+ if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
+ this.setTickRate(300);
+ }
+ }
+
+ onFrameRendered() {
+ ++this.accumulatedFps;
+
+ const now = performance.now();
+ const timeDuration = now - this.accumulatedFpsLastUpdate;
+ if (timeDuration > fpsAccumulationTime) {
+ const avgFps = (this.accumulatedFps / fpsAccumulationTime) * 1000;
+ this.averageFps = avgFps;
+ this.accumulatedFps = 0;
+ this.accumulatedFpsLastUpdate = now;
+ }
+ }
+
+ /**
+ * Sets the tick rate to N updates per second
+ * @param {number} rate
+ */
+ setTickRate(rate) {
+ logger.log("Applying tick-rate of", rate);
+ this.currentTickRate = rate;
+ this.deltaMs = 1000.0 / this.currentTickRate;
+ this.deltaSeconds = 1.0 / this.currentTickRate;
+ }
+
+ /**
+ * Increases the tick rate marginally
+ */
+ increaseTickRate() {
+ if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
+ return;
+ }
+
+ const desiredFps = this.root.app.settings.getDesiredFps();
+ this.setTickRate(Math.round(Math.min(desiredFps, this.currentTickRate * 1.2)));
+ }
+
+ /**
+ * Decreases the tick rate marginally
+ */
+ decreaseTickRate() {
+ if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
+ return;
+ }
+
+ const desiredFps = this.root.app.settings.getDesiredFps();
+ this.setTickRate(Math.round(Math.max(desiredFps / 2, this.currentTickRate * 0.8)));
+ }
+
+ /**
+ * Call whenever a tick began
+ */
+ beginTick() {
+ assert(this.currentTickStart === null, "BeginTick called twice");
+ this.currentTickStart = performance.now();
+
+ if (this.capturedTicks.length > this.currentTickRate * 2) {
+ // Take only a portion of the ticks
+ this.capturedTicks.sort();
+ this.capturedTicks.splice(0, 10);
+ this.capturedTicks.splice(this.capturedTicks.length - 11, 10);
+
+ let average = 0;
+ for (let i = 0; i < this.capturedTicks.length; ++i) {
+ average += this.capturedTicks[i];
+ }
+ average /= this.capturedTicks.length;
+
+ this.averageTickDuration = average;
+
+ const desiredFps = this.root.app.settings.getDesiredFps();
+
+ // Disabled for now: Dynamicall adjusting tick rate
+ // if (this.averageFps > desiredFps * 0.9) {
+ // // if (average < maxTickDuration) {
+ // this.increaseTickRate();
+ // } else if (this.averageFps < desiredFps * 0.7) {
+ // this.decreaseTickRate();
+ // }
+
+ this.capturedTicks = [];
+ }
+ }
+
+ /**
+ * Call whenever a tick ended
+ */
+ endTick() {
+ assert(this.currentTickStart !== null, "EndTick called without BeginTick");
+ const duration = performance.now() - this.currentTickStart;
+ this.capturedTicks.push(duration);
+ this.currentTickStart = null;
+ }
+}
diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js
index bcbb7ccf..b0ae46f2 100644
--- a/src/js/game/game_system_manager.js
+++ b/src/js/game/game_system_manager.js
@@ -110,6 +110,10 @@ export class GameSystemManager {
// Order is important!
+ // IMPORTANT: Item acceptor must be before the belt, because it may not tick after the belt
+ // has put in the item into the acceptor animation, otherwise its off
+ add("itemAcceptor", ItemAcceptorSystem);
+
add("belt", BeltSystem);
add("undergroundBelt", UndergroundBeltSystem);
@@ -134,11 +138,6 @@ export class GameSystemManager {
add("constantSignal", ConstantSignalSystem);
- // IMPORTANT: Must be after belt system since belt system can change the
- // orientation of an entity after it is placed -> the item acceptor cache
- // then would be invalid
- add("itemAcceptor", ItemAcceptorSystem);
-
// WIRES section
add("lever", LeverSystem);
diff --git a/src/js/game/systems/item_acceptor.js b/src/js/game/systems/item_acceptor.js
index 745da4bf..6d6fec77 100644
--- a/src/js/game/systems/item_acceptor.js
+++ b/src/js/game/systems/item_acceptor.js
@@ -1,82 +1,80 @@
-import { globalConfig } from "../../core/config";
-import { DrawParameters } from "../../core/draw_parameters";
-import { fastArrayDelete } from "../../core/utils";
-import { enumDirectionToVector } from "../../core/vector";
-import { ItemAcceptorComponent } from "../components/item_acceptor";
-import { GameSystemWithFilter } from "../game_system_with_filter";
-import { MapChunkView } from "../map_chunk_view";
-
-export class ItemAcceptorSystem extends GameSystemWithFilter {
- constructor(root) {
- super(root, [ItemAcceptorComponent]);
- }
-
- update() {
- const progress = this.root.dynamicTickrate.deltaSeconds * 2; // * 2 because its only a half tile
-
- for (let i = 0; i < this.allEntities.length; ++i) {
- const entity = this.allEntities[i];
- const aceptorComp = entity.components.ItemAcceptor;
- const animations = aceptorComp.itemConsumptionAnimations;
-
- // Process item consumption animations to avoid items popping from the belts
- for (let animIndex = 0; animIndex < animations.length; ++animIndex) {
- const anim = animations[animIndex];
- anim.animProgress +=
- progress * this.root.hubGoals.getBeltBaseSpeed() * globalConfig.itemSpacingOnBelts;
- if (anim.animProgress > 1) {
- // Original
- // animations.splice(animIndex, 1);
-
- // Faster variant
- fastArrayDelete(animations, animIndex);
-
- animIndex -= 1;
- }
- }
- }
- }
-
- /**
- * @param {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 acceptorComp = entity.components.ItemAcceptor;
- if (!acceptorComp) {
- continue;
- }
-
- const staticComp = entity.components.StaticMapEntity;
- for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
- const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
- animIndex
- ];
-
- const slotData = acceptorComp.slots[slotIndex];
- const realSlotPos = staticComp.localTileToWorld(slotData.pos);
-
- if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
- // Not within this chunk
- continue;
- }
-
- const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
- const finalTile = realSlotPos.subScalars(
- fadeOutDirection.x * (animProgress / 2 - 0.5),
- fadeOutDirection.y * (animProgress / 2 - 0.5)
- );
-
- item.drawItemCenteredClipped(
- (finalTile.x + 0.5) * globalConfig.tileSize,
- (finalTile.y + 0.5) * globalConfig.tileSize,
- parameters,
- globalConfig.defaultItemDiameter
- );
- }
- }
- }
-}
+import { globalConfig } from "../../core/config";
+import { DrawParameters } from "../../core/draw_parameters";
+import { fastArrayDelete } from "../../core/utils";
+import { enumDirectionToVector } from "../../core/vector";
+import { ItemAcceptorComponent } from "../components/item_acceptor";
+import { GameSystemWithFilter } from "../game_system_with_filter";
+import { MapChunkView } from "../map_chunk_view";
+
+export class ItemAcceptorSystem extends GameSystemWithFilter {
+ constructor(root) {
+ super(root, [ItemAcceptorComponent]);
+ }
+
+ update() {
+ const progress =
+ this.root.dynamicTickrate.deltaSeconds *
+ 2 *
+ this.root.hubGoals.getBeltBaseSpeed() *
+ globalConfig.itemSpacingOnBelts; // * 2 because its only a half tile
+
+ for (let i = 0; i < this.allEntities.length; ++i) {
+ const entity = this.allEntities[i];
+ const aceptorComp = entity.components.ItemAcceptor;
+ const animations = aceptorComp.itemConsumptionAnimations;
+
+ // Process item consumption animations to avoid items popping from the belts
+ for (let animIndex = 0; animIndex < animations.length; ++animIndex) {
+ const anim = animations[animIndex];
+ anim.animProgress += progress;
+ if (anim.animProgress > 1) {
+ fastArrayDelete(animations, animIndex);
+ animIndex -= 1;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {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 acceptorComp = entity.components.ItemAcceptor;
+ if (!acceptorComp) {
+ continue;
+ }
+
+ const staticComp = entity.components.StaticMapEntity;
+ for (let animIndex = 0; animIndex < acceptorComp.itemConsumptionAnimations.length; ++animIndex) {
+ const { item, slotIndex, animProgress, direction } = acceptorComp.itemConsumptionAnimations[
+ animIndex
+ ];
+
+ const slotData = acceptorComp.slots[slotIndex];
+ const realSlotPos = staticComp.localTileToWorld(slotData.pos);
+
+ if (!chunk.tileSpaceRectangle.containsPoint(realSlotPos.x, realSlotPos.y)) {
+ // Not within this chunk
+ continue;
+ }
+
+ const fadeOutDirection = enumDirectionToVector[staticComp.localDirectionToWorld(direction)];
+ const finalTile = realSlotPos.subScalars(
+ fadeOutDirection.x * (animProgress / 2 - 0.5),
+ fadeOutDirection.y * (animProgress / 2 - 0.5)
+ );
+
+ item.drawItemCenteredClipped(
+ (finalTile.x + 0.5) * globalConfig.tileSize,
+ (finalTile.y + 0.5) * globalConfig.tileSize,
+ parameters,
+ globalConfig.defaultItemDiameter
+ );
+ }
+ }
+ }
+}
diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js
index 1640f6ed..d58aa697 100644
--- a/src/js/game/systems/item_processor.js
+++ b/src/js/game/systems/item_processor.js
@@ -1,4 +1,3 @@
-import { globalConfig } from "../../core/config";
import { BaseItem } from "../base_item";
import { enumColorMixingResults, enumColors } from "../colors";
import {
@@ -12,6 +11,11 @@ import { BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item";
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { ShapeItem } from "../items/shape_item";
+/**
+ * We need to allow queuing charges, otherwise the throughput will stall
+ */
+const MAX_QUEUED_CHARGES = 2;
+
export class ItemProcessorSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ItemProcessorComponent]);
@@ -24,60 +28,64 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
const processorComp = entity.components.ItemProcessor;
const ejectorComp = entity.components.ItemEjector;
- // First of all, process the current recipe
- const newSecondsUntilEject =
- processorComp.secondsUntilEject - this.root.dynamicTickrate.deltaSeconds;
+ const currentCharge = processorComp.ongoingCharges[0];
- processorComp.secondsUntilEject = Math.max(0, newSecondsUntilEject);
+ if (currentCharge) {
+ // Process next charge
+ if (currentCharge.remainingTime > 0.0) {
+ currentCharge.remainingTime -= this.root.dynamicTickrate.deltaSeconds;
+ if (currentCharge.remainingTime < 0.0) {
+ // Add bonus time, this is the time we spent too much
+ processorComp.bonusTime += -currentCharge.remainingTime;
+ }
+ }
- if (newSecondsUntilEject < 0) {
- processorComp.bonusFromLastTick -= newSecondsUntilEject;
- }
+ // Check if it finished
+ if (currentCharge.remainingTime <= 0.0) {
+ const itemsToEject = currentCharge.items;
- if (G_IS_DEV && globalConfig.debug.instantProcessors) {
- processorComp.secondsUntilEject = 0;
- }
+ // Go over all items and try to eject them
+ for (let j = 0; j < itemsToEject.length; ++j) {
+ const { item, requiredSlot, preferredSlot } = itemsToEject[j];
- // Check if we have any finished items we can eject
- if (
- processorComp.secondsUntilEject === 0 && // it was processed in time
- processorComp.itemsToEject.length > 0 // we have some items left to eject
- ) {
- for (let itemIndex = 0; itemIndex < processorComp.itemsToEject.length; ++itemIndex) {
- const { item, requiredSlot, preferredSlot } = processorComp.itemsToEject[itemIndex];
-
- let slot = null;
- if (requiredSlot !== null && requiredSlot !== undefined) {
- // We have a slot override, check if that is free
- if (ejectorComp.canEjectOnSlot(requiredSlot)) {
- slot = requiredSlot;
- }
- } else if (preferredSlot !== null && preferredSlot !== undefined) {
- // We have a slot preference, try using it but otherwise use a free slot
- if (ejectorComp.canEjectOnSlot(preferredSlot)) {
- slot = preferredSlot;
+ let slot = null;
+ if (requiredSlot !== null && requiredSlot !== undefined) {
+ // We have a slot override, check if that is free
+ if (ejectorComp.canEjectOnSlot(requiredSlot)) {
+ slot = requiredSlot;
+ }
+ } else if (preferredSlot !== null && preferredSlot !== undefined) {
+ // We have a slot preference, try using it but otherwise use a free slot
+ if (ejectorComp.canEjectOnSlot(preferredSlot)) {
+ slot = preferredSlot;
+ } else {
+ slot = ejectorComp.getFirstFreeSlot();
+ }
} else {
+ // We can eject on any slot
slot = ejectorComp.getFirstFreeSlot();
}
- } else {
- // We can eject on any slot
- slot = ejectorComp.getFirstFreeSlot();
+
+ if (slot !== null) {
+ // Alright, we can actually eject
+ if (!ejectorComp.tryEject(slot, item)) {
+ assert(false, "Failed to eject");
+ } else {
+ itemsToEject.splice(j, 1);
+ j -= 1;
+ }
+ }
}
- if (slot !== null) {
- // Alright, we can actually eject
- if (!ejectorComp.tryEject(slot, item)) {
- assert(false, "Failed to eject");
- } else {
- processorComp.itemsToEject.splice(itemIndex, 1);
- itemIndex -= 1;
- }
+ // If the charge was entirely emptied to the outputs, start the next charge
+ if (itemsToEject.length === 0) {
+ processorComp.ongoingCharges.shift();
}
}
}
// Check if we have an empty queue and can start a new charge
- if (processorComp.itemsToEject.length === 0) {
+ if (processorComp.ongoingCharges.length < MAX_QUEUED_CHARGES) {
if (this.canProcess(entity)) {
this.startNewCharge(entity);
}
@@ -236,12 +244,6 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
itemsBySlot[items[i].sourceSlot] = items[i];
}
- const baseSpeed = this.root.hubGoals.getProcessorBaseSpeed(processorComp.type);
-
- // Substract one tick because we already process it this frame
- processorComp.secondsUntilEject = Math.max(0, 1 / baseSpeed - processorComp.bonusFromLastTick);
- processorComp.bonusFromLastTick = 0;
-
/** @type {Array<{item: BaseItem, requiredSlot?: number, preferredSlot?: number}>} */
const outItems = [];
@@ -544,6 +546,35 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
}
}
- processorComp.itemsToEject = outItems;
+ // Queue Charge
+ const baseSpeed = this.root.hubGoals.getProcessorBaseSpeed(processorComp.type);
+ const originalTime = 1 / baseSpeed;
+
+ const bonusTimeToApply = Math.min(originalTime, processorComp.bonusTime);
+ const timeToProcess = originalTime - bonusTimeToApply;
+
+ // Substract one tick because we already process it this frame
+ // if (processorComp.bonusTime > originalTime) {
+ // if (processorComp.type === enumItemProcessorTypes.reader) {
+ // console.log(
+ // "Bonus time",
+ // round4Digits(processorComp.bonusTime),
+ // "Original time",
+ // round4Digits(originalTime),
+ // "Overcomit by",
+ // round4Digits(processorComp.bonusTime - originalTime),
+ // "->",
+ // round4Digits(timeToProcess),
+ // "reduced by",
+ // round4Digits(bonusTimeToApply)
+ // );
+ // }
+ // }
+ processorComp.bonusTime -= bonusTimeToApply;
+
+ processorComp.ongoingCharges.push({
+ items: outItems,
+ remainingTime: timeToProcess,
+ });
}
}
diff --git a/src/js/game/systems/static_map_entity.js b/src/js/game/systems/static_map_entity.js
index da6575a5..3e891f7b 100644
--- a/src/js/game/systems/static_map_entity.js
+++ b/src/js/game/systems/static_map_entity.js
@@ -1,82 +1,82 @@
-import { globalConfig } from "../../core/config";
-import { DrawParameters } from "../../core/draw_parameters";
-import { GameSystem } from "../game_system";
-import { MapChunkView } from "../map_chunk_view";
-
-export class StaticMapEntitySystem extends GameSystem {
- constructor(root) {
- super(root);
-
- /** @type {Set} */
- this.drawnUids = new Set();
-
- this.root.signals.gameFrameStarted.add(this.clearUidList, this);
- }
-
- /**
- * Clears the uid list when a new frame started
- */
- clearUidList() {
- this.drawnUids.clear();
- }
-
- /**
- * Draws the static entities
- * @param {DrawParameters} parameters
- * @param {MapChunkView} chunk
- */
- drawChunk(parameters, chunk) {
- if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
- return;
- }
-
- const contents = chunk.containedEntitiesByLayer.regular;
- for (let i = 0; i < contents.length; ++i) {
- const entity = contents[i];
-
- const staticComp = entity.components.StaticMapEntity;
- const sprite = staticComp.getSprite();
- if (sprite) {
- // Avoid drawing an entity twice which has been drawn for
- // another chunk already
- if (this.drawnUids.has(entity.uid)) {
- continue;
- }
-
- this.drawnUids.add(entity.uid);
- staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
- }
- }
- }
-
- /**
- * Draws the static wire entities
- * @param {DrawParameters} parameters
- * @param {MapChunkView} chunk
- */
- drawWiresChunk(parameters, chunk) {
- if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
- return;
- }
-
- const drawnUids = new Set();
- const contents = chunk.wireContents;
- for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
- for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
- const entity = contents[x][y];
- if (entity) {
- if (drawnUids.has(entity.uid)) {
- continue;
- }
- drawnUids.add(entity.uid);
- const staticComp = entity.components.StaticMapEntity;
-
- const sprite = staticComp.getSprite();
- if (sprite) {
- staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
- }
- }
- }
- }
- }
-}
+import { globalConfig } from "../../core/config";
+import { DrawParameters } from "../../core/draw_parameters";
+import { GameSystem } from "../game_system";
+import { MapChunkView } from "../map_chunk_view";
+
+export class StaticMapEntitySystem extends GameSystem {
+ constructor(root) {
+ super(root);
+
+ /** @type {Set} */
+ this.drawnUids = new Set();
+
+ this.root.signals.gameFrameStarted.add(this.clearUidList, this);
+ }
+
+ /**
+ * Clears the uid list when a new frame started
+ */
+ clearUidList() {
+ this.drawnUids.clear();
+ }
+
+ /**
+ * Draws the static entities
+ * @param {DrawParameters} parameters
+ * @param {MapChunkView} chunk
+ */
+ drawChunk(parameters, chunk) {
+ if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
+ return;
+ }
+
+ const contents = chunk.containedEntitiesByLayer.regular;
+ for (let i = 0; i < contents.length; ++i) {
+ const entity = contents[i];
+
+ const staticComp = entity.components.StaticMapEntity;
+ const sprite = staticComp.getSprite();
+ if (sprite) {
+ // Avoid drawing an entity twice which has been drawn for
+ // another chunk already
+ if (this.drawnUids.has(entity.uid)) {
+ continue;
+ }
+
+ this.drawnUids.add(entity.uid);
+ staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
+ }
+ }
+ }
+
+ /**
+ * Draws the static wire entities
+ * @param {DrawParameters} parameters
+ * @param {MapChunkView} chunk
+ */
+ drawWiresChunk(parameters, chunk) {
+ if (G_IS_DEV && globalConfig.debug.doNotRenderStatics) {
+ return;
+ }
+
+ const drawnUids = new Set();
+ const contents = chunk.wireContents;
+ for (let y = 0; y < globalConfig.mapChunkSize; ++y) {
+ for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
+ const entity = contents[x][y];
+ if (entity) {
+ if (drawnUids.has(entity.uid)) {
+ continue;
+ }
+ drawnUids.add(entity.uid);
+ const staticComp = entity.components.StaticMapEntity;
+
+ const sprite = staticComp.getSprite();
+ if (sprite) {
+ staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 2);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js
index 84467b5b..0e7b76ce 100644
--- a/src/js/profile/application_settings.js
+++ b/src/js/profile/application_settings.js
@@ -123,10 +123,9 @@ export const autosaveIntervals = [
},
];
-const refreshRateOptions = ["60", "75", "100", "120", "144", "165", "250", "500"];
+const refreshRateOptions = ["30", "60", "120", "180", "240"];
if (G_IS_DEV) {
- refreshRateOptions.unshift("30");
refreshRateOptions.unshift("10");
refreshRateOptions.unshift("5");
refreshRateOptions.push("1000");
@@ -511,7 +510,7 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
- return 23;
+ return 24;
}
/** @param {{settings: SettingsStorage, version: number}} data */
@@ -614,6 +613,11 @@ export class ApplicationSettings extends ReadWriteProxy {
data.version = 23;
}
+ if (data.version < 24) {
+ data.settings.refreshRate = "60";
+ data.version = 24;
+ }
+
return ExplainedResult.good();
}
}