mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-07 19:21:30 +00:00
Improve player panel (#2060)
## Description: Fixes #2015 Improved the Player Panel UI for better usability and appearance. **Screenshots** <img width="334" height="523" alt="2" src="https://github.com/user-attachments/assets/bd0afaac-07df-4abc-a20f-208a0783e558" /> <img width="337" height="523" alt="3" src="https://github.com/user-attachments/assets/f712ad77-4546-487b-9a9c-2c535b8a45f7" /> **Future Plan** Add a modal for sending gold and troops to other players from the Player Panel. <img width="343" height="494" alt="sending troops" src="https://github.com/user-attachments/assets/9c9c21db-e13a-426f-93e9-b477a9db442a" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: abodcraft1 --------- Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
co-authored by
evanpelle
parent
715775065d
commit
175d492b99
Generated
+10
-10
@@ -3604,13 +3604,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
|
||||||
"integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==",
|
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/core": "^0.15.0",
|
"@eslint/core": "^0.15.2",
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3618,9 +3618,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||||
"version": "0.15.0",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
|
||||||
"integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==",
|
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -18916,9 +18916,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
"node_modules/tar-fs": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
|
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
id="svg10"
|
||||||
|
sodipodi:docname="TraitorIcon.svg"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
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="defs14" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview12"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
height="100px"
|
||||||
|
inkscape:zoom="6.984"
|
||||||
|
inkscape:cx="49.971363"
|
||||||
|
inkscape:cy="62.142039"
|
||||||
|
inkscape:window-width="3072"
|
||||||
|
inkscape:window-height="1653"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="38"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg10" />
|
||||||
|
<g
|
||||||
|
transform="translate(0,-952.36218)"
|
||||||
|
id="g4">
|
||||||
|
<path
|
||||||
|
style="color:#000000;text-indent:0;text-transform:none;direction:ltr;baseline-shift:baseline;enable-background:accumulate"
|
||||||
|
d="m 49.682164,957.35229 c -0.08371,0.007 -0.167129,0.0173 -0.249965,0.0312 l -35.995035,7.00077 C 12.072501,964.6449 10.989932,965.96376 11,967.35339 v 32.00351 c 0.0012,0.13594 0.01165,0.27179 0.03125,0.4063 1.29114,9.0104 3.853487,15.5969 8.498827,22.2837 0.990121,1.4292 3.318267,1.6525 4.561871,0.4375 4.250005,-4.1812 7.885056,-7.78 11.967099,-11.9697 0.44046,-0.4164 0.750052,-0.9694 0.87488,-1.5626 0.628358,-3.8527 1.729676,-9.31167 2.530901,-13.59583 h 15.529108 c 1.243283,0.002 2.441795,-0.85453 2.843357,-2.03147 l 9.998621,-29.00319 c 0.567687,-1.6172 -0.568544,-3.6061 -2.249689,-3.93794 l -14.997932,-3.00033 c -0.298144,-0.0562 -0.604826,-0.0667 -0.906125,-0.0312 z m 23.902953,5.00055 c -1.078435,0.15321 -2.042235,0.94196 -2.405918,1.96897 L 58.899643,998.35679 H 44.995311 c -1.406702,-0.005 -2.733797,1.11295 -2.968341,2.50031 l -1.81225,10.9699 -14.27928,13.3139 c -1.128859,1.0576 -1.217322,3.0338 -0.187474,4.188 6.269465,7.139 13.562725,12.4576 22.778108,17.627 0.892103,0.5009 2.044992,0.5009 2.937095,0 19.535202,-10.9581 34.280717,-24.7622 37.494828,-47.1927 0.06997,-10.76177 0.03124,-21.61653 0.03124,-32.40981 0.0049,-1.40704 -1.112648,-2.73446 -2.499655,-2.96908 l -11.99834,-2.00021 c -0.298144,-0.0562 -0.604826,-0.0667 -0.906125,-0.0312 z m -23.621742,1.06261 11.060975,2.18774 -8.186371,23.75262 H 36.996414 c -1.378861,0.004 -2.67845,1.08304 -2.937095,2.43777 l -2.812112,15.06422 c -2.889686,2.9098 -5.919847,5.9695 -8.686302,8.7197 -2.891489,-4.8594 -4.568269,-9.6725 -5.561733,-16.50187 v -29.25322 z m 25.996415,5.28184 7.03028,1.18763 v 29.19071 c -2.868962,19.61817 -15.058331,31.46397 -32.995449,41.78587 -7.128175,-4.1138 -12.790202,-8.2954 -17.716306,-13.4077 l 12.779487,-11.9076 c 0.479911,-0.4525 0.804009,-1.0673 0.906125,-1.719 l 1.59353,-9.4698 h 13.435647 c 1.225447,-0.01 2.403546,-0.8446 2.812112,-2.0002 z"
|
||||||
|
fill="#FFFFFF"
|
||||||
|
fill-opacity="1"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
stroke="none"
|
||||||
|
marker="none"
|
||||||
|
visibility="visible"
|
||||||
|
display="inline"
|
||||||
|
overflow="visible"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path944" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path983" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path1022" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path1061" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path1100" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff2d2d;fill-opacity:0;stroke-width:0.143184"
|
||||||
|
d="M 22.451679,62.831985 C 22.040716,62.318926 20.614744,59.414324 19.904415,57.643392 18.969118,55.311589 18.276544,53.003918 17.644871,50.114548 L 17.190978,48.038373 17.14702,32.753436 c -0.02418,-8.406715 0.006,-15.283838 0.06717,-15.282495 0.06112,0.0013 7.450905,-1.423883 16.421746,-3.16717 l 16.310618,-3.169613 5.310229,1.037928 c 2.920626,0.570861 5.383063,1.077918 5.472081,1.126793 0.108366,0.0595 -1.175715,3.968788 -3.885785,11.829987 l -4.047636,11.741123 -8.356483,0.07159 c -6.695958,0.05736 -8.427615,0.110521 -8.714445,0.267501 -0.60381,0.330461 -1.419391,1.193902 -1.576025,1.66851 -0.08176,0.247726 -0.783479,3.851808 -1.559383,8.00907 l -1.410734,7.55866 -4.287731,4.287731 c -3.320136,3.320136 -4.321858,4.245124 -4.438961,4.098932 z"
|
||||||
|
id="path1139" />
|
||||||
|
<path
|
||||||
|
style="fill:#FFFFFF;fill-opacity:1;stroke-width:0.143184"
|
||||||
|
d="m 40.914948,36.977471 c 2.185353,-0.02223 5.761384,-0.02223 7.946736,0 2.185352,0.02223 0.397337,0.04042 -3.973368,0.04042 -4.370704,0 -6.15872,-0.01819 -3.973368,-0.04042 z"
|
||||||
|
id="path1178" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff5a5a;fill-opacity:1;stroke-width:0.143184"
|
||||||
|
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
|
||||||
|
id="path9953" />
|
||||||
|
<path
|
||||||
|
style="fill:#ff5a5a;fill-opacity:1;stroke-width:0.143184"
|
||||||
|
d="M 49.327033,94.804719 C 48.714427,94.60513 47.768416,94.086861 45.174685,92.529868 37.917796,88.173624 32.73106,84.14232 27.795635,79.022291 c -1.145161,-1.187994 -2.213316,-2.431436 -2.374239,-2.763859 -0.397355,-0.820826 -0.385516,-1.87334 0.02963,-2.634656 0.211883,-0.388557 2.674852,-2.777493 7.195017,-6.978744 3.780069,-3.513371 7.046744,-6.559837 7.25928,-6.769926 0.367228,-0.363001 0.433179,-0.666405 1.327443,-6.106826 1.040786,-6.331825 1.066013,-6.412508 2.250084,-7.196324 l 0.596675,-0.39498 7.446948,-0.07159 7.446947,-0.07159 6.097509,-16.895762 c 3.353629,-9.292669 6.184278,-17.104473 6.290327,-17.359565 0.271361,-0.652727 1.177566,-1.423686 1.905252,-1.620901 0.55181,-0.149551 1.267997,-0.05876 6.811404,0.863504 3.407116,0.566845 6.423919,1.093902 6.704006,1.171238 0.637026,0.175891 1.521753,0.963097 1.870312,1.664151 0.253992,0.510853 0.264529,1.240952 0.247407,17.14297 -0.01766,16.399934 -0.02182,16.631061 -0.329684,18.327606 -2.58954,14.26993 -9.981308,25.824745 -22.991493,35.940307 -3.466755,2.695441 -7.896512,5.630746 -12.608706,8.354951 -2.064486,1.193517 -2.838015,1.444605 -3.642726,1.182428 z M 52.47709,87.119888 C 59.271254,83.059324 64.365846,79.189404 68.951193,74.605944 76.565106,66.995162 80.844811,58.899716 82.755326,48.494191 l 0.363231,-1.978318 V 32.007709 17.499544 l -0.322165,-0.07398 c -0.957493,-0.219871 -6.829216,-1.157603 -6.884521,-1.099478 -0.03586,0.03769 -2.785127,7.60719 -6.109478,16.821108 -3.324352,9.213917 -6.177636,16.993316 -6.340633,17.287551 -0.353145,0.637482 -1.164147,1.232386 -1.918855,1.407559 -0.300488,0.06975 -3.5908,0.128346 -7.311805,0.130225 -3.721005,0.0019 -6.765464,0.04073 -6.765464,0.08633 0,0.474978 -1.637454,9.59135 -1.804511,10.046442 -0.187845,0.511725 -1.260378,1.573508 -6.797799,6.729668 -3.61612,3.367143 -6.574871,6.194451 -6.575002,6.282908 -3e-4,0.204934 2.512831,2.628316 4.451717,4.292733 1.895466,1.627144 5.431341,4.277542 7.645781,5.731075 2.084726,1.368391 5.558468,3.470987 5.657133,3.424166 0.03938,-0.01869 1.134737,-0.669367 2.434135,-1.44596 z"
|
||||||
|
id="path10066" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 17 KiB |
+24
-5
@@ -566,6 +566,11 @@
|
|||||||
"upgrade": "Upgrade",
|
"upgrade": "Upgrade",
|
||||||
"level": "Level"
|
"level": "Level"
|
||||||
},
|
},
|
||||||
|
"player_type": {
|
||||||
|
"player": "Player",
|
||||||
|
"nation": "Nation",
|
||||||
|
"bot": "Bot"
|
||||||
|
},
|
||||||
"relation": {
|
"relation": {
|
||||||
"hostile": "Hostile",
|
"hostile": "Hostile",
|
||||||
"distrustful": "Distrustful",
|
"distrustful": "Distrustful",
|
||||||
@@ -581,23 +586,37 @@
|
|||||||
"player_panel": {
|
"player_panel": {
|
||||||
"gold": "Gold",
|
"gold": "Gold",
|
||||||
"troops": "Troops",
|
"troops": "Troops",
|
||||||
"betrayals": "Number of betrayals",
|
"betrayals": "Betrayals",
|
||||||
"traitor": "Traitor",
|
"traitor": "Traitor",
|
||||||
|
"stable": "Stable",
|
||||||
|
"trust": "Trust",
|
||||||
|
"trading": "Trading",
|
||||||
|
"active": "Active",
|
||||||
|
"stopped": "Stopped",
|
||||||
"alliance_time_remaining": "Alliance Expires In",
|
"alliance_time_remaining": "Alliance Expires In",
|
||||||
"embargo": "Stopped trading with you",
|
"embargo": "Stopped trading with you",
|
||||||
"nuke": "Nukes sent by them to you",
|
"nuke": "Nukes sent by them to you",
|
||||||
"start_trade": "Start trading",
|
"start_trade": "Start Trading",
|
||||||
"stop_trade": "Stop trading",
|
"stop_trade": "Stop Trading",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"alliances": "Alliances",
|
"alliances": "Alliances",
|
||||||
"flag": "Flag"
|
"flag": "Flag",
|
||||||
|
"chat": "Chat",
|
||||||
|
"target": "Target",
|
||||||
|
"break": "Break",
|
||||||
|
"break_alliance": "Break Alliance",
|
||||||
|
"alliance": "Alliance",
|
||||||
|
"send_alliance": "Send Alliance",
|
||||||
|
"send_troops": "Send Troops",
|
||||||
|
"send_gold": "Send Gold",
|
||||||
|
"emotes": "Emojis"
|
||||||
},
|
},
|
||||||
"replay_panel": {
|
"replay_panel": {
|
||||||
"replay_speed": "Replay speed",
|
"replay_speed": "Replay speed",
|
||||||
"game_speed": "Game speed",
|
"game_speed": "Game speed",
|
||||||
"fastest_game_speed": "max"
|
"fastest_game_speed": "Max"
|
||||||
},
|
},
|
||||||
"error_modal": {
|
"error_modal": {
|
||||||
"crashed": "Game crashed!",
|
"crashed": "Game crashed!",
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { html, TemplateResult } from "lit";
|
||||||
|
|
||||||
|
export type ButtonVariant =
|
||||||
|
| "normal"
|
||||||
|
| "red"
|
||||||
|
| "green"
|
||||||
|
| "indigo"
|
||||||
|
| "yellow"
|
||||||
|
| "sky";
|
||||||
|
export interface ActionButtonProps {
|
||||||
|
onClick: (e: MouseEvent) => void;
|
||||||
|
type?: ButtonVariant;
|
||||||
|
icon: string;
|
||||||
|
iconAlt: string;
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ICON_SIZE =
|
||||||
|
"h-5 w-5 shrink-0 transition-transform group-hover:scale-110 text-zinc-400";
|
||||||
|
const TEXT_SIZE =
|
||||||
|
"text-base sm:text-[14px] leading-5 font-semibold tracking-tight";
|
||||||
|
|
||||||
|
const getButtonStyles = () => {
|
||||||
|
const btnBase =
|
||||||
|
"group w-full min-w-[50px] select-none flex flex-col items-center justify-center " +
|
||||||
|
"gap-1 rounded-lg py-1.5 border border-white/10 bg-white/[0.04] shadow-sm " +
|
||||||
|
"transition-all duration-150 " +
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/20 " +
|
||||||
|
"active:translate-y-[1px]";
|
||||||
|
|
||||||
|
return {
|
||||||
|
normal: `${btnBase} text-white/90 hover:bg-white/10 hover:text-white`,
|
||||||
|
red: `${btnBase} text-red-400 hover:bg-red-500/10 hover:text-red-300 focus-visible:ring-red-400/30`,
|
||||||
|
green: `${btnBase} text-emerald-400 hover:bg-emerald-500/10 hover:text-emerald-300 focus-visible:ring-emerald-400/30`,
|
||||||
|
yellow: `${btnBase} text-[#f59e0b] hover:bg-[#f59e0b]/10 hover:text-[#fbbf24] focus-visible:ring-[#f59e0b]/30`,
|
||||||
|
indigo: `${btnBase} text-indigo-400 hover:bg-indigo-500/10 hover:text-indigo-300 focus-visible:ring-indigo-400/30`,
|
||||||
|
sky: `${btnBase} text-[#38bdf8] hover:bg-[#38bdf8]/10 hover:text-[#0ea5e9] focus-visible:ring-[#38bdf8]/30`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionButton = (props: ActionButtonProps): TemplateResult => {
|
||||||
|
const {
|
||||||
|
onClick,
|
||||||
|
type = "normal",
|
||||||
|
icon,
|
||||||
|
iconAlt,
|
||||||
|
title,
|
||||||
|
label,
|
||||||
|
disabled = false,
|
||||||
|
} = props;
|
||||||
|
const buttonStyles = getButtonStyles();
|
||||||
|
const buttonClass = buttonStyles[type];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
@click=${onClick}
|
||||||
|
class="${buttonClass}"
|
||||||
|
title="${title}"
|
||||||
|
type="button"
|
||||||
|
aria-label="${title}"
|
||||||
|
?disabled=${disabled}
|
||||||
|
>
|
||||||
|
<img src=${icon} alt=${iconAlt} aria-hidden="true" class="${ICON_SIZE}" />
|
||||||
|
<span class="${TEXT_SIZE}">${label}</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
export type DividerSpacing = "sm" | "md" | "lg";
|
||||||
|
@customElement("ui-divider")
|
||||||
|
export class Divider extends LitElement {
|
||||||
|
@property({ type: String })
|
||||||
|
spacing: DividerSpacing = "md";
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
color: string = "bg-zinc-700/80";
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const spacingClasses: Record<DividerSpacing, string> = {
|
||||||
|
sm: "my-0.5",
|
||||||
|
md: "my-1",
|
||||||
|
lg: "my-2",
|
||||||
|
} as const;
|
||||||
|
const spacing = spacingClasses[this.spacing] ?? spacingClasses.md;
|
||||||
|
|
||||||
|
const colorClass = this.color || "bg-zinc-700/80";
|
||||||
|
|
||||||
|
return html`<div
|
||||||
|
role="separator"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="${spacing} h-px ${colorClass}"
|
||||||
|
></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -268,13 +268,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
let playerType = "";
|
let playerType = "";
|
||||||
switch (player.type()) {
|
switch (player.type()) {
|
||||||
case PlayerType.Bot:
|
case PlayerType.Bot:
|
||||||
playerType = translateText("player_info_overlay.bot");
|
playerType = translateText("player_type.bot");
|
||||||
break;
|
break;
|
||||||
case PlayerType.FakeHuman:
|
case PlayerType.FakeHuman:
|
||||||
playerType = translateText("player_info_overlay.nation");
|
playerType = translateText("player_type.nation");
|
||||||
break;
|
break;
|
||||||
case PlayerType.Human:
|
case PlayerType.Human:
|
||||||
playerType = translateText("player_info_overlay.player");
|
playerType = translateText("player_type.player");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,24 @@ import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
|
|||||||
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
|
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
|
||||||
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
|
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
|
||||||
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
||||||
|
import stopTradingIcon from "../../../../resources/images/StopIconWhite.png";
|
||||||
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
|
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
|
||||||
import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
|
import startTradingIcon from "../../../../resources/images/TradingIconWhite.png";
|
||||||
import { translateText } from "../../../client/Utils";
|
import traitorIcon from "../../../../resources/images/TraitorIconLightRed.svg";
|
||||||
|
import breakAllianceIcon from "../../../../resources/images/TraitorIconWhite.svg";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { AllPlayers, PlayerActions } from "../../../core/game/Game";
|
import {
|
||||||
|
AllPlayers,
|
||||||
|
PlayerActions,
|
||||||
|
PlayerProfile,
|
||||||
|
PlayerType,
|
||||||
|
Relation,
|
||||||
|
} from "../../../core/game/Game";
|
||||||
import { TileRef } from "../../../core/game/GameMap";
|
import { TileRef } from "../../../core/game/GameMap";
|
||||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||||
import { flattenedEmojiTable } from "../../../core/Util";
|
import { flattenedEmojiTable } from "../../../core/Util";
|
||||||
|
import { actionButton } from "../../components/ui/ActionButton";
|
||||||
|
import "../../components/ui/Divider";
|
||||||
import Countries from "../../data/countries.json";
|
import Countries from "../../data/countries.json";
|
||||||
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
|
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +34,12 @@ import {
|
|||||||
SendEmojiIntentEvent,
|
SendEmojiIntentEvent,
|
||||||
SendTargetPlayerIntentEvent,
|
SendTargetPlayerIntentEvent,
|
||||||
} from "../../Transport";
|
} from "../../Transport";
|
||||||
import { renderDuration, renderNumber, renderTroops } from "../../Utils";
|
import {
|
||||||
|
renderDuration,
|
||||||
|
renderNumber,
|
||||||
|
renderTroops,
|
||||||
|
translateText,
|
||||||
|
} from "../../Utils";
|
||||||
import { UIState } from "../UIState";
|
import { UIState } from "../UIState";
|
||||||
import { ChatModal } from "./ChatModal";
|
import { ChatModal } from "./ChatModal";
|
||||||
import { EmojiTable } from "./EmojiTable";
|
import { EmojiTable } from "./EmojiTable";
|
||||||
@@ -36,9 +51,9 @@ export class PlayerPanel extends LitElement implements Layer {
|
|||||||
public eventBus: EventBus;
|
public eventBus: EventBus;
|
||||||
public emojiTable: EmojiTable;
|
public emojiTable: EmojiTable;
|
||||||
public uiState: UIState;
|
public uiState: UIState;
|
||||||
|
|
||||||
private actions: PlayerActions | null = null;
|
private actions: PlayerActions | null = null;
|
||||||
private tile: TileRef | null = null;
|
private tile: TileRef | null = null;
|
||||||
|
private _profileForPlayerId: number | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
public isVisible: boolean = false;
|
public isVisible: boolean = false;
|
||||||
@@ -46,6 +61,74 @@ export class PlayerPanel extends LitElement implements Layer {
|
|||||||
@state()
|
@state()
|
||||||
private allianceExpiryText: string | null = null;
|
private allianceExpiryText: string | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private allianceExpirySeconds: number | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private otherProfile: PlayerProfile | null = null;
|
||||||
|
|
||||||
|
private ctModal: ChatModal;
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
initEventBus(eventBus: EventBus) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
eventBus.on(CloseViewEvent, (e) => {
|
||||||
|
if (this.isVisible) {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.eventBus.on(MouseUpEvent, () => this.hide());
|
||||||
|
|
||||||
|
this.ctModal = document.querySelector("chat-modal") as ChatModal;
|
||||||
|
if (!this.ctModal) {
|
||||||
|
console.warn("ChatModal element not found in DOM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async tick() {
|
||||||
|
if (this.isVisible && this.tile) {
|
||||||
|
const owner = this.g.owner(this.tile);
|
||||||
|
if (owner && owner.isPlayer()) {
|
||||||
|
const pv = owner as PlayerView;
|
||||||
|
const id = pv.id();
|
||||||
|
// fetch only if we don't have it or the player changed
|
||||||
|
if (this._profileForPlayerId !== Number(id)) {
|
||||||
|
this.otherProfile = await pv.profile();
|
||||||
|
this._profileForPlayerId = Number(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh actions & alliance expiry
|
||||||
|
const myPlayer = this.g.myPlayer();
|
||||||
|
if (myPlayer !== null && myPlayer.isAlive()) {
|
||||||
|
this.actions = await myPlayer.actions(this.tile);
|
||||||
|
if (this.actions?.interaction?.allianceExpiresAt !== undefined) {
|
||||||
|
const expiresAt = this.actions.interaction.allianceExpiresAt;
|
||||||
|
const remainingTicks = expiresAt - this.g.ticks();
|
||||||
|
const remainingSeconds = Math.max(0, Math.floor(remainingTicks / 10)); // 10 ticks per second
|
||||||
|
|
||||||
|
if (remainingTicks > 0) {
|
||||||
|
this.allianceExpirySeconds = remainingSeconds;
|
||||||
|
this.allianceExpiryText = renderDuration(remainingSeconds);
|
||||||
|
} else {
|
||||||
|
this.allianceExpirySeconds = null;
|
||||||
|
this.allianceExpiryText = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.allianceExpirySeconds = null;
|
||||||
|
this.allianceExpiryText = null;
|
||||||
|
}
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public show(actions: PlayerActions, tile: TileRef) {
|
public show(actions: PlayerActions, tile: TileRef) {
|
||||||
this.actions = actions;
|
this.actions = actions;
|
||||||
this.tile = tile;
|
this.tile = tile;
|
||||||
@@ -149,6 +232,13 @@ export class PlayerPanel extends LitElement implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleChat(e: Event, sender: PlayerView, other: PlayerView) {
|
private handleChat(e: Event, sender: PlayerView, other: PlayerView) {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!this.ctModal) {
|
||||||
|
console.warn("ChatModal element not found in DOM");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.ctModal.open(sender, other);
|
this.ctModal.open(sender, other);
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
@@ -159,68 +249,338 @@ export class PlayerPanel extends LitElement implements Layer {
|
|||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
createRenderRoot() {
|
private identityChipProps(type: PlayerType) {
|
||||||
return this;
|
switch (type) {
|
||||||
}
|
case PlayerType.FakeHuman:
|
||||||
|
return {
|
||||||
private ctModal: ChatModal;
|
labelKey: "player_type.nation",
|
||||||
|
aria: "Nation player",
|
||||||
initEventBus(eventBus: EventBus) {
|
classes: "border-indigo-400/25 bg-indigo-500/10 text-indigo-200",
|
||||||
this.eventBus = eventBus;
|
icon: "🏛️",
|
||||||
eventBus.on(CloseViewEvent, (e) => {
|
};
|
||||||
if (!this.hidden) {
|
case PlayerType.Bot:
|
||||||
this.hide();
|
return {
|
||||||
}
|
labelKey: "player_type.bot",
|
||||||
});
|
aria: "Bot",
|
||||||
}
|
classes: "border-purple-400/25 bg-purple-500/10 text-purple-200",
|
||||||
|
icon: "🤖",
|
||||||
init() {
|
};
|
||||||
this.eventBus.on(MouseUpEvent, () => this.hide());
|
case PlayerType.Human:
|
||||||
this.eventBus.on(CloseViewEvent, (e) => {
|
default:
|
||||||
this.hide();
|
return {
|
||||||
});
|
labelKey: "player_type.player",
|
||||||
|
aria: "Human player",
|
||||||
this.ctModal = document.querySelector("chat-modal") as ChatModal;
|
classes: "border-zinc-400/20 bg-zinc-500/5 text-zinc-300",
|
||||||
}
|
icon: "👤",
|
||||||
|
};
|
||||||
async tick() {
|
|
||||||
if (this.isVisible && this.tile) {
|
|
||||||
const myPlayer = this.g.myPlayer();
|
|
||||||
if (myPlayer !== null && myPlayer.isAlive()) {
|
|
||||||
this.actions = await myPlayer.actions(this.tile);
|
|
||||||
if (this.actions?.interaction?.allianceExpiresAt !== undefined) {
|
|
||||||
const expiresAt = this.actions.interaction.allianceExpiresAt;
|
|
||||||
const remainingTicks = expiresAt - this.g.ticks();
|
|
||||||
if (remainingTicks > 0) {
|
|
||||||
const remainingSeconds = Math.max(
|
|
||||||
0,
|
|
||||||
Math.floor(remainingTicks / 10),
|
|
||||||
); // 10 ticks per second
|
|
||||||
this.allianceExpiryText = renderDuration(remainingSeconds);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.allianceExpiryText = null;
|
|
||||||
}
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
private getRelationClass(relation: Relation): string {
|
||||||
if (!this.isVisible) {
|
const base =
|
||||||
return html``;
|
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 " +
|
||||||
|
"shadow-[inset_0_0_8px_rgba(255,255,255,0.04)]";
|
||||||
|
|
||||||
|
switch (relation) {
|
||||||
|
case Relation.Hostile:
|
||||||
|
return `${base} border-red-400/30 bg-red-500/10 text-red-200`;
|
||||||
|
case Relation.Distrustful:
|
||||||
|
return `${base} border-red-300/40 bg-red-300/10 text-red-300`;
|
||||||
|
case Relation.Friendly:
|
||||||
|
return `${base} border-emerald-400/30 bg-emerald-500/10 text-emerald-200`;
|
||||||
|
case Relation.Neutral:
|
||||||
|
default:
|
||||||
|
return `${base} border-zinc-400/30 bg-zinc-500/10 text-zinc-200`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRelationName(relation: Relation): string {
|
||||||
|
switch (relation) {
|
||||||
|
case Relation.Hostile:
|
||||||
|
return translateText("relation.hostile");
|
||||||
|
case Relation.Distrustful:
|
||||||
|
return translateText("relation.distrustful");
|
||||||
|
case Relation.Friendly:
|
||||||
|
return translateText("relation.friendly");
|
||||||
|
case Relation.Neutral:
|
||||||
|
default:
|
||||||
|
return translateText("relation.neutral");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getExpiryColorClass(seconds: number | null): string {
|
||||||
|
if (seconds === null) return "text-white";
|
||||||
|
if (seconds <= 30) return "text-red-400";
|
||||||
|
if (seconds <= 60) return "text-yellow-400";
|
||||||
|
return "text-emerald-400";
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTraitorRemainingSeconds(player: PlayerView): number | null {
|
||||||
|
const ticksLeft = player.data.traitorRemainingTicks ?? 0;
|
||||||
|
if (!player.isTraitor() || ticksLeft <= 0) return null;
|
||||||
|
return Math.ceil(ticksLeft / 10); // 10 ticks = 1 second
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderTraitorBadge(other: PlayerView) {
|
||||||
|
if (!other.isTraitor()) return html``;
|
||||||
|
|
||||||
|
const secs = this.getTraitorRemainingSeconds(other);
|
||||||
|
const label = secs !== null ? renderDuration(secs) : null;
|
||||||
|
const dotCls =
|
||||||
|
secs !== null
|
||||||
|
? `mx-1 h-[4px] w-[4px] rounded-full bg-red-400/70 ${secs <= 10 ? "animate-pulse" : ""}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="mt-1" role="status" aria-live="polite" aria-atomic="true">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center gap-2 rounded-full border border-red-400/30
|
||||||
|
bg-red-500/10 px-2.5 py-0.5 text-sm font-semibold text-red-200
|
||||||
|
shadow-[inset_0_0_8px_rgba(239,68,68,0.12)]"
|
||||||
|
title=${translateText("player_panel.traitor")}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src=${traitorIcon}
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-[18px] w-[18px]"
|
||||||
|
/>
|
||||||
|
<span class="tracking-tight"
|
||||||
|
>${translateText("player_panel.traitor")}</span
|
||||||
|
>
|
||||||
|
${label
|
||||||
|
? html`<span class=${dotCls}></span>
|
||||||
|
<span
|
||||||
|
class="tabular-nums font-bold text-red-100 whitespace-nowrap text-sm"
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</span>`
|
||||||
|
: ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderRelationPillIfNation(other: PlayerView, my: PlayerView) {
|
||||||
|
if (other.type() !== PlayerType.FakeHuman) return html``;
|
||||||
|
if (other.isTraitor()) return html``;
|
||||||
|
if (my?.isAlliedWith && my.isAlliedWith(other)) return html``;
|
||||||
|
if (!this.otherProfile || !my) return html``;
|
||||||
|
|
||||||
|
const relation =
|
||||||
|
this.otherProfile.relations?.[my.smallID()] ?? Relation.Neutral;
|
||||||
|
const cls = this.getRelationClass(relation);
|
||||||
|
const name = this.getRelationName(relation);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="mt-1">
|
||||||
|
<span class="text-sm font-semibold ${cls}">${name}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderIdentityRow(other: PlayerView, my: PlayerView) {
|
||||||
|
const flagCode = other.cosmetics.flag;
|
||||||
|
const country =
|
||||||
|
typeof flagCode === "string"
|
||||||
|
? Countries.find((c) => c.code === flagCode)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const chip =
|
||||||
|
other.type() === PlayerType.Human
|
||||||
|
? null
|
||||||
|
: this.identityChipProps(other.type());
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="flex items-center gap-2.5 flex-wrap">
|
||||||
|
${country && typeof flagCode === "string"
|
||||||
|
? html`<img
|
||||||
|
src="/flags/${encodeURIComponent(flagCode)}.svg"
|
||||||
|
alt=${country?.name || "Flag"}
|
||||||
|
class="h-10 w-10 rounded-full object-cover"
|
||||||
|
@error=${(e: Event) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = "none";
|
||||||
|
}}
|
||||||
|
/>`
|
||||||
|
: ""}
|
||||||
|
<h1 class="text-2xl font-bold tracking-[-0.01em] truncate text-zinc-50">
|
||||||
|
${other.name()}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
${chip
|
||||||
|
? html`<span
|
||||||
|
class=${`inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs font-semibold ${chip.classes}`}
|
||||||
|
role="status"
|
||||||
|
aria-label=${chip.aria}
|
||||||
|
title=${translateText(chip.labelKey)}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" class="leading-none">${chip.icon}</span>
|
||||||
|
<span class="tracking-tight"
|
||||||
|
>${translateText(chip.labelKey)}</span
|
||||||
|
>
|
||||||
|
</span>`
|
||||||
|
: html``}
|
||||||
|
</div>
|
||||||
|
${this.renderTraitorBadge(other)}
|
||||||
|
${this.renderRelationPillIfNation(other, my)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderResources(other: PlayerView) {
|
||||||
|
return html`
|
||||||
|
<div class="mb-1 flex justify-between gap-2">
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-full bg-zinc-800 px-2.5 py-1
|
||||||
|
text-lg font-semibold text-zinc-100"
|
||||||
|
>
|
||||||
|
<span class="mr-0.5">💰</span>
|
||||||
|
<span
|
||||||
|
translate="no"
|
||||||
|
class="inline-block w-[45px] text-right text-zinc-50"
|
||||||
|
>
|
||||||
|
${renderNumber(other.gold() || 0)}
|
||||||
|
</span>
|
||||||
|
<span class="opacity-95">${translateText("player_panel.gold")}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-full bg-zinc-800 px-2.5 py-1
|
||||||
|
text-lg font-semibold text-zinc-100"
|
||||||
|
>
|
||||||
|
<span class="mr-0.5">🛡️</span>
|
||||||
|
<span
|
||||||
|
translate="no"
|
||||||
|
class="inline-block w-[45px] text-right text-zinc-50"
|
||||||
|
>
|
||||||
|
${renderTroops(other.troops() || 0)}
|
||||||
|
</span>
|
||||||
|
<span class="opacity-95"
|
||||||
|
>${translateText("player_panel.troops")}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderStats(other: PlayerView, my: PlayerView) {
|
||||||
|
return html`
|
||||||
|
<!-- Betrayals -->
|
||||||
|
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 font-semibold text-zinc-300 leading-snug"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">⚠️</span>
|
||||||
|
<span>${translateText("player_panel.betrayals")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-right font-semibold text-zinc-200">
|
||||||
|
${other.data.betrayals ?? 0}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Trading / Embargo -->
|
||||||
|
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 font-semibold text-zinc-300 leading-snug"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">⚓</span>
|
||||||
|
<span>${translateText("player_panel.trading")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end gap-2 font-semibold">
|
||||||
|
${other.hasEmbargoAgainst(my)
|
||||||
|
? html`<span class="text-[#f59e0b]"
|
||||||
|
>${translateText("player_panel.stopped")}</span
|
||||||
|
>`
|
||||||
|
: html`<span class="text-emerald-400"
|
||||||
|
>${translateText("player_panel.active")}</span
|
||||||
|
>`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAlliances(other: PlayerView) {
|
||||||
|
const allies = other.allies();
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="text-base select-none">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<div class="font-semibold text-zinc-300 text-base">
|
||||||
|
${translateText("player_panel.alliances")}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
aria-label="Alliance count"
|
||||||
|
class="inline-flex items-center justify-center min-w-[20px] h-5 px-[6px] rounded-[10px]
|
||||||
|
text-[12px] text-zinc-100 bg-white/10 border border-white/20"
|
||||||
|
>
|
||||||
|
${allies.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 rounded-lg border border-zinc-600 bg-zinc-800/80">
|
||||||
|
<div
|
||||||
|
class="max-h-[72px] overflow-y-auto p-2 text-zinc-200 text-[12.5px] leading-relaxed"
|
||||||
|
role="list"
|
||||||
|
aria-label="Alliance list"
|
||||||
|
translate="no"
|
||||||
|
>
|
||||||
|
${allies.length > 0
|
||||||
|
? allies.map((p) => {
|
||||||
|
const color = p.territoryColor().toHex();
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
role="listitem"
|
||||||
|
class="grid grid-cols-[16px_1fr] items-center gap-2 w-full h-[30px]
|
||||||
|
px-2 rounded-lg border border-transparent text-left
|
||||||
|
hover:bg-[#141821] hover:border-white/30 transition-colors"
|
||||||
|
title=${p.name()}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inline-block w-3 h-3 rounded-full mr-2"
|
||||||
|
style="background-color: ${color}"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="truncate select-none pointer-events-none font-medium"
|
||||||
|
>
|
||||||
|
${p.name()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
: html`
|
||||||
|
<div class="py-2 text-zinc-300">
|
||||||
|
${translateText("player_panel.none")}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAllianceExpiry() {
|
||||||
|
if (this.allianceExpiryText === null) return html``;
|
||||||
|
return html`
|
||||||
|
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||||
|
<div class="font-semibold text-zinc-400">
|
||||||
|
${translateText("player_panel.alliance_time_remaining")}
|
||||||
|
</div>
|
||||||
|
<div class="text-right font-semibold">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center rounded-full px-2 py-0.5 text-base font-bold ${this.getExpiryColorClass(
|
||||||
|
this.allianceExpirySeconds,
|
||||||
|
)}"
|
||||||
|
>${this.allianceExpiryText}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderActions(my: PlayerView, other: PlayerView) {
|
||||||
const myPlayer = this.g.myPlayer();
|
const myPlayer = this.g.myPlayer();
|
||||||
if (myPlayer === null) return;
|
|
||||||
if (this.tile === null) return;
|
|
||||||
let other = this.g.owner(this.tile);
|
|
||||||
if (!other.isPlayer()) {
|
|
||||||
this.hide();
|
|
||||||
console.warn("Tile is not owned by a player");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
other = other as PlayerView;
|
|
||||||
|
|
||||||
const canDonateGold = this.actions?.interaction?.canDonateGold;
|
const canDonateGold = this.actions?.interaction?.canDonateGold;
|
||||||
const canDonateTroops = this.actions?.interaction?.canDonateTroops;
|
const canDonateTroops = this.actions?.interaction?.canDonateTroops;
|
||||||
const canSendAllianceRequest =
|
const canSendAllianceRequest =
|
||||||
@@ -233,273 +593,207 @@ export class PlayerPanel extends LitElement implements Layer {
|
|||||||
const canTarget = this.actions?.interaction?.canTarget;
|
const canTarget = this.actions?.interaction?.canTarget;
|
||||||
const canEmbargo = this.actions?.interaction?.canEmbargo;
|
const canEmbargo = this.actions?.interaction?.canEmbargo;
|
||||||
|
|
||||||
//flag icon in the playerPanel
|
return html`
|
||||||
const flagCode = other.cosmetics.flag;
|
<div class="flex flex-col gap-2">
|
||||||
const country =
|
<div class="grid auto-cols-fr grid-flow-col gap-1">
|
||||||
typeof flagCode === "string"
|
${actionButton({
|
||||||
? Countries.find((c) => c.code === flagCode)
|
onClick: (e: MouseEvent) => this.handleChat(e, my, other),
|
||||||
: undefined;
|
icon: chatIcon,
|
||||||
const flagName = country?.name;
|
iconAlt: "Chat",
|
||||||
|
title: translateText("player_panel.chat"),
|
||||||
|
label: translateText("player_panel.chat"),
|
||||||
|
})}
|
||||||
|
${canSendEmoji
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) => this.handleEmojiClick(e, my, other),
|
||||||
|
icon: emojiIcon,
|
||||||
|
iconAlt: "Emoji",
|
||||||
|
title: translateText("player_panel.emotes"),
|
||||||
|
label: translateText("player_panel.emotes"),
|
||||||
|
type: "normal",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
${canTarget
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) => this.handleTargetClick(e, other),
|
||||||
|
icon: targetIcon,
|
||||||
|
iconAlt: "Target",
|
||||||
|
title: translateText("player_panel.target"),
|
||||||
|
label: translateText("player_panel.target"),
|
||||||
|
type: "normal",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
${canDonateTroops
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleDonateTroopClick(e, my, other),
|
||||||
|
icon: donateTroopIcon,
|
||||||
|
iconAlt: "Troops",
|
||||||
|
title: translateText("player_panel.send_troops"),
|
||||||
|
label: translateText("player_panel.troops"),
|
||||||
|
type: "normal",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
${canDonateGold
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleDonateGoldClick(e, my, other),
|
||||||
|
icon: donateGoldIcon,
|
||||||
|
iconAlt: "Gold",
|
||||||
|
title: translateText("player_panel.send_gold"),
|
||||||
|
label: translateText("player_panel.gold"),
|
||||||
|
type: "normal",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid auto-cols-fr grid-flow-col gap-1">
|
||||||
|
${other !== my
|
||||||
|
? canEmbargo
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleEmbargoClick(e, my, other),
|
||||||
|
icon: stopTradingIcon,
|
||||||
|
iconAlt: "Stop Trading",
|
||||||
|
title: translateText("player_panel.stop_trade"),
|
||||||
|
label: translateText("player_panel.stop_trade"),
|
||||||
|
type: "yellow",
|
||||||
|
})
|
||||||
|
: actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleStopEmbargoClick(e, my, other),
|
||||||
|
icon: startTradingIcon,
|
||||||
|
iconAlt: "Start Trading",
|
||||||
|
title: translateText("player_panel.start_trade"),
|
||||||
|
label: translateText("player_panel.start_trade"),
|
||||||
|
type: "green",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
${canBreakAlliance
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleBreakAllianceClick(e, my, other),
|
||||||
|
icon: breakAllianceIcon,
|
||||||
|
iconAlt: "Break Alliance",
|
||||||
|
title: translateText("player_panel.break_alliance"),
|
||||||
|
label: translateText("player_panel.break_alliance"),
|
||||||
|
type: "red",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
${canSendAllianceRequest
|
||||||
|
? actionButton({
|
||||||
|
onClick: (e: MouseEvent) =>
|
||||||
|
this.handleAllianceClick(e, my, other),
|
||||||
|
icon: allianceIcon,
|
||||||
|
iconAlt: "Alliance",
|
||||||
|
title: translateText("player_panel.send_alliance"),
|
||||||
|
label: translateText("player_panel.send_alliance"),
|
||||||
|
type: "indigo",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.isVisible) return html``;
|
||||||
|
|
||||||
|
const my = this.g.myPlayer();
|
||||||
|
if (!my) return html``;
|
||||||
|
if (!this.tile) return html``;
|
||||||
|
|
||||||
|
const owner = this.g.owner(this.tile);
|
||||||
|
if (!owner || !owner.isPlayer()) {
|
||||||
|
this.hide();
|
||||||
|
console.warn("Tile is not owned by a player");
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const other = owner as PlayerView;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
<style>
|
||||||
|
/* Soft glowing ring animation for traitors */
|
||||||
|
.traitor-ring {
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(239, 68, 68, 0.34),
|
||||||
|
0 0 12px 4px rgba(239, 68, 68, 0.22),
|
||||||
|
inset 0 0 14px rgba(239, 68, 68, 0.13);
|
||||||
|
animation: glowPulse 2.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes glowPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(239, 68, 68, 0.22),
|
||||||
|
0 0 8px 2px rgba(239, 68, 68, 0.15),
|
||||||
|
inset 0 0 8px rgba(239, 68, 68, 0.07);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 4px rgba(239, 68, 68, 0.38),
|
||||||
|
0 0 18px 6px rgba(239, 68, 68, 0.26),
|
||||||
|
inset 0 0 18px rgba(239, 68, 68, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 flex items-center justify-center z-[1001] pointer-events-none overflow-auto"
|
class="fixed inset-0 z-[1001] flex items-center justify-center overflow-auto
|
||||||
|
bg-black/40 backdrop-blur-sm backdrop-brightness-110 pointer-events-auto"
|
||||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||||
@wheel=${(e: MouseEvent) => e.stopPropagation()}
|
@wheel=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
|
@click=${() => this.hide()}
|
||||||
>
|
>
|
||||||
|
<!-- Stop clicks inside the panel from closing it -->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-auto max-h-[90vh] overflow-y-auto min-w-[240px] w-auto px-4 py-2"
|
class="pointer-events-auto max-h-[90vh] overflow-y-auto min-w-[240px] w-auto px-4 py-2"
|
||||||
|
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md relative w-full mt-2"
|
class=${`relative mt-2 w-full bg-zinc-900/90 backdrop-blur-sm p-5 shadow-2xl rounded-xl text-zinc-200
|
||||||
|
${other.isTraitor() ? "traitor-ring" : "ring-1 ring-zinc-700"}`}
|
||||||
>
|
>
|
||||||
<!-- Close button -->
|
<!-- Close button -->
|
||||||
<button
|
<button
|
||||||
@click=${this.handleClose}
|
@click=${this.handleClose}
|
||||||
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
|
class="absolute -top-3 -right-3 flex h-7 w-7 items-center justify-center
|
||||||
bg-red-500 hover:bg-red-600 text-white rounded-full
|
rounded-full bg-zinc-700 text-white shadow hover:bg-red-500 transition-colors"
|
||||||
text-sm font-bold transition-colors"
|
aria-label=${translateText("player_panel.close") || "Close"}
|
||||||
|
title=${translateText("player_panel.close") || "Close"}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 min-w-[240px]">
|
<div
|
||||||
<!-- Name section -->
|
class="flex flex-col gap-2 font-sans antialiased text-[14px] leading-relaxed"
|
||||||
<div class="flex items-center gap-1 lg:gap-2">
|
>
|
||||||
<div
|
<!-- Identity (flag, name, type, traitor, relation) -->
|
||||||
class="px-4 h-8 lg:h-10 flex items-center justify-center
|
<div class="mb-1">${this.renderIdentityRow(other, my)}</div>
|
||||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
|
||||||
rounded text-sm lg:text-xl w-full"
|
|
||||||
>
|
|
||||||
${other?.name()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Flag -->
|
|
||||||
${country
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.flag")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="px-4 h-8 lg:h-10 flex items-center justify-center gap-4
|
|
||||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
|
||||||
rounded text-sm lg:text-xl w-full"
|
|
||||||
>
|
|
||||||
${flagName}
|
|
||||||
<img
|
|
||||||
src="/flags/${flagCode}.svg"
|
|
||||||
width="60"
|
|
||||||
height="60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<!-- Resources section -->
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<!-- Gold -->
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.gold")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-opacity-50 bg-gray-700 rounded p-2 text-white"
|
|
||||||
translate="no"
|
|
||||||
>
|
|
||||||
${renderNumber(other.gold() || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<!-- Troops -->
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.troops")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-opacity-50 bg-gray-700 rounded p-2 text-white"
|
|
||||||
translate="no"
|
|
||||||
>
|
|
||||||
${renderTroops(other.troops() || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Attitude section -->
|
<ui-divider></ui-divider>
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.traitor")}
|
|
||||||
</div>
|
|
||||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
|
||||||
${other.isTraitor()
|
|
||||||
? translateText("player_panel.yes")
|
|
||||||
: translateText("player_panel.no")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Betrayals -->
|
<!-- Resources -->
|
||||||
<div class="flex flex-col gap-1">
|
${this.renderResources(other)}
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.betrayals")}
|
|
||||||
</div>
|
|
||||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
|
||||||
${other.data.betrayals ?? 0}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Embargo -->
|
<ui-divider></ui-divider>
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.embargo")}
|
|
||||||
</div>
|
|
||||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
|
||||||
${other.hasEmbargoAgainst(myPlayer)
|
|
||||||
? translateText("player_panel.yes")
|
|
||||||
: translateText("player_panel.no")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Alliances -->
|
<!-- Stats: betrayals / trading -->
|
||||||
<div class="flex flex-col gap-1">
|
${this.renderStats(other, my)}
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.alliances")}
|
|
||||||
(${other.allies().length})
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-opacity-50 bg-gray-700 rounded p-2 text-white max-w-72 max-h-20 overflow-y-auto"
|
|
||||||
translate="no"
|
|
||||||
>
|
|
||||||
${other.allies().length > 0
|
|
||||||
? other
|
|
||||||
.allies()
|
|
||||||
.map((p) => p.name())
|
|
||||||
.join(", ")
|
|
||||||
: translateText("player_panel.none")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this.allianceExpiryText !== null
|
<ui-divider></ui-divider>
|
||||||
? html`
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<div class="text-white text-opacity-80 text-sm px-2">
|
|
||||||
${translateText("player_panel.alliance_time_remaining")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-opacity-50 bg-gray-700 rounded p-2 text-white"
|
|
||||||
>
|
|
||||||
${this.allianceExpiryText}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Alliances list -->
|
||||||
<div class="flex justify-center gap-2">
|
${this.renderAlliances(other)}
|
||||||
<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
<!-- Alliance time remaining -->
|
||||||
this.handleChat(e, myPlayer, other)}
|
${this.renderAllianceExpiry()}
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
<ui-divider></ui-divider>
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
<!-- Actions -->
|
||||||
<img src=${chatIcon} alt="Target" class="w-6 h-6" />
|
${this.renderActions(my, other)}
|
||||||
</button>
|
|
||||||
${canTarget
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleTargetClick(e, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img src=${targetIcon} alt="Target" class="w-6 h-6" />
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${canBreakAlliance
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleBreakAllianceClick(e, myPlayer, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${traitorIcon}
|
|
||||||
alt="Break Alliance"
|
|
||||||
class="w-6 h-6"
|
|
||||||
/>
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${canSendAllianceRequest
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleAllianceClick(e, myPlayer, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img src=${allianceIcon} alt="Alliance" class="w-6 h-6" />
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${canDonateTroops
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleDonateTroopClick(e, myPlayer, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src=${donateTroopIcon}
|
|
||||||
alt="Donate"
|
|
||||||
class="w-6 h-6"
|
|
||||||
/>
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${canDonateGold
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleDonateGoldClick(e, myPlayer, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img src=${donateGoldIcon} alt="Donate" class="w-6 h-6" />
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${canSendEmoji
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleEmojiClick(e, myPlayer, other)}
|
|
||||||
class="w-10 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<img src=${emojiIcon} alt="Emoji" class="w-6 h-6" />
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
${canEmbargo && other !== myPlayer
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleEmbargoClick(e, myPlayer, other)}
|
|
||||||
class="w-100 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
${translateText("player_panel.stop_trade")}
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
${!canEmbargo && other !== myPlayer
|
|
||||||
? html`<button
|
|
||||||
@click=${(e: MouseEvent) =>
|
|
||||||
this.handleStopEmbargoClick(e, myPlayer, other)}
|
|
||||||
class="w-100 h-10 flex items-center justify-center
|
|
||||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
|
||||||
text-white rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
${translateText("player_panel.start_trade")}
|
|
||||||
</button>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export interface PlayerUpdate {
|
|||||||
allies: number[];
|
allies: number[];
|
||||||
embargoes: Set<PlayerID>;
|
embargoes: Set<PlayerID>;
|
||||||
isTraitor: boolean;
|
isTraitor: boolean;
|
||||||
|
traitorRemainingTicks?: number;
|
||||||
targets: number[];
|
targets: number[];
|
||||||
outgoingEmojis: EmojiMessage[];
|
outgoingEmojis: EmojiMessage[];
|
||||||
outgoingAttacks: AttackUpdate[];
|
outgoingAttacks: AttackUpdate[];
|
||||||
|
|||||||
@@ -404,6 +404,9 @@ export class PlayerView {
|
|||||||
isTraitor(): boolean {
|
isTraitor(): boolean {
|
||||||
return this.data.isTraitor;
|
return this.data.isTraitor;
|
||||||
}
|
}
|
||||||
|
getTraitorRemainingTicks(): number {
|
||||||
|
return Math.max(0, this.data.traitorRemainingTicks ?? 0);
|
||||||
|
}
|
||||||
outgoingEmojis(): EmojiMessage[] {
|
outgoingEmojis(): EmojiMessage[] {
|
||||||
return this.data.outgoingEmojis;
|
return this.data.outgoingEmojis;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ export class PlayerImpl implements Player {
|
|||||||
allies: this.alliances().map((a) => a.other(this).smallID()),
|
allies: this.alliances().map((a) => a.other(this).smallID()),
|
||||||
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
|
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
|
||||||
isTraitor: this.isTraitor(),
|
isTraitor: this.isTraitor(),
|
||||||
|
traitorRemainingTicks: this.getTraitorRemainingTicks(),
|
||||||
targets: this.targets().map((p) => p.smallID()),
|
targets: this.targets().map((p) => p.smallID()),
|
||||||
outgoingEmojis: this.outgoingEmojis(),
|
outgoingEmojis: this.outgoingEmojis(),
|
||||||
outgoingAttacks: this._outgoingAttacks.map((a) => {
|
outgoingAttacks: this._outgoingAttacks.map((a) => {
|
||||||
@@ -418,11 +419,15 @@ export class PlayerImpl implements Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isTraitor(): boolean {
|
isTraitor(): boolean {
|
||||||
return (
|
return this.getTraitorRemainingTicks() > 0;
|
||||||
this.markedTraitorTick >= 0 &&
|
}
|
||||||
this.mg.ticks() - this.markedTraitorTick <
|
|
||||||
this.mg.config().traitorDuration()
|
getTraitorRemainingTicks(): number {
|
||||||
);
|
if (this.markedTraitorTick < 0) return 0;
|
||||||
|
const elapsed = this.mg.ticks() - this.markedTraitorTick;
|
||||||
|
const duration = this.mg.config().traitorDuration();
|
||||||
|
const remaining = duration - elapsed;
|
||||||
|
return remaining > 0 ? remaining : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
markTraitor(): void {
|
markTraitor(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user