## Description:

Add a new pixi layer for rendering structure icons
Add new sprites for structures

![image](https://github.com/user-attachments/assets/d5171b31-c83b-431a-a0f6-87b85b460a3f)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

Vivacious Box

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
Vivacious Box
2025-06-28 02:00:01 +02:00
committed by GitHub
parent 09bd6312e3
commit 77dddbf3ee
38 changed files with 1312 additions and 231 deletions
+89 -1
View File
@@ -48,6 +48,7 @@
"nanoid": "^3.3.6",
"obscenity": "^0.4.3",
"pg": "^8.13.3",
"pixi.js": "^8.10.1",
"prom-client": "^15.1.3",
"protobufjs": "^7.3.2",
"pureimage": "^0.4.13",
@@ -7944,6 +7945,12 @@
"node": "^18.19.0 || >=20.6.0"
}
},
"node_modules/@pixi/colord": {
"version": "2.9.6",
"resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz",
"integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==",
"license": "MIT"
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -8995,6 +9002,12 @@
"@types/node": "*"
}
},
"node_modules/@types/css-font-loading-module": {
"version": "0.0.12",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
"integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==",
"license": "MIT"
},
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
@@ -9279,6 +9292,12 @@
"@types/d3-selection": "*"
}
},
"node_modules/@types/earcut": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz",
"integrity": "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==",
"license": "MIT"
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -10388,6 +10407,12 @@
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webgpu/types": {
"version": "0.1.61",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.61.tgz",
"integrity": "sha512-w2HbBvH+qO19SB5pJOJFKs533CdZqxl3fcGonqL321VHkW7W/iBo6H8bjDy6pr/+pbMwIu5dnuaAxH7NxBqUrQ==",
"license": "BSD-3-Clause"
},
"node_modules/@webpack-cli/configtest": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz",
@@ -10432,6 +10457,15 @@
}
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -13026,6 +13060,12 @@
"node": ">= 6"
}
},
"node_modules/earcut": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz",
"integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==",
"license": "ISC"
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -13697,7 +13737,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true,
"license": "MIT"
},
"node_modules/events": {
@@ -14572,6 +14611,15 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/gifuct-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz",
"integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==",
"license": "MIT",
"dependencies": {
"js-binary-schema-parser": "^2.0.3"
}
},
"node_modules/gifwrap": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
@@ -15647,6 +15695,12 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/ismobilejs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz",
"integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==",
"license": "MIT"
},
"node_modules/isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
@@ -17555,6 +17609,12 @@
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"license": "BSD-3-Clause"
},
"node_modules/js-binary-schema-parser": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz",
"integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==",
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -19228,6 +19288,12 @@
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
"license": "MIT"
},
"node_modules/parse-svg-path": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
"integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
"license": "MIT"
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -19533,6 +19599,28 @@
"node": ">=4.0.0"
}
},
"node_modules/pixi.js": {
"version": "8.10.1",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.10.1.tgz",
"integrity": "sha512-wjKJXawhTUxuyKIuwE3jK05eBh5I4GKy+YrRVniURFRkK7pYEvRvnV41dEqz6owSXav/YMXdG5783YDJeamiow==",
"license": "MIT",
"dependencies": {
"@pixi/colord": "^2.9.6",
"@types/css-font-loading-module": "^0.0.12",
"@types/earcut": "^3.0.0",
"@webgpu/types": "^0.1.40",
"@xmldom/xmldom": "^0.8.10",
"earcut": "^3.0.1",
"eventemitter3": "^5.0.1",
"gifuct-js": "^2.1.2",
"ismobilejs": "^1.1.1",
"parse-svg-path": "^0.1.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/pixijs"
}
},
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+1
View File
@@ -122,6 +122,7 @@
"nanoid": "^3.3.6",
"obscenity": "^0.4.3",
"pg": "^8.13.3",
"pixi.js": "^8.10.1",
"prom-client": "^15.1.3",
"protobufjs": "^7.3.2",
"pureimage": "^0.4.13",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 221 B

-2
View File
@@ -3,8 +3,6 @@
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 24 24"
version="1.1"
id="svg4"

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

+42 -4
View File
@@ -1,5 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" fill="#FFF" xml:space="preserve">
<g>
<path d="M 382.702 195.718 L 384 153.6 L 384 102.4 L 320 153.6 L 269.5 194 L 255.568 194.854 L 256 153.6 L 256 102.4 L 192 153.6 L 143.191 191.818 L 128 191.674 L 128 128 L 128 72.215 L 111.057 51.805 L 64 51.2 L 21.179 51.2 L 0 74.4 L 0 256 L 0 460.8 L 256 460.8 L 512 460.8 L 512 281.6 L 512 102.4 L 448 153.6 L 396.932 194.367 L 382.702 195.718 Z M 179.2 384 L 153.6 384 L 128 384 L 128 345.6 L 128 307.2 C 127.777 306.306 138.701 301.558 144.203 301.359 C 148.36 301.209 154.679 301.411 161.651 301.552 C 168.881 301.698 178.003 306.906 179.2 307.2 L 179.2 345.6 L 179.2 384 Z M 307.2 384 L 281.6 384 L 256 384 L 256 345.6 L 256 307.2 L 271.48 300.354 L 289.861 300.122 L 307.2 307.2 L 307.2 345.6 L 307.2 384 Z M 435.2 384 L 409.6 384 L 384 384 L 384 345.6 L 384 307.2 L 399.183 300.354 L 417.852 300.289 L 435.2 307.2 L 435.2 345.6 L 435.2 384 Z" transform="matrix(1, 0, 0, 1, 6.528259374621939, -25.993545919560574)"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 32 32"
fill="#ffffff"
xml:space="preserve"
sodipodi:docname="FactoryIconWhite.svg"
width="32"
height="32"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><sodipodi:namedview
id="namedview1"
pagecolor="#5d5d5d"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="6.0390625"
inkscape:cx="57.956016"
inkscape:cy="54.727038"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<g
id="g1">
<path
d="m 23.203054,12.861316 0.07072,-2.415754 V 7.5088935 l -3.487177,2.9366685 -2.751603,2.317216 -0.759116,0.04898 0.02354,-2.3662 V 7.5088935 l -3.487181,2.9366685 -2.659468,2.192064 -0.8277125,-0.0083 V 8.9772305 5.7775778 L 8.4018789,4.6069268 5.8378723,4.5722089 H 3.504675 L 2.350691,5.9028882 V 16.318899 28.065575 H 16.299415 30.248143 V 17.787234 7.5088935 l -3.487181,2.9366685 -2.782554,2.338265 z M 12.114798,23.660571 H 10.719925 9.3250543 v -2.202501 -2.2025 c -0.012167,-0.05128 0.583066,-0.323608 0.8828537,-0.335023 0.226503,-0.0086 0.57081,0.0029 0.950693,0.01108 0.393944,0.0084 0.890977,0.307088 0.956197,0.323952 v 2.202501 z m 6.974362,0 h -1.394871 -1.394874 v -2.202501 -2.2025 l 0.843464,-0.392665 1.001527,-0.01333 0.944754,0.405974 v 2.202501 z m 6.974365,0 h -1.394873 -1.394874 v -2.202501 -2.2025 l 0.827282,-0.392665 1.017219,-0.0037 0.945246,0.396393 v 2.2025 z"
id="path1"
style="stroke-width:0.0559037" />
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

-86
View File
@@ -1,86 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
height="800px"
width="800px"
version="1.1"
id="Icons"
viewBox="0 0 32 32"
xml:space="preserve"
sodipodi:docname="RocketSiloIconWhite.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs13" /><sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.09125"
inkscape:cx="178.69416"
inkscape:cy="397.70905"
inkscape:window-width="3072"
inkscape:window-height="1653"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="Icons" />
<g
id="g8">
<path
id="path2"
style="stroke-width:25"
transform="scale(0.04)"
d="m 104.49023,583.2168 c -0.0475,0.005 -0.0898,0.0209 -0.125,0.0469 -0.0897,0.0662 -0.1134,0.22145 -0.0859,0.44531 0.0945,-0.16261 0.18903,-0.32383 0.2832,-0.48632 -0.024,-0.001 -0.0506,-0.008 -0.0723,-0.006 z" />
<path
id="path4"
style="stroke-width:25"
transform="scale(0.04)"
d="m 398.75,48.75 c -8.125,0 -16.25,3.75 -21.25,11.25 l -50,75 c 0,5 -2.5,10 -2.5,15 v 167.5 l -72.5,120 c -5,7.5 -5,17.5 0,25 5,7.5 12.5,12.5 22.5,12.5 h 250 c 10,0 17.5,-5 22.5,-12.5 5,-7.5 5,-17.5 0,-25 L 475,317.5 V 150 c 0,-5 -2.5,-10 -5,-15 L 420,60 C 415,52.5 406.875,48.75 398.75,48.75 Z" />
<path
id="path6"
style="stroke-width:25"
transform="scale(0.04)"
d="m 400,500 c -15,0 -25,10 -25,25 v 75 c 0,15 10,25 25,25 15,0 25,-10 25,-25 v -75 c 0,-15 -10,-25 -25,-25 z" />
</g>
<ellipse
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.04"
id="path1551"
cx="16.057112"
cy="27.825054"
rx="12.692628"
ry="1.9371578" /><ellipse
style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.04"
id="path1553"
cx="22.051313"
cy="29.516819"
rx="0.53854793"
ry="0.019182237" /><path
style="fill:#ffffff;stroke:none;stroke-width:0.91638"
d="m 336.31157,742.57746 c -64.10675,-2.73184 -96.35975,-5.10869 -135.62428,-9.99467 -65.84565,-8.19368 -108.369339,-20.55125 -115.027982,-33.42765 -9.213823,-17.81757 52.596912,-35.62711 155.348712,-44.76066 48.06575,-4.27253 78.28696,-5.61411 140.20618,-6.224 88.15143,-0.86828 156.77477,2.44417 223.5968,10.79301 71.65567,8.95277 116.73192,24.08998 113.2343,38.02562 -5.44535,21.696 -102.07526,39.68224 -243.23455,45.27454 -20.17937,0.79944 -121.7049,1.02948 -138.49918,0.31381 z"
id="path1672"
transform="scale(0.04)" /><path
style="fill:#ffffff;stroke-width:0.91638"
d="m 316.1512,741.30843 c -50.59618,-2.71561 -71.33695,-4.25003 -99.42726,-7.3557 C 138.8581,725.34385 89.219492,711.32972 85.606719,696.93529 81.493809,680.54815 143.8897,663.72225 241.9244,654.78211 c 54.02316,-4.92657 98.75384,-6.34315 180.06253,-5.70242 69.63124,0.54871 91.66091,1.45537 139.296,5.73293 83.98725,7.54194 144.60633,21.76524 154.80219,36.32187 12.95197,18.49152 -57.41631,37.71396 -170.38065,46.54273 -46.47603,3.63235 -56.00093,3.92443 -135.62429,4.15884 -43.84879,0.1291 -86.11684,-0.10834 -93.92898,-0.52763 z"
id="path1748"
transform="scale(0.04)" /><path
style="fill:#ffffff;stroke-width:0.91638"
d="m 395.4181,624.03958 c -8.54628,-1.74507 -14.15841,-5.98769 -17.54748,-13.26544 l -2.13679,-4.58857 -0.009,-43.18376 c -0.01,-46.57633 0.0787,-47.54106 4.93893,-53.91311 1.18953,-1.55955 4.37731,-4.05284 7.08397,-5.54065 4.29402,-2.36035 5.85547,-2.7051 12.25223,-2.7051 6.39676,0 7.95821,0.34475 12.25223,2.7051 2.70666,1.48781 5.89444,3.9811 7.08397,5.54065 4.8602,6.37205 4.94858,7.33678 4.93893,53.91311 l -0.009,43.18376 -2.1573,4.63286 c -4.50315,9.67058 -16.0552,15.39283 -26.69077,13.22115 z"
id="path1787"
transform="scale(0.04)" /><path
style="fill:#ffffff;stroke-width:0.91638"
d="m 267.74187,473.33145 c -14.07258,-4.41441 -22.07846,-19.50972 -16.66853,-31.42897 0.9777,-2.1541 18.08205,-30.88885 38.00965,-63.85502 l 36.23202,-59.93847 0.0313,-85.05678 c 0.0289,-78.40313 0.15927,-85.59443 1.66727,-91.92964 1.54167,-6.47669 3.12153,-9.09962 27.40785,-45.50342 16.49492,-24.724954 26.94454,-39.525002 29.0296,-41.115363 9.88792,-7.541884 24.92944,-6.440366 33.16408,2.428667 4.11259,4.429421 52.40823,77.221396 55.24103,83.260156 l 2.37067,5.05367 0.24734,86.46152 0.24733,86.46153 36.23928,59.95051 c 19.93161,32.97277 37.08285,61.85753 38.11388,64.18833 4.58482,10.36476 -1.20515,23.65555 -12.78176,29.34034 l -5.25049,2.57829 -129.66781,0.1742 c -107.00742,0.14375 -130.3607,-0.0432 -133.6327,-1.06955 z"
id="path1826"
transform="scale(0.04)" /><path
style="fill:#ffffff;stroke-width:0.91638"
d="m 306.52921,740.81162 c -87.19059,-4.45544 -154.17018,-13.39697 -191.52348,-25.56769 -16.873321,-5.49779 -29.782362,-13.67791 -29.782362,-18.8723 0,-10.70958 23.369172,-20.94319 67.691822,-29.64297 36.72878,-7.20924 87.73185,-12.78609 149.03212,-16.29568 28.05995,-1.60649 170.34184,-1.59744 198.85452,0.0127 41.28348,2.33126 79.77543,5.77849 110.42383,9.88924 54.44397,7.30236 94.73314,18.84269 103.92658,29.76847 3.65577,4.34464 3.19598,7.70457 -1.69574,12.39166 -18.84113,18.05298 -99.16958,32.16166 -218.61114,38.39627 -33.70301,1.75923 -153.32023,1.70864 -188.31615,-0.0796 z"
id="path1902"
transform="scale(0.04)" /></svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

After

Width:  |  Height:  |  Size: 266 B

-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="svg4" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" viewBox="0 0 24 24">
<!-- Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151) -->
<defs>
<style>
.st0 {
fill: #333;
}
.st0, .st1 {
fill-opacity: 0;
}
.st1 {
fill: #ececec;
}
.st2 {
fill: #fff;
}
</style>
</defs>
<sodipodi:namedview id="namedview6" bordercolor="#666666" borderopacity="1.0" inkscape:current-layer="svg4" inkscape:cx="11.986254" inkscape:cy="11.931271" inkscape:pagecheckerboard="0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="1653" inkscape:window-maximized="1" inkscape:window-width="3072" inkscape:window-x="0" inkscape:window-y="38" inkscape:zoom="36.375" pagecolor="#ffffff" showgrid="false"/>
<path id="path2" d="M12,0c-3.4,2.9-5.5,3-9,3v11.5c0,4.6,3.2,5.8,9,9.5,5.8-3.7,9-4.9,9-9.5V3c-3.5,0-5.6,0-9-3ZM12,2.5c2.5,1.8,4.6,2.3,7,2.4v9.6c0,3-1.7,3.8-7,7.1-5.3-3.3-7-4.1-7-7.1V5c2.4,0,4.5-.6,7-2.4h0ZM13.2,6.1h0ZM16.6,7.1h0ZM16.9,13.8h0ZM13.5,14h0Z"/>
<path id="path862" class="st1" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
<path id="path1012" class="st0" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
<path id="path1162" class="st0" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
<path id="path1201" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
<path id="path3032" class="st2" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.2c0,.4,0,.8.2,1.2.4,1.7,1.5,3,4,4.6.7.4.9.6,3.3,2,.6.4,1.2.8,1.3.8h.2c0,.1.2,0,.5-.2.3-.2.9-.6,1.5-.9,3.4-2.1,4.2-2.6,5.2-3.6s1.7-2.4,1.9-4V3h-2.5c-2.4-.2-4-.9-6.1-2.6l-.4-.3h-.1ZM12,2.5l.4.3c1.8,1.2,3.4,1.8,5.5,2.1h1.1v10.7c-.2,1.2-.9,2.1-2.3,3.1-.6.5-1.5,1-3.6,2.3-.6.3-1,.6-1,.6,0,0-1.7-1-2.5-1.5-1.7-1.1-2.6-1.7-3.2-2.3-.7-.7-1.1-1.3-1.3-2.3V4.8h.4c2.5-.2,4.2-.8,6.2-2.1l.4-.3h-.1Z"/>
<path id="path1787" class="st2" d="M8.6,17.8c-.2-.1-.2-.3-.2-.5h0c0-.1.5-1,.5-1,.5-1,.6-1,.7-1h.5c.1,0,.2,0,.2.2v.2q0,.2-.5,1.2l-.5.9h0c-.2.2-.5.2-.7,0h0Z"/>
<path id="path1826" class="st2" d="M7.7,13.2c-.2-.3-.2-.7,0-.8,0,0,.7-.4,1.5-.9l1.4-.8,1-1.7c.9-1.6,1-1.8,1.1-1.9s.2-.2,1.1-.6c.6-.3,1-.5,1.1-.5.3,0,.6.2.7.4,0,0,.2,2.2.2,2.4h0c0,.1-1,1.9-1,1.9l-1,1.8v3.5c0,.3-.3.5-.6.5h-.1l-2.7-1.5-2.7-1.8Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

After

Width:  |  Height:  |  Size: 238 B

-2
View File
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg4"

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
version="1.1"
id="Icons"
viewBox="0 0 32 32"
xml:space="preserve"
sodipodi:docname="MissileSiloIconWhite.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs13"><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect4"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.98396776,0,1 @ F,0,0,1,0,0.90902474,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3-5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3-7"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /></defs><sodipodi:namedview
id="namedview11"
pagecolor="#999999"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.1825"
inkscape:cx="-105.38373"
inkscape:cy="0.68728522"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Icons"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<path
id="path2"
style="fill:#ffffff;stroke-width:0.677186"
d="m 25.898883,7.6636543 c -0.04144,0.04884 -3.863384,-3.2777509 -3.863384,-3.2777509 1.46078,-1.721777 3.509825,-2.3838038 4.576668,-1.4786782 1.066845,0.9051262 0.747497,3.0346514 -0.713284,4.7564291 z"
sodipodi:nodetypes="ccsc" /><path
id="rect3"
style="fill:#ffffff;stroke-width:1.1798"
d="m 12.748325,7.3692592 h 6.599784 v 9.7262258 a 0.98396776,0.98396776 135 0 1 -0.983968,0.983968 H 13.65735 a 0.90902474,0.90902474 45 0 1 -0.909025,-0.909025 z"
inkscape:path-effect="#path-effect4"
inkscape:original-d="m 12.748325,7.3692592 h 6.599784 V 18.079453 h -6.599784 z"
transform="matrix(0.58656255,0.49764806,-0.57368061,0.67617979,18.00944,-6.0548174)" /><path
style="fill:#ffffff"
d="M 12.316287,10.791417 12.02519,14.786754 9.5403643,15.849128 A 0.64473841,0.64473841 33.887593 0 1 8.6422492,15.245902 l 0.010113,-0.626994 a 1.8979735,1.8979735 116.0463 0 1 0.7076373,-1.447901 z"
id="path3"
inkscape:path-effect="#path-effect3"
inkscape:original-d="m 12.316287,10.791417 -0.291097,3.995337 -3.3989742,1.453213 0.040499,-2.510918 z"
transform="matrix(0.57903787,0.50638361,-0.89799211,1.0268331,20.723968,-10.08514)"
sodipodi:nodetypes="ccccc" /><path
style="fill:#ffffff"
d="M 12.316287,10.791417 12.02519,14.786754 9.5403643,15.849128 A 0.64473841,0.64473841 33.887593 0 1 8.6422492,15.245902 l 0.010113,-0.626994 a 1.8979735,1.8979735 116.0463 0 1 0.7076373,-1.447901 z"
id="path3-1"
inkscape:path-effect="#path-effect3-7"
inkscape:original-d="m 12.316287,10.791417 -0.291097,3.995337 -3.3989742,1.453213 0.040499,-2.510918 z"
transform="matrix(-0.59419498,-0.4885095,-0.86629519,1.0537118,40.501308,6.456681)"
sodipodi:nodetypes="ccccc" /><path
style="fill:#ffffff;stroke-width:1.06472"
d="m 13.737178,16.80511 c 0,0 -9.3199199,7.391956 -7.1372902,13.873745 l 5.2775782,0.03513 c -2.0517202,-5.22962 3.28918,-12.748302 3.28918,-12.748302 z"
id="path4"
sodipodi:nodetypes="ccccc" /><circle
style="fill:#ffffff"
id="path5"
cx="3.7514319"
cy="29.066437"
r="0.88774341" /><circle
style="fill:#ffffff"
id="path5-4"
cx="12.046965"
cy="29.945017"
r="0.88774341" /><ellipse
style="fill:#ffffff;stroke-width:1.33861"
id="path5-3"
cx="11.284078"
cy="27.854525"
rx="1.1741122"
ry="1.2027493" /><circle
style="fill:#ffffff"
id="path5-2"
cx="6.2749138"
cy="27.315006"
r="0.88774341" /><ellipse
style="fill:#ffffff;stroke-width:2.01452"
id="path5-6"
cx="5.9450173"
cy="29.290951"
rx="1.8613975"
ry="1.7182131" /><ellipse
style="fill:#ffffff;stroke-width:0.807137"
id="path5-6-9"
cx="2.4461236"
cy="26.341803"
rx="0.70718539"
ry="0.7259956" /><circle
style="fill:#ffffff;stroke-width:0.806452"
id="path5-5"
cx="14.434135"
cy="28.200459"
r="0.71592212" /><circle
style="fill:#ffffff;stroke-width:1.29032"
id="path5-61"
cx="4.2863698"
cy="23.691866"
r="1.1454754" /></svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg4"
version="1.1"
viewBox="0 0 24 24"
sodipodi:docname="SamLauncherIconWhite.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<!-- Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151) -->
<defs
id="defs1">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect11"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.31531758,0,1 @ F,0,0,1,0,0.056759862,0,1 @ F,0,0,1,0,0.16487468,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.19973466,0,1 @ F,0,0,1,0,0.075242437,0,1 @ F,0,0,1,0,0.3011792,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect10"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect9"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49680354,0,1 @ F,0,0,1,0,0.46383368,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect8"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.20544103,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect6"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.6771002,0,1 @ F,0,0,1,0,0.21443289,0,1 @ F,0,0,1,0,0.20068725,0,1 @ F,0,0,1,0,0.61305832,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<style
id="style1">
.st0 {
fill: #333;
}
.st0, .st1 {
fill-opacity: 0;
}
.st1 {
fill: #ececec;
}
.st2 {
fill: #fff;
}
</style>
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect6-9"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.6771002,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.21443289,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="namedview6"
bordercolor="#666666"
borderopacity="1.0"
inkscape:current-layer="layer1"
inkscape:cx="-1.3745704"
inkscape:cy="8.2199313"
inkscape:pagecheckerboard="0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="991"
inkscape:window-maximized="1"
inkscape:window-width="1920"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:zoom="18.1875"
pagecolor="#828282"
showgrid="false"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1">
<path
style="fill:#ffffff;stroke-width:1.32815"
d="m 17.680103,4.3837562 1.471937,2.3505899 c 0,0 1.557345,-0.6986001 0.828902,-1.8430742 -0.709885,-1.115248 -2.300839,-0.5075157 -2.300839,-0.5075157 z"
id="path3"
sodipodi:nodetypes="ccsc" />
<path
id="rect4"
style="fill:#ffffff;stroke-width:1.06832"
d="m 7.412439,8.3385684 c 2.2265471,0.022033 5.06556,-0.00697 7.576035,-0.0086 0.118341,-7.68e-5 0.214275,0.095934 0.214275,0.2143622 l 0,2.2790372 a 0.20068725,0.20068725 135 0 1 -0.200687,0.200687 H 7.3484537 A 0.61305832,0.61305832 45 0 1 6.7353954,10.410996 V 9.0069971 c 0,-0.3739521 0.3031104,-0.672129 0.6770436,-0.6684287 z"
inkscape:path-effect="#path-effect6"
inkscape:original-d="m 6.7353954,8.3298969 c 2.3661435,0.038026 5.6449026,0 8.4673536,0 V 11.024054 H 6.7353954 Z"
sodipodi:nodetypes="ccccc"
transform="matrix(1.3871173,-0.56218381,0.7027184,1.1097117,-10.354938,3.8136388)" />
<path
style="fill:#ffffff;stroke-width:1.32815"
d="m 17.46913,1.7477688 0.735969,1.1752951 c 0,0 0.998735,-0.2087137 1.51062,0.2066682 0.110775,-0.097941 0.30661,-0.5279917 0.05425,-0.8744476 -0.813874,-1.0688142 -2.30084,-0.5075157 -2.30084,-0.5075157 z"
id="path3-8"
sodipodi:nodetypes="ccccc" />
<path
id="rect4-8"
style="fill:#ffffff;stroke-width:1.48584"
d="M 16.185391,1.9343439 C 12.701012,3.3485891 8.7751898,4.9780406 5.6675841,6.2067613 5.1462949,6.4128743 4.9569987,6.9059035 5.2454361,7.3098295 l 0.3037033,0.425192 11.6233046,-4.9169042 -0.5456,-0.776552 C 16.540403,1.9185247 16.342728,1.8704833 16.185391,1.9343439 Z"
sodipodi:nodetypes="cscccc" />
<path
id="rect6"
style="fill:#ffffff"
d="m 9.9518898,14.020619 2.9965632,-1.457045 v 1.949931 a 0.4923079,0.4923079 135.26041 0 1 -0.496783,0.492288 l -2.063457,-0.01876 a 0.45488806,0.45488806 46.078703 0 1 -0.4505676,-0.46786 z"
sodipodi:nodetypes="ccccc"
inkscape:path-effect="#path-effect9"
inkscape:original-d="m 9.9518898,14.020619 2.9965632,-1.457045 v 2.446735 l -3.0240546,-0.02749 z"
transform="matrix(1.5549622,0,0,1.2439898,-5.3629748,-5.3468075)" />
<rect
style="fill:#ffffff;stroke-width:1.27479"
id="rect7"
width="2.9068708"
height="3.1029518"
x="11.007972"
y="14.623526" />
<path
id="rect8"
style="fill:#ffffff;stroke-width:1.80595"
d="m 6.3154251,19.421168 v 0.803136 a 0.10556598,0.10575084 0 0 1 -0.102369,0.105707 l -1.2284353,0.03728 a 0.30883774,0.30937857 0 0 0 -0.2994769,0.307034 l -0.012108,1.703993 15.9080751,-0.01409 0.003,-1.626855 a 0.37070023,0.37134941 0 0 0 -0.359663,-0.371867 l -1.146325,-0.03449 a 0.13990479,0.14014979 0 0 1 -0.135706,-0.140085 v -0.795287 a 0.54344015,0.54439183 0 0 0 -0.543439,-0.544393 l -11.5146875,-1.7e-5 a 0.56894912,0.56994548 0 0 0 -0.5688864,0.569952 z"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
version="1.1"
id="Icons"
viewBox="0 0 32 32"
xml:space="preserve"
sodipodi:docname="MissileSiloIconWhite.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs13"><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect4"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.98396776,0,1 @ F,0,0,1,0,0.90902474,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3-5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /><inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3-7"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.99419457,0,1 @ F,0,0,1,0,0.88997446,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" /></defs><sodipodi:namedview
id="namedview11"
pagecolor="#999999"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.1825"
inkscape:cx="-105.38373"
inkscape:cy="0.68728522"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Icons"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<path
id="path2"
style="fill:#ffffff;stroke-width:0.677186"
d="m 25.898883,7.6636543 c -0.04144,0.04884 -3.863384,-3.2777509 -3.863384,-3.2777509 1.46078,-1.721777 3.509825,-2.3838038 4.576668,-1.4786782 1.066845,0.9051262 0.747497,3.0346514 -0.713284,4.7564291 z"
sodipodi:nodetypes="ccsc" /><path
id="rect3"
style="fill:#ffffff;stroke-width:1.1798"
d="m 12.748325,7.3692592 h 6.599784 v 9.7262258 a 0.98396776,0.98396776 135 0 1 -0.983968,0.983968 H 13.65735 a 0.90902474,0.90902474 45 0 1 -0.909025,-0.909025 z"
inkscape:path-effect="#path-effect4"
inkscape:original-d="m 12.748325,7.3692592 h 6.599784 V 18.079453 h -6.599784 z"
transform="matrix(0.58656255,0.49764806,-0.57368061,0.67617979,18.00944,-6.0548174)" /><path
style="fill:#ffffff"
d="M 12.316287,10.791417 12.02519,14.786754 9.5403643,15.849128 A 0.64473841,0.64473841 33.887593 0 1 8.6422492,15.245902 l 0.010113,-0.626994 a 1.8979735,1.8979735 116.0463 0 1 0.7076373,-1.447901 z"
id="path3"
inkscape:path-effect="#path-effect3"
inkscape:original-d="m 12.316287,10.791417 -0.291097,3.995337 -3.3989742,1.453213 0.040499,-2.510918 z"
transform="matrix(0.57903787,0.50638361,-0.89799211,1.0268331,20.723968,-10.08514)"
sodipodi:nodetypes="ccccc" /><path
style="fill:#ffffff"
d="M 12.316287,10.791417 12.02519,14.786754 9.5403643,15.849128 A 0.64473841,0.64473841 33.887593 0 1 8.6422492,15.245902 l 0.010113,-0.626994 a 1.8979735,1.8979735 116.0463 0 1 0.7076373,-1.447901 z"
id="path3-1"
inkscape:path-effect="#path-effect3-7"
inkscape:original-d="m 12.316287,10.791417 -0.291097,3.995337 -3.3989742,1.453213 0.040499,-2.510918 z"
transform="matrix(-0.59419498,-0.4885095,-0.86629519,1.0537118,40.501308,6.456681)"
sodipodi:nodetypes="ccccc" /><path
style="fill:#ffffff;stroke-width:1.06472"
d="m 13.737178,16.80511 c 0,0 -9.3199199,7.391956 -7.1372902,13.873745 l 5.2775782,0.03513 c -2.0517202,-5.22962 3.28918,-12.748302 3.28918,-12.748302 z"
id="path4"
sodipodi:nodetypes="ccccc" /><circle
style="fill:#ffffff"
id="path5"
cx="3.7514319"
cy="29.066437"
r="0.88774341" /><circle
style="fill:#ffffff"
id="path5-4"
cx="12.046965"
cy="29.945017"
r="0.88774341" /><ellipse
style="fill:#ffffff;stroke-width:1.33861"
id="path5-3"
cx="11.284078"
cy="27.854525"
rx="1.1741122"
ry="1.2027493" /><circle
style="fill:#ffffff"
id="path5-2"
cx="6.2749138"
cy="27.315006"
r="0.88774341" /><ellipse
style="fill:#ffffff;stroke-width:2.01452"
id="path5-6"
cx="5.9450173"
cy="29.290951"
rx="1.8613975"
ry="1.7182131" /><ellipse
style="fill:#ffffff;stroke-width:0.807137"
id="path5-6-9"
cx="2.4461236"
cy="26.341803"
rx="0.70718539"
ry="0.7259956" /><circle
style="fill:#ffffff;stroke-width:0.806452"
id="path5-5"
cx="14.434135"
cy="28.200459"
r="0.71592212" /><circle
style="fill:#ffffff;stroke-width:1.29032"
id="path5-61"
cx="4.2863698"
cy="23.691866"
r="1.1454754" /></svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg4"
version="1.1"
viewBox="0 0 24 24"
sodipodi:docname="SamLauncherIconWhite.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<!-- Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151) -->
<defs
id="defs1">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect11"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.31531758,0,1 @ F,0,0,1,0,0.056759862,0,1 @ F,0,0,1,0,0.16487468,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.19973466,0,1 @ F,0,0,1,0,0.075242437,0,1 @ F,0,0,1,0,0.3011792,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect10"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect9"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49680354,0,1 @ F,0,0,1,0,0.46383368,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect8"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.20544103,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect6"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.6771002,0,1 @ F,0,0,1,0,0.21443289,0,1 @ F,0,0,1,0,0.20068725,0,1 @ F,0,0,1,0,0.61305832,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<style
id="style1">
.st0 {
fill: #333;
}
.st0, .st1 {
fill-opacity: 0;
}
.st1 {
fill: #ececec;
}
.st2 {
fill: #fff;
}
</style>
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect6-9"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0.6771002,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.21443289,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="namedview6"
bordercolor="#666666"
borderopacity="1.0"
inkscape:current-layer="layer1"
inkscape:cx="-1.3745704"
inkscape:cy="8.2199313"
inkscape:pagecheckerboard="0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="991"
inkscape:window-maximized="1"
inkscape:window-width="1920"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:zoom="18.1875"
pagecolor="#828282"
showgrid="false"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1">
<path
style="fill:#ffffff;stroke-width:1.32815"
d="m 17.680103,4.3837562 1.471937,2.3505899 c 0,0 1.557345,-0.6986001 0.828902,-1.8430742 -0.709885,-1.115248 -2.300839,-0.5075157 -2.300839,-0.5075157 z"
id="path3"
sodipodi:nodetypes="ccsc" />
<path
id="rect4"
style="fill:#ffffff;stroke-width:1.06832"
d="m 7.412439,8.3385684 c 2.2265471,0.022033 5.06556,-0.00697 7.576035,-0.0086 0.118341,-7.68e-5 0.214275,0.095934 0.214275,0.2143622 l 0,2.2790372 a 0.20068725,0.20068725 135 0 1 -0.200687,0.200687 H 7.3484537 A 0.61305832,0.61305832 45 0 1 6.7353954,10.410996 V 9.0069971 c 0,-0.3739521 0.3031104,-0.672129 0.6770436,-0.6684287 z"
inkscape:path-effect="#path-effect6"
inkscape:original-d="m 6.7353954,8.3298969 c 2.3661435,0.038026 5.6449026,0 8.4673536,0 V 11.024054 H 6.7353954 Z"
sodipodi:nodetypes="ccccc"
transform="matrix(1.3871173,-0.56218381,0.7027184,1.1097117,-10.354938,3.8136388)" />
<path
style="fill:#ffffff;stroke-width:1.32815"
d="m 17.46913,1.7477688 0.735969,1.1752951 c 0,0 0.998735,-0.2087137 1.51062,0.2066682 0.110775,-0.097941 0.30661,-0.5279917 0.05425,-0.8744476 -0.813874,-1.0688142 -2.30084,-0.5075157 -2.30084,-0.5075157 z"
id="path3-8"
sodipodi:nodetypes="ccccc" />
<path
id="rect4-8"
style="fill:#ffffff;stroke-width:1.48584"
d="M 16.185391,1.9343439 C 12.701012,3.3485891 8.7751898,4.9780406 5.6675841,6.2067613 5.1462949,6.4128743 4.9569987,6.9059035 5.2454361,7.3098295 l 0.3037033,0.425192 11.6233046,-4.9169042 -0.5456,-0.776552 C 16.540403,1.9185247 16.342728,1.8704833 16.185391,1.9343439 Z"
sodipodi:nodetypes="cscccc" />
<path
id="rect6"
style="fill:#ffffff"
d="m 9.9518898,14.020619 2.9965632,-1.457045 v 1.949931 a 0.4923079,0.4923079 135.26041 0 1 -0.496783,0.492288 l -2.063457,-0.01876 a 0.45488806,0.45488806 46.078703 0 1 -0.4505676,-0.46786 z"
sodipodi:nodetypes="ccccc"
inkscape:path-effect="#path-effect9"
inkscape:original-d="m 9.9518898,14.020619 2.9965632,-1.457045 v 2.446735 l -3.0240546,-0.02749 z"
transform="matrix(1.5549622,0,0,1.2439898,-5.3629748,-5.3468075)" />
<rect
style="fill:#ffffff;stroke-width:1.27479"
id="rect7"
width="2.9068708"
height="3.1029518"
x="11.007972"
y="14.623526" />
<path
id="rect8"
style="fill:#ffffff;stroke-width:1.80595"
d="m 6.3154251,19.421168 v 0.803136 a 0.10556598,0.10575084 0 0 1 -0.102369,0.105707 l -1.2284353,0.03728 a 0.30883774,0.30937857 0 0 0 -0.2994769,0.307034 l -0.012108,1.703993 15.9080751,-0.01409 0.003,-1.626855 a 0.37070023,0.37134941 0 0 0 -0.359663,-0.371867 l -1.146325,-0.03449 a 0.13990479,0.14014979 0 0 1 -0.135706,-0.140085 v -0.795287 a 0.54344015,0.54439183 0 0 0 -0.543439,-0.544393 l -11.5146875,-1.7e-5 a 0.56894912,0.56994548 0 0 0 -0.5688864,0.569952 z"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

+27 -15
View File
@@ -28,6 +28,7 @@ import { RailroadLayer } from "./layers/RailroadLayer";
import { ReplayPanel } from "./layers/ReplayPanel";
import { SpawnAd } from "./layers/SpawnAd";
import { SpawnTimer } from "./layers/SpawnTimer";
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
import { StructureLayer } from "./layers/StructureLayer";
import { TeamStats } from "./layers/TeamStats";
import { TerrainLayer } from "./layers/TerrainLayer";
@@ -227,6 +228,7 @@ export function createRenderer(
new TerritoryLayer(game, eventBus, transformHandler, userSettings),
new RailroadLayer(game),
structureLayer,
new StructureIconsLayer(game, transformHandler),
new UnitLayer(game, eventBus, transformHandler),
new FxLayer(game),
new UILayer(game, eventBus, transformHandler),
@@ -312,6 +314,7 @@ export class GameRenderer {
resizeCanvas() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.transformHandler.updateCanvasBoundingRect();
//this.redraw()
}
@@ -325,24 +328,33 @@ export class GameRenderer {
.toHex();
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Save the current context state
this.context.save();
this.transformHandler.handleTransform(this.context);
this.layers.forEach((l) => {
if (l.shouldTransform?.()) {
l.renderLayer?.(this.context);
const handleTransformState = (
needsTransform: boolean,
active: boolean,
): boolean => {
if (needsTransform && !active) {
this.context.save();
this.transformHandler.handleTransform(this.context);
return true;
} else if (!needsTransform && active) {
this.context.restore();
return false;
}
});
return active;
};
this.context.restore();
let isTransformActive = false;
this.layers.forEach((l) => {
if (!l.shouldTransform?.()) {
l.renderLayer?.(this.context);
}
});
for (const layer of this.layers) {
const needsTransform = layer.shouldTransform?.() ?? false;
isTransformActive = handleTransformState(
needsTransform,
isTransformActive,
);
layer.renderLayer?.(this.context);
}
handleTransformState(false, isTransformActive); // Ensure context is clean after rendering
this.transformHandler.resetChanged();
requestAnimationFrame(() => this.renderGame());
+10 -2
View File
@@ -14,6 +14,7 @@ export const CAMERA_SMOOTHING = 0.03;
export class TransformHandler {
public scale: number = 1.8;
private _boundingRect: DOMRect;
private offsetX: number = -350;
private offsetY: number = -200;
private lastGoToCallTime: number | null = null;
@@ -27,6 +28,7 @@ export class TransformHandler {
private eventBus: EventBus,
private canvas: HTMLCanvasElement,
) {
this._boundingRect = this.canvas.getBoundingClientRect();
this.eventBus.on(ZoomEvent, (e) => this.onZoom(e));
this.eventBus.on(DragEvent, (e) => this.onMove(e));
this.eventBus.on(GoToPlayerEvent, (e) => this.onGoToPlayer(e));
@@ -35,8 +37,12 @@ export class TransformHandler {
this.eventBus.on(CenterCameraEvent, () => this.centerCamera());
}
public updateCanvasBoundingRect() {
this._boundingRect = this.canvas.getBoundingClientRect();
}
boundingRect(): DOMRect {
return this.canvas.getBoundingClientRect();
return this._boundingRect;
}
width(): number {
@@ -45,6 +51,9 @@ export class TransformHandler {
hasChanged(): boolean {
return this.changed;
}
resetChanged() {
this.changed = false;
}
handleTransform(context: CanvasRenderingContext2D) {
// Disable image smoothing for pixelated effect
@@ -59,7 +68,6 @@ export class TransformHandler {
this.game.width() / 2 - this.offsetX * this.scale,
this.game.height() / 2 - this.offsetY * this.scale,
);
this.changed = false;
}
worldToScreenCoordinates(cell: Cell): { x: number; y: number } {
+2 -2
View File
@@ -5,12 +5,12 @@ import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg";
import atomBombIcon from "../../../../resources/images/NukeIconWhite.svg";
import portIcon from "../../../../resources/images/PortIcon.svg";
import samlauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg";
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import missileSiloIcon from "../../../../resources/non-commercial/svg/MissileSiloIconWhite.svg";
import samlauncherIcon from "../../../../resources/non-commercial/svg/SamLauncherIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { Cell, Gold, PlayerActions, UnitType } from "../../../core/game/Game";
@@ -0,0 +1,300 @@
import * as PIXI from "pixi.js";
import anchorIcon from "../../../../resources/images/AnchorIcon.png";
import cityIcon from "../../../../resources/images/CityIcon.png";
import factoryIcon from "../../../../resources/images/FactoryUnit.png";
import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png";
import SAMMissileIcon from "../../../../resources/images/SamLauncherUnit.png";
import shieldIcon from "../../../../resources/images/ShieldIcon.png";
import { Theme } from "../../../core/configuration/Config";
import { Cell, PlayerID, UnitType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
class StructureRenderInfo {
public isOnScreen: boolean = false;
constructor(
public unit: UnitView,
public owner: PlayerID,
public pixiSprite: PIXI.Sprite,
) {}
}
const ZOOM_THRESHOLD = 2.8; // below this zoom level, structures are not rendered
const ICON_SIZE = 24;
const OFFSET_ZOOM_Y = 15; // offset for the y position of the icon to avoid hiding the structure beneath
export class StructureIconsLayer implements Layer {
private pixicanvas: HTMLCanvasElement;
private stage: PIXI.Container;
private shouldRedraw: boolean = true;
private textureCache: Map<string, PIXI.Texture> = new Map();
private theme: Theme;
private renderer: PIXI.Renderer;
private renders: StructureRenderInfo[] = [];
private seenUnits: Set<UnitView> = new Set();
private structures: Map<
UnitType,
{ iconPath: string; image: HTMLImageElement | null }
> = new Map([
[UnitType.City, { iconPath: cityIcon, image: null }],
[UnitType.Factory, { iconPath: factoryIcon, image: null }],
[UnitType.DefensePost, { iconPath: shieldIcon, image: null }],
[UnitType.Port, { iconPath: anchorIcon, image: null }],
[UnitType.MissileSilo, { iconPath: missileSiloIcon, image: null }],
[UnitType.SAMLauncher, { iconPath: SAMMissileIcon, image: null }],
]);
constructor(
private game: GameView,
private transformHandler: TransformHandler,
) {
this.theme = game.config().theme();
this.structures.forEach((u, unitType) => this.loadIcon(u, unitType));
}
async setupRenderer() {
this.renderer = new PIXI.WebGLRenderer();
this.pixicanvas = document.createElement("canvas");
this.pixicanvas.width = window.innerWidth;
this.pixicanvas.height = window.innerHeight;
this.stage = new PIXI.Container();
this.stage.position.set(0, 0);
this.stage.width = this.pixicanvas.width;
this.stage.height = this.pixicanvas.height;
await this.renderer.init({
canvas: this.pixicanvas,
resolution: 1,
width: this.pixicanvas.width,
height: this.pixicanvas.height,
clearBeforeRender: true,
backgroundAlpha: 0,
backgroundColor: 0x00000000,
});
}
private loadIcon(
unitInfo: {
iconPath: string;
image: HTMLImageElement | null;
},
unitType: UnitType,
) {
const image = new Image();
image.src = unitInfo.iconPath;
image.onload = () => {
unitInfo.image = image;
};
image.onerror = () => {
console.error(
`Failed to load icon for ${unitType}: ${unitInfo.iconPath}`,
);
};
}
shouldTransform(): boolean {
return false;
}
async init() {
window.addEventListener("resize", () => this.resizeCanvas());
await this.setupRenderer();
this.redraw();
}
resizeCanvas() {
if (this.renderer.view) {
this.pixicanvas.width = window.innerWidth;
this.pixicanvas.height = window.innerHeight;
this.renderer.resize(innerWidth, innerHeight, 1);
this.shouldRedraw = true;
}
}
public tick() {
this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
?.forEach((unitView) => {
if (unitView === undefined) return;
if (unitView.isActive()) {
if (this.seenUnits.has(unitView)) {
// check if owner has changed
const render = this.renders.find(
(r) => r.unit.id() === unitView.id(),
);
if (render) {
this.ownerChangeCheck(render, unitView);
}
} else if (this.structures.has(unitView.type())) {
// new unit, create render info
this.seenUnits.add(unitView);
const render = new StructureRenderInfo(
unitView,
unitView.owner().id(),
this.createPixiSprite(unitView),
);
this.renders.push(render);
this.computeNewLocation(render);
this.shouldRedraw = true;
}
}
if (!unitView.isActive() && this.seenUnits.has(unitView)) {
const render = this.renders.find(
(r) => r.unit.id() === unitView.id(),
);
if (render) {
this.deleteStructure(render);
}
this.shouldRedraw = true;
return;
}
});
}
redraw() {
this.resizeCanvas();
}
renderLayer(mainContext: CanvasRenderingContext2D) {
if (!this.renderer || this.transformHandler.scale > ZOOM_THRESHOLD) {
return;
}
if (this.transformHandler.hasChanged()) {
for (const render of this.renders) {
this.computeNewLocation(render);
}
}
if (this.transformHandler.hasChanged() || this.shouldRedraw) {
this.renderer.render(this.stage);
this.shouldRedraw = false;
}
mainContext.drawImage(this.renderer.canvas, 0, 0);
}
private ownerChangeCheck(render: StructureRenderInfo, unit: UnitView) {
if (render.owner !== unit.owner().id()) {
render.owner = unit.owner().id();
render.pixiSprite?.destroy();
render.pixiSprite = this.createPixiSprite(unit);
this.shouldRedraw = true;
}
}
private createTexture(unit: UnitView): PIXI.Texture {
const cacheKey = `${unit.owner().id()}-${unit.type()}`;
if (this.textureCache.has(cacheKey)) {
return this.textureCache.get(cacheKey)!;
}
const structureCanvas = document.createElement("canvas");
structureCanvas.width = ICON_SIZE;
structureCanvas.height = ICON_SIZE;
const context = structureCanvas.getContext("2d")!;
context.fillStyle = this.theme
.territoryColor(unit.owner())
.lighten(0.1)
.toRgbString();
const borderColor = this.theme
.borderColor(unit.owner())
.darken(0.2)
.toRgbString();
context.strokeStyle = borderColor;
context.beginPath();
context.arc(
ICON_SIZE / 2,
ICON_SIZE / 2,
ICON_SIZE / 2 - 1,
0,
Math.PI * 2,
);
context.fill();
context.lineWidth = 1;
context.stroke();
const structureInfo = this.structures.get(unit.type());
if (!structureInfo?.image) {
console.warn(`Image not loaded for unit type: ${unit.type()}`);
return PIXI.Texture.from(structureCanvas);
}
context.drawImage(
this.getImageColored(structureInfo.image, borderColor),
4,
4,
);
const texture = PIXI.Texture.from(structureCanvas);
this.textureCache.set(cacheKey, texture);
return texture;
}
private createPixiSprite(unit: UnitView): PIXI.Sprite {
const sprite = new PIXI.Sprite(this.createTexture(unit));
sprite.anchor.set(0.5, 0.5);
const tile = unit.tile();
const worldX = this.game.x(tile);
const worldY = this.game.y(tile);
const screenPos = this.transformHandler.worldToScreenCoordinates(
new Cell(worldX, worldY),
);
sprite.x = screenPos.x;
sprite.y = screenPos.y - this.transformHandler.scale * OFFSET_ZOOM_Y;
sprite.scale.set(Math.min(1, this.transformHandler.scale));
this.stage.addChild(sprite);
return sprite;
}
private getImageColored(
image: HTMLImageElement,
color: string,
): HTMLCanvasElement {
const imageCanvas = document.createElement("canvas");
imageCanvas.width = image.width;
imageCanvas.height = image.height;
const ctx = imageCanvas.getContext("2d")!;
ctx.fillStyle = color;
ctx.fillRect(0, 0, imageCanvas.width, imageCanvas.height);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(image, 0, 0);
return imageCanvas;
}
private computeNewLocation(render: StructureRenderInfo) {
const tile = render.unit.tile();
const worldX = this.game.x(tile);
const worldY = this.game.y(tile);
const screenPos = this.transformHandler.worldToScreenCoordinates(
new Cell(worldX, worldY),
);
screenPos.x = Math.round(screenPos.x);
screenPos.y = Math.round(
screenPos.y - this.transformHandler.scale * OFFSET_ZOOM_Y,
);
// Check if the sprite is on screen (with margin for partial visibility)
const margin = ICON_SIZE;
const onScreen =
screenPos.x + margin > 0 &&
screenPos.x - margin < this.pixicanvas.width &&
screenPos.y + margin > 0 &&
screenPos.y - margin < this.pixicanvas.height;
if (onScreen) {
render.pixiSprite.x = screenPos.x;
render.pixiSprite.y = screenPos.y;
render.pixiSprite.scale.set(Math.min(1, this.transformHandler.scale));
}
if (render.isOnScreen !== onScreen) {
// prevent unnecessary updates
render.isOnScreen = onScreen;
render.pixiSprite.visible = onScreen;
}
}
private deleteStructure(render: StructureRenderInfo) {
render.pixiSprite?.destroy();
this.renders = this.renders.filter((r) => r.unit !== render.unit);
this.seenUnits.delete(render.unit);
}
}
+15 -78
View File
@@ -6,26 +6,18 @@ import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
import { UnitInfoModal } from "./UnitInfoModal";
import cityIcon from "../../../../resources/images/buildings/cityAlt1.png";
import factoryIcon from "../../../../resources/images/buildings/factoryAlt1.png";
import shieldIcon from "../../../../resources/images/buildings/fortAlt2.png";
import anchorIcon from "../../../../resources/images/buildings/port1.png";
import MissileSiloReloadingIcon from "../../../../resources/images/buildings/silo1-reloading.png";
import missileSiloIcon from "../../../../resources/images/buildings/silo1.png";
import SAMMissileReloadingIcon from "../../../../resources/images/buildings/silo4-reloading.png";
import SAMMissileIcon from "../../../../resources/images/buildings/silo4.png";
import cityIcon from "../../../../resources/non-commercial/images/buildings/cityAlt1.png";
import factoryIcon from "../../../../resources/non-commercial/images/buildings/factoryAlt1.png";
import shieldIcon from "../../../../resources/non-commercial/images/buildings/fortAlt3.png";
import anchorIcon from "../../../../resources/non-commercial/images/buildings/port1.png";
import missileSiloIcon from "../../../../resources/non-commercial/images/buildings/silo1.png";
import SAMMissileIcon from "../../../../resources/non-commercial/images/buildings/silo4.png";
import { Cell, UnitType } from "../../../core/game/Game";
import {
euclDistFN,
hexDistFN,
manhattanDistFN,
rectDistFN,
} from "../../../core/game/GameMap";
import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
const underConstructionColor = colord({ r: 150, g: 150, b: 150 });
const reloadingColor = colord({ r: 255, g: 0, b: 0 });
const selectedUnitColor = colord({ r: 0, g: 255, b: 255 });
// Base radius values and scaling factor for unit borders and territories
@@ -33,20 +25,10 @@ const BASE_BORDER_RADIUS = 16.5;
const BASE_TERRITORY_RADIUS = 13.5;
const RADIUS_SCALE_FACTOR = 0.5;
type DistanceFunction = typeof euclDistFN;
enum UnitBorderType {
Round,
Diamond,
Square,
Hexagon,
}
interface UnitRenderConfig {
icon: string;
borderRadius: number;
territoryRadius: number;
borderType: UnitBorderType;
}
export class StructureLayer implements Layer {
@@ -65,37 +47,31 @@ export class StructureLayer implements Layer {
icon: anchorIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Round,
},
[UnitType.City]: {
icon: cityIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Round,
},
[UnitType.Factory]: {
icon: factoryIcon,
borderRadius: 8.525,
territoryRadius: 6.525,
borderType: UnitBorderType.Round,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
},
[UnitType.MissileSilo]: {
icon: missileSiloIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Square,
},
[UnitType.DefensePost]: {
icon: shieldIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Hexagon,
},
[UnitType.SAMLauncher]: {
icon: SAMMissileIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Square,
},
};
@@ -117,18 +93,6 @@ export class StructureLayer implements Layer {
if (tempContext === null) throw new Error("2d context not supported");
this.tempContext = tempContext;
this.loadIconData();
this.loadIcon("reloadingSam", {
icon: SAMMissileReloadingIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Square,
});
this.loadIcon("reloadingSilo", {
icon: MissileSiloReloadingIcon,
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
borderType: UnitBorderType.Square,
});
}
private loadIcon(unitType: string, config: UnitRenderConfig) {
@@ -204,12 +168,11 @@ export class StructureLayer implements Layer {
unit: UnitView,
borderColor: Colord,
config: UnitRenderConfig,
distanceFN: DistanceFunction,
) {
// Draw border and territory
for (const tile of this.game.bfs(
unit.tile(),
distanceFN(unit.tile(), config.borderRadius, true),
isometricDistFN(unit.tile(), config.borderRadius, true),
)) {
this.paintCell(
new Cell(this.game.x(tile), this.game.y(tile)),
@@ -220,7 +183,7 @@ export class StructureLayer implements Layer {
for (const tile of this.game.bfs(
unit.tile(),
distanceFN(unit.tile(), config.territoryRadius, true),
isometricDistFN(unit.tile(), config.territoryRadius, true),
)) {
this.paintCell(
new Cell(this.game.x(tile), this.game.y(tile)),
@@ -232,19 +195,6 @@ export class StructureLayer implements Layer {
}
}
private getDrawFN(type: UnitBorderType) {
switch (type) {
case UnitBorderType.Round:
return euclDistFN;
case UnitBorderType.Diamond:
return manhattanDistFN;
case UnitBorderType.Square:
return rectDistFN;
case UnitBorderType.Hexagon:
return hexDistFN;
}
}
private handleUnitRendering(unit: UnitView) {
const unitType = unit.constructionType() ?? unit.type();
const iconType = unitType;
@@ -255,13 +205,7 @@ export class StructureLayer implements Layer {
let borderColor = this.theme.borderColor(unit.owner());
// Handle cooldown states and special icons
if (unitType === UnitType.SAMLauncher && unit.isInCooldown()) {
icon = this.unitIcons.get("reloadingSam");
borderColor = reloadingColor;
} else if (unitType === UnitType.MissileSilo && unit.isInCooldown()) {
icon = this.unitIcons.get("reloadingSilo");
borderColor = reloadingColor;
} else if (unit.type() === UnitType.Construction) {
if (unit.type() === UnitType.Construction) {
icon = this.unitIcons.get(iconType);
borderColor = underConstructionColor;
} else {
@@ -270,11 +214,10 @@ export class StructureLayer implements Layer {
if (!config || !icon) return;
const drawFunction = this.getDrawFN(config.borderType);
// Clear previous rendering
for (const tile of this.game.bfs(
unit.tile(),
drawFunction(unit.tile(), config.borderRadius, true),
euclDistFN(unit.tile(), config.borderRadius + 1, true),
)) {
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
}
@@ -284,8 +227,7 @@ export class StructureLayer implements Layer {
if (this.selectedStructureUnit === unit) {
borderColor = selectedUnitColor;
}
this.drawBorder(unit, borderColor, config, drawFunction);
this.drawBorder(unit, borderColor, config);
// Render icon at 1/2 scale for better quality
const scaledWidth = icon.width >> 1;
@@ -293,7 +235,7 @@ export class StructureLayer implements Layer {
const startX = this.game.x(unit.tile()) - (scaledWidth >> 1);
const startY = this.game.y(unit.tile()) - (scaledHeight >> 1);
this.renderIcon(icon, startX, startY, scaledWidth, scaledHeight, unit);
this.renderIcon(icon, startX, startY - 4, scaledWidth, scaledHeight, unit);
}
private renderIcon(
@@ -320,11 +262,6 @@ export class StructureLayer implements Layer {
// Draw the image at final size with high quality scaling
this.tempContext.drawImage(image, 0, 0, width * 2, height * 2);
// Apply color tinting using multiply blend mode
this.tempContext.globalCompositeOperation = "multiply";
this.tempContext.fillStyle = color.toRgbString();
this.tempContext.fillRect(0, 0, width * 2, height * 2);
// Restore the alpha channel
this.tempContext.globalCompositeOperation = "destination-in";
this.tempContext.drawImage(image, 0, 0, width * 2, height * 2);
+3 -3
View File
@@ -19,7 +19,7 @@ const COLOR_PROGRESSION = [
"rgb(44, 239, 18)",
];
const HEALTHBAR_WIDTH = 11; // Width of the health bar
const LOADINGBAR_WIDTH = 18; // Width of the loading bar
const LOADINGBAR_WIDTH = 14; // Width of the loading bar
const PROGRESSBAR_HEIGHT = 3; // Height of a bar
/**
@@ -378,8 +378,8 @@ export class UILayer implements Layer {
const progressBar = new ProgressBar(
COLOR_PROGRESSION,
this.context,
this.game.x(unit.tile()) - 8,
this.game.y(unit.tile()) - 10,
this.game.x(unit.tile()) - 6,
this.game.y(unit.tile()) + 6,
LOADINGBAR_WIDTH,
PROGRESSBAR_HEIGHT,
0,
+4 -4
View File
@@ -376,13 +376,13 @@ label.option-card:hover {
}
#helpModal .missile-silo-icon {
mask: url("../../resources/images/MissileSiloIconWhite.svg") no-repeat
center / cover;
mask: url("../../resources/non-commercial/svg/MissileSiloIconWhite.svg")
no-repeat center / cover;
}
#helpModal .sam-launcher-icon {
mask: url("../../resources/images/SamLauncherIconWhite.svg") no-repeat
center / cover;
mask: url("../../resources/non-commercial/svg/SamLauncherIconWhite.svg")
no-repeat center / cover;
}
#helpModal .atom-bomb-icon {
+33
View File
@@ -401,6 +401,39 @@ export function rectDistFN(
}
}
function isInIsometricTile(
center: { x: number; y: number },
tile: { x: number; y: number },
yOffset: number,
distance: number,
): boolean {
const dx = Math.abs(tile.x - center.x);
const dy = Math.abs(tile.y - (center.y + yOffset));
return dx + dy * 2 <= distance + 1;
}
export function isometricDistFN(
root: TileRef,
dist: number,
center: boolean = false,
): (gm: GameMap, tile: TileRef) => boolean {
if (!center) {
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
} else {
return (gm: GameMap, n: TileRef) => {
const rootX = gm.x(root) - 0.5;
const rootY = gm.y(root) - 0.5;
return isInIsometricTile(
{ x: rootX, y: rootY },
{ x: gm.x(n), y: gm.y(n) },
0,
dist,
);
};
}
}
export function hexDistFN(
root: TileRef,
dist: number,