diff --git a/res_built/atlas/atlas0_hq.json b/res_built/atlas/atlas0_hq.json index 36bd58b0..aa1f8f44 100644 --- a/res_built/atlas/atlas0_hq.json +++ b/res_built/atlas/atlas0_hq.json @@ -34,7 +34,7 @@ }, "sprites/belt/built/forward_4.png": { - "frame": {"x":971,"y":1317,"w":116,"h":144}, + "frame": {"x":421,"y":1482,"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":421,"y":1482,"w":116,"h":144}, + "frame": {"x":3,"y":1870,"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":414,"y":1630,"w":116,"h":144}, + "frame": {"x":123,"y":1870,"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":969,"y":1465,"w":116,"h":144}, + "frame": {"x":243,"y":1839,"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":412,"y":1778,"w":116,"h":144}, + "frame": {"x":971,"y":1297,"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":961,"y":1613,"w":116,"h":144}, + "frame": {"x":420,"y":1630,"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":1530,"y":1435,"w":116,"h":144}, + "frame": {"x":1532,"y":1435,"w":116,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144}, @@ -130,7 +130,7 @@ }, "sprites/belt/built/left_2.png": { - "frame": {"x":3,"y":1696,"w":130,"h":130}, + "frame": {"x":541,"y":1525,"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":541,"y":1580,"w":130,"h":130}, + "frame": {"x":1091,"y":1363,"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":278,"y":1687,"w":130,"h":130}, + "frame": {"x":675,"y":1557,"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":137,"y":1705,"w":130,"h":130}, + "frame": {"x":540,"y":1659,"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":3,"y":1830,"w":130,"h":130}, + "frame": {"x":809,"y":1590,"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":1091,"y":1354,"w":130,"h":130}, + "frame": {"x":674,"y":1691,"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":827,"y":1578,"w":130,"h":130}, + "frame": {"x":1225,"y":1404,"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":534,"y":1714,"w":130,"h":130}, + "frame": {"x":1088,"y":1497,"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":280,"y":1553,"w":130,"h":130}, + "frame": {"x":145,"y":1571,"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":144,"y":1571,"w":130,"h":130}, + "frame": {"x":144,"y":1705,"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":271,"y":1821,"w":130,"h":130}, + "frame": {"x":943,"y":1593,"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":137,"y":1839,"w":130,"h":130}, + "frame": {"x":808,"y":1724,"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":532,"y":1848,"w":130,"h":130}, + "frame": {"x":363,"y":1778,"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":1359,"y":1447,"w":130,"h":130}, + "frame": {"x":363,"y":1912,"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":1223,"y":1539,"w":130,"h":130}, + "frame": {"x":497,"y":1793,"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":1081,"y":1622,"w":130,"h":130}, + "frame": {"x":631,"y":1825,"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":1357,"y":1581,"w":130,"h":130}, + "frame": {"x":765,"y":1858,"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":1215,"y":1673,"w":130,"h":130}, + "frame": {"x":1031,"y":1861,"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":1081,"y":1756,"w":130,"h":130}, + "frame": {"x":1165,"y":1765,"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":798,"y":1909,"w":130,"h":130}, + "frame": {"x":1165,"y":1899,"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":1225,"y":1405,"w":130,"h":130}, + "frame": {"x":1359,"y":1449,"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":1089,"y":1488,"w":130,"h":130}, + "frame": {"x":1222,"y":1538,"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":827,"y":1712,"w":130,"h":130}, + "frame": {"x":1077,"y":1631,"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":668,"y":1717,"w":130,"h":130}, + "frame": {"x":942,"y":1727,"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":932,"y":1909,"w":130,"h":130}, + "frame": {"x":1356,"y":1583,"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":1066,"y":1909,"w":130,"h":130}, + "frame": {"x":1490,"y":1583,"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":961,"y":1761,"w":116,"h":144}, + "frame": {"x":968,"y":1445,"w":116,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":14,"y":0,"w":116,"h":144}, @@ -362,7 +362,7 @@ }, "sprites/blueprints/constant_signal.png": { - "frame": {"x":1938,"y":759,"w":105,"h":127}, + "frame": {"x":1938,"y":847,"w":105,"h":127}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":105,"h":127}, @@ -386,7 +386,7 @@ }, "sprites/blueprints/display.png": { - "frame": {"x":666,"y":1909,"w":128,"h":136}, + "frame": {"x":899,"y":1861,"w":128,"h":136}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":8,"y":8,"w":128,"h":136}, @@ -450,7 +450,7 @@ }, "sprites/blueprints/miner-chainable.png": { - "frame": {"x":689,"y":1424,"w":136,"h":143}, + "frame": {"x":3,"y":1723,"w":136,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143}, @@ -458,7 +458,7 @@ }, "sprites/blueprints/miner.png": { - "frame": {"x":547,"y":1433,"w":136,"h":143}, + "frame": {"x":831,"y":1297,"w":136,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":136,"h":143}, @@ -514,7 +514,7 @@ }, "sprites/blueprints/rotater-fl.png": { - "frame": {"x":948,"y":1169,"w":142,"h":144}, + "frame": {"x":1242,"y":1116,"w":142,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":142,"h":144}, @@ -522,7 +522,7 @@ }, "sprites/blueprints/rotater.png": { - "frame": {"x":1899,"y":1169,"w":143,"h":144}, + "frame": {"x":1897,"y":1257,"w":143,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":143,"h":144}, @@ -530,7 +530,7 @@ }, "sprites/blueprints/splitter-compact-inverse.png": { - "frame": {"x":1242,"y":1116,"w":142,"h":138}, + "frame": {"x":1095,"y":1221,"w":142,"h":138}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":142,"h":138}, @@ -538,7 +538,7 @@ }, "sprites/blueprints/splitter-compact-merge-inverse.png": { - "frame": {"x":1094,"y":1212,"w":142,"h":138}, + "frame": {"x":1388,"y":1160,"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.png": { - "frame": {"x":1650,"y":1493,"w":139,"h":138}, + "frame": {"x":1881,"y":1552,"w":139,"h":138}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":2,"w":139,"h":138}, @@ -554,7 +554,7 @@ }, "sprites/blueprints/splitter-compact.png": { - "frame": {"x":1844,"y":1604,"w":139,"h":138}, + "frame": {"x":559,"y":1036,"w":139,"h":138}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":2,"w":139,"h":138}, @@ -594,7 +594,7 @@ }, "sprites/blueprints/underground_belt_entry-tier2.png": { - "frame": {"x":553,"y":1176,"w":138,"h":125}, + "frame": {"x":3,"y":1466,"w":138,"h":125}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":19,"w":138,"h":125}, @@ -602,7 +602,7 @@ }, "sprites/blueprints/underground_belt_entry.png": { - "frame": {"x":285,"y":1322,"w":138,"h":112}, + "frame": {"x":696,"y":1180,"w":138,"h":112}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":32,"w":138,"h":112}, @@ -610,7 +610,7 @@ }, "sprites/blueprints/underground_belt_exit-tier2.png": { - "frame": {"x":3,"y":1326,"w":139,"h":112}, + "frame": {"x":553,"y":1178,"w":139,"h":112}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":139,"h":112}, @@ -618,7 +618,7 @@ }, "sprites/blueprints/underground_belt_exit.png": { - "frame": {"x":3,"y":1442,"w":138,"h":112}, + "frame": {"x":548,"y":1294,"w":138,"h":112}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":138,"h":112}, @@ -634,7 +634,7 @@ }, "sprites/blueprints/virtual_processor-rotater.png": { - "frame": {"x":1925,"y":1021,"w":118,"h":144}, + "frame": {"x":1925,"y":1109,"w":118,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":14,"y":0,"w":118,"h":144}, @@ -706,7 +706,7 @@ }, "sprites/blueprints/wire_tunnel.png": { - "frame": {"x":702,"y":1170,"w":138,"h":135}, + "frame": {"x":285,"y":1322,"w":138,"h":135}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":4,"w":138,"h":135}, @@ -722,7 +722,7 @@ }, "sprites/buildings/belt_right.png": { - "frame": {"x":271,"y":1821,"w":130,"h":130}, + "frame": {"x":943,"y":1593,"w":130,"h":130}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":14,"y":14,"w":130,"h":130}, @@ -738,7 +738,7 @@ }, "sprites/buildings/constant_signal.png": { - "frame": {"x":1941,"y":628,"w":104,"h":127}, + "frame": {"x":1941,"y":716,"w":104,"h":127}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":104,"h":127}, @@ -802,7 +802,7 @@ }, "sprites/buildings/logic_gate-or.png": { - "frame": {"x":1095,"y":1085,"w":143,"h":123}, + "frame": {"x":948,"y":1170,"w":143,"h":123}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":143,"h":123}, @@ -818,7 +818,7 @@ }, "sprites/buildings/logic_gate-xor.png": { - "frame": {"x":1897,"y":1317,"w":143,"h":143}, + "frame": {"x":801,"y":1033,"w":143,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":143,"h":143}, @@ -826,7 +826,7 @@ }, "sprites/buildings/logic_gate.png": { - "frame": {"x":948,"y":1033,"w":143,"h":132}, + "frame": {"x":1095,"y":1085,"w":143,"h":132}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":143,"h":132}, @@ -834,7 +834,7 @@ }, "sprites/buildings/miner-chainable.png": { - "frame": {"x":829,"y":1432,"w":136,"h":142}, + "frame": {"x":688,"y":1411,"w":136,"h":142}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142}, @@ -842,7 +842,7 @@ }, "sprites/buildings/miner.png": { - "frame": {"x":687,"y":1571,"w":136,"h":142}, + "frame": {"x":828,"y":1444,"w":136,"h":142}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":136,"h":142}, @@ -890,7 +890,7 @@ }, "sprites/buildings/rotater-ccw.png": { - "frame": {"x":1240,"y":1258,"w":141,"h":143}, + "frame": {"x":1387,"y":1302,"w":141,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143}, @@ -898,7 +898,7 @@ }, "sprites/buildings/rotater-fl.png": { - "frame": {"x":1385,"y":1300,"w":141,"h":143}, + "frame": {"x":1654,"y":1346,"w":141,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":141,"h":143}, @@ -906,7 +906,7 @@ }, "sprites/buildings/rotater.png": { - "frame": {"x":1654,"y":1346,"w":141,"h":143}, + "frame": {"x":1884,"y":1405,"w":141,"h":143}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":0,"w":141,"h":143}, @@ -914,7 +914,7 @@ }, "sprites/buildings/splitter-compact-inverse.png": { - "frame": {"x":1884,"y":1464,"w":141,"h":136}, + "frame": {"x":1652,"y":1493,"w":141,"h":136}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":3,"w":141,"h":136}, @@ -922,7 +922,7 @@ }, "sprites/buildings/splitter-compact-merge-inverse.png": { - "frame": {"x":1388,"y":1160,"w":142,"h":136}, + "frame": {"x":1241,"y":1264,"w":142,"h":136}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":3,"w":142,"h":136}, @@ -930,7 +930,7 @@ }, "sprites/buildings/splitter-compact-merge.png": { - "frame": {"x":559,"y":1036,"w":139,"h":136}, + "frame": {"x":285,"y":1182,"w":139,"h":136}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":3,"w":139,"h":136}, @@ -938,7 +938,7 @@ }, "sprites/buildings/splitter-compact.png": { - "frame": {"x":285,"y":1182,"w":139,"h":136}, + "frame": {"x":3,"y":1326,"w":139,"h":136}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":3,"w":139,"h":136}, @@ -978,7 +978,7 @@ }, "sprites/buildings/underground_belt_entry-tier2.png": { - "frame": {"x":548,"y":1305,"w":137,"h":124}, + "frame": {"x":3,"y":1595,"w":137,"h":124}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":20,"w":137,"h":124}, @@ -986,7 +986,7 @@ }, "sprites/buildings/underground_belt_entry.png": { - "frame": {"x":280,"y":1438,"w":137,"h":111}, + "frame": {"x":690,"y":1296,"w":137,"h":111}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":33,"w":137,"h":111}, @@ -994,7 +994,7 @@ }, "sprites/buildings/underground_belt_exit-tier2.png": { - "frame": {"x":689,"y":1309,"w":137,"h":111}, + "frame": {"x":547,"y":1410,"w":137,"h":111}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111}, @@ -1002,7 +1002,7 @@ }, "sprites/buildings/underground_belt_exit.png": { - "frame": {"x":830,"y":1317,"w":137,"h":111}, + "frame": {"x":279,"y":1599,"w":137,"h":111}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":137,"h":111}, @@ -1026,7 +1026,7 @@ }, "sprites/buildings/virtual_processor-shapecompare.png": { - "frame": {"x":801,"y":1033,"w":143,"h":133}, + "frame": {"x":948,"y":1033,"w":143,"h":133}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":143,"h":133}, @@ -1066,7 +1066,7 @@ }, "sprites/buildings/wire-turn.png": { - "frame": {"x":1812,"y":1230,"w":81,"h":81}, + "frame": {"x":1941,"y":631,"w":81,"h":81}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81}, @@ -1074,7 +1074,7 @@ }, "sprites/buildings/wire.png": { - "frame": {"x":2027,"y":184,"w":18,"h":144}, + "frame": {"x":2027,"y":272,"w":18,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144}, @@ -1090,7 +1090,7 @@ }, "sprites/buildings/wire_tunnel.png": { - "frame": {"x":3,"y":1558,"w":137,"h":134}, + "frame": {"x":280,"y":1461,"w":137,"h":134}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":5,"w":137,"h":134}, @@ -1114,12 +1114,28 @@ }, "sprites/misc/hub_direction_indicator.png": { - "frame": {"x":1975,"y":184,"w":48,"h":48}, + "frame": {"x":1975,"y":272,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, "sourceSize": {"w":48,"h":48} }, +"sprites/misc/processor_disabled.png": +{ + "frame": {"x":1799,"y":1485,"w":78,"h":81}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":10,"y":10,"w":78,"h":81}, + "sourceSize": {"w":96,"h":96} +}, +"sprites/misc/processor_disconnected.png": +{ + "frame": {"x":1975,"y":3,"w":65,"h":84}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":17,"y":8,"w":65,"h":84}, + "sourceSize": {"w":96,"h":96} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":255,"y":638,"w":35,"h":35}, @@ -1146,7 +1162,7 @@ }, "sprites/misc/waypoint.png": { - "frame": {"x":1987,"y":1656,"w":38,"h":48}, + "frame": {"x":702,"y":1084,"w":38,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":38,"h":48}, @@ -1170,7 +1186,7 @@ }, "sprites/wires/display/blue.png": { - "frame": {"x":1975,"y":288,"w":47,"h":47}, + "frame": {"x":1975,"y":376,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1178,7 +1194,7 @@ }, "sprites/wires/display/cyan.png": { - "frame": {"x":1975,"y":339,"w":47,"h":47}, + "frame": {"x":1975,"y":427,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1186,7 +1202,7 @@ }, "sprites/wires/display/green.png": { - "frame": {"x":1975,"y":390,"w":47,"h":47}, + "frame": {"x":1975,"y":478,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1194,7 +1210,7 @@ }, "sprites/wires/display/purple.png": { - "frame": {"x":1975,"y":441,"w":47,"h":47}, + "frame": {"x":1975,"y":529,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1202,7 +1218,7 @@ }, "sprites/wires/display/red.png": { - "frame": {"x":1975,"y":492,"w":47,"h":47}, + "frame": {"x":1975,"y":580,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1210,7 +1226,7 @@ }, "sprites/wires/display/white.png": { - "frame": {"x":1975,"y":543,"w":47,"h":47}, + "frame": {"x":1797,"y":1570,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1218,7 +1234,7 @@ }, "sprites/wires/display/yellow.png": { - "frame": {"x":1793,"y":1570,"w":47,"h":47}, + "frame": {"x":1797,"y":1621,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":47}, @@ -1226,7 +1242,7 @@ }, "sprites/wires/lever_on.png": { - "frame": {"x":1936,"y":890,"w":109,"h":127}, + "frame": {"x":1936,"y":978,"w":109,"h":127}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":18,"y":5,"w":109,"h":127}, @@ -1234,7 +1250,7 @@ }, "sprites/wires/logical_acceptor.png": { - "frame": {"x":1975,"y":3,"w":62,"h":106}, + "frame": {"x":1975,"y":91,"w":62,"h":106}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":43,"y":0,"w":62,"h":106}, @@ -1242,7 +1258,7 @@ }, "sprites/wires/logical_ejector.png": { - "frame": {"x":1975,"y":113,"w":60,"h":67}, + "frame": {"x":1975,"y":201,"w":60,"h":67}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":44,"y":0,"w":60,"h":67}, @@ -1250,7 +1266,7 @@ }, "sprites/wires/network_conflict.png": { - "frame": {"x":1793,"y":1621,"w":47,"h":44}, + "frame": {"x":702,"y":1036,"w":47,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":2,"w":47,"h":44}, @@ -1258,7 +1274,7 @@ }, "sprites/wires/network_empty.png": { - "frame": {"x":1987,"y":1604,"w":41,"h":48}, + "frame": {"x":753,"y":1036,"w":41,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":41,"h":48}, @@ -1282,7 +1298,7 @@ }, "sprites/wires/sets/color_forward.png": { - "frame": {"x":2026,"y":332,"w":18,"h":144}, + "frame": {"x":2026,"y":420,"w":18,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144}, @@ -1298,7 +1314,7 @@ }, "sprites/wires/sets/color_turn.png": { - "frame": {"x":1812,"y":1315,"w":81,"h":81}, + "frame": {"x":1812,"y":1230,"w":81,"h":81}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81}, @@ -1314,7 +1330,7 @@ }, "sprites/wires/sets/conflict_forward.png": { - "frame": {"x":2026,"y":480,"w":18,"h":144}, + "frame": {"x":2026,"y":568,"w":18,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144}, @@ -1330,7 +1346,7 @@ }, "sprites/wires/sets/conflict_turn.png": { - "frame": {"x":1799,"y":1400,"w":81,"h":81}, + "frame": {"x":1812,"y":1315,"w":81,"h":81}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81}, @@ -1346,7 +1362,7 @@ }, "sprites/wires/sets/regular_forward.png": { - "frame": {"x":2027,"y":184,"w":18,"h":144}, + "frame": {"x":2027,"y":272,"w":18,"h":144}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":0,"w":18,"h":144}, @@ -1362,7 +1378,7 @@ }, "sprites/wires/sets/regular_turn.png": { - "frame": {"x":1812,"y":1230,"w":81,"h":81}, + "frame": {"x":1941,"y":631,"w":81,"h":81}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81}, @@ -1394,7 +1410,7 @@ }, "sprites/wires/sets/shape_turn.png": { - "frame": {"x":1799,"y":1485,"w":81,"h":81}, + "frame": {"x":1799,"y":1400,"w":81,"h":81}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":63,"y":63,"w":81,"h":81}, @@ -1402,7 +1418,7 @@ }, "sprites/wires/wires_preview.png": { - "frame": {"x":1975,"y":236,"w":48,"h":48}, + "frame": {"x":1975,"y":324,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1415,6 +1431,6 @@ "format": "RGBA8888", "size": {"w":2048,"h":2048}, "scale": "0.75", - "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$" + "smartupdate": "$TexturePacker:SmartUpdate:1923b9c910205fb09957b67f92de02b9:0d87df06f18965307bf9d18b414f603d:908b89f5ca8ff73e331a35a3b14d0604$" } } diff --git a/res_built/atlas/atlas0_hq.png b/res_built/atlas/atlas0_hq.png index 5ffbe2bd..72d7c1e5 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 419beb4d..fc28e1e9 100644 --- a/res_built/atlas/atlas0_lq.json +++ b/res_built/atlas/atlas0_lq.json @@ -2,7 +2,7 @@ "sprites/belt/built/forward_0.png": { - "frame": {"x":98,"y":963,"w":40,"h":48}, + "frame": {"x":3,"y":957,"w":40,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48}, @@ -10,7 +10,7 @@ }, "sprites/belt/built/forward_1.png": { - "frame": {"x":399,"y":759,"w":40,"h":48}, + "frame": {"x":47,"y":957,"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":142,"y":940,"w":40,"h":48}, + "frame": {"x":448,"y":821,"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":186,"y":962,"w":40,"h":48}, + "frame": {"x":135,"y":938,"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":230,"y":962,"w":40,"h":48}, + "frame": {"x":179,"y":938,"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":443,"y":765,"w":40,"h":48}, + "frame": {"x":223,"y":937,"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":389,"y":811,"w":40,"h":48}, + "frame": {"x":349,"y":867,"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":338,"y":858,"w":40,"h":48}, + "frame": {"x":393,"y":867,"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":287,"y":904,"w":40,"h":48}, + "frame": {"x":293,"y":877,"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":274,"y":956,"w":40,"h":48}, + "frame": {"x":437,"y":873,"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":345,"y":806,"w":40,"h":48}, + "frame": {"x":91,"y":933,"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":294,"y":852,"w":40,"h":48}, + "frame": {"x":249,"y":847,"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":243,"y":890,"w":40,"h":48}, + "frame": {"x":198,"y":885,"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":195,"y":910,"w":40,"h":48}, + "frame": {"x":147,"y":886,"w":40,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48}, @@ -114,7 +114,7 @@ }, "sprites/belt/built/left_0.png": { - "frame": {"x":105,"y":730,"w":44,"h":44}, + "frame": {"x":326,"y":499,"w":44,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44}, @@ -122,7 +122,7 @@ }, "sprites/belt/built/left_1.png": { - "frame": {"x":54,"y":771,"w":44,"h":44}, + "frame": {"x":465,"y":519,"w":44,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44}, @@ -130,7 +130,7 @@ }, "sprites/belt/built/left_2.png": { - "frame": {"x":102,"y":778,"w":44,"h":44}, + "frame": {"x":208,"y":741,"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":51,"y":819,"w":44,"h":44}, + "frame": {"x":157,"y":742,"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":3,"y":851,"w":44,"h":44}, + "frame": {"x":105,"y":749,"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":255,"y":746,"w":44,"h":44}, + "frame": {"x":54,"y":789,"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":201,"y":766,"w":44,"h":44}, + "frame": {"x":3,"y":813,"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":150,"y":796,"w":44,"h":44}, + "frame": {"x":364,"y":723,"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":99,"y":826,"w":44,"h":44}, + "frame": {"x":311,"y":733,"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":51,"y":867,"w":44,"h":44}, + "frame": {"x":256,"y":751,"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":3,"y":803,"w":44,"h":44}, + "frame": {"x":465,"y":567,"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":207,"y":718,"w":44,"h":44}, + "frame": {"x":325,"y":547,"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":310,"y":708,"w":44,"h":44}, + "frame": {"x":325,"y":595,"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":153,"y":748,"w":44,"h":44}, + "frame": {"x":324,"y":643,"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":3,"y":899,"w":44,"h":44}, + "frame": {"x":205,"y":789,"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":358,"y":710,"w":44,"h":44}, + "frame": {"x":153,"y":790,"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":99,"y":874,"w":44,"h":44}, + "frame": {"x":304,"y":781,"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":915,"w":44,"h":44}, + "frame": {"x":253,"y":799,"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":947,"w":44,"h":44}, + "frame": {"x":201,"y":837,"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":406,"y":711,"w":44,"h":44}, + "frame": {"x":150,"y":838,"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":351,"y":758,"w":44,"h":44}, + "frame": {"x":99,"y":845,"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":297,"y":804,"w":44,"h":44}, + "frame": {"x":51,"y":885,"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":246,"y":842,"w":44,"h":44}, + "frame": {"x":3,"y":909,"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":195,"y":862,"w":44,"h":44}, + "frame": {"x":352,"y":819,"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":303,"y":756,"w":44,"h":44}, + "frame": {"x":102,"y":797,"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":249,"y":794,"w":44,"h":44}, + "frame": {"x":51,"y":837,"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":198,"y":814,"w":44,"h":44}, + "frame": {"x":3,"y":861,"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":147,"y":844,"w":44,"h":44}, + "frame": {"x":359,"y":771,"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":147,"y":892,"w":44,"h":44}, + "frame": {"x":400,"y":819,"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":454,"y":717,"w":44,"h":44}, + "frame": {"x":301,"y":829,"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":318,"y":956,"w":40,"h":48}, + "frame": {"x":267,"y":929,"w":40,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48}, @@ -362,7 +362,7 @@ }, "sprites/blueprints/constant_signal.png": { - "frame": {"x":329,"y":390,"w":36,"h":43}, + "frame": {"x":426,"y":411,"w":36,"h":43}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43}, @@ -378,7 +378,7 @@ }, "sprites/blueprints/cutter.png": { - "frame": {"x":187,"y":315,"w":87,"h":48}, + "frame": {"x":95,"y":296,"w":87,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48}, @@ -386,7 +386,7 @@ }, "sprites/blueprints/display.png": { - "frame": {"x":211,"y":567,"w":44,"h":46}, + "frame": {"x":326,"y":399,"w":44,"h":46}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46}, @@ -402,7 +402,7 @@ }, "sprites/blueprints/lever.png": { - "frame": {"x":470,"y":257,"w":38,"h":44}, + "frame": {"x":231,"y":467,"w":38,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44}, @@ -410,7 +410,7 @@ }, "sprites/blueprints/logic_gate-not.png": { - "frame": {"x":321,"y":509,"w":42,"h":48}, + "frame": {"x":467,"y":311,"w":42,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":42,"h":48}, @@ -418,7 +418,7 @@ }, "sprites/blueprints/logic_gate-or.png": { - "frame": {"x":311,"y":662,"w":48,"h":42}, + "frame": {"x":159,"y":571,"w":48,"h":42}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42}, @@ -426,7 +426,7 @@ }, "sprites/blueprints/logic_gate-transistor.png": { - "frame": {"x":144,"y":448,"w":35,"h":48}, + "frame": {"x":426,"y":505,"w":35,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48}, @@ -442,7 +442,7 @@ }, "sprites/blueprints/logic_gate.png": { - "frame": {"x":311,"y":613,"w":48,"h":45}, + "frame": {"x":55,"y":504,"w":48,"h":45}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45}, @@ -450,7 +450,7 @@ }, "sprites/blueprints/miner-chainable.png": { - "frame": {"x":462,"y":353,"w":47,"h":48}, + "frame": {"x":107,"y":646,"w":47,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48}, @@ -458,7 +458,7 @@ }, "sprites/blueprints/miner.png": { - "frame": {"x":55,"y":626,"w":47,"h":48}, + "frame": {"x":55,"y":686,"w":47,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48}, @@ -514,7 +514,7 @@ }, "sprites/blueprints/rotater-fl.png": { - "frame": {"x":285,"y":263,"w":48,"h":48}, + "frame": {"x":274,"y":399,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -522,7 +522,7 @@ }, "sprites/blueprints/rotater.png": { - "frame": {"x":460,"y":405,"w":48,"h":48}, + "frame": {"x":374,"y":411,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -530,7 +530,7 @@ }, "sprites/blueprints/splitter-compact-inverse.png": { - "frame": {"x":183,"y":367,"w":48,"h":48}, + "frame": {"x":274,"y":451,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -538,7 +538,7 @@ }, "sprites/blueprints/splitter-compact-merge-inverse.png": { - "frame": {"x":277,"y":390,"w":48,"h":48}, + "frame": {"x":374,"y":463,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -546,7 +546,7 @@ }, "sprites/blueprints/splitter-compact-merge.png": { - "frame": {"x":54,"y":678,"w":47,"h":47}, + "frame": {"x":158,"y":649,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47}, @@ -554,7 +554,7 @@ }, "sprites/blueprints/splitter-compact.png": { - "frame": {"x":3,"y":710,"w":47,"h":47}, + "frame": {"x":106,"y":698,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47}, @@ -562,7 +562,7 @@ }, "sprites/blueprints/splitter.png": { - "frame": {"x":278,"y":338,"w":87,"h":48}, + "frame": {"x":186,"y":315,"w":87,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48}, @@ -570,7 +570,7 @@ }, "sprites/blueprints/stacker.png": { - "frame": {"x":369,"y":307,"w":89,"h":48}, + "frame": {"x":374,"y":307,"w":89,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":89,"h":48}, @@ -578,7 +578,7 @@ }, "sprites/blueprints/trash-storage.png": { - "frame": {"x":94,"y":348,"w":85,"h":96}, + "frame": {"x":285,"y":299,"w":85,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96}, @@ -586,7 +586,7 @@ }, "sprites/blueprints/trash.png": { - "frame": {"x":183,"y":419,"w":48,"h":48}, + "frame": {"x":374,"y":515,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -594,7 +594,7 @@ }, "sprites/blueprints/underground_belt_entry-tier2.png": { - "frame": {"x":259,"y":649,"w":48,"h":43}, + "frame": {"x":107,"y":553,"w":48,"h":43}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":5,"w":48,"h":43}, @@ -602,7 +602,7 @@ }, "sprites/blueprints/underground_belt_entry.png": { - "frame": {"x":363,"y":668,"w":48,"h":38}, + "frame": {"x":55,"y":602,"w":48,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":10,"w":48,"h":38}, @@ -610,7 +610,7 @@ }, "sprites/blueprints/underground_belt_exit-tier2.png": { - "frame": {"x":3,"y":552,"w":48,"h":38}, + "frame": {"x":3,"y":604,"w":48,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38}, @@ -618,7 +618,7 @@ }, "sprites/blueprints/underground_belt_exit.png": { - "frame": {"x":55,"y":552,"w":48,"h":38}, + "frame": {"x":55,"y":644,"w":48,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":38}, @@ -626,7 +626,7 @@ }, "sprites/blueprints/virtual_processor-analyzer.png": { - "frame": {"x":92,"y":448,"w":48,"h":48}, + "frame": {"x":3,"y":400,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -634,7 +634,7 @@ }, "sprites/blueprints/virtual_processor-rotater.png": { - "frame": {"x":467,"y":613,"w":41,"h":48}, + "frame": {"x":466,"y":415,"w":41,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":41,"h":48}, @@ -642,7 +642,7 @@ }, "sprites/blueprints/virtual_processor-shapecompare.png": { - "frame": {"x":363,"y":619,"w":48,"h":45}, + "frame": {"x":107,"y":504,"w":48,"h":45}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45}, @@ -650,7 +650,7 @@ }, "sprites/blueprints/virtual_processor-unstacker.png": { - "frame": {"x":369,"y":411,"w":48,"h":48}, + "frame": {"x":55,"y":400,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -658,7 +658,7 @@ }, "sprites/blueprints/virtual_processor.png": { - "frame": {"x":460,"y":457,"w":48,"h":48}, + "frame": {"x":107,"y":400,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -666,7 +666,7 @@ }, "sprites/blueprints/wire-cross.png": { - "frame": {"x":275,"y":442,"w":48,"h":48}, + "frame": {"x":3,"y":452,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -674,7 +674,7 @@ }, "sprites/blueprints/wire-split.png": { - "frame": {"x":3,"y":594,"w":48,"h":28}, + "frame": {"x":285,"y":263,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -682,7 +682,7 @@ }, "sprites/blueprints/wire-turn.png": { - "frame": {"x":479,"y":129,"w":28,"h":28}, + "frame": {"x":479,"y":162,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -690,7 +690,7 @@ }, "sprites/blueprints/wire.png": { - "frame": {"x":357,"y":286,"w":8,"h":48}, + "frame": {"x":437,"y":693,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -698,7 +698,7 @@ }, "sprites/blueprints/wire_tunnel-coating.png": { - "frame": {"x":235,"y":462,"w":13,"h":47}, + "frame": {"x":425,"y":609,"w":13,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":17,"y":0,"w":13,"h":47}, @@ -706,7 +706,7 @@ }, "sprites/blueprints/wire_tunnel.png": { - "frame": {"x":107,"y":500,"w":48,"h":47}, + "frame": {"x":449,"y":719,"w":48,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47}, @@ -714,7 +714,7 @@ }, "sprites/buildings/belt_left.png": { - "frame": {"x":105,"y":730,"w":44,"h":44}, + "frame": {"x":326,"y":499,"w":44,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":4,"w":44,"h":44}, @@ -722,7 +722,7 @@ }, "sprites/buildings/belt_right.png": { - "frame": {"x":3,"y":899,"w":44,"h":44}, + "frame": {"x":205,"y":789,"w":44,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":4,"w":44,"h":44}, @@ -730,7 +730,7 @@ }, "sprites/buildings/belt_top.png": { - "frame": {"x":98,"y":963,"w":40,"h":48}, + "frame": {"x":3,"y":957,"w":40,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":40,"h":48}, @@ -738,7 +738,7 @@ }, "sprites/buildings/constant_signal.png": { - "frame": {"x":235,"y":415,"w":36,"h":43}, + "frame": {"x":426,"y":458,"w":36,"h":43}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":0,"w":36,"h":43}, @@ -754,7 +754,7 @@ }, "sprites/buildings/cutter.png": { - "frame": {"x":369,"y":359,"w":87,"h":48}, + "frame": {"x":3,"y":348,"w":87,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48}, @@ -762,7 +762,7 @@ }, "sprites/buildings/display.png": { - "frame": {"x":211,"y":617,"w":44,"h":46}, + "frame": {"x":326,"y":449,"w":44,"h":46}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":2,"w":44,"h":46}, @@ -786,7 +786,7 @@ }, "sprites/buildings/lever.png": { - "frame": {"x":470,"y":305,"w":38,"h":44}, + "frame": {"x":231,"y":515,"w":38,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44}, @@ -794,7 +794,7 @@ }, "sprites/buildings/logic_gate-not.png": { - "frame": {"x":51,"y":963,"w":43,"h":48}, + "frame": {"x":466,"y":363,"w":43,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":43,"h":48}, @@ -802,7 +802,7 @@ }, "sprites/buildings/logic_gate-or.png": { - "frame": {"x":415,"y":665,"w":48,"h":42}, + "frame": {"x":107,"y":600,"w":48,"h":42}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":42}, @@ -810,7 +810,7 @@ }, "sprites/buildings/logic_gate-transistor.png": { - "frame": {"x":421,"y":411,"w":35,"h":48}, + "frame": {"x":426,"y":557,"w":35,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":35,"h":48}, @@ -818,7 +818,7 @@ }, "sprites/buildings/logic_gate-xor.png": { - "frame": {"x":183,"y":471,"w":48,"h":48}, + "frame": {"x":55,"y":452,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -826,7 +826,7 @@ }, "sprites/buildings/logic_gate.png": { - "frame": {"x":107,"y":551,"w":48,"h":45}, + "frame": {"x":55,"y":553,"w":48,"h":45}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45}, @@ -834,7 +834,7 @@ }, "sprites/buildings/miner-chainable.png": { - "frame": {"x":3,"y":658,"w":47,"h":48}, + "frame": {"x":3,"y":710,"w":47,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48}, @@ -842,7 +842,7 @@ }, "sprites/buildings/miner.png": { - "frame": {"x":106,"y":632,"w":47,"h":48}, + "frame": {"x":211,"y":643,"w":47,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":48}, @@ -850,7 +850,7 @@ }, "sprites/buildings/mixer.png": { - "frame": {"x":3,"y":296,"w":88,"h":48}, + "frame": {"x":374,"y":359,"w":88,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48}, @@ -882,7 +882,7 @@ }, "sprites/buildings/painter.png": { - "frame": {"x":370,"y":255,"w":96,"h":48}, + "frame": {"x":375,"y":255,"w":96,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":48}, @@ -890,7 +890,7 @@ }, "sprites/buildings/rotater-ccw.png": { - "frame": {"x":269,"y":494,"w":48,"h":48}, + "frame": {"x":107,"y":452,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -898,7 +898,7 @@ }, "sprites/buildings/rotater-fl.png": { - "frame": {"x":367,"y":463,"w":48,"h":48}, + "frame": {"x":179,"y":467,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -906,7 +906,7 @@ }, "sprites/buildings/rotater.png": { - "frame": {"x":259,"y":546,"w":48,"h":48}, + "frame": {"x":273,"y":503,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -914,7 +914,7 @@ }, "sprites/buildings/splitter-compact-inverse.png": { - "frame": {"x":159,"y":523,"w":48,"h":47}, + "frame": {"x":448,"y":770,"w":48,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47}, @@ -922,7 +922,7 @@ }, "sprites/buildings/splitter-compact-merge-inverse.png": { - "frame": {"x":259,"y":598,"w":48,"h":47}, + "frame": {"x":3,"y":504,"w":48,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":47}, @@ -930,7 +930,7 @@ }, "sprites/buildings/splitter-compact-merge.png": { - "frame": {"x":157,"y":655,"w":47,"h":47}, + "frame": {"x":54,"y":738,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47}, @@ -938,7 +938,7 @@ }, "sprites/buildings/splitter-compact.png": { - "frame": {"x":208,"y":667,"w":47,"h":47}, + "frame": {"x":3,"y":762,"w":47,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":47}, @@ -946,7 +946,7 @@ }, "sprites/buildings/splitter.png": { - "frame": {"x":3,"y":348,"w":87,"h":48}, + "frame": {"x":94,"y":348,"w":87,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":0,"w":87,"h":48}, @@ -954,7 +954,7 @@ }, "sprites/buildings/stacker.png": { - "frame": {"x":95,"y":296,"w":88,"h":48}, + "frame": {"x":3,"y":296,"w":88,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":88,"h":48}, @@ -962,7 +962,7 @@ }, "sprites/buildings/trash-storage.png": { - "frame": {"x":3,"y":400,"w":85,"h":96}, + "frame": {"x":185,"y":367,"w":85,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":0,"w":85,"h":96}, @@ -970,7 +970,7 @@ }, "sprites/buildings/trash.png": { - "frame": {"x":419,"y":509,"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}, @@ -978,7 +978,7 @@ }, "sprites/buildings/underground_belt_entry-tier2.png": { - "frame": {"x":105,"y":684,"w":47,"h":42}, + "frame": {"x":209,"y":695,"w":47,"h":42}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":6,"w":47,"h":42}, @@ -986,7 +986,7 @@ }, "sprites/buildings/underground_belt_entry.png": { - "frame": {"x":54,"y":729,"w":47,"h":38}, + "frame": {"x":157,"y":700,"w":47,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":10,"w":47,"h":38}, @@ -994,7 +994,7 @@ }, "sprites/buildings/underground_belt_exit-tier2.png": { - "frame": {"x":3,"y":761,"w":47,"h":38}, + "frame": {"x":313,"y":691,"w":47,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38}, @@ -1002,7 +1002,7 @@ }, "sprites/buildings/underground_belt_exit.png": { - "frame": {"x":156,"y":706,"w":47,"h":38}, + "frame": {"x":260,"y":709,"w":47,"h":38}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":47,"h":38}, @@ -1010,7 +1010,7 @@ }, "sprites/buildings/virtual_processor-analyzer.png": { - "frame": {"x":367,"y":515,"w":48,"h":48}, + "frame": {"x":373,"y":567,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1018,7 +1018,7 @@ }, "sprites/buildings/virtual_processor-rotater.png": { - "frame": {"x":467,"y":665,"w":41,"h":48}, + "frame": {"x":466,"y":467,"w":41,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":41,"h":48}, @@ -1026,7 +1026,7 @@ }, "sprites/buildings/virtual_processor-shapecompare.png": { - "frame": {"x":159,"y":574,"w":48,"h":45}, + "frame": {"x":3,"y":555,"w":48,"h":45}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":45}, @@ -1034,7 +1034,7 @@ }, "sprites/buildings/virtual_processor-unstacker.png": { - "frame": {"x":311,"y":561,"w":48,"h":48}, + "frame": {"x":178,"y":519,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1042,7 +1042,7 @@ }, "sprites/buildings/virtual_processor.png": { - "frame": {"x":419,"y":561,"w":48,"h":48}, + "frame": {"x":272,"y":607,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1050,7 +1050,7 @@ }, "sprites/buildings/wire-cross.png": { - "frame": {"x":363,"y":567,"w":48,"h":48}, + "frame": {"x":373,"y":619,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1058,7 +1058,7 @@ }, "sprites/buildings/wire-split.png": { - "frame": {"x":55,"y":594,"w":48,"h":28}, + "frame": {"x":3,"y":646,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -1066,7 +1066,7 @@ }, "sprites/buildings/wire-turn.png": { - "frame": {"x":479,"y":161,"w":28,"h":28}, + "frame": {"x":479,"y":194,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -1074,7 +1074,7 @@ }, "sprites/buildings/wire.png": { - "frame": {"x":235,"y":513,"w":8,"h":48}, + "frame": {"x":424,"y":698,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -1082,7 +1082,7 @@ }, "sprites/buildings/wire_tunnel-coating.png": { - "frame": {"x":327,"y":457,"w":12,"h":46}, + "frame": {"x":442,"y":643,"w":12,"h":46}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":18,"y":1,"w":12,"h":46}, @@ -1090,7 +1090,7 @@ }, "sprites/buildings/wire_tunnel.png": { - "frame": {"x":259,"y":696,"w":47,"h":46}, + "frame": {"x":262,"y":659,"w":47,"h":46}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":47,"h":46}, @@ -1120,9 +1120,25 @@ "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, "sourceSize": {"w":16,"h":16} }, +"sprites/misc/processor_disabled.png": +{ + "frame": {"x":479,"y":129,"w":28,"h":29}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":28,"h":29}, + "sourceSize": {"w":32,"h":32} +}, +"sprites/misc/processor_disconnected.png": +{ + "frame": {"x":475,"y":258,"w":23,"h":29}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":5,"y":2,"w":23,"h":29}, + "sourceSize": {"w":32,"h":32} +}, "sprites/misc/slot_bad_arrow.png": { - "frame": {"x":252,"y":462,"w":13,"h":13}, + "frame": {"x":442,"y":609,"w":13,"h":13}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13}, @@ -1130,7 +1146,7 @@ }, "sprites/misc/slot_good_arrow.png": { - "frame": {"x":252,"y":479,"w":13,"h":13}, + "frame": {"x":442,"y":626,"w":13,"h":13}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":13,"h":13}, @@ -1146,7 +1162,7 @@ }, "sprites/misc/waypoint.png": { - "frame": {"x":349,"y":437,"w":14,"h":16}, + "frame": {"x":495,"y":291,"w":14,"h":16}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":14,"h":16}, @@ -1154,7 +1170,7 @@ }, "sprites/wires/boolean_false.png": { - "frame": {"x":252,"y":496,"w":12,"h":15}, + "frame": {"x":425,"y":660,"w":12,"h":15}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":1,"w":12,"h":15}, @@ -1162,7 +1178,7 @@ }, "sprites/wires/boolean_true.png": { - "frame": {"x":357,"y":267,"w":9,"h":15}, + "frame": {"x":424,"y":679,"w":9,"h":15}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":1,"w":9,"h":15}, @@ -1194,7 +1210,7 @@ }, "sprites/wires/display/purple.png": { - "frame": {"x":337,"y":267,"w":16,"h":16}, + "frame": {"x":475,"y":291,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1202,7 +1218,7 @@ }, "sprites/wires/display/red.png": { - "frame": {"x":337,"y":287,"w":16,"h":16}, + "frame": {"x":159,"y":400,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1210,7 +1226,7 @@ }, "sprites/wires/display/white.png": { - "frame": {"x":337,"y":307,"w":16,"h":16}, + "frame": {"x":159,"y":420,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1218,7 +1234,7 @@ }, "sprites/wires/display/yellow.png": { - "frame": {"x":278,"y":315,"w":16,"h":16}, + "frame": {"x":159,"y":440,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1226,7 +1242,7 @@ }, "sprites/wires/lever_on.png": { - "frame": {"x":235,"y":367,"w":38,"h":44}, + "frame": {"x":230,"y":563,"w":38,"h":44}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":1,"w":38,"h":44}, @@ -1234,7 +1250,7 @@ }, "sprites/wires/logical_acceptor.png": { - "frame": {"x":343,"y":227,"w":23,"h":36}, + "frame": {"x":343,"y":259,"w":23,"h":36}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":13,"y":0,"w":23,"h":36}, @@ -1250,7 +1266,7 @@ }, "sprites/wires/network_conflict.png": { - "frame": {"x":298,"y":315,"w":16,"h":16}, + "frame": {"x":159,"y":460,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1258,7 +1274,7 @@ }, "sprites/wires/network_empty.png": { - "frame": {"x":318,"y":315,"w":15,"h":16}, + "frame": {"x":159,"y":500,"w":15,"h":16}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":15,"h":16}, @@ -1274,7 +1290,7 @@ }, "sprites/wires/sets/color_cross.png": { - "frame": {"x":415,"y":613,"w":48,"h":48}, + "frame": {"x":459,"y":615,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1282,7 +1298,7 @@ }, "sprites/wires/sets/color_forward.png": { - "frame": {"x":247,"y":515,"w":8,"h":48}, + "frame": {"x":501,"y":719,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -1290,7 +1306,7 @@ }, "sprites/wires/sets/color_split.png": { - "frame": {"x":107,"y":600,"w":48,"h":28}, + "frame": {"x":3,"y":678,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -1298,7 +1314,7 @@ }, "sprites/wires/sets/color_turn.png": { - "frame": {"x":479,"y":193,"w":28,"h":28}, + "frame": {"x":343,"y":195,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -1306,7 +1322,7 @@ }, "sprites/wires/sets/conflict_cross.png": { - "frame": {"x":3,"y":500,"w":48,"h":48}, + "frame": {"x":372,"y":671,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1314,7 +1330,7 @@ }, "sprites/wires/sets/conflict_forward.png": { - "frame": {"x":343,"y":457,"w":8,"h":48}, + "frame": {"x":436,"y":745,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -1322,7 +1338,7 @@ }, "sprites/wires/sets/conflict_split.png": { - "frame": {"x":159,"y":623,"w":48,"h":28}, + "frame": {"x":211,"y":611,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -1330,7 +1346,7 @@ }, "sprites/wires/sets/conflict_turn.png": { - "frame": {"x":343,"y":195,"w":28,"h":28}, + "frame": {"x":475,"y":226,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -1338,7 +1354,7 @@ }, "sprites/wires/sets/regular_cross.png": { - "frame": {"x":363,"y":567,"w":48,"h":48}, + "frame": {"x":373,"y":619,"w":48,"h":48}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48}, @@ -1346,7 +1362,7 @@ }, "sprites/wires/sets/regular_forward.png": { - "frame": {"x":235,"y":513,"w":8,"h":48}, + "frame": {"x":424,"y":698,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -1354,7 +1370,7 @@ }, "sprites/wires/sets/regular_split.png": { - "frame": {"x":55,"y":594,"w":48,"h":28}, + "frame": {"x":3,"y":646,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -1362,7 +1378,7 @@ }, "sprites/wires/sets/regular_turn.png": { - "frame": {"x":479,"y":161,"w":28,"h":28}, + "frame": {"x":479,"y":194,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -1370,7 +1386,7 @@ }, "sprites/wires/sets/shape_cross.png": { - "frame": {"x":55,"y":500,"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}, @@ -1378,7 +1394,7 @@ }, "sprites/wires/sets/shape_forward.png": { - "frame": {"x":355,"y":457,"w":8,"h":48}, + "frame": {"x":500,"y":771,"w":8,"h":48}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":0,"w":8,"h":48}, @@ -1386,7 +1402,7 @@ }, "sprites/wires/sets/shape_split.png": { - "frame": {"x":3,"y":626,"w":48,"h":28}, + "frame": {"x":159,"y":617,"w":48,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":20,"w":48,"h":28}, @@ -1394,7 +1410,7 @@ }, "sprites/wires/sets/shape_turn.png": { - "frame": {"x":475,"y":225,"w":28,"h":28}, + "frame": {"x":343,"y":227,"w":28,"h":28}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":20,"y":20,"w":28,"h":28}, @@ -1402,7 +1418,7 @@ }, "sprites/wires/wires_preview.png": { - "frame": {"x":329,"y":437,"w":16,"h":16}, + "frame": {"x":159,"y":480,"w":16,"h":16}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":16,"h":16}, @@ -1415,6 +1431,6 @@ "format": "RGBA8888", "size": {"w":512,"h":1024}, "scale": "0.25", - "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$" + "smartupdate": "$TexturePacker:SmartUpdate:1923b9c910205fb09957b67f92de02b9:0d87df06f18965307bf9d18b414f603d:908b89f5ca8ff73e331a35a3b14d0604$" } } diff --git a/res_built/atlas/atlas0_lq.png b/res_built/atlas/atlas0_lq.png index fd8aa06b..5fc693bb 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 14de3876..927fcb26 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":439,"y":803,"w":78,"h":96}, + "frame": {"x":3,"y":1759,"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":758,"y":1549,"w":78,"h":96}, + "frame": {"x":557,"y":1587,"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":658,"y":1555,"w":78,"h":96}, + "frame": {"x":459,"y":1653,"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":556,"y":1580,"w":78,"h":96}, + "frame": {"x":362,"y":1663,"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":459,"y":1617,"w":78,"h":96}, + "frame": {"x":268,"y":1704,"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":359,"y":1647,"w":78,"h":96}, + "frame": {"x":177,"y":1761,"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":268,"y":1671,"w":78,"h":96}, + "frame": {"x":85,"y":1805,"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":176,"y":1749,"w":78,"h":96}, + "frame": {"x":3,"y":1859,"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":85,"y":1821,"w":78,"h":96}, + "frame": {"x":85,"y":1905,"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":94,"y":1721,"w":78,"h":96}, + "frame": {"x":860,"y":1408,"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":3,"y":1737,"w":78,"h":96}, + "frame": {"x":942,"y":1408,"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":859,"y":1500,"w":78,"h":96}, + "frame": {"x":757,"y":1465,"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":941,"y":1500,"w":78,"h":96}, + "frame": {"x":656,"y":1539,"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":495,"y":1250,"w":87,"h":87}, + "frame": {"x":931,"y":942,"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":394,"y":1274,"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}, @@ -130,7 +130,7 @@ }, "sprites/belt/built/left_2.png": { - "frame": {"x":789,"y":1185,"w":87,"h":87}, + "frame": {"x":99,"y":1432,"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":880,"y":1227,"w":87,"h":87}, + "frame": {"x":3,"y":1486,"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":789,"y":1276,"w":87,"h":87}, + "frame": {"x":786,"y":1192,"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":690,"y":1282,"w":87,"h":87}, + "frame": {"x":687,"y":1266,"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":586,"y":1303,"w":87,"h":87}, + "frame": {"x":587,"y":1310,"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":485,"y":1341,"w":87,"h":87}, + "frame": {"x":484,"y":1377,"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":386,"y":1365,"w":87,"h":87}, + "frame": {"x":384,"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":286,"y":1389,"w":87,"h":87}, + "frame": {"x":287,"y":1422,"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":295,"y":1298,"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}, @@ -202,7 +202,7 @@ }, "sprites/belt/built/left_11.png": { - "frame": {"x":195,"y":1376,"w":87,"h":87}, + "frame": {"x":393,"y":1290,"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":99,"y":1448,"w":87,"h":87}, + "frame": {"x":293,"y":1331,"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":3,"y":1464,"w":87,"h":87}, + "frame": {"x":196,"y":1388,"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":190,"y":1467,"w":87,"h":87}, + "frame": {"x":190,"y":1479,"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":94,"y":1539,"w":87,"h":87}, + "frame": {"x":94,"y":1523,"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":576,"y":1394,"w":87,"h":87}, + "frame": {"x":575,"y":1401,"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":477,"y":1432,"w":87,"h":87}, + "frame": {"x":475,"y":1468,"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":377,"y":1456,"w":87,"h":87}, + "frame": {"x":378,"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":281,"y":1480,"w":87,"h":87}, + "frame": {"x":281,"y":1513,"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":185,"y":1558,"w":87,"h":87}, + "frame": {"x":185,"y":1570,"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":1630,"w":87,"h":87}, + "frame": {"x":94,"y":1614,"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":3,"y":1646,"w":87,"h":87}, + "frame": {"x":3,"y":1668,"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":872,"y":1409,"w":87,"h":87}, + "frame": {"x":869,"y":1317,"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":3,"y":1555,"w":87,"h":87}, + "frame": {"x":3,"y":1577,"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":880,"y":1318,"w":87,"h":87}, + "frame": {"x":877,"y":1226,"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":781,"y":1367,"w":87,"h":87}, + "frame": {"x":778,"y":1283,"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":677,"y":1373,"w":87,"h":87}, + "frame": {"x":678,"y":1357,"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":768,"y":1458,"w":87,"h":87}, + "frame": {"x":769,"y":1374,"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":667,"y":1464,"w":87,"h":87}, + "frame": {"x":666,"y":1448,"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":3,"y":1837,"w":78,"h":96}, + "frame": {"x":167,"y":1861,"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":568,"y":1485,"w":86,"h":91}, + "frame": {"x":566,"y":1492,"w":86,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":5,"w":86,"h":91}, @@ -410,7 +410,7 @@ }, "sprites/blueprints/logic_gate-not.png": { - "frame": {"x":372,"y":1547,"w":83,"h":96}, + "frame": {"x":372,"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":3,"y":874,"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}, @@ -426,7 +426,7 @@ }, "sprites/blueprints/logic_gate-transistor.png": { - "frame": {"x":449,"y":703,"w":68,"h":96}, + "frame": {"x":451,"y":703,"w":68,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96}, @@ -442,7 +442,7 @@ }, "sprites/blueprints/logic_gate.png": { - "frame": {"x":521,"y":999,"w":96,"h":89}, + "frame": {"x":910,"y":1133,"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":929,"y":942,"w":92,"h":96}, + "frame": {"x":496,"y":1178,"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":298,"y":1198,"w":92,"h":96}, + "frame": {"x":396,"y":1190,"w":92,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":92,"h":96}, @@ -514,7 +514,7 @@ }, "sprites/blueprints/rotater-fl.png": { - "frame": {"x":203,"y":990,"w":95,"h":96}, + "frame": {"x":3,"y":926,"w":95,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96}, @@ -530,7 +530,7 @@ }, "sprites/blueprints/splitter-compact-inverse.png": { - "frame": {"x":202,"y":1090,"w":95,"h":93}, + "frame": {"x":102,"y":1083,"w":95,"h":93}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":1,"w":95,"h":93}, @@ -538,7 +538,7 @@ }, "sprites/blueprints/splitter-compact-merge-inverse.png": { - "frame": {"x":102,"y":1091,"w":95,"h":93}, + "frame": {"x":3,"y":1126,"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.png": { - "frame": {"x":401,"y":1098,"w":93,"h":93}, + "frame": {"x":3,"y":1223,"w":93,"h":93}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":1,"w":93,"h":93}, @@ -554,7 +554,7 @@ }, "sprites/blueprints/splitter-compact.png": { - "frame": {"x":301,"y":1101,"w":93,"h":93}, + "frame": {"x":399,"y":1093,"w":93,"h":93}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":1,"w":93,"h":93}, @@ -586,7 +586,7 @@ }, "sprites/blueprints/trash.png": { - "frame": {"x":349,"y":703,"w":96,"h":96}, + "frame": {"x":351,"y":703,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -594,7 +594,7 @@ }, "sprites/blueprints/underground_belt_entry-tier2.png": { - "frame": {"x":597,"y":1116,"w":93,"h":84}, + "frame": {"x":3,"y":1320,"w":93,"h":84}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":12,"w":93,"h":84}, @@ -602,7 +602,7 @@ }, "sprites/blueprints/underground_belt_entry.png": { - "frame": {"x":498,"y":1171,"w":93,"h":75}, + "frame": {"x":496,"y":1099,"w":93,"h":75}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":21,"w":93,"h":75}, @@ -610,7 +610,7 @@ }, "sprites/blueprints/underground_belt_exit-tier2.png": { - "frame": {"x":499,"y":1092,"w":94,"h":75}, + "frame": {"x":102,"y":1180,"w":94,"h":75}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":0,"w":94,"h":75}, @@ -618,7 +618,7 @@ }, "sprites/blueprints/underground_belt_exit.png": { - "frame": {"x":398,"y":1195,"w":93,"h":75}, + "frame": {"x":593,"y":1132,"w":93,"h":75}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":0,"w":93,"h":75}, @@ -626,7 +626,7 @@ }, "sprites/blueprints/virtual_processor-analyzer.png": { - "frame": {"x":521,"y":799,"w":96,"h":96}, + "frame": {"x":523,"y":799,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -634,7 +634,7 @@ }, "sprites/blueprints/virtual_processor-rotater.png": { - "frame": {"x":276,"y":1571,"w":79,"h":96}, + "frame": {"x":185,"y":1661,"w":79,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":9,"y":0,"w":79,"h":96}, @@ -642,7 +642,7 @@ }, "sprites/blueprints/virtual_processor-shapecompare.png": { - "frame": {"x":621,"y":1023,"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}, @@ -650,7 +650,7 @@ }, "sprites/blueprints/virtual_processor-unstacker.png": { - "frame": {"x":729,"y":890,"w":96,"h":96}, + "frame": {"x":349,"y":803,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -658,7 +658,7 @@ }, "sprites/blueprints/virtual_processor.png": { - "frame": {"x":921,"y":1042,"w":96,"h":94}, + "frame": {"x":710,"y":1090,"w":96,"h":94}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":96,"h":94}, @@ -666,7 +666,7 @@ }, "sprites/blueprints/wire-cross.png": { - "frame": {"x":829,"y":894,"w":96,"h":96}, + "frame": {"x":522,"y":899,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -674,7 +674,7 @@ }, "sprites/blueprints/wire-split.png": { - "frame": {"x":103,"y":874,"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}, @@ -706,7 +706,7 @@ }, "sprites/blueprints/wire_tunnel.png": { - "frame": {"x":201,"y":1187,"w":93,"h":91}, + "frame": {"x":299,"y":1142,"w":93,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":2,"y":2,"w":93,"h":91}, @@ -714,7 +714,7 @@ }, "sprites/buildings/belt_left.png": { - "frame": {"x":495,"y":1250,"w":87,"h":87}, + "frame": {"x":931,"y":942,"w":87,"h":87}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":9,"w":87,"h":87}, @@ -722,7 +722,7 @@ }, "sprites/buildings/belt_right.png": { - "frame": {"x":190,"y":1467,"w":87,"h":87}, + "frame": {"x":190,"y":1479,"w":87,"h":87}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":9,"y":9,"w":87,"h":87}, @@ -762,7 +762,7 @@ }, "sprites/buildings/display.png": { - "frame": {"x":468,"y":1523,"w":84,"h":90}, + "frame": {"x":469,"y":1559,"w":84,"h":90}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":6,"w":84,"h":90}, @@ -794,7 +794,7 @@ }, "sprites/buildings/logic_gate-not.png": { - "frame": {"x":435,"y":903,"w":82,"h":96}, + "frame": {"x":276,"y":1604,"w":82,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":8,"y":0,"w":82,"h":96}, @@ -802,7 +802,7 @@ }, "sprites/buildings/logic_gate-or.png": { - "frame": {"x":921,"y":1140,"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}, @@ -810,7 +810,7 @@ }, "sprites/buildings/logic_gate-transistor.png": { - "frame": {"x":621,"y":799,"w":68,"h":96}, + "frame": {"x":623,"y":799,"w":68,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":68,"h":96}, @@ -818,7 +818,7 @@ }, "sprites/buildings/logic_gate-xor.png": { - "frame": {"x":821,"y":994,"w":96,"h":95}, + "frame": {"x":610,"y":1033,"w":96,"h":95}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":95}, @@ -826,7 +826,7 @@ }, "sprites/buildings/logic_gate.png": { - "frame": {"x":821,"y":1093,"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}, @@ -834,7 +834,7 @@ }, "sprites/buildings/miner-chainable.png": { - "frame": {"x":694,"y":1183,"w":91,"h":95}, + "frame": {"x":592,"y":1211,"w":91,"h":95}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95}, @@ -842,7 +842,7 @@ }, "sprites/buildings/miner.png": { - "frame": {"x":595,"y":1204,"w":91,"h":95}, + "frame": {"x":492,"y":1278,"w":91,"h":95}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":91,"h":95}, @@ -890,7 +890,7 @@ }, "sprites/buildings/rotater-ccw.png": { - "frame": {"x":103,"y":991,"w":95,"h":96}, + "frame": {"x":102,"y":983,"w":95,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":0,"w":95,"h":96}, @@ -898,7 +898,7 @@ }, "sprites/buildings/rotater-fl.png": { - "frame": {"x":3,"y":1018,"w":95,"h":96}, + "frame": {"x":3,"y":1026,"w":95,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":95,"h":96}, @@ -906,7 +906,7 @@ }, "sprites/buildings/rotater.png": { - "frame": {"x":302,"y":1001,"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 +914,7 @@ }, "sprites/buildings/splitter-compact-inverse.png": { - "frame": {"x":401,"y":1003,"w":94,"h":91}, + "frame": {"x":201,"y":1111,"w":94,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":94,"h":91}, @@ -922,7 +922,7 @@ }, "sprites/buildings/splitter-compact-merge-inverse.png": { - "frame": {"x":3,"y":1118,"w":95,"h":91}, + "frame": {"x":300,"y":1047,"w":95,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":95,"h":91}, @@ -930,7 +930,7 @@ }, "sprites/buildings/splitter-compact-merge.png": { - "frame": {"x":102,"y":1188,"w":93,"h":91}, + "frame": {"x":200,"y":1206,"w":93,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":2,"w":93,"h":91}, @@ -938,7 +938,7 @@ }, "sprites/buildings/splitter-compact.png": { - "frame": {"x":3,"y":1213,"w":93,"h":91}, + "frame": {"x":100,"y":1259,"w":93,"h":91}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":2,"w":93,"h":91}, @@ -970,7 +970,7 @@ }, "sprites/buildings/trash.png": { - "frame": {"x":3,"y":774,"w":96,"h":96}, + "frame": {"x":731,"y":890,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -978,7 +978,7 @@ }, "sprites/buildings/underground_belt_entry-tier2.png": { - "frame": {"x":100,"y":1283,"w":92,"h":83}, + "frame": {"x":197,"y":1301,"w":92,"h":83}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":13,"w":92,"h":83}, @@ -986,7 +986,7 @@ }, "sprites/buildings/underground_belt_entry.png": { - "frame": {"x":3,"y":1308,"w":92,"h":74}, + "frame": {"x":100,"y":1354,"w":92,"h":74}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":22,"w":92,"h":74}, @@ -994,7 +994,7 @@ }, "sprites/buildings/underground_belt_exit-tier2.png": { - "frame": {"x":99,"y":1370,"w":92,"h":74}, + "frame": {"x":3,"y":1408,"w":92,"h":74}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74}, @@ -1002,7 +1002,7 @@ }, "sprites/buildings/underground_belt_exit.png": { - "frame": {"x":3,"y":1386,"w":92,"h":74}, + "frame": {"x":690,"y":1188,"w":92,"h":74}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":0,"w":92,"h":74}, @@ -1010,7 +1010,7 @@ }, "sprites/buildings/virtual_processor-analyzer.png": { - "frame": {"x":103,"y":774,"w":96,"h":96}, + "frame": {"x":622,"y":933,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1018,7 +1018,7 @@ }, "sprites/buildings/virtual_processor-rotater.png": { - "frame": {"x":185,"y":1649,"w":79,"h":96}, + "frame": {"x":94,"y":1705,"w":79,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":9,"y":0,"w":79,"h":96}, @@ -1026,7 +1026,7 @@ }, "sprites/buildings/virtual_processor-shapecompare.png": { - "frame": {"x":721,"y":1090,"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}, @@ -1034,7 +1034,7 @@ }, "sprites/buildings/virtual_processor-unstacker.png": { - "frame": {"x":203,"y":774,"w":96,"h":96}, + "frame": {"x":510,"y":999,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1042,7 +1042,7 @@ }, "sprites/buildings/virtual_processor.png": { - "frame": {"x":335,"y":903,"w":96,"h":94}, + "frame": {"x":810,"y":1094,"w":96,"h":94}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":96,"h":94}, @@ -1050,7 +1050,7 @@ }, "sprites/buildings/wire-cross.png": { - "frame": {"x":339,"y":803,"w":96,"h":96}, + "frame": {"x":831,"y":894,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1058,7 +1058,7 @@ }, "sprites/buildings/wire-split.png": { - "frame": {"x":203,"y":874,"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}, @@ -1090,7 +1090,7 @@ }, "sprites/buildings/wire_tunnel.png": { - "frame": {"x":199,"y":1282,"w":92,"h":90}, + "frame": {"x":297,"y":1237,"w":92,"h":90}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":3,"y":3,"w":92,"h":90}, @@ -1114,12 +1114,28 @@ }, "sprites/misc/hub_direction_indicator.png": { - "frame": {"x":693,"y":851,"w":32,"h":32}, + "frame": {"x":695,"y":851,"w":32,"h":32}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, "sourceSize": {"w":32,"h":32} }, +"sprites/misc/processor_disabled.png": +{ + "frame": {"x":449,"y":803,"w":53,"h":55}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":6,"y":6,"w":53,"h":55}, + "sourceSize": {"w":64,"h":64} +}, +"sprites/misc/processor_disconnected.png": +{ + "frame": {"x":303,"y":674,"w":44,"h":57}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":11,"y":5,"w":44,"h":57}, + "sourceSize": {"w":64,"h":64} +}, "sprites/misc/slot_bad_arrow.png": { "frame": {"x":717,"y":605,"w":24,"h":24}, @@ -1218,7 +1234,7 @@ }, "sprites/wires/display/yellow.png": { - "frame": {"x":693,"y":814,"w":33,"h":33}, + "frame": {"x":695,"y":814,"w":33,"h":33}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":33,"h":33}, @@ -1234,7 +1250,7 @@ }, "sprites/wires/logical_acceptor.png": { - "frame": {"x":303,"y":674,"w":42,"h":71}, + "frame": {"x":303,"y":735,"w":42,"h":71}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":28,"y":0,"w":42,"h":71}, @@ -1242,7 +1258,7 @@ }, "sprites/wires/logical_ejector.png": { - "frame": {"x":303,"y":749,"w":41,"h":45}, + "frame": {"x":449,"y":862,"w":41,"h":45}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":29,"y":0,"w":41,"h":45}, @@ -1250,7 +1266,7 @@ }, "sprites/wires/network_conflict.png": { - "frame": {"x":303,"y":798,"w":32,"h":30}, + "frame": {"x":622,"y":899,"w":32,"h":30}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":1,"w":32,"h":30}, @@ -1274,7 +1290,7 @@ }, "sprites/wires/sets/color_cross.png": { - "frame": {"x":521,"y":899,"w":96,"h":96}, + "frame": {"x":722,"y":990,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1290,7 +1306,7 @@ }, "sprites/wires/sets/color_split.png": { - "frame": {"x":203,"y":932,"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}, @@ -1306,7 +1322,7 @@ }, "sprites/wires/sets/conflict_cross.png": { - "frame": {"x":621,"y":923,"w":96,"h":96}, + "frame": {"x":822,"y":994,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1314,7 +1330,7 @@ }, "sprites/wires/sets/conflict_forward.png": { - "frame": {"x":303,"y":832,"w":12,"h":96}, + "frame": {"x":506,"y":803,"w":12,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96}, @@ -1322,7 +1338,7 @@ }, "sprites/wires/sets/conflict_split.png": { - "frame": {"x":103,"y":933,"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}, @@ -1338,7 +1354,7 @@ }, "sprites/wires/sets/regular_cross.png": { - "frame": {"x":339,"y":803,"w":96,"h":96}, + "frame": {"x":831,"y":894,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1354,7 +1370,7 @@ }, "sprites/wires/sets/regular_split.png": { - "frame": {"x":203,"y":874,"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}, @@ -1370,7 +1386,7 @@ }, "sprites/wires/sets/shape_cross.png": { - "frame": {"x":721,"y":990,"w":96,"h":96}, + "frame": {"x":922,"y":1033,"w":96,"h":96}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":96}, @@ -1378,7 +1394,7 @@ }, "sprites/wires/sets/shape_forward.png": { - "frame": {"x":319,"y":832,"w":12,"h":96}, + "frame": {"x":494,"y":903,"w":12,"h":96}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":42,"y":0,"w":12,"h":96}, @@ -1386,7 +1402,7 @@ }, "sprites/wires/sets/shape_split.png": { - "frame": {"x":3,"y":960,"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}, @@ -1402,7 +1418,7 @@ }, "sprites/wires/wires_preview.png": { - "frame": {"x":693,"y":887,"w":32,"h":32}, + "frame": {"x":695,"y":887,"w":32,"h":32}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32}, @@ -1415,6 +1431,6 @@ "format": "RGBA8888", "size": {"w":1024,"h":2048}, "scale": "0.5", - "smartupdate": "$TexturePacker:SmartUpdate:65dccccf359b3f3582914eac20260366:f9eee0054558f8bf77da34f281176a03:908b89f5ca8ff73e331a35a3b14d0604$" + "smartupdate": "$TexturePacker:SmartUpdate:1923b9c910205fb09957b67f92de02b9:0d87df06f18965307bf9d18b414f603d:908b89f5ca8ff73e331a35a3b14d0604$" } } diff --git a/res_built/atlas/atlas0_mq.png b/res_built/atlas/atlas0_mq.png index 5f962bcb..bbf553b4 100644 Binary files a/res_built/atlas/atlas0_mq.png and b/res_built/atlas/atlas0_mq.png differ diff --git a/res_raw/sprites/blueprints/painter-quad.png b/res_raw/sprites/blueprints/painter-quad.png index c3c755f2..6f8ce2a0 100644 Binary files a/res_raw/sprites/blueprints/painter-quad.png and b/res_raw/sprites/blueprints/painter-quad.png differ diff --git a/res_raw/sprites/buildings/painter-quad.png b/res_raw/sprites/buildings/painter-quad.png index 18790a78..d14bd382 100644 Binary files a/res_raw/sprites/buildings/painter-quad.png and b/res_raw/sprites/buildings/painter-quad.png differ diff --git a/res_raw/sprites/misc/processor_disabled.png b/res_raw/sprites/misc/processor_disabled.png new file mode 100644 index 00000000..37dfe32b Binary files /dev/null and b/res_raw/sprites/misc/processor_disabled.png differ diff --git a/res_raw/sprites/misc/processor_disconnected.png b/res_raw/sprites/misc/processor_disconnected.png new file mode 100644 index 00000000..eb99ca65 Binary files /dev/null and b/res_raw/sprites/misc/processor_disconnected.png differ diff --git a/src/js/core/utils.js b/src/js/core/utils.js index 4cb41087..31d00d69 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -1,724 +1,733 @@ -import { T } from "../translations"; - -const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"]; - -/** - * Returns if this platform is android - * @returns {boolean} - */ -export function isAndroid() { - if (!G_IS_MOBILE_APP) { - return false; - } - const platform = window.device.platform; - return platform === "Android" || platform === "amazon-fireos"; -} - -/** - * Returns if this platform is iOs - * @returns {boolean} - */ -export function isIos() { - if (!G_IS_MOBILE_APP) { - return false; - } - return window.device.platform === "iOS"; -} - -/** - * Returns a platform name - * @returns {"android" | "browser" | "ios" | "standalone" | "unknown"} - */ -export function getPlatformName() { - if (G_IS_STANDALONE) { - return "standalone"; - } else if (G_IS_BROWSER) { - return "browser"; - } else if (G_IS_MOBILE_APP && isAndroid()) { - return "android"; - } else if (G_IS_MOBILE_APP && isIos()) { - return "ios"; - } - return "unknown"; -} - -/** - * Returns the IPC renderer, or null if not within the standalone - * @returns {object|null} - */ -let ipcRenderer = null; -export function getIPCRenderer() { - if (!G_IS_STANDALONE) { - return null; - } - if (!ipcRenderer) { - ipcRenderer = eval("require")("electron").ipcRenderer; - } - return ipcRenderer; -} - -/** - * Makes a new 2D array with undefined contents - * @param {number} w - * @param {number} h - * @returns {Array>} - */ -export function make2DUndefinedArray(w, h) { - const result = new Array(w); - for (let x = 0; x < w; ++x) { - result[x] = new Array(h); - } - return result; -} - -/** - * Creates a new map (an empty object without any props) - */ -export function newEmptyMap() { - return Object.create(null); -} - -/** - * Returns a random integer in the range [start,end] - * @param {number} start - * @param {number} end - */ -export function randomInt(start, end) { - return start + Math.round(Math.random() * (end - start)); -} - -/** - * Access an object in a very annoying way, used for obsfuscation. - * @param {any} obj - * @param {Array} keys - */ -export function accessNestedPropertyReverse(obj, keys) { - let result = obj; - for (let i = keys.length - 1; i >= 0; --i) { - result = result[keys[i]]; - } - return result; -} - -/** - * Chooses a random entry of an array - * @template T - * @param {T[]} arr - * @returns {T} - */ -export function randomChoice(arr) { - return arr[Math.floor(Math.random() * arr.length)]; -} - -/** - * Deletes from an array by swapping with the last element - * @param {Array} array - * @param {number} index - */ -export function fastArrayDelete(array, index) { - if (index < 0 || index >= array.length) { - throw new Error("Out of bounds"); - } - // When the element is not the last element - if (index !== array.length - 1) { - // Get the last element, and swap it with the one we want to delete - const last = array[array.length - 1]; - array[index] = last; - } - - // Finally remove the last element - array.length -= 1; -} - -/** - * Deletes from an array by swapping with the last element. Searches - * for the value in the array first - * @param {Array} array - * @param {any} value - */ -export function fastArrayDeleteValue(array, value) { - if (array == null) { - throw new Error("Tried to delete from non array!"); - } - const index = array.indexOf(value); - if (index < 0) { - console.error("Value", value, "not contained in array:", array, "!"); - return value; - } - return fastArrayDelete(array, index); -} - -/** - * @see fastArrayDeleteValue - * @param {Array} array - * @param {any} value - */ -export function fastArrayDeleteValueIfContained(array, value) { - if (array == null) { - throw new Error("Tried to delete from non array!"); - } - const index = array.indexOf(value); - if (index < 0) { - return value; - } - return fastArrayDelete(array, index); -} - -/** - * Deletes from an array at the given index - * @param {Array} array - * @param {number} index - */ -export function arrayDelete(array, index) { - if (index < 0 || index >= array.length) { - throw new Error("Out of bounds"); - } - array.splice(index, 1); -} - -/** - * Deletes the given value from an array - * @param {Array} array - * @param {any} value - */ -export function arrayDeleteValue(array, value) { - if (array == null) { - throw new Error("Tried to delete from non array!"); - } - const index = array.indexOf(value); - if (index < 0) { - console.error("Value", value, "not contained in array:", array, "!"); - return value; - } - return arrayDelete(array, index); -} - -/** - * Compare two floats for epsilon equality - * @param {number} a - * @param {number} b - * @returns {boolean} - */ -export function epsilonCompare(a, b, epsilon = 1e-5) { - return Math.abs(a - b) < epsilon; -} - -/** - * Interpolates two numbers - * @param {number} a - * @param {number} b - * @param {number} x Mix factor, 0 means 100% a, 1 means 100%b, rest is interpolated - */ -export function lerp(a, b, x) { - return a * (1 - x) + b * x; -} - -/** - * Finds a value which is nice to display, e.g. 15669 -> 15000. Also handles fractional stuff - * @param {number} num - */ -export function findNiceValue(num) { - if (num > 1e8) { - return num; - } - if (num < 0.00001) { - return 0; - } - - let roundAmount = 1; - if (num > 50000) { - roundAmount = 10000; - } else if (num > 20000) { - roundAmount = 5000; - } else if (num > 5000) { - roundAmount = 1000; - } else if (num > 2000) { - roundAmount = 500; - } else if (num > 1000) { - roundAmount = 100; - } else if (num > 100) { - roundAmount = 20; - } else if (num > 20) { - roundAmount = 5; - } - - const niceValue = Math.floor(num / roundAmount) * roundAmount; - if (num >= 10) { - return Math.round(niceValue); - } - if (num >= 1) { - return Math.round(niceValue * 10) / 10; - } - - return Math.round(niceValue * 100) / 100; -} - -/** - * Finds a nice integer value - * @see findNiceValue - * @param {number} num - */ -export function findNiceIntegerValue(num) { - return Math.ceil(findNiceValue(num)); -} - -/** - * Formats a big number - * @param {number} num - * @param {string=} separator The decimal separator for numbers like 50.1 (separator='.') - * @returns {string} - */ -export function formatBigNumber(num, separator = T.global.decimalSeparator) { - const sign = num < 0 ? "-" : ""; - num = Math.abs(num); - - if (num > 1e54) { - return sign + T.global.infinite; - } - - if (num < 10 && !Number.isInteger(num)) { - return sign + num.toFixed(2); - } - if (num < 50 && !Number.isInteger(num)) { - return sign + num.toFixed(1); - } - num = Math.floor(num); - - if (num < 1000) { - return sign + "" + num; - } else { - let leadingDigits = num; - let suffix = ""; - for (let suffixIndex = 0; suffixIndex < bigNumberSuffixTranslationKeys.length; ++suffixIndex) { - leadingDigits = leadingDigits / 1000; - suffix = T.global.suffix[bigNumberSuffixTranslationKeys[suffixIndex]]; - if (leadingDigits < 1000) { - break; - } - } - const leadingDigitsRounded = round1Digit(leadingDigits); - const leadingDigitsNoTrailingDecimal = leadingDigitsRounded - .toString() - .replace(".0", "") - .replace(".", separator); - return sign + leadingDigitsNoTrailingDecimal + suffix; - } -} - -/** - * Formats a big number, but does not add any suffix and instead uses its full representation - * @param {number} num - * @param {string=} divider The divider for numbers like 50,000 (divider=',') - * @returns {string} - */ -export function formatBigNumberFull(num, divider = T.global.thousandsDivider) { - if (num < 1000) { - return num + ""; - } - if (num > 1e54) { - return T.global.infinite; - } - let rest = num; - let out = ""; - while (rest >= 1000) { - out = (rest % 1000).toString().padStart(3, "0") + divider + out; - rest = Math.floor(rest / 1000); - } - out = rest + divider + out; - - return out.substring(0, out.length - 1); -} - -/** - * Waits two frames so the ui is updated - * @returns {Promise} - */ -export function waitNextFrame() { - return new Promise(function (resolve) { - window.requestAnimationFrame(function () { - window.requestAnimationFrame(function () { - resolve(); - }); - }); - }); -} - -/** - * Rounds 1 digit - * @param {number} n - * @returns {number} - */ -export function round1Digit(n) { - return Math.floor(n * 10.0) / 10.0; -} - -/** - * Rounds 2 digits - * @param {number} n - * @returns {number} - */ -export function round2Digits(n) { - return Math.floor(n * 100.0) / 100.0; -} - -/** - * Rounds 3 digits - * @param {number} n - * @returns {number} - */ -export function round3Digits(n) { - return Math.floor(n * 1000.0) / 1000.0; -} - -/** - * Rounds 4 digits - * @param {number} n - * @returns {number} - */ -export function round4Digits(n) { - return Math.floor(n * 10000.0) / 10000.0; -} - -/** - * Clamps a value between [min, max] - * @param {number} v - * @param {number=} minimum Default 0 - * @param {number=} maximum Default 1 - */ -export function clamp(v, minimum = 0, maximum = 1) { - return Math.max(minimum, Math.min(maximum, v)); -} - -/** - * Helper method to create a new div element - * @param {string=} id - * @param {Array=} classes - * @param {string=} innerHTML - */ -function makeDivElement(id = null, classes = [], innerHTML = "") { - const div = document.createElement("div"); - if (id) { - div.id = id; - } - for (let i = 0; i < classes.length; ++i) { - div.classList.add(classes[i]); - } - div.innerHTML = innerHTML; - return div; -} - -/** - * Helper method to create a new div - * @param {Element} parent - * @param {string=} id - * @param {Array=} classes - * @param {string=} innerHTML - */ -export function makeDiv(parent, id = null, classes = [], innerHTML = "") { - const div = makeDivElement(id, classes, innerHTML); - parent.appendChild(div); - return div; -} - -/** - * Helper method to create a new button element - * @param {Array=} classes - * @param {string=} innerHTML - */ -export function makeButtonElement(classes = [], innerHTML = "") { - const element = document.createElement("button"); - for (let i = 0; i < classes.length; ++i) { - element.classList.add(classes[i]); - } - element.classList.add("styledButton"); - element.innerHTML = innerHTML; - return element; -} - -/** - * Helper method to create a new button - * @param {Element} parent - * @param {Array=} classes - * @param {string=} innerHTML - */ -export function makeButton(parent, classes = [], innerHTML = "") { - const element = makeButtonElement(classes, innerHTML); - parent.appendChild(element); - return element; -} - -/** - * Removes all children of the given element - * @param {Element} elem - */ -export function removeAllChildren(elem) { - if (elem) { - var range = document.createRange(); - range.selectNodeContents(elem); - range.deleteContents(); - } -} - -/** - * Fixes lockstep simulation by converting times like 34.0000000003 to 34.00. - * We use 3 digits of precision, this allows us to store precision of 1 ms without - * the risking simulation errors due to resync issues - * @param {number} value - */ -export function quantizeFloat(value) { - return Math.round(value * 1000.0) / 1000.0; -} - -/** - * Safe check to check if a timer is expired. quantizes numbers - * @param {number} now Current time - * @param {number} lastTick Last tick of the timer - * @param {number} tickRate Interval of the timer - */ -export function checkTimerExpired(now, lastTick, tickRate) { - if (G_IS_DEV) { - if (quantizeFloat(now) !== now) { - console.error("Got non-quantizied time:" + now + " vs " + quantizeFloat(now)); - now = quantizeFloat(now); - } - if (quantizeFloat(lastTick) !== lastTick) { - // FIXME: REENABLE - // console.error("Got non-quantizied timer:" + lastTick + " vs " + quantizeFloat(lastTick)); - lastTick = quantizeFloat(lastTick); - } - } else { - // just to be safe - now = quantizeFloat(now); - lastTick = quantizeFloat(lastTick); - } - /* - Ok, so heres the issue (Died a bit while debugging it): - - In multiplayer lockstep simulation, client A will simulate everything at T, but client B - will simulate it at T + 3. So we are running into the following precision issue: - Lets say on client A the time is T = 30. Then on clientB the time is T = 33. - Now, our timer takes 0.1 seconds and ticked at 29.90 - What does happen now? - Client A computes the timer and checks T > lastTick + interval. He computes - - 30 >= 29.90 + 0.1 <=> 30 >= 30.0000 <=> True <=> Tick performed - - However, this is what it looks on client B: - - 33 >= 32.90 + 0.1 <=> 33 >= 32.999999999999998 <=> False <=> No tick performed! - - This means that Client B will only tick at the *next* frame, which means it from now is out - of sync by one tick, which means the game will resync further or later and be not able to recover, - since it will run into the same issue over and over. - */ - - // The next tick, in our example it would be 30.0000 / 32.99999999998. In order to fix it, we quantize - // it, so its now 30.0000 / 33.0000 - const nextTick = quantizeFloat(lastTick + tickRate); - - // This check is safe, but its the only check where you may compare times. You always need to use - // this method! - return now >= nextTick; -} - -/** - * Returns if the game supports this browser - */ -export function isSupportedBrowser() { - // please note, - // that IE11 now returns undefined again for window.chrome - // and new Opera 30 outputs true for window.chrome - // but needs to check if window.opr is not undefined - // and new IE Edge outputs to true now for window.chrome - // and if not iOS Chrome check - // so use the below updated condition - - if (G_IS_MOBILE_APP || G_IS_STANDALONE) { - return true; - } - - // @ts-ignore - var isChromium = window.chrome; - var winNav = window.navigator; - var vendorName = winNav.vendor; - // @ts-ignore - var isIEedge = winNav.userAgent.indexOf("Edge") > -1; - var isIOSChrome = winNav.userAgent.match("CriOS"); - - if (isIOSChrome) { - // is Google Chrome on IOS - return false; - } else if ( - isChromium !== null && - typeof isChromium !== "undefined" && - vendorName === "Google Inc." && - isIEedge === false - ) { - // is Google Chrome - return true; - } else { - // not Google Chrome - return false; - } -} - -/** - * Formats an amount of seconds into something like "5s ago" - * @param {number} secs Seconds - * @returns {string} - */ -export function formatSecondsToTimeAgo(secs) { - const seconds = Math.floor(secs); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - - if (seconds < 60) { - if (seconds === 1) { - return T.global.time.oneSecondAgo; - } - return T.global.time.xSecondsAgo.replace("", "" + seconds); - } else if (minutes < 60) { - if (minutes === 1) { - return T.global.time.oneMinuteAgo; - } - return T.global.time.xMinutesAgo.replace("", "" + minutes); - } else if (hours < 24) { - if (hours === 1) { - return T.global.time.oneHourAgo; - } - return T.global.time.xHoursAgo.replace("", "" + hours); - } else { - if (days === 1) { - return T.global.time.oneDayAgo; - } - return T.global.time.xDaysAgo.replace("", "" + days); - } -} - -/** - * Formats seconds into a readable string like "5h 23m" - * @param {number} secs Seconds - * @returns {string} - */ -export function formatSeconds(secs) { - const trans = T.global.time; - secs = Math.ceil(secs); - if (secs < 60) { - return trans.secondsShort.replace("", "" + secs); - } else if (secs < 60 * 60) { - const minutes = Math.floor(secs / 60); - const seconds = secs % 60; - return trans.minutesAndSecondsShort - .replace("", "" + seconds) - .replace("", "" + minutes); - } else { - const hours = Math.floor(secs / 3600); - const minutes = Math.floor(secs / 60) % 60; - return trans.hoursAndMinutesShort.replace("", "" + minutes).replace("", "" + hours); - } -} - -/** - * Formats a number like 2.5 to "2.5 items / s" - * @param {number} speed - * @param {boolean=} double - * @param {string=} separator The decimal separator for numbers like 50.1 (separator='.') - */ -export function formatItemsPerSecond(speed, double = false, separator = T.global.decimalSeparator) { - return speed === 1.0 - ? T.ingame.buildingPlacement.infoTexts.oneItemPerSecond - : T.ingame.buildingPlacement.infoTexts.itemsPerSecond.replace( - "", - round2Digits(speed).toString().replace(".", separator) - ) + (double ? " " + T.ingame.buildingPlacement.infoTexts.itemsPerSecondDouble : ""); -} - -/** - * Rotates a flat 3x3 matrix clockwise - * Entries: - * 0 lo - * 1 mo - * 2 ro - * 3 lm - * 4 mm - * 5 rm - * 6 lu - * 7 mu - * 8 ru - * @param {Array} flatMatrix - */ - -export function rotateFlatMatrix3x3(flatMatrix) { - return [ - flatMatrix[6], - flatMatrix[3], - flatMatrix[0], - flatMatrix[7], - flatMatrix[4], - flatMatrix[1], - flatMatrix[8], - flatMatrix[5], - flatMatrix[2], - ]; -} - -/** - * Generates rotated variants of the matrix - * @param {Array} originalMatrix - * @returns {Object>} - */ -export function generateMatrixRotations(originalMatrix) { - const result = { - 0: originalMatrix, - }; - - originalMatrix = rotateFlatMatrix3x3(originalMatrix); - result[90] = originalMatrix; - - originalMatrix = rotateFlatMatrix3x3(originalMatrix); - result[180] = originalMatrix; - - originalMatrix = rotateFlatMatrix3x3(originalMatrix); - result[270] = originalMatrix; - - return result; -} - -/** - * - * @typedef {{ - * top: any, - * right: any, - * bottom: any, - * left: any - * }} DirectionalObject - */ - -/** - * Rotates a directional object - * @param {DirectionalObject} obj - * @returns {DirectionalObject} - */ -export function rotateDirectionalObject(obj, rotation) { - const queue = [obj.top, obj.right, obj.bottom, obj.left]; - while (rotation !== 0) { - rotation -= 90; - queue.push(queue.shift()); - } - - return { - top: queue[0], - right: queue[1], - bottom: queue[2], - left: queue[3], - }; -} - -/** - * Modulo which works for negative numbers - * @param {number} n - * @param {number} m - */ -export function safeModulo(n, m) { - return ((n % m) + m) % m; -} +import { T } from "../translations"; + +const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"]; + +/** + * Returns if this platform is android + * @returns {boolean} + */ +export function isAndroid() { + if (!G_IS_MOBILE_APP) { + return false; + } + const platform = window.device.platform; + return platform === "Android" || platform === "amazon-fireos"; +} + +/** + * Returns if this platform is iOs + * @returns {boolean} + */ +export function isIos() { + if (!G_IS_MOBILE_APP) { + return false; + } + return window.device.platform === "iOS"; +} + +/** + * Returns a platform name + * @returns {"android" | "browser" | "ios" | "standalone" | "unknown"} + */ +export function getPlatformName() { + if (G_IS_STANDALONE) { + return "standalone"; + } else if (G_IS_BROWSER) { + return "browser"; + } else if (G_IS_MOBILE_APP && isAndroid()) { + return "android"; + } else if (G_IS_MOBILE_APP && isIos()) { + return "ios"; + } + return "unknown"; +} + +/** + * Returns the IPC renderer, or null if not within the standalone + * @returns {object|null} + */ +let ipcRenderer = null; +export function getIPCRenderer() { + if (!G_IS_STANDALONE) { + return null; + } + if (!ipcRenderer) { + ipcRenderer = eval("require")("electron").ipcRenderer; + } + return ipcRenderer; +} + +/** + * Makes a new 2D array with undefined contents + * @param {number} w + * @param {number} h + * @returns {Array>} + */ +export function make2DUndefinedArray(w, h) { + const result = new Array(w); + for (let x = 0; x < w; ++x) { + result[x] = new Array(h); + } + return result; +} + +/** + * Creates a new map (an empty object without any props) + */ +export function newEmptyMap() { + return Object.create(null); +} + +/** + * Returns a random integer in the range [start,end] + * @param {number} start + * @param {number} end + */ +export function randomInt(start, end) { + return start + Math.round(Math.random() * (end - start)); +} + +/** + * Access an object in a very annoying way, used for obsfuscation. + * @param {any} obj + * @param {Array} keys + */ +export function accessNestedPropertyReverse(obj, keys) { + let result = obj; + for (let i = keys.length - 1; i >= 0; --i) { + result = result[keys[i]]; + } + return result; +} + +/** + * Chooses a random entry of an array + * @template T + * @param {T[]} arr + * @returns {T} + */ +export function randomChoice(arr) { + return arr[Math.floor(Math.random() * arr.length)]; +} + +/** + * Deletes from an array by swapping with the last element + * @param {Array} array + * @param {number} index + */ +export function fastArrayDelete(array, index) { + if (index < 0 || index >= array.length) { + throw new Error("Out of bounds"); + } + // When the element is not the last element + if (index !== array.length - 1) { + // Get the last element, and swap it with the one we want to delete + const last = array[array.length - 1]; + array[index] = last; + } + + // Finally remove the last element + array.length -= 1; +} + +/** + * Deletes from an array by swapping with the last element. Searches + * for the value in the array first + * @param {Array} array + * @param {any} value + */ +export function fastArrayDeleteValue(array, value) { + if (array == null) { + throw new Error("Tried to delete from non array!"); + } + const index = array.indexOf(value); + if (index < 0) { + console.error("Value", value, "not contained in array:", array, "!"); + return value; + } + return fastArrayDelete(array, index); +} + +/** + * @see fastArrayDeleteValue + * @param {Array} array + * @param {any} value + */ +export function fastArrayDeleteValueIfContained(array, value) { + if (array == null) { + throw new Error("Tried to delete from non array!"); + } + const index = array.indexOf(value); + if (index < 0) { + return value; + } + return fastArrayDelete(array, index); +} + +/** + * Deletes from an array at the given index + * @param {Array} array + * @param {number} index + */ +export function arrayDelete(array, index) { + if (index < 0 || index >= array.length) { + throw new Error("Out of bounds"); + } + array.splice(index, 1); +} + +/** + * Deletes the given value from an array + * @param {Array} array + * @param {any} value + */ +export function arrayDeleteValue(array, value) { + if (array == null) { + throw new Error("Tried to delete from non array!"); + } + const index = array.indexOf(value); + if (index < 0) { + console.error("Value", value, "not contained in array:", array, "!"); + return value; + } + return arrayDelete(array, index); +} + +/** + * Compare two floats for epsilon equality + * @param {number} a + * @param {number} b + * @returns {boolean} + */ +export function epsilonCompare(a, b, epsilon = 1e-5) { + return Math.abs(a - b) < epsilon; +} + +/** + * Interpolates two numbers + * @param {number} a + * @param {number} b + * @param {number} x Mix factor, 0 means 100% a, 1 means 100%b, rest is interpolated + */ +export function lerp(a, b, x) { + return a * (1 - x) + b * x; +} + +/** + * Finds a value which is nice to display, e.g. 15669 -> 15000. Also handles fractional stuff + * @param {number} num + */ +export function findNiceValue(num) { + if (num > 1e8) { + return num; + } + if (num < 0.00001) { + return 0; + } + + let roundAmount = 1; + if (num > 50000) { + roundAmount = 10000; + } else if (num > 20000) { + roundAmount = 5000; + } else if (num > 5000) { + roundAmount = 1000; + } else if (num > 2000) { + roundAmount = 500; + } else if (num > 1000) { + roundAmount = 100; + } else if (num > 100) { + roundAmount = 20; + } else if (num > 20) { + roundAmount = 5; + } + + const niceValue = Math.floor(num / roundAmount) * roundAmount; + if (num >= 10) { + return Math.round(niceValue); + } + if (num >= 1) { + return Math.round(niceValue * 10) / 10; + } + + return Math.round(niceValue * 100) / 100; +} + +/** + * Finds a nice integer value + * @see findNiceValue + * @param {number} num + */ +export function findNiceIntegerValue(num) { + return Math.ceil(findNiceValue(num)); +} + +/** + * Formats a big number + * @param {number} num + * @param {string=} separator The decimal separator for numbers like 50.1 (separator='.') + * @returns {string} + */ +export function formatBigNumber(num, separator = T.global.decimalSeparator) { + const sign = num < 0 ? "-" : ""; + num = Math.abs(num); + + if (num > 1e54) { + return sign + T.global.infinite; + } + + if (num < 10 && !Number.isInteger(num)) { + return sign + num.toFixed(2); + } + if (num < 50 && !Number.isInteger(num)) { + return sign + num.toFixed(1); + } + num = Math.floor(num); + + if (num < 1000) { + return sign + "" + num; + } else { + let leadingDigits = num; + let suffix = ""; + for (let suffixIndex = 0; suffixIndex < bigNumberSuffixTranslationKeys.length; ++suffixIndex) { + leadingDigits = leadingDigits / 1000; + suffix = T.global.suffix[bigNumberSuffixTranslationKeys[suffixIndex]]; + if (leadingDigits < 1000) { + break; + } + } + const leadingDigitsRounded = round1Digit(leadingDigits); + const leadingDigitsNoTrailingDecimal = leadingDigitsRounded + .toString() + .replace(".0", "") + .replace(".", separator); + return sign + leadingDigitsNoTrailingDecimal + suffix; + } +} + +/** + * Formats a big number, but does not add any suffix and instead uses its full representation + * @param {number} num + * @param {string=} divider The divider for numbers like 50,000 (divider=',') + * @returns {string} + */ +export function formatBigNumberFull(num, divider = T.global.thousandsDivider) { + if (num < 1000) { + return num + ""; + } + if (num > 1e54) { + return T.global.infinite; + } + let rest = num; + let out = ""; + while (rest >= 1000) { + out = (rest % 1000).toString().padStart(3, "0") + divider + out; + rest = Math.floor(rest / 1000); + } + out = rest + divider + out; + + return out.substring(0, out.length - 1); +} + +/** + * Waits two frames so the ui is updated + * @returns {Promise} + */ +export function waitNextFrame() { + return new Promise(function (resolve) { + window.requestAnimationFrame(function () { + window.requestAnimationFrame(function () { + resolve(); + }); + }); + }); +} + +/** + * Rounds 1 digit + * @param {number} n + * @returns {number} + */ +export function round1Digit(n) { + return Math.floor(n * 10.0) / 10.0; +} + +/** + * Rounds 2 digits + * @param {number} n + * @returns {number} + */ +export function round2Digits(n) { + return Math.floor(n * 100.0) / 100.0; +} + +/** + * Rounds 3 digits + * @param {number} n + * @returns {number} + */ +export function round3Digits(n) { + return Math.floor(n * 1000.0) / 1000.0; +} + +/** + * Rounds 4 digits + * @param {number} n + * @returns {number} + */ +export function round4Digits(n) { + return Math.floor(n * 10000.0) / 10000.0; +} + +/** + * Clamps a value between [min, max] + * @param {number} v + * @param {number=} minimum Default 0 + * @param {number=} maximum Default 1 + */ +export function clamp(v, minimum = 0, maximum = 1) { + return Math.max(minimum, Math.min(maximum, v)); +} + +/** + * Helper method to create a new div element + * @param {string=} id + * @param {Array=} classes + * @param {string=} innerHTML + */ +function makeDivElement(id = null, classes = [], innerHTML = "") { + const div = document.createElement("div"); + if (id) { + div.id = id; + } + for (let i = 0; i < classes.length; ++i) { + div.classList.add(classes[i]); + } + div.innerHTML = innerHTML; + return div; +} + +/** + * Helper method to create a new div + * @param {Element} parent + * @param {string=} id + * @param {Array=} classes + * @param {string=} innerHTML + */ +export function makeDiv(parent, id = null, classes = [], innerHTML = "") { + const div = makeDivElement(id, classes, innerHTML); + parent.appendChild(div); + return div; +} + +/** + * Helper method to create a new button element + * @param {Array=} classes + * @param {string=} innerHTML + */ +export function makeButtonElement(classes = [], innerHTML = "") { + const element = document.createElement("button"); + for (let i = 0; i < classes.length; ++i) { + element.classList.add(classes[i]); + } + element.classList.add("styledButton"); + element.innerHTML = innerHTML; + return element; +} + +/** + * Helper method to create a new button + * @param {Element} parent + * @param {Array=} classes + * @param {string=} innerHTML + */ +export function makeButton(parent, classes = [], innerHTML = "") { + const element = makeButtonElement(classes, innerHTML); + parent.appendChild(element); + return element; +} + +/** + * Removes all children of the given element + * @param {Element} elem + */ +export function removeAllChildren(elem) { + if (elem) { + var range = document.createRange(); + range.selectNodeContents(elem); + range.deleteContents(); + } +} + +/** + * Fixes lockstep simulation by converting times like 34.0000000003 to 34.00. + * We use 3 digits of precision, this allows us to store precision of 1 ms without + * the risking simulation errors due to resync issues + * @param {number} value + */ +export function quantizeFloat(value) { + return Math.round(value * 1000.0) / 1000.0; +} + +/** + * Safe check to check if a timer is expired. quantizes numbers + * @param {number} now Current time + * @param {number} lastTick Last tick of the timer + * @param {number} tickRate Interval of the timer + */ +export function checkTimerExpired(now, lastTick, tickRate) { + if (G_IS_DEV) { + if (quantizeFloat(now) !== now) { + console.error("Got non-quantizied time:" + now + " vs " + quantizeFloat(now)); + now = quantizeFloat(now); + } + if (quantizeFloat(lastTick) !== lastTick) { + // FIXME: REENABLE + // console.error("Got non-quantizied timer:" + lastTick + " vs " + quantizeFloat(lastTick)); + lastTick = quantizeFloat(lastTick); + } + } else { + // just to be safe + now = quantizeFloat(now); + lastTick = quantizeFloat(lastTick); + } + /* + Ok, so heres the issue (Died a bit while debugging it): + + In multiplayer lockstep simulation, client A will simulate everything at T, but client B + will simulate it at T + 3. So we are running into the following precision issue: + Lets say on client A the time is T = 30. Then on clientB the time is T = 33. + Now, our timer takes 0.1 seconds and ticked at 29.90 - What does happen now? + Client A computes the timer and checks T > lastTick + interval. He computes + + 30 >= 29.90 + 0.1 <=> 30 >= 30.0000 <=> True <=> Tick performed + + However, this is what it looks on client B: + + 33 >= 32.90 + 0.1 <=> 33 >= 32.999999999999998 <=> False <=> No tick performed! + + This means that Client B will only tick at the *next* frame, which means it from now is out + of sync by one tick, which means the game will resync further or later and be not able to recover, + since it will run into the same issue over and over. + */ + + // The next tick, in our example it would be 30.0000 / 32.99999999998. In order to fix it, we quantize + // it, so its now 30.0000 / 33.0000 + const nextTick = quantizeFloat(lastTick + tickRate); + + // This check is safe, but its the only check where you may compare times. You always need to use + // this method! + return now >= nextTick; +} + +/** + * Returns if the game supports this browser + */ +export function isSupportedBrowser() { + // please note, + // that IE11 now returns undefined again for window.chrome + // and new Opera 30 outputs true for window.chrome + // but needs to check if window.opr is not undefined + // and new IE Edge outputs to true now for window.chrome + // and if not iOS Chrome check + // so use the below updated condition + + if (G_IS_MOBILE_APP || G_IS_STANDALONE) { + return true; + } + + // @ts-ignore + var isChromium = window.chrome; + var winNav = window.navigator; + var vendorName = winNav.vendor; + // @ts-ignore + var isIEedge = winNav.userAgent.indexOf("Edge") > -1; + var isIOSChrome = winNav.userAgent.match("CriOS"); + + if (isIOSChrome) { + // is Google Chrome on IOS + return false; + } else if ( + isChromium !== null && + typeof isChromium !== "undefined" && + vendorName === "Google Inc." && + isIEedge === false + ) { + // is Google Chrome + return true; + } else { + // not Google Chrome + return false; + } +} + +/** + * Formats an amount of seconds into something like "5s ago" + * @param {number} secs Seconds + * @returns {string} + */ +export function formatSecondsToTimeAgo(secs) { + const seconds = Math.floor(secs); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (seconds < 60) { + if (seconds === 1) { + return T.global.time.oneSecondAgo; + } + return T.global.time.xSecondsAgo.replace("", "" + seconds); + } else if (minutes < 60) { + if (minutes === 1) { + return T.global.time.oneMinuteAgo; + } + return T.global.time.xMinutesAgo.replace("", "" + minutes); + } else if (hours < 24) { + if (hours === 1) { + return T.global.time.oneHourAgo; + } + return T.global.time.xHoursAgo.replace("", "" + hours); + } else { + if (days === 1) { + return T.global.time.oneDayAgo; + } + return T.global.time.xDaysAgo.replace("", "" + days); + } +} + +/** + * Formats seconds into a readable string like "5h 23m" + * @param {number} secs Seconds + * @returns {string} + */ +export function formatSeconds(secs) { + const trans = T.global.time; + secs = Math.ceil(secs); + if (secs < 60) { + return trans.secondsShort.replace("", "" + secs); + } else if (secs < 60 * 60) { + const minutes = Math.floor(secs / 60); + const seconds = secs % 60; + return trans.minutesAndSecondsShort + .replace("", "" + seconds) + .replace("", "" + minutes); + } else { + const hours = Math.floor(secs / 3600); + const minutes = Math.floor(secs / 60) % 60; + return trans.hoursAndMinutesShort.replace("", "" + minutes).replace("", "" + hours); + } +} + +/** + * Formats a number like 2.5 to "2.5 items / s" + * @param {number} speed + * @param {boolean=} double + * @param {string=} separator The decimal separator for numbers like 50.1 (separator='.') + */ +export function formatItemsPerSecond(speed, double = false, separator = T.global.decimalSeparator) { + return speed === 1.0 + ? T.ingame.buildingPlacement.infoTexts.oneItemPerSecond + : T.ingame.buildingPlacement.infoTexts.itemsPerSecond.replace( + "", + round2Digits(speed).toString().replace(".", separator) + ) + (double ? " " + T.ingame.buildingPlacement.infoTexts.itemsPerSecondDouble : ""); +} + +/** + * Rotates a flat 3x3 matrix clockwise + * Entries: + * 0 lo + * 1 mo + * 2 ro + * 3 lm + * 4 mm + * 5 rm + * 6 lu + * 7 mu + * 8 ru + * @param {Array} flatMatrix + */ + +export function rotateFlatMatrix3x3(flatMatrix) { + return [ + flatMatrix[6], + flatMatrix[3], + flatMatrix[0], + flatMatrix[7], + flatMatrix[4], + flatMatrix[1], + flatMatrix[8], + flatMatrix[5], + flatMatrix[2], + ]; +} + +/** + * Generates rotated variants of the matrix + * @param {Array} originalMatrix + * @returns {Object>} + */ +export function generateMatrixRotations(originalMatrix) { + const result = { + 0: originalMatrix, + }; + + originalMatrix = rotateFlatMatrix3x3(originalMatrix); + result[90] = originalMatrix; + + originalMatrix = rotateFlatMatrix3x3(originalMatrix); + result[180] = originalMatrix; + + originalMatrix = rotateFlatMatrix3x3(originalMatrix); + result[270] = originalMatrix; + + return result; +} + +/** + * + * @typedef {{ + * top: any, + * right: any, + * bottom: any, + * left: any + * }} DirectionalObject + */ + +/** + * Rotates a directional object + * @param {DirectionalObject} obj + * @returns {DirectionalObject} + */ +export function rotateDirectionalObject(obj, rotation) { + const queue = [obj.top, obj.right, obj.bottom, obj.left]; + while (rotation !== 0) { + rotation -= 90; + queue.push(queue.shift()); + } + + return { + top: queue[0], + right: queue[1], + bottom: queue[2], + left: queue[3], + }; +} + +/** + * Modulo which works for negative numbers + * @param {number} n + * @param {number} m + */ +export function safeModulo(n, m) { + return ((n % m) + m) % m; +} + +/** + * Returns a smooth pulse between 0 and 1 + * @param {number} time time in seconds + * @returns {number} + */ +export function smoothPulse(time) { + return Math.sin(time * 4) * 0.5 + 0.5; +} diff --git a/src/js/game/buildings/filter.js b/src/js/game/buildings/filter.js index 5637a21a..3017004a 100644 --- a/src/js/game/buildings/filter.js +++ b/src/js/game/buildings/filter.js @@ -1,86 +1,91 @@ -import { enumDirection, Vector } from "../../core/vector"; -import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; -import { Entity } from "../entity"; -import { MetaBuilding } from "../meta_building"; -import { GameRoot } from "../root"; -import { LeverComponent } from "../components/lever"; -import { ItemAcceptorComponent } from "../components/item_acceptor"; -import { ItemEjectorComponent } from "../components/item_ejector"; -import { ItemProcessorComponent, enumItemProcessorTypes } from "../components/item_processor"; - -export class MetaFilterBuilding extends MetaBuilding { - constructor() { - super("filter"); - } - - getSilhouetteColor() { - return "#c45c2e"; - } - - /** - * @param {GameRoot} root - */ - getIsUnlocked(root) { - // @todo - return true; - } - - getDimensions() { - return new Vector(2, 1); - } - - getShowWiresLayerPreview() { - return true; - } - - /** - * Creates the entity at the given location - * @param {Entity} entity - */ - setupEntityComponents(entity) { - entity.addComponent( - new WiredPinsComponent({ - slots: [ - { - pos: new Vector(0, 0), - direction: enumDirection.left, - type: enumPinSlotType.logicalAcceptor, - }, - ], - }) - ); - - entity.addComponent( - new ItemAcceptorComponent({ - slots: [ - { - pos: new Vector(0, 0), - directions: [enumDirection.bottom], - }, - ], - }) - ); - - entity.addComponent( - new ItemEjectorComponent({ - slots: [ - { - pos: new Vector(0, 0), - direction: enumDirection.top, - }, - { - pos: new Vector(1, 0), - direction: enumDirection.right, - }, - ], - }) - ); - - entity.addComponent( - new ItemProcessorComponent({ - processorType: enumItemProcessorTypes.filter, - inputsPerCharge: 1, - }) - ); - } -} +import { enumDirection, Vector } from "../../core/vector"; +import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; +import { Entity } from "../entity"; +import { MetaBuilding } from "../meta_building"; +import { GameRoot } from "../root"; +import { LeverComponent } from "../components/lever"; +import { ItemAcceptorComponent } from "../components/item_acceptor"; +import { ItemEjectorComponent } from "../components/item_ejector"; +import { + ItemProcessorComponent, + enumItemProcessorTypes, + enumItemProcessorRequirements, +} from "../components/item_processor"; + +export class MetaFilterBuilding extends MetaBuilding { + constructor() { + super("filter"); + } + + getSilhouetteColor() { + return "#c45c2e"; + } + + /** + * @param {GameRoot} root + */ + getIsUnlocked(root) { + // @todo + return true; + } + + getDimensions() { + return new Vector(2, 1); + } + + getShowWiresLayerPreview() { + return true; + } + + /** + * Creates the entity at the given location + * @param {Entity} entity + */ + setupEntityComponents(entity) { + entity.addComponent( + new WiredPinsComponent({ + slots: [ + { + pos: new Vector(0, 0), + direction: enumDirection.left, + type: enumPinSlotType.logicalAcceptor, + }, + ], + }) + ); + + entity.addComponent( + new ItemAcceptorComponent({ + slots: [ + { + pos: new Vector(0, 0), + directions: [enumDirection.bottom], + }, + ], + }) + ); + + entity.addComponent( + new ItemEjectorComponent({ + slots: [ + { + pos: new Vector(0, 0), + direction: enumDirection.top, + }, + { + pos: new Vector(1, 0), + direction: enumDirection.right, + }, + ], + }) + ); + + entity.addComponent( + new ItemProcessorComponent({ + processorType: enumItemProcessorTypes.filter, + inputsPerCharge: 1, + processingRequirement: enumItemProcessorRequirements.filter, + }) + ); + } +} diff --git a/src/js/game/buildings/painter.js b/src/js/game/buildings/painter.js index aae3a79d..b74d24b9 100644 --- a/src/js/game/buildings/painter.js +++ b/src/js/game/buildings/painter.js @@ -3,7 +3,11 @@ import { enumDirection, Vector } from "../../core/vector"; import { T } from "../../translations"; import { ItemAcceptorComponent } from "../components/item_acceptor"; import { ItemEjectorComponent } from "../components/item_ejector"; -import { enumItemProcessorTypes, ItemProcessorComponent, enumItemProcessorRequirements } from "../components/item_processor"; +import { + enumItemProcessorTypes, + ItemProcessorComponent, + enumItemProcessorRequirements, +} from "../components/item_processor"; import { Entity } from "../entity"; import { defaultBuildingVariant, MetaBuilding } from "../meta_building"; import { GameRoot } from "../root"; @@ -120,8 +124,10 @@ export class MetaPainterBuilding extends MetaBuilding { switch (variant) { case defaultBuildingVariant: case enumPainterVariants.mirrored: { + // REGULAR PAINTER + if (entity.components.WiredPins) { - entity.removeComponent(WiredPinsComponent) + entity.removeComponent(WiredPinsComponent); } entity.components.ItemAcceptor.setSlots([ @@ -139,18 +145,22 @@ export class MetaPainterBuilding extends MetaBuilding { }, ]); + entity.components.ItemEjector.setSlots([ + { pos: new Vector(1, 0), direction: enumDirection.right }, + ]); + entity.components.ItemProcessor.type = enumItemProcessorTypes.painter; entity.components.ItemProcessor.processingRequirement = null; entity.components.ItemProcessor.inputsPerCharge = 2; - entity.components.ItemEjector.setSlots([ - { pos: new Vector(1, 0), direction: enumDirection.right }, - ]); break; } + case enumPainterVariants.double: { + // DOUBLE PAINTER + if (entity.components.WiredPins) { - entity.removeComponent(WiredPinsComponent) + entity.removeComponent(WiredPinsComponent); } entity.components.ItemAcceptor.setSlots([ @@ -171,43 +181,46 @@ export class MetaPainterBuilding extends MetaBuilding { }, ]); - entity.components.ItemProcessor.type = enumItemProcessorTypes.painterDouble; - entity.components.ItemProcessor.processingRequirement = null; - entity.components.ItemProcessor.inputsPerCharge = 3; - entity.components.ItemEjector.setSlots([ { pos: new Vector(1, 0), direction: enumDirection.right }, ]); + + entity.components.ItemProcessor.type = enumItemProcessorTypes.painterDouble; + entity.components.ItemProcessor.processingRequirement = null; + entity.components.ItemProcessor.inputsPerCharge = 3; break; } + case enumPainterVariants.quad: { + // QUAD PAINTER + if (!entity.components.WiredPins) { - entity.addComponent(new WiredPinsComponent({ - slots: [ - { - pos: new Vector(0, 0), - direction: enumDirection.bottom, - type: enumPinSlotType.logicalAcceptor - }, - { - pos: new Vector(1, 0), - direction: enumDirection.bottom, - type: enumPinSlotType.logicalAcceptor - }, - { - pos: new Vector(2, 0), - direction: enumDirection.bottom, - type: enumPinSlotType.logicalAcceptor - }, - { - pos: new Vector(3, 0), - direction: enumDirection.bottom, - type: enumPinSlotType.logicalAcceptor - }, - ] - })); + entity.addComponent(new WiredPinsComponent({ slots: [] })); } + entity.components.WiredPins.setSlots([ + { + pos: new Vector(0, 0), + direction: enumDirection.bottom, + type: enumPinSlotType.logicalAcceptor, + }, + { + pos: new Vector(1, 0), + direction: enumDirection.bottom, + type: enumPinSlotType.logicalAcceptor, + }, + { + pos: new Vector(2, 0), + direction: enumDirection.bottom, + type: enumPinSlotType.logicalAcceptor, + }, + { + pos: new Vector(3, 0), + direction: enumDirection.bottom, + type: enumPinSlotType.logicalAcceptor, + }, + ]); + entity.components.ItemAcceptor.setSlots([ { pos: new Vector(0, 0), @@ -236,15 +249,18 @@ export class MetaPainterBuilding extends MetaBuilding { }, ]); - entity.components.ItemProcessor.type = enumItemProcessorTypes.painterQuad; - entity.components.ItemProcessor.processingRequirement = enumItemProcessorRequirements.painterQuad; - entity.components.ItemProcessor.inputsPerCharge = 5; - entity.components.ItemEjector.setSlots([ { pos: new Vector(0, 0), direction: enumDirection.top }, ]); + + entity.components.ItemProcessor.type = enumItemProcessorTypes.painterQuad; + entity.components.ItemProcessor.processingRequirement = + enumItemProcessorRequirements.painterQuad; + entity.components.ItemProcessor.inputsPerCharge = 5; + break; } + default: assertAlways(false, "Unknown painter variant: " + variant); } diff --git a/src/js/game/component_registry.js b/src/js/game/component_registry.js index 65cee157..84e6307a 100644 --- a/src/js/game/component_registry.js +++ b/src/js/game/component_registry.js @@ -43,7 +43,7 @@ export function initComponentRegistry() { assert( // @ts-ignore require.context("./components", false, /.*\.js/i).keys().length === - gComponentRegistry.getNumEntries(), + gComponentRegistry.getNumEntries(), "Not all components are registered" ); diff --git a/src/js/game/components/item_processor.js b/src/js/game/components/item_processor.js index cfc45ddb..53e03ce0 100644 --- a/src/js/game/components/item_processor.js +++ b/src/js/game/components/item_processor.js @@ -24,7 +24,8 @@ export const enumItemProcessorTypes = { /** @enum {string} */ export const enumItemProcessorRequirements = { - painterQuad: "painterQuad" + painterQuad: "painterQuad", + filter: "filter", }; export class ItemProcessorComponent extends Component { @@ -71,7 +72,7 @@ export class ItemProcessorComponent extends Component { constructor({ processorType = enumItemProcessorTypes.splitter, processingRequirement = null, - inputsPerCharge = 1 + inputsPerCharge = 1, }) { super(); diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index afcaf194..c26f5b7f 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -1,165 +1,171 @@ -/* typehints:start */ -import { GameRoot } from "./root"; -/* typehints:end */ - -import { createLogger } from "../core/logging"; -import { BeltSystem } from "./systems/belt"; -import { ItemEjectorSystem } from "./systems/item_ejector"; -import { MapResourcesSystem } from "./systems/map_resources"; -import { MinerSystem } from "./systems/miner"; -import { ItemProcessorSystem } from "./systems/item_processor"; -import { UndergroundBeltSystem } from "./systems/underground_belt"; -import { HubSystem } from "./systems/hub"; -import { StaticMapEntitySystem } from "./systems/static_map_entity"; -import { ItemAcceptorSystem } from "./systems/item_acceptor"; -import { StorageSystem } from "./systems/storage"; -import { WiredPinsSystem } from "./systems/wired_pins"; -import { BeltUnderlaysSystem } from "./systems/belt_underlays"; -import { WireSystem } from "./systems/wire"; -import { ConstantSignalSystem } from "./systems/constant_signal"; -import { LogicGateSystem } from "./systems/logic_gate"; -import { LeverSystem } from "./systems/lever"; -import { DisplaySystem } from "./systems/display"; - -const logger = createLogger("game_system_manager"); - -export class GameSystemManager { - /** - * - * @param {GameRoot} root - */ - constructor(root) { - this.root = root; - - this.systems = { - /* typehints:start */ - /** @type {BeltSystem} */ - belt: null, - - /** @type {ItemEjectorSystem} */ - itemEjector: null, - - /** @type {MapResourcesSystem} */ - mapResources: null, - - /** @type {MinerSystem} */ - miner: null, - - /** @type {ItemProcessorSystem} */ - itemProcessor: null, - - /** @type {UndergroundBeltSystem} */ - undergroundBelt: null, - - /** @type {HubSystem} */ - hub: null, - - /** @type {StaticMapEntitySystem} */ - staticMapEntities: null, - - /** @type {ItemAcceptorSystem} */ - itemAcceptor: null, - - /** @type {StorageSystem} */ - storage: null, - - /** @type {WiredPinsSystem} */ - wiredPins: null, - - /** @type {BeltUnderlaysSystem} */ - beltUnderlays: null, - - /** @type {WireSystem} */ - wire: null, - - /** @type {ConstantSignalSystem} */ - constantSignal: null, - - /** @type {LogicGateSystem} */ - logicGate: null, - - /** @type {LeverSystem} */ - lever: null, - - /** @type {DisplaySystem} */ - display: null, - - /* typehints:end */ - }; - this.systemUpdateOrder = []; - - this.internalInitSystems(); - } - - /** - * Initializes all systems - */ - internalInitSystems() { - const add = (id, systemClass) => { - this.systems[id] = new systemClass(this.root); - this.systemUpdateOrder.push(id); - }; - - // Order is important! - - add("belt", BeltSystem); - - add("undergroundBelt", UndergroundBeltSystem); - - add("miner", MinerSystem); - - add("storage", StorageSystem); - - add("itemProcessor", ItemProcessorSystem); - - add("itemEjector", ItemEjectorSystem); - - add("mapResources", MapResourcesSystem); - - add("hub", HubSystem); - - add("staticMapEntities", StaticMapEntitySystem); - - add("wiredPins", WiredPinsSystem); - - add("beltUnderlays", BeltUnderlaysSystem); - - 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); - - // IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates, - // processors etc. In phase 2 we propagate it through the wires network - add("logicGate", LogicGateSystem); - - // Wires must be after all gate, signal etc logic! - add("wire", WireSystem); - - add("display", DisplaySystem); - - logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); - } - - /** - * Updates all systems - */ - update() { - for (let i = 0; i < this.systemUpdateOrder.length; ++i) { - const system = this.systems[this.systemUpdateOrder[i]]; - system.update(); - } - } - - refreshCaches() { - for (let i = 0; i < this.systemUpdateOrder.length; ++i) { - const system = this.systems[this.systemUpdateOrder[i]]; - system.refreshCaches(); - } - } -} +/* typehints:start */ +import { GameRoot } from "./root"; +/* typehints:end */ + +import { createLogger } from "../core/logging"; +import { BeltSystem } from "./systems/belt"; +import { ItemEjectorSystem } from "./systems/item_ejector"; +import { MapResourcesSystem } from "./systems/map_resources"; +import { MinerSystem } from "./systems/miner"; +import { ItemProcessorSystem } from "./systems/item_processor"; +import { UndergroundBeltSystem } from "./systems/underground_belt"; +import { HubSystem } from "./systems/hub"; +import { StaticMapEntitySystem } from "./systems/static_map_entity"; +import { ItemAcceptorSystem } from "./systems/item_acceptor"; +import { StorageSystem } from "./systems/storage"; +import { WiredPinsSystem } from "./systems/wired_pins"; +import { BeltUnderlaysSystem } from "./systems/belt_underlays"; +import { WireSystem } from "./systems/wire"; +import { ConstantSignalSystem } from "./systems/constant_signal"; +import { LogicGateSystem } from "./systems/logic_gate"; +import { LeverSystem } from "./systems/lever"; +import { DisplaySystem } from "./systems/display"; +import { ItemProcessorOverlaysSystem } from "./systems/item_processor_overlays"; + +const logger = createLogger("game_system_manager"); + +export class GameSystemManager { + /** + * + * @param {GameRoot} root + */ + constructor(root) { + this.root = root; + + this.systems = { + /* typehints:start */ + /** @type {BeltSystem} */ + belt: null, + + /** @type {ItemEjectorSystem} */ + itemEjector: null, + + /** @type {MapResourcesSystem} */ + mapResources: null, + + /** @type {MinerSystem} */ + miner: null, + + /** @type {ItemProcessorSystem} */ + itemProcessor: null, + + /** @type {UndergroundBeltSystem} */ + undergroundBelt: null, + + /** @type {HubSystem} */ + hub: null, + + /** @type {StaticMapEntitySystem} */ + staticMapEntities: null, + + /** @type {ItemAcceptorSystem} */ + itemAcceptor: null, + + /** @type {StorageSystem} */ + storage: null, + + /** @type {WiredPinsSystem} */ + wiredPins: null, + + /** @type {BeltUnderlaysSystem} */ + beltUnderlays: null, + + /** @type {WireSystem} */ + wire: null, + + /** @type {ConstantSignalSystem} */ + constantSignal: null, + + /** @type {LogicGateSystem} */ + logicGate: null, + + /** @type {LeverSystem} */ + lever: null, + + /** @type {DisplaySystem} */ + display: null, + + /** @type {ItemProcessorOverlaysSystem} */ + itemProcessorOverlays: null, + + /* typehints:end */ + }; + this.systemUpdateOrder = []; + + this.internalInitSystems(); + } + + /** + * Initializes all systems + */ + internalInitSystems() { + const add = (id, systemClass) => { + this.systems[id] = new systemClass(this.root); + this.systemUpdateOrder.push(id); + }; + + // Order is important! + + add("belt", BeltSystem); + + add("undergroundBelt", UndergroundBeltSystem); + + add("miner", MinerSystem); + + add("storage", StorageSystem); + + add("itemProcessor", ItemProcessorSystem); + + add("itemEjector", ItemEjectorSystem); + + add("mapResources", MapResourcesSystem); + + add("hub", HubSystem); + + add("staticMapEntities", StaticMapEntitySystem); + + add("wiredPins", WiredPinsSystem); + + add("beltUnderlays", BeltUnderlaysSystem); + + 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); + + // IMPORTANT: We have 2 phases: In phase 1 we compute the output values of all gates, + // processors etc. In phase 2 we propagate it through the wires network + add("logicGate", LogicGateSystem); + + // Wires must be after all gate, signal etc logic! + add("wire", WireSystem); + + add("display", DisplaySystem); + + add("itemProcessorOverlays", ItemProcessorOverlaysSystem); + + logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); + } + + /** + * Updates all systems + */ + update() { + for (let i = 0; i < this.systemUpdateOrder.length; ++i) { + const system = this.systems[this.systemUpdateOrder[i]]; + system.update(); + } + } + + refreshCaches() { + for (let i = 0; i < this.systemUpdateOrder.length; ++i) { + const system = this.systems[this.systemUpdateOrder[i]]; + system.refreshCaches(); + } + } +} diff --git a/src/js/game/items/boolean_item.js b/src/js/game/items/boolean_item.js index 38422398..ba09b459 100644 --- a/src/js/game/items/boolean_item.js +++ b/src/js/game/items/boolean_item.js @@ -1,62 +1,70 @@ -import { DrawParameters } from "../../core/draw_parameters"; -import { Loader } from "../../core/loader"; -import { types } from "../../savegame/serialization"; -import { BaseItem } from "../base_item"; -import { globalConfig } from "../../core/config"; - -export class BooleanItem extends BaseItem { - static getId() { - return "boolean_item"; - } - - static getSchema() { - return types.uint; - } - - serialize() { - return this.value; - } - - deserialize(data) { - this.value = data; - } - - /** @returns {"boolean"} **/ - getItemType() { - return "boolean"; - } - - /** - * @param {number} value - */ - constructor(value) { - super(); - this.value = value ? 1 : 0; - } - - /** - * @param {BaseItem} other - */ - equalsImpl(other) { - return this.value === /** @type {BooleanItem} */ (other).value; - } - - /** - * @param {number} x - * @param {number} y - * @param {number} diameter - * @param {DrawParameters} parameters - */ - drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) { - let sprite; - if (this.value) { - sprite = Loader.getSprite("sprites/wires/boolean_true.png"); - } else { - sprite = Loader.getSprite("sprites/wires/boolean_false.png"); - } - sprite.drawCachedCentered(parameters, x, y, diameter); - } -} - -export const BOOL_FALSE_SINGLETON = new BooleanItem(0); -export const BOOL_TRUE_SINGLETON = new BooleanItem(1); +import { DrawParameters } from "../../core/draw_parameters"; +import { Loader } from "../../core/loader"; +import { types } from "../../savegame/serialization"; +import { BaseItem } from "../base_item"; +import { globalConfig } from "../../core/config"; + +export class BooleanItem extends BaseItem { + static getId() { + return "boolean_item"; + } + + static getSchema() { + return types.uint; + } + + serialize() { + return this.value; + } + + deserialize(data) { + this.value = data; + } + + /** @returns {"boolean"} **/ + getItemType() { + return "boolean"; + } + + /** + * @param {number} value + */ + constructor(value) { + super(); + this.value = value ? 1 : 0; + } + + /** + * @param {BaseItem} other + */ + equalsImpl(other) { + return this.value === /** @type {BooleanItem} */ (other).value; + } + + /** + * @param {number} x + * @param {number} y + * @param {number} diameter + * @param {DrawParameters} parameters + */ + drawItemCenteredImpl(x, y, parameters, diameter = globalConfig.defaultItemDiameter) { + let sprite; + if (this.value) { + sprite = Loader.getSprite("sprites/wires/boolean_true.png"); + } else { + sprite = Loader.getSprite("sprites/wires/boolean_false.png"); + } + sprite.drawCachedCentered(parameters, x, y, diameter); + } +} + +export const BOOL_FALSE_SINGLETON = new BooleanItem(0); +export const BOOL_TRUE_SINGLETON = new BooleanItem(1); + +/** + * + * @param {BaseItem} item + */ +export function isTrueItem(item) { + return item && item.getItemType() === "boolean" && /** @type {BooleanItem} */ (item).value; +} diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index 1ea04955..dc3e9e98 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -69,6 +69,7 @@ export class MapChunkView extends MapChunk { systems.lever.drawChunk(parameters, this); systems.display.drawChunk(parameters, this); systems.storage.drawChunk(parameters, this); + systems.itemProcessorOverlays.drawChunk(parameters, this); } /** diff --git a/src/js/game/systems/display.js b/src/js/game/systems/display.js index b1bd9e69..2ad551f0 100644 --- a/src/js/game/systems/display.js +++ b/src/js/game/systems/display.js @@ -1,99 +1,97 @@ -import { globalConfig } from "../../core/config"; -import { Loader } from "../../core/loader"; -import { BaseItem } from "../base_item"; -import { enumColors } from "../colors"; -import { DisplayComponent } from "../components/display"; -import { GameSystemWithFilter } from "../game_system_with_filter"; -import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; -import { MapChunkView } from "../map_chunk_view"; -import { BooleanItem } from "../items/boolean_item"; - -export class DisplaySystem extends GameSystemWithFilter { - constructor(root) { - super(root, [DisplayComponent]); - - /** @type {Object} */ - this.displaySprites = {}; - - for (const colorId in enumColors) { - if (colorId === enumColors.uncolored) { - continue; - } - this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png"); - } - } - - /** - * Returns the color / value a display should show - * @param {BaseItem} value - * @returns {BaseItem} - */ - getDisplayItem(value) { - if (!value) { - return null; - } - - switch (value.getItemType()) { - case "boolean": { - return /** @type {BooleanItem} */ (value).value - ? COLOR_ITEM_SINGLETONS[enumColors.white] - : null; - } - - case "color": { - const item = /**@type {ColorItem} */ (value); - return item.color === enumColors.uncolored ? null : item; - } - - case "shape": { - return value; - } - - default: - assertAlways(false, "Unknown item type: " + value.getItemType()); - } - } - - /** - * Draws a given chunk - * @param {import("../../core/draw_utils").DrawParameters} parameters - * @param {MapChunkView} chunk - */ - drawChunk(parameters, chunk) { - const contents = chunk.containedEntitiesByLayer.regular; - for (let i = 0; i < contents.length; ++i) { - const entity = contents[i]; - if (entity && entity.components.Display) { - const pinsComp = entity.components.WiredPins; - const network = pinsComp.slots[0].linkedNetwork; - - if (!network || !network.currentValue) { - continue; - } - - const value = this.getDisplayItem(network.currentValue); - - if (!value) { - continue; - } - - const origin = entity.components.StaticMapEntity.origin; - if (value.getItemType() === "color") { - this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered( - parameters, - (origin.x + 0.5) * globalConfig.tileSize, - (origin.y + 0.5) * globalConfig.tileSize, - globalConfig.tileSize - ); - } else if (value.getItemType() === "shape") { - value.drawItemCenteredClipped( - (origin.x + 0.5) * globalConfig.tileSize, - (origin.y + 0.5) * globalConfig.tileSize, - parameters, - 30 - ); - } - } - } - } -} +import { globalConfig } from "../../core/config"; +import { Loader } from "../../core/loader"; +import { BaseItem } from "../base_item"; +import { enumColors } from "../colors"; +import { DisplayComponent } from "../components/display"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { isTrueItem } from "../items/boolean_item"; +import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; +import { MapChunkView } from "../map_chunk_view"; + +export class DisplaySystem extends GameSystemWithFilter { + constructor(root) { + super(root, [DisplayComponent]); + + /** @type {Object} */ + this.displaySprites = {}; + + for (const colorId in enumColors) { + if (colorId === enumColors.uncolored) { + continue; + } + this.displaySprites[colorId] = Loader.getSprite("sprites/wires/display/" + colorId + ".png"); + } + } + + /** + * Returns the color / value a display should show + * @param {BaseItem} value + * @returns {BaseItem} + */ + getDisplayItem(value) { + if (!value) { + return null; + } + + switch (value.getItemType()) { + case "boolean": { + return isTrueItem(value) ? COLOR_ITEM_SINGLETONS[enumColors.white] : null; + } + + case "color": { + const item = /**@type {ColorItem} */ (value); + return item.color === enumColors.uncolored ? null : item; + } + + case "shape": { + return value; + } + + default: + assertAlways(false, "Unknown item type: " + value.getItemType()); + } + } + + /** + * Draws a given chunk + * @param {import("../../core/draw_utils").DrawParameters} parameters + * @param {MapChunkView} chunk + */ + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const entity = contents[i]; + if (entity && entity.components.Display) { + const pinsComp = entity.components.WiredPins; + const network = pinsComp.slots[0].linkedNetwork; + + if (!network || !network.currentValue) { + continue; + } + + const value = this.getDisplayItem(network.currentValue); + + if (!value) { + continue; + } + + const origin = entity.components.StaticMapEntity.origin; + if (value.getItemType() === "color") { + this.displaySprites[/** @type {ColorItem} */ (value).color].drawCachedCentered( + parameters, + (origin.x + 0.5) * globalConfig.tileSize, + (origin.y + 0.5) * globalConfig.tileSize, + globalConfig.tileSize + ); + } else if (value.getItemType() === "shape") { + value.drawItemCenteredClipped( + (origin.x + 0.5) * globalConfig.tileSize, + (origin.y + 0.5) * globalConfig.tileSize, + parameters, + 30 + ); + } + } + } + } +} diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 6804925d..8ff710a8 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -1,388 +1,381 @@ -import { globalConfig } from "../../core/config"; -import { DrawParameters } from "../../core/draw_parameters"; -import { createLogger } from "../../core/logging"; -import { Rectangle } from "../../core/rectangle"; -import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector"; -import { BaseItem } from "../base_item"; -import { ItemEjectorComponent } from "../components/item_ejector"; -import { Entity } from "../entity"; -import { GameSystemWithFilter } from "../game_system_with_filter"; -import { enumItemProcessorTypes } from "../components/item_processor"; -import { MapChunkView } from "../map_chunk_view"; - -const logger = createLogger("systems/ejector"); - -export class ItemEjectorSystem extends GameSystemWithFilter { - constructor(root) { - super(root, [ItemEjectorComponent]); - - this.root.signals.entityAdded.add(this.checkForCacheInvalidation, this); - this.root.signals.entityDestroyed.add(this.checkForCacheInvalidation, this); - this.root.signals.postLoadHook.add(this.recomputeCache, this); - - /** - * @type {Rectangle} - */ - this.areaToRecompute = null; - } - - /** - * - * @param {Entity} entity - */ - checkForCacheInvalidation(entity) { - if (!this.root.gameInitialized) { - return; - } - if (!entity.components.StaticMapEntity) { - return; - } - - // Optimize for the common case: adding or removing one building at a time. Clicking - // and dragging can cause up to 4 add/remove signals. - const staticComp = entity.components.StaticMapEntity; - const bounds = staticComp.getTileSpaceBounds(); - const expandedBounds = bounds.expandedInAllDirections(2); - - if (this.areaToRecompute) { - this.areaToRecompute = this.areaToRecompute.getUnion(expandedBounds); - } else { - this.areaToRecompute = expandedBounds; - } - } - - /** - * Precomputes the cache, which makes up for a huge performance improvement - */ - recomputeCache() { - if (this.areaToRecompute) { - logger.log("Recomputing cache using rectangle"); - if (G_IS_DEV && globalConfig.debug.renderChanges) { - this.root.hud.parts.changesDebugger.renderChange( - "ejector-area", - this.areaToRecompute, - "#fe50a6" - ); - } - this.recomputeAreaCache(); - this.areaToRecompute = null; - } else { - logger.log("Full cache recompute"); - if (G_IS_DEV && globalConfig.debug.renderChanges) { - this.root.hud.parts.changesDebugger.renderChange( - "ejector-full", - new Rectangle(-1000, -1000, 2000, 2000), - "#fe50a6" - ); - } - - // Try to find acceptors for every ejector - for (let i = 0; i < this.allEntities.length; ++i) { - const entity = this.allEntities[i]; - this.recomputeSingleEntityCache(entity); - } - } - } - - /** - * Recomputes the cache in the given area - */ - recomputeAreaCache() { - const area = this.areaToRecompute; - let entryCount = 0; - - logger.log("Recomputing area:", area.x, area.y, "/", area.w, area.h); - - // Store the entities we already recomputed, so we don't do work twice - const recomputedEntities = new Set(); - - for (let x = area.x; x < area.right(); ++x) { - for (let y = area.y; y < area.bottom(); ++y) { - const entities = this.root.map.getLayersContentsMultipleXY(x, y); - for (let i = 0; i < entities.length; ++i) { - const entity = entities[i]; - - // Recompute the entity in case its relevant for this system and it - // hasn't already been computed - if (!recomputedEntities.has(entity.uid) && entity.components.ItemEjector) { - recomputedEntities.add(entity.uid); - this.recomputeSingleEntityCache(entity); - } - } - } - } - return entryCount; - } - - /** - * @param {Entity} entity - */ - recomputeSingleEntityCache(entity) { - const ejectorComp = entity.components.ItemEjector; - const staticComp = entity.components.StaticMapEntity; - - for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) { - const ejectorSlot = ejectorComp.slots[slotIndex]; - - // Clear the old cache. - ejectorSlot.cachedDestSlot = null; - ejectorSlot.cachedTargetEntity = null; - ejectorSlot.cachedBeltPath = null; - - // Figure out where and into which direction we eject items - const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos); - const ejectSlotWsDirection = staticComp.localDirectionToWorld(ejectorSlot.direction); - const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection]; - const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector); - - // Try to find the given acceptor component to take the item - // Since there can be cross layer dependencies, check on all layers - const targetEntities = this.root.map.getLayersContentsMultipleXY( - ejectSlotTargetWsTile.x, - ejectSlotTargetWsTile.y - ); - - for (let i = 0; i < targetEntities.length; ++i) { - const targetEntity = targetEntities[i]; - - 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) { - ejectorSlot.cachedTargetEntity = targetEntity; - ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath; - break; - } - } - - // Check for item acceptors - const targetAcceptorComp = targetEntity.components.ItemAcceptor; - if (!targetAcceptorComp) { - // Entity doesn't accept items - continue; - } - - const matchingSlot = targetAcceptorComp.findMatchingSlot( - targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile), - targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection) - ); - - if (!matchingSlot) { - // No matching slot found - continue; - } - - // A slot can always be connected to one other slot only - ejectorSlot.cachedTargetEntity = targetEntity; - ejectorSlot.cachedDestSlot = matchingSlot; - break; - } - } - } - - update() { - if (this.areaToRecompute) { - this.recomputeCache(); - } - - // Precompute effective belt speed - let progressGrowth = 2 * this.root.dynamicTickrate.deltaSeconds; - - if (G_IS_DEV && globalConfig.debug.instantBelts) { - progressGrowth = 1; - } - - // Go over all cache entries - for (let i = 0; i < this.allEntities.length; ++i) { - const sourceEntity = this.allEntities[i]; - const sourceEjectorComp = sourceEntity.components.ItemEjector; - if (!sourceEjectorComp.enabled) { - continue; - } - - const slots = sourceEjectorComp.slots; - for (let j = 0; j < slots.length; ++j) { - const sourceSlot = slots[j]; - const item = sourceSlot.item; - if (!item) { - // No item available to be ejected - continue; - } - - const targetEntity = sourceSlot.cachedTargetEntity; - - // Advance items on the slot - sourceSlot.progress = Math.min( - 1, - sourceSlot.progress + - progressGrowth * - this.root.hubGoals.getBeltBaseSpeed() * - globalConfig.itemSpacingOnBelts - ); - - // Check if we are still in the process of ejecting, can't proceed then - if (sourceSlot.progress < 1.0) { - continue; - } - - // Check if we are ejecting to a belt path - const destPath = sourceSlot.cachedBeltPath; - if (destPath) { - // Try passing the item over - if (destPath.tryAcceptItem(item)) { - sourceSlot.item = null; - } - - // Always stop here, since there can *either* be a belt path *or* - // a slot - continue; - } - - // Check if the target acceptor can actually accept this item - const destSlot = sourceSlot.cachedDestSlot; - if (destSlot) { - const targetAcceptorComp = targetEntity.components.ItemAcceptor; - if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) { - continue; - } - - // Try to hand over the item - if (this.tryPassOverItem(item, targetEntity, destSlot.index)) { - // Handover successful, clear slot - targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item); - sourceSlot.item = null; - continue; - } - } - } - } - } - - /** - * - * @param {BaseItem} item - * @param {Entity} receiver - * @param {number} slotIndex - */ - tryPassOverItem(item, receiver, slotIndex) { - // Try figuring out how what to do with the item - // TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell. - // Also its just a few cases (hope it stays like this .. :x). - - const beltComp = receiver.components.Belt; - if (beltComp) { - const path = beltComp.assignedPath; - assert(path, "belt has no path"); - if (path.tryAcceptItem(item)) { - return true; - } - // Belt can have nothing else - return false; - } - - const itemProcessorComp = receiver.components.ItemProcessor; - if (itemProcessorComp) { - // @todo HACK - // Check if there are pins, and if so if they are connected - if (itemProcessorComp.type === enumItemProcessorTypes.filter) { - const pinsComp = receiver.components.WiredPins; - if (pinsComp && pinsComp.slots.length === 1) { - const network = pinsComp.slots[0].linkedNetwork; - if (!network || !network.currentValue) { - return false; - } - } - } - - // Its an item processor .. - if (itemProcessorComp.tryTakeItem(item, slotIndex)) { - return true; - } - // Item processor can have nothing else - return false; - } - - const undergroundBeltComp = receiver.components.UndergroundBelt; - if (undergroundBeltComp) { - // Its an underground belt. yay. - if ( - undergroundBeltComp.tryAcceptExternalItem( - item, - this.root.hubGoals.getUndergroundBeltBaseSpeed() - ) - ) { - return true; - } - - // Underground belt can have nothing else - return false; - } - - const storageComp = receiver.components.Storage; - if (storageComp) { - // It's a storage - if (storageComp.canAcceptItem(item)) { - storageComp.takeItem(item); - return true; - } - - // Storage can't have anything else - return false; - } - - return false; - } - - /** - * @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 ejectorComp = entity.components.ItemEjector; - if (!ejectorComp) { - continue; - } - - const staticComp = entity.components.StaticMapEntity; - - for (let i = 0; i < ejectorComp.slots.length; ++i) { - const slot = ejectorComp.slots[i]; - const ejectedItem = slot.item; - - if (!ejectedItem) { - // No item - continue; - } - - const realPosition = staticComp.localTileToWorld(slot.pos); - if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) { - // Not within this chunk - continue; - } - - const realDirection = staticComp.localDirectionToWorld(slot.direction); - const realDirectionVector = enumDirectionToVector[realDirection]; - - const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress; - const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress; - - const worldX = tileX * globalConfig.tileSize; - const worldY = tileY * globalConfig.tileSize; - - ejectedItem.drawItemCenteredClipped( - worldX, - worldY, - parameters, - globalConfig.defaultItemDiameter - ); - } - } - } -} +import { globalConfig } from "../../core/config"; +import { DrawParameters } from "../../core/draw_parameters"; +import { createLogger } from "../../core/logging"; +import { Rectangle } from "../../core/rectangle"; +import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector"; +import { BaseItem } from "../base_item"; +import { ItemEjectorComponent } from "../components/item_ejector"; +import { Entity } from "../entity"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { enumItemProcessorTypes } from "../components/item_processor"; +import { MapChunkView } from "../map_chunk_view"; + +const logger = createLogger("systems/ejector"); + +export class ItemEjectorSystem extends GameSystemWithFilter { + constructor(root) { + super(root, [ItemEjectorComponent]); + + this.root.signals.entityAdded.add(this.checkForCacheInvalidation, this); + this.root.signals.entityDestroyed.add(this.checkForCacheInvalidation, this); + this.root.signals.postLoadHook.add(this.recomputeCache, this); + + /** + * @type {Rectangle} + */ + this.areaToRecompute = null; + } + + /** + * + * @param {Entity} entity + */ + checkForCacheInvalidation(entity) { + if (!this.root.gameInitialized) { + return; + } + if (!entity.components.StaticMapEntity) { + return; + } + + // Optimize for the common case: adding or removing one building at a time. Clicking + // and dragging can cause up to 4 add/remove signals. + const staticComp = entity.components.StaticMapEntity; + const bounds = staticComp.getTileSpaceBounds(); + const expandedBounds = bounds.expandedInAllDirections(2); + + if (this.areaToRecompute) { + this.areaToRecompute = this.areaToRecompute.getUnion(expandedBounds); + } else { + this.areaToRecompute = expandedBounds; + } + } + + /** + * Precomputes the cache, which makes up for a huge performance improvement + */ + recomputeCache() { + if (this.areaToRecompute) { + logger.log("Recomputing cache using rectangle"); + if (G_IS_DEV && globalConfig.debug.renderChanges) { + this.root.hud.parts.changesDebugger.renderChange( + "ejector-area", + this.areaToRecompute, + "#fe50a6" + ); + } + this.recomputeAreaCache(); + this.areaToRecompute = null; + } else { + logger.log("Full cache recompute"); + if (G_IS_DEV && globalConfig.debug.renderChanges) { + this.root.hud.parts.changesDebugger.renderChange( + "ejector-full", + new Rectangle(-1000, -1000, 2000, 2000), + "#fe50a6" + ); + } + + // Try to find acceptors for every ejector + for (let i = 0; i < this.allEntities.length; ++i) { + const entity = this.allEntities[i]; + this.recomputeSingleEntityCache(entity); + } + } + } + + /** + * Recomputes the cache in the given area + */ + recomputeAreaCache() { + const area = this.areaToRecompute; + let entryCount = 0; + + logger.log("Recomputing area:", area.x, area.y, "/", area.w, area.h); + + // Store the entities we already recomputed, so we don't do work twice + const recomputedEntities = new Set(); + + for (let x = area.x; x < area.right(); ++x) { + for (let y = area.y; y < area.bottom(); ++y) { + const entities = this.root.map.getLayersContentsMultipleXY(x, y); + for (let i = 0; i < entities.length; ++i) { + const entity = entities[i]; + + // Recompute the entity in case its relevant for this system and it + // hasn't already been computed + if (!recomputedEntities.has(entity.uid) && entity.components.ItemEjector) { + recomputedEntities.add(entity.uid); + this.recomputeSingleEntityCache(entity); + } + } + } + } + return entryCount; + } + + /** + * @param {Entity} entity + */ + recomputeSingleEntityCache(entity) { + const ejectorComp = entity.components.ItemEjector; + const staticComp = entity.components.StaticMapEntity; + + for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) { + const ejectorSlot = ejectorComp.slots[slotIndex]; + + // Clear the old cache. + ejectorSlot.cachedDestSlot = null; + ejectorSlot.cachedTargetEntity = null; + ejectorSlot.cachedBeltPath = null; + + // Figure out where and into which direction we eject items + const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos); + const ejectSlotWsDirection = staticComp.localDirectionToWorld(ejectorSlot.direction); + const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection]; + const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector); + + // Try to find the given acceptor component to take the item + // Since there can be cross layer dependencies, check on all layers + const targetEntities = this.root.map.getLayersContentsMultipleXY( + ejectSlotTargetWsTile.x, + ejectSlotTargetWsTile.y + ); + + for (let i = 0; i < targetEntities.length; ++i) { + const targetEntity = targetEntities[i]; + + 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) { + ejectorSlot.cachedTargetEntity = targetEntity; + ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath; + break; + } + } + + // Check for item acceptors + const targetAcceptorComp = targetEntity.components.ItemAcceptor; + if (!targetAcceptorComp) { + // Entity doesn't accept items + continue; + } + + const matchingSlot = targetAcceptorComp.findMatchingSlot( + targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile), + targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection) + ); + + if (!matchingSlot) { + // No matching slot found + continue; + } + + // A slot can always be connected to one other slot only + ejectorSlot.cachedTargetEntity = targetEntity; + ejectorSlot.cachedDestSlot = matchingSlot; + break; + } + } + } + + update() { + if (this.areaToRecompute) { + this.recomputeCache(); + } + + // Precompute effective belt speed + let progressGrowth = 2 * this.root.dynamicTickrate.deltaSeconds; + + if (G_IS_DEV && globalConfig.debug.instantBelts) { + progressGrowth = 1; + } + + // Go over all cache entries + for (let i = 0; i < this.allEntities.length; ++i) { + const sourceEntity = this.allEntities[i]; + const sourceEjectorComp = sourceEntity.components.ItemEjector; + if (!sourceEjectorComp.enabled) { + continue; + } + + const slots = sourceEjectorComp.slots; + for (let j = 0; j < slots.length; ++j) { + const sourceSlot = slots[j]; + const item = sourceSlot.item; + if (!item) { + // No item available to be ejected + continue; + } + + const targetEntity = sourceSlot.cachedTargetEntity; + + // Advance items on the slot + sourceSlot.progress = Math.min( + 1, + sourceSlot.progress + + progressGrowth * + this.root.hubGoals.getBeltBaseSpeed() * + globalConfig.itemSpacingOnBelts + ); + + // Check if we are still in the process of ejecting, can't proceed then + if (sourceSlot.progress < 1.0) { + continue; + } + + // Check if we are ejecting to a belt path + const destPath = sourceSlot.cachedBeltPath; + if (destPath) { + // Try passing the item over + if (destPath.tryAcceptItem(item)) { + sourceSlot.item = null; + } + + // Always stop here, since there can *either* be a belt path *or* + // a slot + continue; + } + + // Check if the target acceptor can actually accept this item + const destSlot = sourceSlot.cachedDestSlot; + if (destSlot) { + const targetAcceptorComp = targetEntity.components.ItemAcceptor; + if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) { + continue; + } + + // Try to hand over the item + if (this.tryPassOverItem(item, targetEntity, destSlot.index)) { + // Handover successful, clear slot + targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item); + sourceSlot.item = null; + continue; + } + } + } + } + } + + /** + * + * @param {BaseItem} item + * @param {Entity} receiver + * @param {number} slotIndex + */ + tryPassOverItem(item, receiver, slotIndex) { + // Try figuring out how what to do with the item + // TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell. + // Also its just a few cases (hope it stays like this .. :x). + + const beltComp = receiver.components.Belt; + if (beltComp) { + const path = beltComp.assignedPath; + assert(path, "belt has no path"); + if (path.tryAcceptItem(item)) { + return true; + } + // Belt can have nothing else + return false; + } + + const itemProcessorComp = receiver.components.ItemProcessor; + if (itemProcessorComp) { + // Check for potential filters + if (!this.root.systemMgr.systems.itemProcessor.checkRequirements(receiver, item, slotIndex)) { + return false; + } + + // Its an item processor .. + if (itemProcessorComp.tryTakeItem(item, slotIndex)) { + return true; + } + // Item processor can have nothing else + return false; + } + + const undergroundBeltComp = receiver.components.UndergroundBelt; + if (undergroundBeltComp) { + // Its an underground belt. yay. + if ( + undergroundBeltComp.tryAcceptExternalItem( + item, + this.root.hubGoals.getUndergroundBeltBaseSpeed() + ) + ) { + return true; + } + + // Underground belt can have nothing else + return false; + } + + const storageComp = receiver.components.Storage; + if (storageComp) { + // It's a storage + if (storageComp.canAcceptItem(item)) { + storageComp.takeItem(item); + return true; + } + + // Storage can't have anything else + return false; + } + + return false; + } + + /** + * @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 ejectorComp = entity.components.ItemEjector; + if (!ejectorComp) { + continue; + } + + const staticComp = entity.components.StaticMapEntity; + + for (let i = 0; i < ejectorComp.slots.length; ++i) { + const slot = ejectorComp.slots[i]; + const ejectedItem = slot.item; + + if (!ejectedItem) { + // No item + continue; + } + + const realPosition = staticComp.localTileToWorld(slot.pos); + if (!chunk.tileSpaceRectangle.containsPoint(realPosition.x, realPosition.y)) { + // Not within this chunk + continue; + } + + const realDirection = staticComp.localDirectionToWorld(slot.direction); + const realDirectionVector = enumDirectionToVector[realDirection]; + + const tileX = realPosition.x + 0.5 + realDirectionVector.x * 0.5 * slot.progress; + const tileY = realPosition.y + 0.5 + realDirectionVector.y * 0.5 * slot.progress; + + const worldX = tileX * globalConfig.tileSize; + const worldY = tileY * globalConfig.tileSize; + + ejectedItem.drawItemCenteredClipped( + worldX, + worldY, + parameters, + globalConfig.defaultItemDiameter + ); + } + } + } +} diff --git a/src/js/game/systems/item_processor.js b/src/js/game/systems/item_processor.js index 13d98dc8..774a4eb8 100644 --- a/src/js/game/systems/item_processor.js +++ b/src/js/game/systems/item_processor.js @@ -1,10 +1,14 @@ import { globalConfig } from "../../core/config"; import { BaseItem } from "../base_item"; -import { enumColors, enumColorMixingResults } from "../colors"; -import { enumItemProcessorTypes, ItemProcessorComponent, enumItemProcessorRequirements } from "../components/item_processor"; +import { enumColorMixingResults, enumColors } from "../colors"; +import { + enumItemProcessorRequirements, + enumItemProcessorTypes, + ItemProcessorComponent, +} from "../components/item_processor"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; -import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item"; +import { BOOL_TRUE_SINGLETON, isTrueItem } from "../items/boolean_item"; import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { ShapeItem } from "../items/shape_item"; @@ -68,78 +72,154 @@ export class ItemProcessorSystem extends GameSystemWithFilter { } } - - // Check if we have an empty queue and can start a new charge if (processorComp.itemsToEject.length === 0) { - if (entity.components.ItemProcessor.processingRequirement) { - if (this.canProcess(entity)) { - this.startNewCharge(entity); - } - } else if (processorComp.inputSlots.length >= processorComp.inputsPerCharge) { + if (this.canProcess(entity)) { this.startNewCharge(entity); } } } } + /** + * Returns true if the entity should accept the given item on the given slot. + * This should only be called with matching items! I.e. if a color item is expected + * on the given slot, then only a color item must be passed. + * @param {Entity} entity + * @param {BaseItem} item The item to accept + * @param {number} slotIndex The slot index + * @returns {boolean} + */ + checkRequirements(entity, item, slotIndex) { + const itemProcessorComp = entity.components.ItemProcessor; + const pinsComp = entity.components.WiredPins; + + switch (itemProcessorComp.processingRequirement) { + case enumItemProcessorRequirements.painterQuad: { + if (slotIndex === 0) { + // Always accept the shape + return true; + } + + // Check the network value at the given slot + const network = pinsComp.slots[slotIndex - 1].linkedNetwork; + const slotIsEnabled = network && isTrueItem(network.currentValue); + if (!slotIsEnabled) { + return false; + } + return true; + } + + case enumItemProcessorRequirements.filter: { + const network = pinsComp.slots[0].linkedNetwork; + if (!network || !network.currentValue) { + // Item filter is not connected + return false; + } + + // Check if "false" was passed in + const item = network.currentValue; + if (item.getItemType() === "boolean") { + if (!(/** @type {BooleanItem} */ (item).value)) { + return false; + } + } + + // Otherwise, all good + return true; + } + + // By default, everything is accepted + default: + return true; + } + } + /** * Checks whether it's possible to process something * @param {Entity} entity */ canProcess(entity) { - switch (entity.components.ItemProcessor.processingRequirement) { - case enumItemProcessorRequirements.painterQuad: { - // For quad-painter, pins match slots - // boolean true means "disable input" - // a color means "disable if not matched" + const processorComp = entity.components.ItemProcessor; + + switch (processorComp.processingRequirement) { + // DEFAULT + // By default, we can start processing once all inputs are there + case null: { + return processorComp.inputSlots.length >= processorComp.inputsPerCharge; + } - const processorComp = entity.components.ItemProcessor; + // QUAD PAINTER + // For the quad painter, it might be possible to start processing earlier + case enumItemProcessorRequirements.painterQuad: { const pinsComp = entity.components.WiredPins; - /** @type {Object.} */ + /** @type {Object.} */ const itemsBySlot = {}; for (let i = 0; i < processorComp.inputSlots.length; ++i) { itemsBySlot[processorComp.inputSlots[i].sourceSlot] = processorComp.inputSlots[i]; } - // first slot is the shape - if (!itemsBySlot[0]) return false; + // First slot is the shape, so if it's not there we can't do anything + if (!itemsBySlot[0]) { + return false; + } + const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); + const slotStatus = []; - // Here we check just basic things` - // Stop processing if anything except TRUE is - // set and there is no item. + // Check which slots are enabled for (let i = 0; i < 4; ++i) { - const netValue = pinsComp.slots[i].linkedNetwork ? - pinsComp.slots[i].linkedNetwork.currentValue : - null; + // Extract the network value on the Nth pin + const networkValue = pinsComp.slots[i].linkedNetwork + ? pinsComp.slots[i].linkedNetwork.currentValue + : null; + + // If there is no "1" on that slot, don't paint there + if (!isTrueItem(networkValue)) { + slotStatus.push(false); + continue; + } - const currentItem = itemsBySlot[i + 1]; + slotStatus.push(true); + } - if ((netValue == null || !netValue.equals(BOOL_TRUE_SINGLETON)) && currentItem == null) { - let quadCount = 0; + // All slots are disabled + if (!slotStatus.includes(true)) { + return false; + } + // Check if all colors of the enabled slots are there + for (let i = 0; i < slotStatus.length; ++i) { + if (slotStatus[i] && !itemsBySlot[1 + i]) { + // A slot which is enabled wasn't enabled. Make sure if there is anything on the quadrant, + // it is not possible to paint, but if there is nothing we can ignore it for (let j = 0; j < 4; ++j) { const layer = shapeItem.definition.layers[j]; if (layer && layer[i]) { - quadCount++; + return false; } } - - if (quadCount > 0) { - return false; - } } } return true; } + + // FILTER + // Double check with linked network + case enumItemProcessorRequirements.filter: { + const network = entity.components.WiredPins.slots[0].linkedNetwork; + if (!network || !network.currentValue) { + // Item filter is not connected + return false; + } + + return processorComp.inputSlots.length >= processorComp.inputsPerCharge; + } + default: - assertAlways( - false, - "Unknown requirement for " + entity.components.ItemProcessor.processingRequirement - ); + assertAlways(false, "Unknown requirement for " + processorComp.processingRequirement); } } @@ -375,60 +455,22 @@ export class ItemProcessorSystem extends GameSystemWithFilter { const shapeItem = /** @type {ShapeItem} */ (itemsBySlot[0].item); assert(shapeItem instanceof ShapeItem, "Input for painter is not a shape"); - /** @type {Array} */ - const colorItems = [].fill(null, 0, 4); - + /** @type {Array} */ + const colors = [null, null, null, null]; for (let i = 0; i < 4; ++i) { if (itemsBySlot[i + 1]) { - colorItems[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item); - assert(colorItems[i] instanceof ColorItem, "Input for painter is not a color"); + colors[i] = /** @type {ColorItem} */ (itemsBySlot[i + 1].item).color; } } - const pinValues = entity.components.WiredPins.slots - .map(slot => slot.linkedNetwork ? slot.linkedNetwork.currentValue : BOOL_FALSE_SINGLETON); - - // @todo cleanup - const colorTL = colorItems[0]; - const colorTR = colorItems[1]; - const colorBR = colorItems[2]; - const colorBL = colorItems[3]; - - /** @type {Array} */ - let skipped = []; - for (let i = 0; i < 4; ++i) { - skipped[i] = pinValues[i] ? pinValues[i].equals(BOOL_TRUE_SINGLETON) : false; - } - - for (let i = 0; i < 4; ++i) { - if (colorItems[i] == null) { - skipped[i] = false; // make sure we never insert null item back - } else if (pinValues[i] instanceof ColorItem) { - // if pin value is a color, skip anything except that color - // but still require any color, because it would not work on - // slow factories. - if (!colorItems[i].equals(pinValues[i])) { - skipped[i] = true; - } - } - } - - const toColor = [ - (!skipped[0] && colorTL) ? colorTL.color : null, - (!skipped[1] && colorTR) ? colorTR.color : null, - (!skipped[2] && colorBR) ? colorBR.color : null, - (!skipped[3] && colorBL) ? colorBL.color : null, - ]; - const colorizedDefinition = this.root.shapeDefinitionMgr.shapeActionPaintWith4Colors( shapeItem.definition, - /** @type {[enumColors, enumColors, enumColors, enumColors]} */(toColor) + /** @type {[string, string, string, string]} */ (colors) ); outItems.push({ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(colorizedDefinition), }); - break; } diff --git a/src/js/game/systems/item_processor_overlays.js b/src/js/game/systems/item_processor_overlays.js new file mode 100644 index 00000000..84c4ca54 --- /dev/null +++ b/src/js/game/systems/item_processor_overlays.js @@ -0,0 +1,101 @@ +import { GameSystem } from "../game_system"; +import { MapChunkView } from "../map_chunk_view"; +import { enumItemProcessorRequirements } from "../components/item_processor"; +import { Entity } from "../entity"; +import { isTrueItem } from "../items/boolean_item"; +import { globalConfig } from "../../core/config"; +import { Loader } from "../../core/loader"; +import { smoothPulse } from "../../core/utils"; + +export class ItemProcessorOverlaysSystem extends GameSystem { + constructor(root) { + super(root); + + this.spriteDisabled = Loader.getSprite("sprites/misc/processor_disabled.png"); + this.spriteDisconnected = Loader.getSprite("sprites/misc/processor_disconnected.png"); + + this.drawnUids = new Set(); + + this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this); + } + + clearDrawnUids() { + this.drawnUids.clear(); + } + + /** + * + * @param {import("../../core/draw_utils").DrawParameters} parameters + * @param {MapChunkView} chunk + */ + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const entity = contents[i]; + const processorComp = entity.components.ItemProcessor; + if (!processorComp) { + continue; + } + + const requirement = processorComp.processingRequirement; + if (!requirement) { + continue; + } + + if (this.drawnUids.has(entity.uid)) { + continue; + } + + this.drawnUids.add(entity.uid); + + switch (requirement) { + case enumItemProcessorRequirements.painterQuad: { + this.drawConnectedSlotRequirement(parameters, entity); + break; + } + case enumItemProcessorRequirements.filter: { + this.drawConnectedSlotRequirement(parameters, entity); + break; + } + } + } + } + + /** + * + * @param {import("../../core/draw_utils").DrawParameters} parameters + * @param {Entity} entity + */ + drawConnectedSlotRequirement(parameters, entity) { + const staticComp = entity.components.StaticMapEntity; + const pinsComp = entity.components.WiredPins; + + let anySlotConnected = false; + + // Check if any slot has a value + for (let i = 0; i < pinsComp.slots.length; ++i) { + const slot = pinsComp.slots[i]; + const network = slot.linkedNetwork; + if (network && network.currentValue) { + anySlotConnected = true; + + if (isTrueItem(network.currentValue)) { + // No need to draw anything + return; + } + } + } + + const pulse = smoothPulse(this.root.time.now()); + parameters.context.globalAlpha = 0.6 + 0.4 * pulse; + const sprite = anySlotConnected ? this.spriteDisabled : this.spriteDisconnected; + sprite.drawCachedCentered( + parameters, + (staticComp.origin.x + 0.5) * globalConfig.tileSize, + (staticComp.origin.y + 0.5) * globalConfig.tileSize, + globalConfig.tileSize * (0.7 + 0.2 * pulse) + ); + + parameters.context.globalAlpha = 1; + } +} diff --git a/src/js/game/systems/logic_gate.js b/src/js/game/systems/logic_gate.js index 6914410e..f89f9724 100644 --- a/src/js/game/systems/logic_gate.js +++ b/src/js/game/systems/logic_gate.js @@ -3,7 +3,7 @@ import { enumColors } from "../colors"; import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate"; import { enumPinSlotType } from "../components/wired_pins"; import { GameSystemWithFilter } from "../game_system_with_filter"; -import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem } from "../items/boolean_item"; +import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTrueItem } from "../items/boolean_item"; import { COLOR_ITEM_SINGLETONS } from "../items/color_item"; import { ShapeDefinition } from "../shape_definition"; import { ShapeItem } from "../items/shape_item"; @@ -76,28 +76,9 @@ export class LogicGateSystem extends GameSystemWithFilter { */ compute_AND(parameters) { assert(parameters.length === 2, "bad parameter count for AND"); - - const param1 = parameters[0]; - const param2 = parameters[1]; - if (!param1 || !param2) { - // Not enough params - return BOOL_FALSE_SINGLETON; - } - - const itemType = param1.getItemType(); - - if (itemType !== param2.getItemType()) { - // Differing type - return BOOL_FALSE_SINGLETON; - } - - if (itemType === "boolean") { - return /** @type {BooleanItem} */ (param1).value && /** @type {BooleanItem} */ (param2).value - ? BOOL_TRUE_SINGLETON - : BOOL_FALSE_SINGLETON; - } - - return BOOL_FALSE_SINGLETON; + return isTrueItem(parameters[0]) && isTrueItem(parameters[1]) + ? BOOL_TRUE_SINGLETON + : BOOL_FALSE_SINGLETON; } /** @@ -105,18 +86,7 @@ export class LogicGateSystem extends GameSystemWithFilter { * @returns {BaseItem} */ compute_NOT(parameters) { - const item = parameters[0]; - if (!item) { - return BOOL_TRUE_SINGLETON; - } - - if (item.getItemType() !== "boolean") { - // Not a boolean actually - return BOOL_FALSE_SINGLETON; - } - - const value = /** @type {BooleanItem} */ (item).value; - return value ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON; + return isTrueItem(parameters[0]) ? BOOL_FALSE_SINGLETON : BOOL_TRUE_SINGLETON; } /** @@ -125,27 +95,9 @@ export class LogicGateSystem extends GameSystemWithFilter { */ compute_XOR(parameters) { assert(parameters.length === 2, "bad parameter count for XOR"); - - const param1 = parameters[0]; - const param2 = parameters[1]; - if (!param1 && !param2) { - // Not enough params - return BOOL_FALSE_SINGLETON; - } - - // Check for the right types - if (param1 && param1.getItemType() !== "boolean") { - return BOOL_FALSE_SINGLETON; - } - - if (param2 && param2.getItemType() !== "boolean") { - return BOOL_FALSE_SINGLETON; - } - - const valueParam1 = param1 ? /** @type {BooleanItem} */ (param1).value : 0; - const valueParam2 = param2 ? /** @type {BooleanItem} */ (param2).value : 0; - - return valueParam1 ^ valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON; + return isTrueItem(parameters[0]) ^ isTrueItem(parameters[1]) + ? BOOL_TRUE_SINGLETON + : BOOL_FALSE_SINGLETON; } /** @@ -154,20 +106,9 @@ export class LogicGateSystem extends GameSystemWithFilter { */ compute_OR(parameters) { assert(parameters.length === 2, "bad parameter count for OR"); - - const param1 = parameters[0]; - const param2 = parameters[1]; - if (!param1 && !param2) { - // Not enough params - return BOOL_FALSE_SINGLETON; - } - - const valueParam1 = - param1 && param1.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param1).value : 0; - const valueParam2 = - param2 && param2.getItemType() === "boolean" ? /** @type {BooleanItem} */ (param2).value : 0; - - return valueParam1 || valueParam2 ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON; + return isTrueItem(parameters[0]) || isTrueItem(parameters[1]) + ? BOOL_TRUE_SINGLETON + : BOOL_FALSE_SINGLETON; } /** @@ -176,21 +117,11 @@ export class LogicGateSystem extends GameSystemWithFilter { */ compute_IF(parameters) { assert(parameters.length === 2, "bad parameter count for IF"); - const flag = parameters[0]; const value = parameters[1]; - if (!flag || !value) { - // Not enough params - return null; - } - - if (flag.getItemType() !== "boolean") { - // Flag is not a boolean - return null; - } // pass through item - if (/** @type {BooleanItem} */ (flag).value) { + if (isTrueItem(flag)) { return value; } diff --git a/src/js/game/systems/wire.js b/src/js/game/systems/wire.js index b8d8031e..853fc725 100644 --- a/src/js/game/systems/wire.js +++ b/src/js/game/systems/wire.js @@ -1,768 +1,768 @@ -import { globalConfig } from "../../core/config"; -import { gMetaBuildingRegistry } from "../../core/global_registries"; -import { Loader } from "../../core/loader"; -import { createLogger } from "../../core/logging"; -import { Rectangle } from "../../core/rectangle"; -import { StaleAreaDetector } from "../../core/stale_area_detector"; -import { fastArrayDeleteValueIfContained } from "../../core/utils"; -import { - arrayAllDirections, - enumDirection, - enumDirectionToVector, - enumInvertedDirections, - Vector, -} from "../../core/vector"; -import { BaseItem } from "../base_item"; -import { BooleanItem } from "../items/boolean_item"; -import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire"; -import { getCodeFromBuildingData } from "../building_codes"; -import { enumWireType, WireComponent } from "../components/wire"; -import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; -import { WireTunnelComponent } from "../components/wire_tunnel"; -import { Entity } from "../entity"; -import { GameSystemWithFilter } from "../game_system_with_filter"; -import { MapChunkView } from "../map_chunk_view"; -import { defaultBuildingVariant } from "../meta_building"; - -const logger = createLogger("wires"); - -let networkUidCounter = 0; - -const VERBOSE_WIRES = G_IS_DEV && false; - -export class WireNetwork { - constructor() { - /** - * Who contributes to this network - * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */ - this.providers = []; - - /** - * Who takes values from this network - * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */ - this.receivers = []; - - /** - * All connected slots - * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} - */ - this.allSlots = []; - - /** - * All connected tunnels - * @type {Array} - */ - this.tunnels = []; - - /** - * Which wires are in this network - * @type {Array} - */ - this.wires = []; - - /** - * The current value of this network - * @type {BaseItem} - */ - this.currentValue = null; - - /** - * Whether this network has a value conflict, that is, more than one - * sender has sent a value - * @type {boolean} - */ - this.valueConflict = false; - - /** - * Unique network identifier - * @type {number} - */ - this.uid = ++networkUidCounter; - } -} - -export class WireSystem extends GameSystemWithFilter { - constructor(root) { - super(root, [WireComponent]); - - this.wireSprites = { - regular: { - [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/regular_forward.png"), - [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/regular_turn.png"), - [enumWireType.split]: Loader.getSprite("sprites/wires/sets/regular_split.png"), - [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/regular_cross.png"), - }, - conflict: { - [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/conflict_forward.png"), - [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/conflict_turn.png"), - [enumWireType.split]: Loader.getSprite("sprites/wires/sets/conflict_split.png"), - [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/conflict_cross.png"), - }, - shape: { - [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/shape_forward.png"), - [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/shape_turn.png"), - [enumWireType.split]: Loader.getSprite("sprites/wires/sets/shape_split.png"), - [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/shape_cross.png"), - }, - color: { - [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/color_forward.png"), - [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/color_turn.png"), - [enumWireType.split]: Loader.getSprite("sprites/wires/sets/color_split.png"), - [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/color_cross.png"), - }, - }; - - this.root.signals.entityDestroyed.add(this.queuePlacementUpdate, this); - this.root.signals.entityAdded.add(this.queuePlacementUpdate, this); - - this.root.signals.entityDestroyed.add(this.queueRecomputeIfWire, this); - this.root.signals.entityChanged.add(this.queueRecomputeIfWire, this); - this.root.signals.entityAdded.add(this.queueRecomputeIfWire, this); - - this.needsRecompute = true; - - this.staleArea = new StaleAreaDetector({ - root: this.root, - name: "wires", - recomputeMethod: this.updateSurroundingWirePlacement.bind(this), - }); - - /** - * @type {Array} - */ - this.networks = []; - } - - /** - * Invalidates the wires network if the given entity is relevant for it - * @param {Entity} entity - */ - queueRecomputeIfWire(entity) { - if (!this.root.gameInitialized) { - return; - } - - if (this.isEntityRelevantForWires(entity)) { - this.needsRecompute = true; - this.networks = []; - } - } - - /** - * Recomputes the whole wires network - */ - recomputeWiresNetwork() { - this.needsRecompute = false; - logger.log("Recomputing wires network"); - - this.networks = []; - - // Clear all network references - const wireEntities = this.root.entityMgr.getAllWithComponent(WireComponent); - for (let i = 0; i < wireEntities.length; ++i) { - wireEntities[i].components.Wire.linkedNetwork = null; - } - - const tunnelEntities = this.root.entityMgr.getAllWithComponent(WireTunnelComponent); - for (let i = 0; i < tunnelEntities.length; ++i) { - tunnelEntities[i].components.WireTunnel.linkedNetworks = []; - } - - const pinEntities = this.root.entityMgr.getAllWithComponent(WiredPinsComponent); - for (let i = 0; i < pinEntities.length; ++i) { - const slots = pinEntities[i].components.WiredPins.slots; - for (let k = 0; k < slots.length; ++k) { - slots[k].linkedNetwork = null; - } - } - - VERBOSE_WIRES && logger.log("Recomputing slots"); - - // Iterate over all ejector slots - for (let i = 0; i < pinEntities.length; ++i) { - const entity = pinEntities[i]; - const slots = entity.components.WiredPins.slots; - for (let k = 0; k < slots.length; ++k) { - const slot = slots[k]; - - // Ejectors are computed directly, acceptors are just set - if (slot.type === enumPinSlotType.logicalEjector && !slot.linkedNetwork) { - this.findNetworkForEjector(entity, slot); - } - } - } - } - - /** - * Finds the network for the given slot - * @param {Entity} initialEntity - * @param {import("../components/wired_pins").WirePinSlot} slot - */ - findNetworkForEjector(initialEntity, slot) { - let currentNetwork = new WireNetwork(); - VERBOSE_WIRES && - logger.log( - "Finding network for entity", - initialEntity.uid, - initialEntity.components.StaticMapEntity.origin.toString(), - "(nw-id:", - currentNetwork.uid, - ")" - ); - const entitiesToVisit = [ - { - entity: initialEntity, - slot, - }, - ]; - - while (entitiesToVisit.length > 0) { - const nextData = entitiesToVisit.pop(); - const nextEntity = nextData.entity; - - const wireComp = nextEntity.components.Wire; - const staticComp = nextEntity.components.StaticMapEntity; - - VERBOSE_WIRES && logger.log("Visiting", staticComp.origin.toString(), "(", nextEntity.uid, ")"); - - // Where to search for neighbours - let newSearchDirections = []; - let newSearchTile = null; - - //// WIRE - if (wireComp) { - // Sanity check - assert( - !wireComp.linkedNetwork || wireComp.linkedNetwork === currentNetwork, - "Mismatching wire network on wire entity " + - (wireComp.linkedNetwork ? wireComp.linkedNetwork.uid : "") + - " vs " + - currentNetwork.uid + - " @ " + - staticComp.origin.toString() - ); - - if (!wireComp.linkedNetwork) { - // This one is new! :D - VERBOSE_WIRES && logger.log(" Visited new wire:", staticComp.origin.toString()); - wireComp.linkedNetwork = currentNetwork; - currentNetwork.wires.push(nextEntity); - - newSearchDirections = arrayAllDirections; - newSearchTile = nextEntity.components.StaticMapEntity.origin; - } - } - - //// PINS - const pinsComp = nextEntity.components.WiredPins; - if (pinsComp) { - const slot = nextData.slot; - assert(slot, "No slot set for next entity"); - - if (slot.type === enumPinSlotType.logicalEjector) { - VERBOSE_WIRES && - logger.log(" Visiting ejector slot", staticComp.origin.toString(), "->", slot.type); - } else if (slot.type === enumPinSlotType.logicalAcceptor) { - VERBOSE_WIRES && - logger.log(" Visiting acceptor slot", staticComp.origin.toString(), "->", slot.type); - } else { - assertAlways(false, "Bad slot type: " + slot.type); - } - - // Sanity check - assert( - !slot.linkedNetwork || slot.linkedNetwork === currentNetwork, - "Mismatching wire network on pin slot entity " + - (slot.linkedNetwork ? slot.linkedNetwork.uid : "") + - " vs " + - currentNetwork.uid - ); - if (!slot.linkedNetwork) { - // This one is new - VERBOSE_WIRES && logger.log(" Visited new slot:", staticComp.origin.toString()); - - // Add to the right list - if (slot.type === enumPinSlotType.logicalEjector) { - currentNetwork.providers.push({ entity: nextEntity, slot }); - } else if (slot.type === enumPinSlotType.logicalAcceptor) { - currentNetwork.receivers.push({ entity: nextEntity, slot }); - } else { - assertAlways(false, "unknown slot type:" + slot.type); - } - - // Register on the network - currentNetwork.allSlots.push({ entity: nextEntity, slot }); - slot.linkedNetwork = currentNetwork; - - // Specify where to search next - newSearchDirections = [staticComp.localDirectionToWorld(slot.direction)]; - newSearchTile = staticComp.localTileToWorld(slot.pos); - } - } - - if (newSearchTile) { - // Find new surrounding wire targets - const newTargets = this.findSurroundingWireTargets( - newSearchTile, - newSearchDirections, - currentNetwork - ); - - VERBOSE_WIRES && logger.log(" Found", newTargets, "new targets to visit!"); - for (let i = 0; i < newTargets.length; ++i) { - entitiesToVisit.push(newTargets[i]); - } - } - } - - if ( - currentNetwork.providers.length > 0 && - (currentNetwork.wires.length > 0 || - currentNetwork.receivers.length > 0 || - currentNetwork.tunnels.length > 0) - ) { - this.networks.push(currentNetwork); - VERBOSE_WIRES && logger.log("Attached new network with uid", currentNetwork); - } else { - // Unregister network again - for (let i = 0; i < currentNetwork.wires.length; ++i) { - currentNetwork.wires[i].components.Wire.linkedNetwork = null; - } - - for (let i = 0; i < currentNetwork.tunnels.length; ++i) { - fastArrayDeleteValueIfContained( - currentNetwork.tunnels[i].components.WireTunnel.linkedNetworks, - currentNetwork - ); - } - - for (let i = 0; i < currentNetwork.allSlots.length; ++i) { - currentNetwork.allSlots[i].slot.linkedNetwork = null; - } - } - } - - /** - * Finds surrounding entities which are not yet assigned to a network - * @param {Vector} initialTile - * @param {Array} directions - * @param {WireNetwork} network - * @returns {Array} - */ - findSurroundingWireTargets(initialTile, directions, network) { - let result = []; - - VERBOSE_WIRES && - logger.log(" Searching for new targets at", initialTile.toString(), "and d=", directions); - - // Go over all directions we should search for - for (let i = 0; i < directions.length; ++i) { - const direction = directions[i]; - const offset = enumDirectionToVector[direction]; - const initialSearchTile = initialTile.add(offset); - - // Store which tunnels we already visited to avoid infinite loops - const visitedTunnels = new Set(); - - // First, find the initial connected entities - const initialContents = this.root.map.getLayersContentsMultipleXY( - initialSearchTile.x, - initialSearchTile.y - ); - - // Link the initial tile to the initial entities, since it may change - /** @type {Array<{entity: Entity, tile: Vector}>} */ - const contents = []; - for (let j = 0; j < initialContents.length; ++j) { - contents.push({ - entity: initialContents[j], - tile: initialSearchTile, - }); - } - - for (let k = 0; k < contents.length; ++k) { - const { entity, tile } = contents[k]; - const wireComp = entity.components.Wire; - - // Check for wire - if (wireComp && !wireComp.linkedNetwork) { - // Wires accept connections from everywhere - result.push({ - entity, - }); - } - - // Check for connected slots - const pinComp = entity.components.WiredPins; - if (pinComp) { - const staticComp = entity.components.StaticMapEntity; - - // Go over all slots and see if they are connected - const pinSlots = pinComp.slots; - for (let j = 0; j < pinSlots.length; ++j) { - const slot = pinSlots[j]; - - // Check if the position matches - const pinPos = staticComp.localTileToWorld(slot.pos); - if (!pinPos.equals(tile)) { - continue; - } - - // Check if the direction (inverted) matches - const pinDirection = staticComp.localDirectionToWorld(slot.direction); - if (pinDirection !== enumInvertedDirections[direction]) { - continue; - } - - if (!slot.linkedNetwork) { - result.push({ - entity, - slot, - }); - } - } - - // Pin slots mean it can be nothing else - continue; - } - - // Check if its a tunnel, if so, go to the forwarded item - const tunnelComp = entity.components.WireTunnel; - if (tunnelComp) { - if (visitedTunnels.has(entity.uid)) { - continue; - } - - const staticComp = entity.components.StaticMapEntity; - - if ( - !tunnelComp.multipleDirections && - !( - direction === staticComp.localDirectionToWorld(enumDirection.top) || - direction === staticComp.localDirectionToWorld(enumDirection.bottom) - ) - ) { - // It's a coating, and it doesn't connect here - continue; - } - - // Compute where this tunnel connects to - const forwardedTile = staticComp.origin.add(offset); - VERBOSE_WIRES && - logger.log( - " Found tunnel", - entity.uid, - "at", - tile, - "-> forwarding to", - forwardedTile - ); - - // Figure out which entities are connected - const connectedContents = this.root.map.getLayersContentsMultipleXY( - forwardedTile.x, - forwardedTile.y - ); - - // Attach the entities and the tile we search at, because it may change - for (let h = 0; h < connectedContents.length; ++h) { - contents.push({ - entity: connectedContents[h], - tile: forwardedTile, - }); - } - - // Add the tunnel to the network - if (tunnelComp.linkedNetworks.indexOf(network) < 0) { - tunnelComp.linkedNetworks.push(network); - } - if (network.tunnels.indexOf(entity) < 0) { - network.tunnels.push(entity); - } - - // Remember this tunnel - visitedTunnels.add(entity.uid); - } - } - } - - VERBOSE_WIRES && logger.log(" -> Found", result.length); - - return result; - } - - /** - * Updates the wires network - */ - update() { - this.staleArea.update(); - - if (this.needsRecompute) { - this.recomputeWiresNetwork(); - } - - // Re-compute values of all networks - for (let i = 0; i < this.networks.length; ++i) { - const network = this.networks[i]; - - // Reset conflicts - network.valueConflict = false; - - // Aggregate values of all senders - const senders = network.providers; - let value = null; - for (let k = 0; k < senders.length; ++k) { - const senderSlot = senders[k]; - const slotValue = senderSlot.slot.value; - - // The first sender can just put in his value - if (!value) { - value = slotValue; - continue; - } - - // If the slot is empty itself, just skip it - if (!slotValue) { - continue; - } - - // If there is already an value, compare if it matches -> - // otherwise there is a conflict - if (value.equals(slotValue)) { - // All good - continue; - } - - // There is a conflict, this means the value will be null anyways - network.valueConflict = true; - break; - } - - // Assign value - if (network.valueConflict) { - network.currentValue = null; - } else { - network.currentValue = value; - } - } - } - - /** - * Returns the given tileset and opacity - * @param {WireComponent} wireComp - * @returns {{ spriteSet: Object, opacity: number}} - */ - getSpriteSetAndOpacityForWire(wireComp) { - if (!wireComp.linkedNetwork) { - // There is no network, it's empty - return { - spriteSet: this.wireSprites.regular, - opacity: 0.3, - }; - } - - const network = wireComp.linkedNetwork; - if (network.valueConflict) { - // There is a conflict - return { - spriteSet: this.wireSprites.conflict, - opacity: 1, - }; - } - - const value = network.currentValue; - if (!value) { - // There is no value stored - return { - spriteSet: this.wireSprites.regular, - opacity: 0.3, - }; - } - - const valueType = value.getItemType(); - if (valueType === "shape") { - return { - spriteSet: this.wireSprites.shape, - opacity: 1, - }; - } else if (valueType === "color") { - return { - spriteSet: this.wireSprites.color, - opacity: 1, - }; - } else if (valueType === "boolean") { - return { - spriteSet: this.wireSprites.regular, - opacity: /** @type {BooleanItem} */ (value).value ? 1 : 0.5, - }; - } else { - assertAlways(false, "Unknown item type: " + valueType); - } - - return { - spriteSet: this.wireSprites.regular, - opacity: 1, - }; - } - - /** - * Draws a given chunk - * @param {import("../../core/draw_utils").DrawParameters} parameters - * @param {MapChunkView} chunk - */ - drawChunk(parameters, chunk) { - 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 && entity.components.Wire) { - const wireComp = entity.components.Wire; - const wireType = wireComp.type; - - const { opacity, spriteSet } = this.getSpriteSetAndOpacityForWire(wireComp); - - const sprite = spriteSet[wireType]; - - assert(sprite, "Unknown wire type: " + wireType); - const staticComp = entity.components.StaticMapEntity; - parameters.context.globalAlpha = opacity; - staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0); - parameters.context.globalAlpha = 1; - - if (G_IS_DEV && globalConfig.debug.renderWireRotations) { - parameters.context.fillStyle = "red"; - parameters.context.font = "5px Tahoma"; - parameters.context.fillText( - "" + staticComp.originalRotation, - staticComp.origin.x * globalConfig.tileSize, - staticComp.origin.y * globalConfig.tileSize + 5 - ); - - parameters.context.fillStyle = "rgba(255, 0, 0, 0.2)"; - if (staticComp.originalRotation % 180 === 0) { - parameters.context.fillRect( - (staticComp.origin.x + 0.5) * globalConfig.tileSize, - staticComp.origin.y * globalConfig.tileSize, - 3, - globalConfig.tileSize - ); - } else { - parameters.context.fillRect( - staticComp.origin.x * globalConfig.tileSize, - (staticComp.origin.y + 0.5) * globalConfig.tileSize, - globalConfig.tileSize, - 3 - ); - } - } - } - - // DEBUG Rendering - if (G_IS_DEV && globalConfig.debug.renderWireNetworkInfos) { - if (entity) { - const staticComp = entity.components.StaticMapEntity; - const wireComp = entity.components.Wire; - - // Draw network info for wires - if (wireComp && wireComp.linkedNetwork) { - parameters.context.fillStyle = "red"; - parameters.context.font = "5px Tahoma"; - parameters.context.fillText( - "W" + wireComp.linkedNetwork.uid, - (staticComp.origin.x + 0.5) * globalConfig.tileSize, - (staticComp.origin.y + 0.5) * globalConfig.tileSize - ); - } - } - } - } - } - } - - /** - * Returns whether this entity is relevant for the wires network - * @param {Entity} entity - */ - isEntityRelevantForWires(entity) { - return entity.components.Wire || entity.components.WiredPins || entity.components.WireTunnel; - } - - /** - * - * @param {Entity} entity - */ - queuePlacementUpdate(entity) { - if (!this.root.gameInitialized) { - return; - } - - if (!this.isEntityRelevantForWires(entity)) { - return; - } - - const staticComp = entity.components.StaticMapEntity; - if (!staticComp) { - return; - } - - // Invalidate affected area - const originalRect = staticComp.getTileSpaceBounds(); - const affectedArea = originalRect.expandedInAllDirections(1); - this.staleArea.invalidate(affectedArea); - } - - /** - * Updates the wire placement after an entity has been added / deleted - * @param {Rectangle} affectedArea - */ - updateSurroundingWirePlacement(affectedArea) { - const metaWire = gMetaBuildingRegistry.findByClass(MetaWireBuilding); - - for (let x = affectedArea.x; x < affectedArea.right(); ++x) { - for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) { - const targetEntities = this.root.map.getLayersContentsMultipleXY(x, y); - for (let i = 0; i < targetEntities.length; ++i) { - const targetEntity = targetEntities[i]; - - const targetWireComp = targetEntity.components.Wire; - const targetStaticComp = targetEntity.components.StaticMapEntity; - - if (!targetWireComp) { - // Not a wire - continue; - } - - const { - rotation, - rotationVariant, - } = metaWire.computeOptimalDirectionAndRotationVariantAtTile({ - root: this.root, - tile: new Vector(x, y), - rotation: targetStaticComp.originalRotation, - variant: defaultBuildingVariant, - layer: targetEntity.layer, - }); - - // Compute delta to see if anything changed - const newType = arrayWireRotationVariantToType[rotationVariant]; - - if (targetStaticComp.rotation !== rotation || newType !== targetWireComp.type) { - // Change stuff - targetStaticComp.rotation = rotation; - metaWire.updateVariants(targetEntity, rotationVariant, defaultBuildingVariant); - - // Update code as well - targetStaticComp.code = getCodeFromBuildingData( - metaWire, - defaultBuildingVariant, - rotationVariant - ); - - // Make sure the chunks know about the update - this.root.signals.entityChanged.dispatch(targetEntity); - } - } - } - } - } -} +import { globalConfig } from "../../core/config"; +import { gMetaBuildingRegistry } from "../../core/global_registries"; +import { Loader } from "../../core/loader"; +import { createLogger } from "../../core/logging"; +import { Rectangle } from "../../core/rectangle"; +import { StaleAreaDetector } from "../../core/stale_area_detector"; +import { fastArrayDeleteValueIfContained } from "../../core/utils"; +import { + arrayAllDirections, + enumDirection, + enumDirectionToVector, + enumInvertedDirections, + Vector, +} from "../../core/vector"; +import { BaseItem } from "../base_item"; +import { isTrueItem } from "../items/boolean_item"; +import { arrayWireRotationVariantToType, MetaWireBuilding } from "../buildings/wire"; +import { getCodeFromBuildingData } from "../building_codes"; +import { enumWireType, WireComponent } from "../components/wire"; +import { enumPinSlotType, WiredPinsComponent } from "../components/wired_pins"; +import { WireTunnelComponent } from "../components/wire_tunnel"; +import { Entity } from "../entity"; +import { GameSystemWithFilter } from "../game_system_with_filter"; +import { MapChunkView } from "../map_chunk_view"; +import { defaultBuildingVariant } from "../meta_building"; + +const logger = createLogger("wires"); + +let networkUidCounter = 0; + +const VERBOSE_WIRES = G_IS_DEV && false; + +export class WireNetwork { + constructor() { + /** + * Who contributes to this network + * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */ + this.providers = []; + + /** + * Who takes values from this network + * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} */ + this.receivers = []; + + /** + * All connected slots + * @type {Array<{ entity: Entity, slot: import("../components/wired_pins").WirePinSlot }>} + */ + this.allSlots = []; + + /** + * All connected tunnels + * @type {Array} + */ + this.tunnels = []; + + /** + * Which wires are in this network + * @type {Array} + */ + this.wires = []; + + /** + * The current value of this network + * @type {BaseItem} + */ + this.currentValue = null; + + /** + * Whether this network has a value conflict, that is, more than one + * sender has sent a value + * @type {boolean} + */ + this.valueConflict = false; + + /** + * Unique network identifier + * @type {number} + */ + this.uid = ++networkUidCounter; + } +} + +export class WireSystem extends GameSystemWithFilter { + constructor(root) { + super(root, [WireComponent]); + + this.wireSprites = { + regular: { + [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/regular_forward.png"), + [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/regular_turn.png"), + [enumWireType.split]: Loader.getSprite("sprites/wires/sets/regular_split.png"), + [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/regular_cross.png"), + }, + conflict: { + [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/conflict_forward.png"), + [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/conflict_turn.png"), + [enumWireType.split]: Loader.getSprite("sprites/wires/sets/conflict_split.png"), + [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/conflict_cross.png"), + }, + shape: { + [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/shape_forward.png"), + [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/shape_turn.png"), + [enumWireType.split]: Loader.getSprite("sprites/wires/sets/shape_split.png"), + [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/shape_cross.png"), + }, + color: { + [enumWireType.regular]: Loader.getSprite("sprites/wires/sets/color_forward.png"), + [enumWireType.turn]: Loader.getSprite("sprites/wires/sets/color_turn.png"), + [enumWireType.split]: Loader.getSprite("sprites/wires/sets/color_split.png"), + [enumWireType.cross]: Loader.getSprite("sprites/wires/sets/color_cross.png"), + }, + }; + + this.root.signals.entityDestroyed.add(this.queuePlacementUpdate, this); + this.root.signals.entityAdded.add(this.queuePlacementUpdate, this); + + this.root.signals.entityDestroyed.add(this.queueRecomputeIfWire, this); + this.root.signals.entityChanged.add(this.queueRecomputeIfWire, this); + this.root.signals.entityAdded.add(this.queueRecomputeIfWire, this); + + this.needsRecompute = true; + + this.staleArea = new StaleAreaDetector({ + root: this.root, + name: "wires", + recomputeMethod: this.updateSurroundingWirePlacement.bind(this), + }); + + /** + * @type {Array} + */ + this.networks = []; + } + + /** + * Invalidates the wires network if the given entity is relevant for it + * @param {Entity} entity + */ + queueRecomputeIfWire(entity) { + if (!this.root.gameInitialized) { + return; + } + + if (this.isEntityRelevantForWires(entity)) { + this.needsRecompute = true; + this.networks = []; + } + } + + /** + * Recomputes the whole wires network + */ + recomputeWiresNetwork() { + this.needsRecompute = false; + logger.log("Recomputing wires network"); + + this.networks = []; + + // Clear all network references + const wireEntities = this.root.entityMgr.getAllWithComponent(WireComponent); + for (let i = 0; i < wireEntities.length; ++i) { + wireEntities[i].components.Wire.linkedNetwork = null; + } + + const tunnelEntities = this.root.entityMgr.getAllWithComponent(WireTunnelComponent); + for (let i = 0; i < tunnelEntities.length; ++i) { + tunnelEntities[i].components.WireTunnel.linkedNetworks = []; + } + + const pinEntities = this.root.entityMgr.getAllWithComponent(WiredPinsComponent); + for (let i = 0; i < pinEntities.length; ++i) { + const slots = pinEntities[i].components.WiredPins.slots; + for (let k = 0; k < slots.length; ++k) { + slots[k].linkedNetwork = null; + } + } + + VERBOSE_WIRES && logger.log("Recomputing slots"); + + // Iterate over all ejector slots + for (let i = 0; i < pinEntities.length; ++i) { + const entity = pinEntities[i]; + const slots = entity.components.WiredPins.slots; + for (let k = 0; k < slots.length; ++k) { + const slot = slots[k]; + + // Ejectors are computed directly, acceptors are just set + if (slot.type === enumPinSlotType.logicalEjector && !slot.linkedNetwork) { + this.findNetworkForEjector(entity, slot); + } + } + } + } + + /** + * Finds the network for the given slot + * @param {Entity} initialEntity + * @param {import("../components/wired_pins").WirePinSlot} slot + */ + findNetworkForEjector(initialEntity, slot) { + let currentNetwork = new WireNetwork(); + VERBOSE_WIRES && + logger.log( + "Finding network for entity", + initialEntity.uid, + initialEntity.components.StaticMapEntity.origin.toString(), + "(nw-id:", + currentNetwork.uid, + ")" + ); + const entitiesToVisit = [ + { + entity: initialEntity, + slot, + }, + ]; + + while (entitiesToVisit.length > 0) { + const nextData = entitiesToVisit.pop(); + const nextEntity = nextData.entity; + + const wireComp = nextEntity.components.Wire; + const staticComp = nextEntity.components.StaticMapEntity; + + VERBOSE_WIRES && logger.log("Visiting", staticComp.origin.toString(), "(", nextEntity.uid, ")"); + + // Where to search for neighbours + let newSearchDirections = []; + let newSearchTile = null; + + //// WIRE + if (wireComp) { + // Sanity check + assert( + !wireComp.linkedNetwork || wireComp.linkedNetwork === currentNetwork, + "Mismatching wire network on wire entity " + + (wireComp.linkedNetwork ? wireComp.linkedNetwork.uid : "") + + " vs " + + currentNetwork.uid + + " @ " + + staticComp.origin.toString() + ); + + if (!wireComp.linkedNetwork) { + // This one is new! :D + VERBOSE_WIRES && logger.log(" Visited new wire:", staticComp.origin.toString()); + wireComp.linkedNetwork = currentNetwork; + currentNetwork.wires.push(nextEntity); + + newSearchDirections = arrayAllDirections; + newSearchTile = nextEntity.components.StaticMapEntity.origin; + } + } + + //// PINS + const pinsComp = nextEntity.components.WiredPins; + if (pinsComp) { + const slot = nextData.slot; + assert(slot, "No slot set for next entity"); + + if (slot.type === enumPinSlotType.logicalEjector) { + VERBOSE_WIRES && + logger.log(" Visiting ejector slot", staticComp.origin.toString(), "->", slot.type); + } else if (slot.type === enumPinSlotType.logicalAcceptor) { + VERBOSE_WIRES && + logger.log(" Visiting acceptor slot", staticComp.origin.toString(), "->", slot.type); + } else { + assertAlways(false, "Bad slot type: " + slot.type); + } + + // Sanity check + assert( + !slot.linkedNetwork || slot.linkedNetwork === currentNetwork, + "Mismatching wire network on pin slot entity " + + (slot.linkedNetwork ? slot.linkedNetwork.uid : "") + + " vs " + + currentNetwork.uid + ); + if (!slot.linkedNetwork) { + // This one is new + VERBOSE_WIRES && logger.log(" Visited new slot:", staticComp.origin.toString()); + + // Add to the right list + if (slot.type === enumPinSlotType.logicalEjector) { + currentNetwork.providers.push({ entity: nextEntity, slot }); + } else if (slot.type === enumPinSlotType.logicalAcceptor) { + currentNetwork.receivers.push({ entity: nextEntity, slot }); + } else { + assertAlways(false, "unknown slot type:" + slot.type); + } + + // Register on the network + currentNetwork.allSlots.push({ entity: nextEntity, slot }); + slot.linkedNetwork = currentNetwork; + + // Specify where to search next + newSearchDirections = [staticComp.localDirectionToWorld(slot.direction)]; + newSearchTile = staticComp.localTileToWorld(slot.pos); + } + } + + if (newSearchTile) { + // Find new surrounding wire targets + const newTargets = this.findSurroundingWireTargets( + newSearchTile, + newSearchDirections, + currentNetwork + ); + + VERBOSE_WIRES && logger.log(" Found", newTargets, "new targets to visit!"); + for (let i = 0; i < newTargets.length; ++i) { + entitiesToVisit.push(newTargets[i]); + } + } + } + + if ( + currentNetwork.providers.length > 0 && + (currentNetwork.wires.length > 0 || + currentNetwork.receivers.length > 0 || + currentNetwork.tunnels.length > 0) + ) { + this.networks.push(currentNetwork); + VERBOSE_WIRES && logger.log("Attached new network with uid", currentNetwork); + } else { + // Unregister network again + for (let i = 0; i < currentNetwork.wires.length; ++i) { + currentNetwork.wires[i].components.Wire.linkedNetwork = null; + } + + for (let i = 0; i < currentNetwork.tunnels.length; ++i) { + fastArrayDeleteValueIfContained( + currentNetwork.tunnels[i].components.WireTunnel.linkedNetworks, + currentNetwork + ); + } + + for (let i = 0; i < currentNetwork.allSlots.length; ++i) { + currentNetwork.allSlots[i].slot.linkedNetwork = null; + } + } + } + + /** + * Finds surrounding entities which are not yet assigned to a network + * @param {Vector} initialTile + * @param {Array} directions + * @param {WireNetwork} network + * @returns {Array} + */ + findSurroundingWireTargets(initialTile, directions, network) { + let result = []; + + VERBOSE_WIRES && + logger.log(" Searching for new targets at", initialTile.toString(), "and d=", directions); + + // Go over all directions we should search for + for (let i = 0; i < directions.length; ++i) { + const direction = directions[i]; + const offset = enumDirectionToVector[direction]; + const initialSearchTile = initialTile.add(offset); + + // Store which tunnels we already visited to avoid infinite loops + const visitedTunnels = new Set(); + + // First, find the initial connected entities + const initialContents = this.root.map.getLayersContentsMultipleXY( + initialSearchTile.x, + initialSearchTile.y + ); + + // Link the initial tile to the initial entities, since it may change + /** @type {Array<{entity: Entity, tile: Vector}>} */ + const contents = []; + for (let j = 0; j < initialContents.length; ++j) { + contents.push({ + entity: initialContents[j], + tile: initialSearchTile, + }); + } + + for (let k = 0; k < contents.length; ++k) { + const { entity, tile } = contents[k]; + const wireComp = entity.components.Wire; + + // Check for wire + if (wireComp && !wireComp.linkedNetwork) { + // Wires accept connections from everywhere + result.push({ + entity, + }); + } + + // Check for connected slots + const pinComp = entity.components.WiredPins; + if (pinComp) { + const staticComp = entity.components.StaticMapEntity; + + // Go over all slots and see if they are connected + const pinSlots = pinComp.slots; + for (let j = 0; j < pinSlots.length; ++j) { + const slot = pinSlots[j]; + + // Check if the position matches + const pinPos = staticComp.localTileToWorld(slot.pos); + if (!pinPos.equals(tile)) { + continue; + } + + // Check if the direction (inverted) matches + const pinDirection = staticComp.localDirectionToWorld(slot.direction); + if (pinDirection !== enumInvertedDirections[direction]) { + continue; + } + + if (!slot.linkedNetwork) { + result.push({ + entity, + slot, + }); + } + } + + // Pin slots mean it can be nothing else + continue; + } + + // Check if its a tunnel, if so, go to the forwarded item + const tunnelComp = entity.components.WireTunnel; + if (tunnelComp) { + if (visitedTunnels.has(entity.uid)) { + continue; + } + + const staticComp = entity.components.StaticMapEntity; + + if ( + !tunnelComp.multipleDirections && + !( + direction === staticComp.localDirectionToWorld(enumDirection.top) || + direction === staticComp.localDirectionToWorld(enumDirection.bottom) + ) + ) { + // It's a coating, and it doesn't connect here + continue; + } + + // Compute where this tunnel connects to + const forwardedTile = staticComp.origin.add(offset); + VERBOSE_WIRES && + logger.log( + " Found tunnel", + entity.uid, + "at", + tile, + "-> forwarding to", + forwardedTile + ); + + // Figure out which entities are connected + const connectedContents = this.root.map.getLayersContentsMultipleXY( + forwardedTile.x, + forwardedTile.y + ); + + // Attach the entities and the tile we search at, because it may change + for (let h = 0; h < connectedContents.length; ++h) { + contents.push({ + entity: connectedContents[h], + tile: forwardedTile, + }); + } + + // Add the tunnel to the network + if (tunnelComp.linkedNetworks.indexOf(network) < 0) { + tunnelComp.linkedNetworks.push(network); + } + if (network.tunnels.indexOf(entity) < 0) { + network.tunnels.push(entity); + } + + // Remember this tunnel + visitedTunnels.add(entity.uid); + } + } + } + + VERBOSE_WIRES && logger.log(" -> Found", result.length); + + return result; + } + + /** + * Updates the wires network + */ + update() { + this.staleArea.update(); + + if (this.needsRecompute) { + this.recomputeWiresNetwork(); + } + + // Re-compute values of all networks + for (let i = 0; i < this.networks.length; ++i) { + const network = this.networks[i]; + + // Reset conflicts + network.valueConflict = false; + + // Aggregate values of all senders + const senders = network.providers; + let value = null; + for (let k = 0; k < senders.length; ++k) { + const senderSlot = senders[k]; + const slotValue = senderSlot.slot.value; + + // The first sender can just put in his value + if (!value) { + value = slotValue; + continue; + } + + // If the slot is empty itself, just skip it + if (!slotValue) { + continue; + } + + // If there is already an value, compare if it matches -> + // otherwise there is a conflict + if (value.equals(slotValue)) { + // All good + continue; + } + + // There is a conflict, this means the value will be null anyways + network.valueConflict = true; + break; + } + + // Assign value + if (network.valueConflict) { + network.currentValue = null; + } else { + network.currentValue = value; + } + } + } + + /** + * Returns the given tileset and opacity + * @param {WireComponent} wireComp + * @returns {{ spriteSet: Object, opacity: number}} + */ + getSpriteSetAndOpacityForWire(wireComp) { + if (!wireComp.linkedNetwork) { + // There is no network, it's empty + return { + spriteSet: this.wireSprites.regular, + opacity: 0.3, + }; + } + + const network = wireComp.linkedNetwork; + if (network.valueConflict) { + // There is a conflict + return { + spriteSet: this.wireSprites.conflict, + opacity: 1, + }; + } + + const value = network.currentValue; + if (!value) { + // There is no value stored + return { + spriteSet: this.wireSprites.regular, + opacity: 0.3, + }; + } + + const valueType = value.getItemType(); + if (valueType === "shape") { + return { + spriteSet: this.wireSprites.shape, + opacity: 1, + }; + } else if (valueType === "color") { + return { + spriteSet: this.wireSprites.color, + opacity: 1, + }; + } else if (valueType === "boolean") { + return { + spriteSet: this.wireSprites.regular, + opacity: isTrueItem(value) ? 1 : 0.5, + }; + } else { + assertAlways(false, "Unknown item type: " + valueType); + } + + return { + spriteSet: this.wireSprites.regular, + opacity: 1, + }; + } + + /** + * Draws a given chunk + * @param {import("../../core/draw_utils").DrawParameters} parameters + * @param {MapChunkView} chunk + */ + drawChunk(parameters, chunk) { + 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 && entity.components.Wire) { + const wireComp = entity.components.Wire; + const wireType = wireComp.type; + + const { opacity, spriteSet } = this.getSpriteSetAndOpacityForWire(wireComp); + + const sprite = spriteSet[wireType]; + + assert(sprite, "Unknown wire type: " + wireType); + const staticComp = entity.components.StaticMapEntity; + parameters.context.globalAlpha = opacity; + staticComp.drawSpriteOnBoundsClipped(parameters, sprite, 0); + parameters.context.globalAlpha = 1; + + if (G_IS_DEV && globalConfig.debug.renderWireRotations) { + parameters.context.fillStyle = "red"; + parameters.context.font = "5px Tahoma"; + parameters.context.fillText( + "" + staticComp.originalRotation, + staticComp.origin.x * globalConfig.tileSize, + staticComp.origin.y * globalConfig.tileSize + 5 + ); + + parameters.context.fillStyle = "rgba(255, 0, 0, 0.2)"; + if (staticComp.originalRotation % 180 === 0) { + parameters.context.fillRect( + (staticComp.origin.x + 0.5) * globalConfig.tileSize, + staticComp.origin.y * globalConfig.tileSize, + 3, + globalConfig.tileSize + ); + } else { + parameters.context.fillRect( + staticComp.origin.x * globalConfig.tileSize, + (staticComp.origin.y + 0.5) * globalConfig.tileSize, + globalConfig.tileSize, + 3 + ); + } + } + } + + // DEBUG Rendering + if (G_IS_DEV && globalConfig.debug.renderWireNetworkInfos) { + if (entity) { + const staticComp = entity.components.StaticMapEntity; + const wireComp = entity.components.Wire; + + // Draw network info for wires + if (wireComp && wireComp.linkedNetwork) { + parameters.context.fillStyle = "red"; + parameters.context.font = "5px Tahoma"; + parameters.context.fillText( + "W" + wireComp.linkedNetwork.uid, + (staticComp.origin.x + 0.5) * globalConfig.tileSize, + (staticComp.origin.y + 0.5) * globalConfig.tileSize + ); + } + } + } + } + } + } + + /** + * Returns whether this entity is relevant for the wires network + * @param {Entity} entity + */ + isEntityRelevantForWires(entity) { + return entity.components.Wire || entity.components.WiredPins || entity.components.WireTunnel; + } + + /** + * + * @param {Entity} entity + */ + queuePlacementUpdate(entity) { + if (!this.root.gameInitialized) { + return; + } + + if (!this.isEntityRelevantForWires(entity)) { + return; + } + + const staticComp = entity.components.StaticMapEntity; + if (!staticComp) { + return; + } + + // Invalidate affected area + const originalRect = staticComp.getTileSpaceBounds(); + const affectedArea = originalRect.expandedInAllDirections(1); + this.staleArea.invalidate(affectedArea); + } + + /** + * Updates the wire placement after an entity has been added / deleted + * @param {Rectangle} affectedArea + */ + updateSurroundingWirePlacement(affectedArea) { + const metaWire = gMetaBuildingRegistry.findByClass(MetaWireBuilding); + + for (let x = affectedArea.x; x < affectedArea.right(); ++x) { + for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) { + const targetEntities = this.root.map.getLayersContentsMultipleXY(x, y); + for (let i = 0; i < targetEntities.length; ++i) { + const targetEntity = targetEntities[i]; + + const targetWireComp = targetEntity.components.Wire; + const targetStaticComp = targetEntity.components.StaticMapEntity; + + if (!targetWireComp) { + // Not a wire + continue; + } + + const { + rotation, + rotationVariant, + } = metaWire.computeOptimalDirectionAndRotationVariantAtTile({ + root: this.root, + tile: new Vector(x, y), + rotation: targetStaticComp.originalRotation, + variant: defaultBuildingVariant, + layer: targetEntity.layer, + }); + + // Compute delta to see if anything changed + const newType = arrayWireRotationVariantToType[rotationVariant]; + + if (targetStaticComp.rotation !== rotation || newType !== targetWireComp.type) { + // Change stuff + targetStaticComp.rotation = rotation; + metaWire.updateVariants(targetEntity, rotationVariant, defaultBuildingVariant); + + // Update code as well + targetStaticComp.code = getCodeFromBuildingData( + metaWire, + defaultBuildingVariant, + rotationVariant + ); + + // Make sure the chunks know about the update + this.root.signals.entityChanged.dispatch(targetEntity); + } + } + } + } + } +}