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(); } }