From 77dddbf3ee512ffa521708db66338f5f9b05f4aa Mon Sep 17 00:00:00 2001 From: Vivacious Box Date: Sat, 28 Jun 2025 02:00:01 +0200 Subject: [PATCH] New icons (#1287) ## 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 --- package-lock.json | 90 +++++- package.json | 1 + resources/images/AnchorIcon.png | Bin 136 -> 198 bytes resources/images/CityIcon.png | Bin 129 -> 221 bytes resources/images/CityIconWhite.svg | 2 - resources/images/FactoryIconWhite.svg | 46 ++- resources/images/FactoryUnit.png | Bin 0 -> 310 bytes resources/images/MissileSiloIconWhite.svg | 86 ----- resources/images/MissileSiloUnit.png | Bin 115 -> 266 bytes resources/images/SamLauncherIconWhite.svg | 32 -- resources/images/SamLauncherUnit.png | Bin 0 -> 361 bytes resources/images/ShieldIcon.png | Bin 126 -> 238 bytes resources/images/ShieldIconWhite.svg | 2 - resources/images/buildings/cityAlt1.png | Bin 2997 -> 0 bytes resources/images/buildings/factoryAlt1.png | Bin 176 -> 0 bytes resources/images/buildings/fortAlt2.png | Bin 2991 -> 0 bytes resources/images/buildings/port1.png | Bin 2998 -> 0 bytes resources/images/buildings/silo1.png | Bin 2965 -> 0 bytes resources/images/buildings/silo4.png | Bin 2967 -> 0 bytes .../images/buildings/cityAlt1.png | Bin 0 -> 587 bytes .../images/buildings/factoryAlt1.png | Bin 0 -> 643 bytes .../images/buildings/fortAlt2.png | Bin 0 -> 3092 bytes .../images/buildings/fortAlt3.png | Bin 0 -> 551 bytes .../non-commercial/images/buildings/port1.png | Bin 0 -> 3348 bytes .../non-commercial/images/buildings/silo1.png | Bin 0 -> 3005 bytes .../non-commercial/images/buildings/silo4.png | Bin 0 -> 3192 bytes .../images/svg/MissileSiloIconWhite.svg | 172 ++++++++++ .../images/svg/SamLauncherIconWhite.svg | 221 +++++++++++++ .../svg/MissileSiloIconWhite.svg | 172 ++++++++++ .../svg/SamLauncherIconWhite.svg | 221 +++++++++++++ src/client/graphics/GameRenderer.ts | 42 ++- src/client/graphics/TransformHandler.ts | 12 +- src/client/graphics/layers/BuildMenu.ts | 4 +- .../graphics/layers/StructureIconsLayer.ts | 300 ++++++++++++++++++ src/client/graphics/layers/StructureLayer.ts | 93 +----- src/client/graphics/layers/UILayer.ts | 6 +- src/client/styles.css | 8 +- src/core/game/GameMap.ts | 33 ++ 38 files changed, 1312 insertions(+), 231 deletions(-) create mode 100644 resources/images/FactoryUnit.png delete mode 100644 resources/images/MissileSiloIconWhite.svg delete mode 100644 resources/images/SamLauncherIconWhite.svg create mode 100644 resources/images/SamLauncherUnit.png delete mode 100644 resources/images/buildings/cityAlt1.png delete mode 100644 resources/images/buildings/factoryAlt1.png delete mode 100644 resources/images/buildings/fortAlt2.png delete mode 100644 resources/images/buildings/port1.png delete mode 100644 resources/images/buildings/silo1.png delete mode 100644 resources/images/buildings/silo4.png create mode 100644 resources/non-commercial/images/buildings/cityAlt1.png create mode 100644 resources/non-commercial/images/buildings/factoryAlt1.png create mode 100644 resources/non-commercial/images/buildings/fortAlt2.png create mode 100644 resources/non-commercial/images/buildings/fortAlt3.png create mode 100644 resources/non-commercial/images/buildings/port1.png create mode 100644 resources/non-commercial/images/buildings/silo1.png create mode 100644 resources/non-commercial/images/buildings/silo4.png create mode 100644 resources/non-commercial/images/svg/MissileSiloIconWhite.svg create mode 100644 resources/non-commercial/images/svg/SamLauncherIconWhite.svg create mode 100644 resources/non-commercial/svg/MissileSiloIconWhite.svg create mode 100644 resources/non-commercial/svg/SamLauncherIconWhite.svg create mode 100644 src/client/graphics/layers/StructureIconsLayer.ts diff --git a/package-lock.json b/package-lock.json index 7a86eb0af..319f78472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3f40007b1..049023cb2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/resources/images/AnchorIcon.png b/resources/images/AnchorIcon.png index 78bdfe73e870d468d8ff1c8d7afe63d6148b8124..cd0582fc7f76f5ed96b35ec8bbd82ed0e492d678 100644 GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|8a!PbLo9le z6BZc$;6Iwun9jq)BMZb)KwN*)FhP}>`EWWLTibIWfBT72R!4nll}1kaX*7FRi(a$g^O+@z;5pMN%^ZiLB$N8!oF50p6BVvapO{vW7$W(3a(C9}wM og-n;@nJ>B|Id`-~Y<zopr03X*t`2YX_ literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VjKx9jP7LeL$-D$|oIG6|Lo|Yy zPB_TLV8G$@_P>4AVTTPiZ#LxJa^B>0jxivQpV8p}gZYt$^h@$v9y@Hq)$ literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VjKx9jP7LeL$-D$|tUX;ELo|Yu zQyf_TPu$UCU^eIAfddS|3_{Ln#tFeKIc5=Z%Q#%sLqubEVg+XjAB;K1XuxTivtef^ aBg2|rUQQ{KkiS4<7(8A5T-G@yGywpi*CKlW diff --git a/resources/images/CityIconWhite.svg b/resources/images/CityIconWhite.svg index 0c55a2d06..4591cfb7c 100644 --- a/resources/images/CityIconWhite.svg +++ b/resources/images/CityIconWhite.svg @@ -3,8 +3,6 @@ - - + + + + - \ No newline at end of file + diff --git a/resources/images/FactoryUnit.png b/resources/images/FactoryUnit.png new file mode 100644 index 0000000000000000000000000000000000000000..ce25ced1dfd9cb1daeb4b7e0cf11793cdb66e368 GIT binary patch literal 310 zcmV-60m=S}P)Px#@JU2LR5*>zkTFWcP!vT^hOrt8i$EGd2HUAp7#pp!60w*CSj`Sxf`tpn*hUe8 zmAxV0XMA;)OEECd0RbD-_#>@ zOMO&r13ans3;A-vU#QQYp$*Vmtvgsn%K#6+A@CaV47dvZ9GC)j0UF>GcnNt9+y;LQ zoB;1#fDy0{tcAP-Yz99LZ_$s5KV4!J8t|)DpW!I7$G2{M0AA=M>cSRQ2LJ#707*qo IM6N<$f(IFPw*UYD literal 0 HcmV?d00001 diff --git a/resources/images/MissileSiloIconWhite.svg b/resources/images/MissileSiloIconWhite.svg deleted file mode 100644 index 071fbf245..000000000 --- a/resources/images/MissileSiloIconWhite.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - diff --git a/resources/images/MissileSiloUnit.png b/resources/images/MissileSiloUnit.png index bc94a189437a2428e1e415ba6eca7784c8d19c46..81b56b9596d9fe4dfcfa3a702d5bad753c9d4b07 100644 GIT binary patch literal 266 zcmV+l0rmcgP)Px##7RU!R5*>zlCcWHFcd{kw1SIJL3eitS6v+3^=tZ%enG)aoLqHM1icQXHccNA zaq%t*c{eAyFM%KBtVK!HbPgPWGjMXwwA%nl8`YWx&?G>vrf!mQ%iBre0U6M1|%O$WD@{VjKx9jP7LeL$-D$|bUj@hLo|Yu zQx35Fmz0o@nAjR - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/images/SamLauncherUnit.png b/resources/images/SamLauncherUnit.png new file mode 100644 index 0000000000000000000000000000000000000000..ab29b96179ecbc62c23618b469f2bc39cf9f6ed5 GIT binary patch literal 361 zcmV-v0ha!WP)Px$BS}O-R5*>L(!VRjaTo{i*ZJ97i(W0Vj$Y_!06D-O9#Fz_g54|zFolI?3^r2wV>rfq)JsVQZ7n?N z$3CVpls51iKs(lheicW50qDaf7Ng$9N!nneiO`K5%tc+ncIa-&AcJ?TVhbPmj7-j? z0@UII!DHwy#0$J7<|YEL(#WP1hw|~}%Lz$87tI92Vw#*B%6RqdelD?>#Ts649(8K; z;3DK)$0r)WRrJaYz^T!N72Jf0RosT8&Q^z$k;P$n)0FZT!C*suyNSd300000NkvXX Hu0mjfgkqx} literal 0 HcmV?d00001 diff --git a/resources/images/ShieldIcon.png b/resources/images/ShieldIcon.png index e1db2168ef86a3cb0d43013244fef804624055ba..5720eb7ec54142c8fd0d7789574d19874d542473 100644 GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|R(QHNhFJ8z zoxG9vu!4w-{7N0M4R@A&P>R%wZ9A#6GANzX>-)5v$tEivLHK_4>Fq9jZ+i3eHXtp0e%V zF~<*{0*&h)`lQ=F);`_Xy7^nk_WpV8j3b literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VjKx9jP7LeL$-D$|EIeHtLo|Yu zQyd!pd*nz=7Cgcr%)`T@z-nN&DQd%ygz%O(f-}+&B%QEpVrBH;nc4d=_sNDPX$FSu XKLT7wPxku)&0z3!^>bP0l+XkKU*{wj diff --git a/resources/images/ShieldIconWhite.svg b/resources/images/ShieldIconWhite.svg index 495260851..28373f308 100644 --- a/resources/images/ShieldIconWhite.svg +++ b/resources/images/ShieldIconWhite.svg @@ -1,7 +1,5 @@ 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 PlR=cHtDnm{r-UW|u01&{ diff --git a/resources/images/buildings/fortAlt2.png b/resources/images/buildings/fortAlt2.png deleted file mode 100644 index a6234de45f727dc73d064a8b0d64c10a5d291422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2991 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 diff --git a/resources/images/buildings/silo1.png b/resources/images/buildings/silo1.png deleted file mode 100644 index 66342084f7ccc4e37f48f4651e42dc8dc4ac396d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2965 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 ZzX7KU-S<5AysiKM002ovPDHLkV1gUK4KV-! literal 0 HcmV?d00001 diff --git a/resources/non-commercial/images/buildings/factoryAlt1.png b/resources/non-commercial/images/buildings/factoryAlt1.png new file mode 100644 index 0000000000000000000000000000000000000000..948376f3f95cecdb0fb66c6fabdd3ad25b85f2ca GIT binary patch literal 643 zcmV-}0(||6P)Px%JxN4CR9J=WmA_9DVI0PvlA5T4n+py~QU)VY%PkFtA94-RgjHZD1iCRPEc^wS zIB?j>321t!p*j$O*h*-PT__2Mt!WpviAL`d;-G`NIJ^$!y&QM%d-t9a9qco`x$k@5 z=kq?__i{i>Ew%JNC|{|Pe`qXktX`AU@x-5O=S7Zx=J0A)GD z-IK3W$&IU5LabUgo#wvh3BWUsPEM0o=?5WNKYQjhW|C7(hzr?O)FPee+26yKD1hII zXOj~^a)Qu$LcWH@)0<$xYzRCzEZ=0i!Xm7G97s`MQ-O6`z2zRXbPTpKLy8JvxuyTJf!~)@s=6B?ofK$9g z>U9taD-&-jl3oFHA2WC5Hy5f(B&767URRU;IDbu002ovPDHLkV1iHDBFz8* literal 0 HcmV?d00001 diff --git a/resources/non-commercial/images/buildings/fortAlt2.png b/resources/non-commercial/images/buildings/fortAlt2.png new file mode 100644 index 0000000000000000000000000000000000000000..0b493a18545d253f040284a602d7e1fde552dbe8 GIT binary patch literal 3092 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 literal 0 HcmV?d00001 diff --git a/resources/non-commercial/images/buildings/port1.png b/resources/non-commercial/images/buildings/port1.png new file mode 100644 index 0000000000000000000000000000000000000000..3342bae53d44d852fea49d7449b70c8a16ebea39 GIT binary patch literal 3348 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 e0z`-#p!fsR^)OPcIzcc10000*-S^(FQ9 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 literal 0 HcmV?d00001 diff --git a/resources/non-commercial/images/buildings/silo4.png b/resources/non-commercial/images/buildings/silo4.png new file mode 100644 index 0000000000000000000000000000000000000000..a21cb1c409e0e83ba2452b261cc31aedae3b6284 GIT binary patch literal 3192 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 literal 0 HcmV?d00001 diff --git a/resources/non-commercial/images/svg/MissileSiloIconWhite.svg b/resources/non-commercial/images/svg/MissileSiloIconWhite.svg new file mode 100644 index 000000000..d55a9d6d8 --- /dev/null +++ b/resources/non-commercial/images/svg/MissileSiloIconWhite.svg @@ -0,0 +1,172 @@ + + + + + + diff --git a/resources/non-commercial/images/svg/SamLauncherIconWhite.svg b/resources/non-commercial/images/svg/SamLauncherIconWhite.svg new file mode 100644 index 000000000..7d127b47d --- /dev/null +++ b/resources/non-commercial/images/svg/SamLauncherIconWhite.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/non-commercial/svg/MissileSiloIconWhite.svg b/resources/non-commercial/svg/MissileSiloIconWhite.svg new file mode 100644 index 000000000..d55a9d6d8 --- /dev/null +++ b/resources/non-commercial/svg/MissileSiloIconWhite.svg @@ -0,0 +1,172 @@ + + + + + + diff --git a/resources/non-commercial/svg/SamLauncherIconWhite.svg b/resources/non-commercial/svg/SamLauncherIconWhite.svg new file mode 100644 index 000000000..7d127b47d --- /dev/null +++ b/resources/non-commercial/svg/SamLauncherIconWhite.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index a72aaf6e4..f61de2d5a 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -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()); diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 40de3fa3e..47b569d5a 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -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 } { diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts index 580fdcae6..f1cfe17b8 100644 --- a/src/client/graphics/layers/BuildMenu.ts +++ b/src/client/graphics/layers/BuildMenu.ts @@ -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"; diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts new file mode 100644 index 000000000..ffdf680a5 --- /dev/null +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -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 = new Map(); + private theme: Theme; + private renderer: PIXI.Renderer; + private renders: StructureRenderInfo[] = []; + private seenUnits: Set = 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); + } +} diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts index c80b44f19..4c68f222b 100644 --- a/src/client/graphics/layers/StructureLayer.ts +++ b/src/client/graphics/layers/StructureLayer.ts @@ -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); diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 804eed21f..a743c5ae7 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -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, diff --git a/src/client/styles.css b/src/client/styles.css index 05240ef5b..dba13d5d7 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -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 { diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index d05d8d499..7a3bd8e6d 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -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,