From 4e48eba910e3d68357f6d1d736c8ae14989fb99c Mon Sep 17 00:00:00 2001 From: DiesselOne <49088589+DiesselOne@users.noreply.github.com> Date: Tue, 1 Jul 2025 04:49:42 +0200 Subject: [PATCH] Better In Game UI (#1243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Top Bar Refactor – UI & UX Improvement Proposal This update overhauls the top game bar to improve clarity, responsiveness, and overall user experience across desktop and mobile. It consolidates player resources (e.g., building counts), integrates game controls (pause, settings, time), and enhances visual contrast. Key changes: Redesigned top bar with player data and game options. Team color indicator bar (team games only). Countdown bar during "Choose Starting Position" phase. Removed redundant info (e.g., troop/worker counts shown elsewhere). Inspired by strategy games like Travian Legends, this refactor offers a cleaner and more intuitive layout, especially for smaller screens. ⚠️ Note: This is a large change and likely contains visual or functional bugs I can’t currently spot due to fatigue. Thorough testing is required before approval. ## 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 - [ ] 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: Diessel ![Snímek obrazovky 2025-06-21 v 8 13 46](https://github.c ![Snímek obrazovky 2025-06-21 v 8 13 35](https://github.com/user-attachments/assets/e166ee1b-6173-48f5-8e2d-598d796a7e2d) om/user-attachments/assets/3a0edbef-e621-4fc4-b6b7-c1ed ![Snímek obrazovky 2025-06-21 v 8 13 20](https://github.com/user-attachments/assets/1214ab61-323c-4317-8722-eaa4b932a60c) 8f9a8219) ![Snímek obrazovky 2025-06-21 v 8 10 04](https://github.com/user-attachments/assets/374fe15a-bfad-4469-9950-3ec631b6e2d3) Closes #1165 --------- Co-authored-by: Scott Anderson Co-authored-by: evanpelle --- resources/images/DarkModeIconWhite.svg | 1 + resources/images/EmojiIconWhite.svg | 259 +--------- resources/images/ExitIconWhite.svg | 1 + resources/images/ExplosionIconWhite.svg | 1 + resources/images/FocusIconWhite.svg | 1 + resources/images/InfoIconRegularWhite.svg | 1 + resources/images/InfoIconSolidWhite.svg | 1 + .../images/LeaderboardIconRegularWhite.svg | 1 + .../images/LeaderboardIconSolidWhite.svg | 1 + resources/images/MouseIconWhite.svg | 1 + resources/images/NinjaIconWhite.svg | 1 + resources/images/PauseIconWhite.svg | 1 + resources/images/PlayIconWhite.svg | 1 + resources/images/PopulationIconSolidWhite.svg | 1 + resources/images/ReplayRegularIconWhite.svg | 1 + resources/images/ReplaySolidIconWhite.svg | 1 + resources/images/SamLauncherUnitWhite.png | Bin 0 -> 1992 bytes resources/images/TeamIconRegularWhite.svg | 1 + resources/images/TeamIconSolidWhite.svg | 1 + resources/images/TreeIconWhite.svg | 1 + resources/images/TroopIconWhite.svg | 4 + resources/images/WorkerIconBlack.svg | 1 + resources/images/WorkerIconWhite.svg | 4 + resources/lang/ar.json | 2 +- resources/lang/bg.json | 2 +- resources/lang/cs.json | 2 +- resources/lang/en.json | 12 +- resources/lang/eo.json | 2 +- resources/lang/es.json | 2 +- resources/lang/fr.json | 2 +- resources/lang/he.json | 2 +- resources/lang/it.json | 2 +- resources/lang/nl.json | 2 +- resources/lang/pl.json | 2 +- resources/lang/ru.json | 2 +- resources/lang/tp.json | 2 +- resources/lang/uk.json | 2 +- src/client/Main.ts | 2 +- src/client/graphics/GameRenderer.ts | 33 +- .../graphics/icons/LeaderboardRegularIcon.ts | 35 -- .../graphics/icons/LeaderboardSolidIcon.ts | 35 -- src/client/graphics/icons/TeamRegularIcon.ts | 35 -- src/client/graphics/icons/TeamSolidIcon.ts | 35 -- src/client/graphics/layers/ControlPanel.ts | 89 +--- src/client/graphics/layers/GameLeftSidebar.ts | 38 +- .../graphics/layers/GameRightSidebar.ts | 139 ++++++ src/client/graphics/layers/GameTopBar.ts | 471 ++++++++++++++++++ src/client/graphics/layers/HeadsUpMessage.ts | 4 +- .../graphics/layers/PlayerInfoOverlay.ts | 4 +- src/client/graphics/layers/ReplayPanel.ts | 112 ++--- src/client/graphics/layers/SpawnTimer.ts | 35 +- src/client/graphics/layers/TopBar.ts | 93 ---- src/client/index.html | 11 +- src/client/styles.css | 8 + src/core/game/GameView.ts | 7 + 55 files changed, 795 insertions(+), 715 deletions(-) create mode 100644 resources/images/DarkModeIconWhite.svg create mode 100644 resources/images/ExitIconWhite.svg create mode 100644 resources/images/ExplosionIconWhite.svg create mode 100644 resources/images/FocusIconWhite.svg create mode 100644 resources/images/InfoIconRegularWhite.svg create mode 100644 resources/images/InfoIconSolidWhite.svg create mode 100644 resources/images/LeaderboardIconRegularWhite.svg create mode 100644 resources/images/LeaderboardIconSolidWhite.svg create mode 100644 resources/images/MouseIconWhite.svg create mode 100644 resources/images/NinjaIconWhite.svg create mode 100644 resources/images/PauseIconWhite.svg create mode 100644 resources/images/PlayIconWhite.svg create mode 100644 resources/images/PopulationIconSolidWhite.svg create mode 100644 resources/images/ReplayRegularIconWhite.svg create mode 100644 resources/images/ReplaySolidIconWhite.svg create mode 100644 resources/images/SamLauncherUnitWhite.png create mode 100644 resources/images/TeamIconRegularWhite.svg create mode 100644 resources/images/TeamIconSolidWhite.svg create mode 100644 resources/images/TreeIconWhite.svg create mode 100644 resources/images/TroopIconWhite.svg create mode 100644 resources/images/WorkerIconBlack.svg create mode 100644 resources/images/WorkerIconWhite.svg delete mode 100644 src/client/graphics/icons/LeaderboardRegularIcon.ts delete mode 100644 src/client/graphics/icons/LeaderboardSolidIcon.ts delete mode 100644 src/client/graphics/icons/TeamRegularIcon.ts delete mode 100644 src/client/graphics/icons/TeamSolidIcon.ts create mode 100644 src/client/graphics/layers/GameRightSidebar.ts create mode 100644 src/client/graphics/layers/GameTopBar.ts delete mode 100644 src/client/graphics/layers/TopBar.ts diff --git a/resources/images/DarkModeIconWhite.svg b/resources/images/DarkModeIconWhite.svg new file mode 100644 index 000000000..dfaa4880e --- /dev/null +++ b/resources/images/DarkModeIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/EmojiIconWhite.svg b/resources/images/EmojiIconWhite.svg index f60610a35..08da8d0f5 100644 --- a/resources/images/EmojiIconWhite.svg +++ b/resources/images/EmojiIconWhite.svg @@ -1,258 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/resources/images/ExitIconWhite.svg b/resources/images/ExitIconWhite.svg new file mode 100644 index 000000000..7a963caa1 --- /dev/null +++ b/resources/images/ExitIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/ExplosionIconWhite.svg b/resources/images/ExplosionIconWhite.svg new file mode 100644 index 000000000..8eb4bd102 --- /dev/null +++ b/resources/images/ExplosionIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/FocusIconWhite.svg b/resources/images/FocusIconWhite.svg new file mode 100644 index 000000000..d198044a6 --- /dev/null +++ b/resources/images/FocusIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/InfoIconRegularWhite.svg b/resources/images/InfoIconRegularWhite.svg new file mode 100644 index 000000000..134c3e502 --- /dev/null +++ b/resources/images/InfoIconRegularWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/InfoIconSolidWhite.svg b/resources/images/InfoIconSolidWhite.svg new file mode 100644 index 000000000..f932784ec --- /dev/null +++ b/resources/images/InfoIconSolidWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/LeaderboardIconRegularWhite.svg b/resources/images/LeaderboardIconRegularWhite.svg new file mode 100644 index 000000000..173cd60cb --- /dev/null +++ b/resources/images/LeaderboardIconRegularWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/LeaderboardIconSolidWhite.svg b/resources/images/LeaderboardIconSolidWhite.svg new file mode 100644 index 000000000..8926616c2 --- /dev/null +++ b/resources/images/LeaderboardIconSolidWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/MouseIconWhite.svg b/resources/images/MouseIconWhite.svg new file mode 100644 index 000000000..fcbe98b5d --- /dev/null +++ b/resources/images/MouseIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/NinjaIconWhite.svg b/resources/images/NinjaIconWhite.svg new file mode 100644 index 000000000..e46699b7e --- /dev/null +++ b/resources/images/NinjaIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/PauseIconWhite.svg b/resources/images/PauseIconWhite.svg new file mode 100644 index 000000000..e803fcb7e --- /dev/null +++ b/resources/images/PauseIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/PlayIconWhite.svg b/resources/images/PlayIconWhite.svg new file mode 100644 index 000000000..773dd690e --- /dev/null +++ b/resources/images/PlayIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/PopulationIconSolidWhite.svg b/resources/images/PopulationIconSolidWhite.svg new file mode 100644 index 000000000..afe475e60 --- /dev/null +++ b/resources/images/PopulationIconSolidWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/ReplayRegularIconWhite.svg b/resources/images/ReplayRegularIconWhite.svg new file mode 100644 index 000000000..10a1f799d --- /dev/null +++ b/resources/images/ReplayRegularIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/ReplaySolidIconWhite.svg b/resources/images/ReplaySolidIconWhite.svg new file mode 100644 index 000000000..2e731ccf3 --- /dev/null +++ b/resources/images/ReplaySolidIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/SamLauncherUnitWhite.png b/resources/images/SamLauncherUnitWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..5a282e98de85564427fa685bd37596663ed02489 GIT binary patch literal 1992 zcmah~Yfuwc6kfC_pa`f1gwgJVqK-f|glConL=qvEAo7p^RvVH{vgWZNyDuMGfl+W0Cd1VnT5oL~4aOxLTAYu7A<%?iay%?mi$$kK#E4TBVwQv!6lldZpg=%{ zQ6ku&QmRSRz@eFRQSjfmjnH5dg;H>6LO~=PLTEAAhwejX&;qSswpJoVqqr-_jKPXS zlT(xiMG(DSPuF|V39StAWU*KXgNZPi9)RINrl={A!9z{D7$rtExELwc;u;Dk)UZ)g zltkz#4vhwIcubtar4#CEa_smZ5r`2%Jn0PNbvkCi-$6GbQ|KBwK@p^!(2O5ol8bS8 ziXlkf+f582&D+QV!GF_Ls!7KnDc&ZKh_R$jis_XDGA2cXAyF7f=(J*tw+T~IE~ZQv z^MdkKA{iErsb!R$L-X`~1Bk;VZ|)m~lsaQ2V%0cs&73e1PGB6G9~1GMfa2c@8B>i$ z;s#79NC?>%8cYq;;lF&wjYmEUTldthQpe9T}NC4#Z2E-2uiPREOTnR8T zDs%;d@MbVf$|g4G z1&N3f{U6_WqhU6%5D3sPoTLbCifL=~nnz>FqpQ)S5;xs%7&cWI6^V^m=g>4-LZTC6 z5|eWvH;R&kl+uf|Sg;Hfm_rMeN^wx16gbj&b73aTq zU!|>@pZA4Ao#Wh}x;{F;o5gwfyr`Ge+`?)$Q}`b`I8c!uCetR!J02Dlf35$$9)QF-DxxJvi4qPuXIYB9ycU&FXH%CJ30pmFLXC; zdHOirc_=iz-+9`$eyGVuHeB}cx4eap;%Ts5-X_>#T1l<7Yc`deI45t}J}A1%dQ1D9 z-ull^LHBBoZrJA9cfWjhKyl-Nev3Vg$2&Im)R$iy(qzMb*mW2R4Sz1)V|SXIS$|z| zZs1^ZgGExkB!2M5iM}Q`L4(Bur*FDaUf!{DKXbh=E~9nhKxOF~pZz@xZEUH9;Vu2V zd2Pc(fdf4ki*ILVSGN5X`a*u-L1V(r-0b diff --git a/resources/images/TeamIconSolidWhite.svg b/resources/images/TeamIconSolidWhite.svg new file mode 100644 index 000000000..10eca9b55 --- /dev/null +++ b/resources/images/TeamIconSolidWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/TreeIconWhite.svg b/resources/images/TreeIconWhite.svg new file mode 100644 index 000000000..c30908a51 --- /dev/null +++ b/resources/images/TreeIconWhite.svg @@ -0,0 +1 @@ + diff --git a/resources/images/TroopIconWhite.svg b/resources/images/TroopIconWhite.svg new file mode 100644 index 000000000..5f1d53bd1 --- /dev/null +++ b/resources/images/TroopIconWhite.svg @@ -0,0 +1,4 @@ + + + diff --git a/resources/images/WorkerIconBlack.svg b/resources/images/WorkerIconBlack.svg new file mode 100644 index 000000000..3852227a4 --- /dev/null +++ b/resources/images/WorkerIconBlack.svg @@ -0,0 +1 @@ + diff --git a/resources/images/WorkerIconWhite.svg b/resources/images/WorkerIconWhite.svg new file mode 100644 index 000000000..5129642bc --- /dev/null +++ b/resources/images/WorkerIconWhite.svg @@ -0,0 +1,4 @@ + + + diff --git a/resources/lang/ar.json b/resources/lang/ar.json index 68492030d..1c608031c 100644 --- a/resources/lang/ar.json +++ b/resources/lang/ar.json @@ -214,7 +214,7 @@ "emojis_desc": "تبديل ما إذا كانت الرموز التعبيرية تظهر في اللعبة", "anonymous_names_label": "🥷 الأسماء المخفية", "anonymous_names_desc": "إخفاء أسماء اللاعبين الحقيقية باستخدام أسماء عشوائية على شاشتك", - "left_click_label": "🖱️ النقر الأيسر لفتح القائمة", + "left_click_label": " النقر الأيسر لفتح القائمة", "left_click_desc": "عند التفعيل، النقر الأيسر يفتح القائمة وزر السيف للهجوم. عند التعطيل، النقر الأيسر يهاجم مباشرة", "attack_ratio_label": "⚔️ نسبة الهجوم", "attack_ratio_desc": "نسبة القوات المُرسلة في الهجوم (1–100%)", diff --git a/resources/lang/bg.json b/resources/lang/bg.json index 5889cbf2e..ced1fcb61 100644 --- a/resources/lang/bg.json +++ b/resources/lang/bg.json @@ -230,7 +230,7 @@ "special_effects_desc": "Превключване на специалните ефекти. Деактивиране, за да се увеличи производителността", "anonymous_names_label": "🥷 Скрити имена", "anonymous_names_desc": "Скриване на истинските имена на играчите с произволни такива на екрана ти.", - "left_click_label": "🖱️ Щтракване на ляв бутон, за да се отвори менюто", + "left_click_label": "Щтракване на ляв бутон, за да се отвори менюто", "left_click_desc": "Когато е ВКЛЮЧЕНО, щракването с ляв бутон отваря менюто и атаките се извършват чрез бутона на меч. Когато е ИЗКЛЮЧЕНО, щракването с ляв бутон атакува директно.", "attack_ratio_label": "⚔️ Съотношение на атака", "attack_ratio_desc": "Какъв процент от Вашите войници да се изпратят в атака (1–100%)", diff --git a/resources/lang/cs.json b/resources/lang/cs.json index bc3e827ef..2f7410f20 100644 --- a/resources/lang/cs.json +++ b/resources/lang/cs.json @@ -230,7 +230,7 @@ "special_effects_desc": "Určuje zda jsou zapnuté speciální efekty. Deaktivuj pro zlepšení výkonu", "anonymous_names_label": "🥷 Skrýt jména", "anonymous_names_desc": "Skrýt skutečná jména hráčů a nahradit je náhodnými názvy na vaší obrazovce.", - "left_click_label": "🖱️ Levým tlačítkem myši Otevřít Menu", + "left_click_label": "Levým tlačítkem myši Otevřít Menu", "left_click_desc": "Pokud je zapnuto otevře \"Levé tlačítko\" myši menu a \"Útok\" tlačítko zaútočí. Pokud je vypnuto, útočí se přímo levým tlačítkem myši.", "attack_ratio_label": "⚔️ Poměr útoku", "attack_ratio_desc": "Jaké procento tvých vojáků poslat při útoku (1–100%)", diff --git a/resources/lang/en.json b/resources/lang/en.json index 9191490b4..244d1f426 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -247,17 +247,17 @@ "title": "User Settings", "tab_basic": "Basic Settings", "tab_keybinds": "Keybinds", - "dark_mode_label": "🌙 Dark Mode", + "dark_mode_label": "Dark Mode", "dark_mode_desc": "Toggle the site’s appearance between light and dark themes", - "emojis_label": "😊 Emojis", + "emojis_label": "Emojis", "emojis_desc": "Toggle whether emojis are shown in game", - "alert_frame_label": "🚨 Alert Frame", + "alert_frame_label": "Alert Frame", "alert_frame_desc": "Toggle the alert frame. When enabled, the frame will be displayed when you are betrayed.", - "special_effects_label": "💥 Special effects", + "special_effects_label": "Special effects", "special_effects_desc": "Toggle special effects. Deactivate to improve performances", - "anonymous_names_label": "🥷 Hidden Names", + "anonymous_names_label": "Hidden Names", "anonymous_names_desc": "Hide real player names with random ones on your screen.", - "left_click_label": "🖱️ Left Click to Open Menu", + "left_click_label": "Left Click to Open Menu", "left_click_desc": "When ON, left-click opens menu and sword button attacks. When OFF, left-click attacks directly.", "attack_ratio_label": "⚔️ Attack Ratio", "attack_ratio_desc": "What percentage of your troops to send in an attack (1–100%)", diff --git a/resources/lang/eo.json b/resources/lang/eo.json index 937c035e7..fbd9077cd 100644 --- a/resources/lang/eo.json +++ b/resources/lang/eo.json @@ -230,7 +230,7 @@ "special_effects_desc": "Baskuli specialajn efektojn. Malaktivigu por plibonigi funkciadon", "anonymous_names_label": "🥷 Maskitaj Nomoj", "anonymous_names_desc": "Kaŝi verajn uzantnomojn kun hazardaj sur via ekrano.", - "left_click_label": "🖱️Maldekstra alklako por malfermi menuon", + "left_click_label": "Maldekstra alklako por malfermi menuon", "left_click_desc": "Kiam aktiviga, maldekstra alklako malfermas menuon kaj glava atakbutono. Kiam malaktiviga, maldekstra alklako atakas direkten.", "attack_ratio_label": "⚔️ Atakkvociento", "attack_ratio_desc": "Kian procenton de viaj trupoj sendi en atako (1–100%)", diff --git a/resources/lang/es.json b/resources/lang/es.json index 5ff191327..93c877be5 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -230,7 +230,7 @@ "special_effects_desc": "Activa los efectos especiales. Desactiva para mejorar el rendimiento", "anonymous_names_label": "🥷 Nombres ocultos", "anonymous_names_desc": "Oculta los nombres de jugadores reales con nombres aleatorios en tu pantalla.", - "left_click_label": "🖱️ Clic izquierdo para abrir el menú", + "left_click_label": "Clic izquierdo para abrir el menú", "left_click_desc": "Cuando está activado, el clic izquierdo abre el menú y el botón de espada ataca. Cuando está desactivado, el clic izquierdo ataca directamente.", "attack_ratio_label": "⚔️ Ratio de ataque", "attack_ratio_desc": "Porcentaje de tus tropas que se envían en un ataque (1–100%)", diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 4514eaf13..d6edbf01b 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -230,7 +230,7 @@ "special_effects_desc": "Activer/désactiver les effets spéciaux. Désactiver pour améliorer les performances", "anonymous_names_label": "🥷 Noms masqués", "anonymous_names_desc": "Cacher le vrai nom des joueurs avec des noms aléatoires sur votre écran.", - "left_click_label": "🖱️ Clic gauche pour ouvrir le menu", + "left_click_label": "Clic gauche pour ouvrir le menu", "left_click_desc": "Activé, un clic gauche ouvre le menu et le bouton épée d'attaque. Désactivé, un clic gauche attaque directement.", "attack_ratio_label": "⚔️ Ratio d'attaque", "attack_ratio_desc": "Quel pourcentage de vos troupes envoyer dans une attaque (1–100%)", diff --git a/resources/lang/he.json b/resources/lang/he.json index 87f1c4845..407b73ba3 100644 --- a/resources/lang/he.json +++ b/resources/lang/he.json @@ -230,7 +230,7 @@ "special_effects_desc": "מאפשר הצגה של אפקטים מיוחדים. בטל כדי לשפר את הביצועים של המשחק.", "anonymous_names_label": "🥷 שמות חבויים", "anonymous_names_desc": "החלף את שמות השחקנים האמיתיים עם שמות רנדומליים במסך שלך.", - "left_click_label": "לחצן שמאלי לפתיחת תפריט 🖱️", + "left_click_label": "לחצן שמאלי לפתיחת תפריט", "left_click_desc": "כאשר מופעל, הלחצן השמאלי פותח את התפריט וכפתור החרב תוקף. כאשר מכובה, הלחצן השמאלי תוקף ישירות.", "attack_ratio_label": "יחס תקיפה ⚔️", "attack_ratio_desc": "כמה אחוז מהחיילים שלך לשלוח בהתקפה (1-100%)", diff --git a/resources/lang/it.json b/resources/lang/it.json index 65d2e181b..555aec201 100644 --- a/resources/lang/it.json +++ b/resources/lang/it.json @@ -230,7 +230,7 @@ "special_effects_desc": "Attiva/disattiva gli effetti speciali. Disattiva per migliorare le prestazioni", "anonymous_names_label": "🥷 Nomi Nascosti", "anonymous_names_desc": "Nascondi i nomi dei giocatori reali sullo schermo con nomi casuali.", - "left_click_label": "🖱️ Clic sinistro per aprire il menu", + "left_click_label": "Clic sinistro per aprire il menu", "left_click_desc": "Quando attivo, il clic sinistro apre il menu e il pulsante di attacco. Quando non attivo, fare clic con il tasto sinistro attaccherà direttamente.", "attack_ratio_label": "⚔️ Rapporto Di Attacco", "attack_ratio_desc": "Quale percentuale delle tue truppe inviare in attacco (1–100%)", diff --git a/resources/lang/nl.json b/resources/lang/nl.json index 270537185..248d8b5cf 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -230,7 +230,7 @@ "special_effects_desc": "Speciale effecten aanzetten. Zet uit om de prestaties van het spel te verbeteren", "anonymous_names_label": "🥷 Verborgen namen", "anonymous_names_desc": "Vervang echte spelersnamen door willekeurige namen op je scherm.", - "left_click_label": "🖱️ Linkermuisknop voor openen menu", + "left_click_label": "Linkermuisknop voor openen menu", "left_click_desc": "Als AAN: linkermuisknop opent het Radiale menu met zwaard-aanvalsknop. Als UIT: linkermuisknop opent direct de aanval.", "attack_ratio_label": "⚔️ Aanvalsverhouding", "attack_ratio_desc": "Welk percentage van je troepen je bij een aanval stuurt (1-100%)", diff --git a/resources/lang/pl.json b/resources/lang/pl.json index f5bf6b993..92d9b2cfb 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -214,7 +214,7 @@ "emojis_desc": "Przełącz, czy emotki mają być wyświetlane w grze", "anonymous_names_label": "🥷 Ukryte Nazwy", "anonymous_names_desc": "Ukryj prawdziwe nazwy graczy, zastępując je losowymi na swoim ekranie.", - "left_click_label": "🖱️ Kliknij lewym przyciskiem myszy aby otworzyć Menu", + "left_click_label": "Kliknij lewym przyciskiem myszy aby otworzyć Menu", "left_click_desc": "Gdy WŁĄCZONE, lewy przycisk myszy otwiera menu, a przycisk z mieczem atakuje. Gdy WYŁĄCZONE, lewy przycisk myszy atakuje bezpośrednio.", "attack_ratio_label": "⚔️ Współczynnik ataku", "attack_ratio_desc": "Jaki procent swoich żołnierzy chcesz wysłać do ataku (1–100%)", diff --git a/resources/lang/ru.json b/resources/lang/ru.json index 3bb5995b9..6da44d1f3 100644 --- a/resources/lang/ru.json +++ b/resources/lang/ru.json @@ -230,7 +230,7 @@ "special_effects_desc": "Переключить специальные эффекты. Отключите для улучшения производительности", "anonymous_names_label": "🥷 Скрытые имена", "anonymous_names_desc": "Скрыть настоящие имена игроков и заменить их случайными.", - "left_click_label": "🖱️ Открытие меню левой кнопкой мыши", + "left_click_label": "Открытие меню левой кнопкой мыши", "left_click_desc": "ВКЛЮЧЕНО: щелчок левой кнопкой мыши открывает меню, атака совершается кнопкой с мечом. ВЫКЛЮЧЕНО: нажатие левой кнопкой мыши совершает атаку напрямую.", "attack_ratio_label": "⚔️ Соотношение атаки", "attack_ratio_desc": "Какой процент ваших войск отправлять в бой (1–100%)", diff --git a/resources/lang/tp.json b/resources/lang/tp.json index c27219372..5cfeb1bbe 100644 --- a/resources/lang/tp.json +++ b/resources/lang/tp.json @@ -214,7 +214,7 @@ "emojis_desc": "o ante e lon pi sitelen Emosi", "anonymous_names_label": "🥷 nimi len", "anonymous_names_desc": "o len e nimi lon pi jan musi kepeken nimi nasa lon musi sina", - "left_click_label": "🖱️ ilo luka soto li open e lipu ken", + "left_click_label": "ilo luka soto li open e lipu ken", "left_click_desc": "ni li lon la ilo luka soto li open e lipu ken la ilo luka utala li utala. ni li pini la ilo luka soto li utala taso.", "attack_ratio_label": "⚔️ nanpa utala", "attack_ratio_desc": "sina utala kepeken kulupu utala la o pana e kulupu suli ni (kipisi wan tawa wan ale)", diff --git a/resources/lang/uk.json b/resources/lang/uk.json index 5f2f95a63..e54eabec3 100644 --- a/resources/lang/uk.json +++ b/resources/lang/uk.json @@ -230,7 +230,7 @@ "special_effects_desc": "Перемкнути спеціальні ефекти. Вимкніть для поліпшення продуктивності", "anonymous_names_label": "🥷 Приховані імена", "anonymous_names_desc": "Приховати справжні імена гравців і замінити їх випадковими.", - "left_click_label": "🖱️ Відкриття меню лівою кнопкою миші", + "left_click_label": "Відкриття меню лівою кнопкою миші", "left_click_desc": "УВІМКНЕНО: лівий клац відкриває меню, атака здійснюється кнопкою з мечем. ВИМКНЕНО: лівий клац здійснює атаку напряму.", "attack_ratio_label": "⚔️ Співвідношення атаки", "attack_ratio_desc": "Який відсоток ваших військ відправляти в напад (1–100%)", diff --git a/src/client/Main.ts b/src/client/Main.ts index a25a1cd0d..7cc946362 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -366,7 +366,7 @@ class Client { "host-lobby-modal", "join-private-lobby-modal", "game-starting-modal", - "top-bar", + "game-top-bar", "help-modal", "user-setting", ].forEach((tag) => { diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index f61de2d5a..7fc0f547f 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -14,6 +14,8 @@ import { EmojiTable } from "./layers/EmojiTable"; import { EventsDisplay } from "./layers/EventsDisplay"; import { FxLayer } from "./layers/FxLayer"; import { GameLeftSidebar } from "./layers/GameLeftSidebar"; +import { GameRightSidebar } from "./layers/GameRightSidebar"; +import { GameTopBar } from "./layers/GameTopBar"; import { GutterAdModal } from "./layers/GutterAdModal"; import { HeadsUpMessage } from "./layers/HeadsUpMessage"; import { Layer } from "./layers/Layer"; @@ -21,7 +23,6 @@ import { Leaderboard } from "./layers/Leaderboard"; import { MainRadialMenu } from "./layers/MainRadialMenu"; import { MultiTabModal } from "./layers/MultiTabModal"; import { NameLayer } from "./layers/NameLayer"; -import { OptionsMenu } from "./layers/OptionsMenu"; import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { PlayerPanel } from "./layers/PlayerPanel"; import { RailroadLayer } from "./layers/RailroadLayer"; @@ -33,7 +34,6 @@ import { StructureLayer } from "./layers/StructureLayer"; import { TeamStats } from "./layers/TeamStats"; import { TerrainLayer } from "./layers/TerrainLayer"; import { TerritoryLayer } from "./layers/TerritoryLayer"; -import { TopBar } from "./layers/TopBar"; import { UILayer } from "./layers/UILayer"; import { UnitInfoModal } from "./layers/UnitInfoModal"; import { UnitLayer } from "./layers/UnitLayer"; @@ -135,25 +135,28 @@ export function createRenderer( winModal.eventBus = eventBus; winModal.game = game; - const optionsMenu = document.querySelector("options-menu") as OptionsMenu; - if (!(optionsMenu instanceof OptionsMenu)) { - console.error("options menu not found"); - } - optionsMenu.eventBus = eventBus; - optionsMenu.game = game; - const replayPanel = document.querySelector("replay-panel") as ReplayPanel; if (!(replayPanel instanceof ReplayPanel)) { - console.error("ReplayPanel element not found in the DOM"); + console.error("replay panel not found"); } replayPanel.eventBus = eventBus; replayPanel.game = game; - const topBar = document.querySelector("top-bar") as TopBar; - if (!(topBar instanceof TopBar)) { + const gameRightSidebar = document.querySelector( + "game-right-sidebar", + ) as GameRightSidebar; + if (!(gameRightSidebar instanceof GameRightSidebar)) { + console.error("Game Right bar not found"); + } + gameRightSidebar.game = game; + gameRightSidebar.eventBus = eventBus; + + const gameTopBar = document.querySelector("game-top-bar") as GameTopBar; + if (!(gameTopBar instanceof GameTopBar)) { console.error("top bar not found"); } - topBar.game = game; + gameTopBar.game = game; + gameTopBar.eventBus = eventBus; const playerPanel = document.querySelector("player-panel") as PlayerPanel; if (!(playerPanel instanceof PlayerPanel)) { @@ -248,13 +251,13 @@ export function createRenderer( new SpawnTimer(game, transformHandler), leaderboard, gameLeftSidebar, + gameTopBar, + gameRightSidebar, controlPanel, playerInfo, winModal, - optionsMenu, replayPanel, teamStats, - topBar, playerPanel, headsUpMessage, unitInfoModal, diff --git a/src/client/graphics/icons/LeaderboardRegularIcon.ts b/src/client/graphics/icons/LeaderboardRegularIcon.ts deleted file mode 100644 index 46f455fb0..000000000 --- a/src/client/graphics/icons/LeaderboardRegularIcon.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { LitElement, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -@customElement("leaderboard-regular-icon") -export class LeaderboardRegularIcon extends LitElement { - @property({ type: String }) size = "24"; // Accepts "24", "32", etc. - @property({ type: String }) color = "currentColor"; - - static styles = css` - :host { - display: inline-block; - vertical-align: middle; - } - svg { - display: block; - } - `; - - render() { - return html` - - - - `; - } -} diff --git a/src/client/graphics/icons/LeaderboardSolidIcon.ts b/src/client/graphics/icons/LeaderboardSolidIcon.ts deleted file mode 100644 index 31847cda3..000000000 --- a/src/client/graphics/icons/LeaderboardSolidIcon.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { LitElement, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -@customElement("leaderboard-solid-icon") -export class LeaderboardSolidIcon extends LitElement { - @property({ type: String }) size = "24"; // Accepts "24", "32", etc. - @property({ type: String }) color = "currentColor"; - - static styles = css` - :host { - display: inline-block; - vertical-align: middle; - } - svg { - display: block; - } - `; - - render() { - return html` - - - - `; - } -} diff --git a/src/client/graphics/icons/TeamRegularIcon.ts b/src/client/graphics/icons/TeamRegularIcon.ts deleted file mode 100644 index cac1c7acf..000000000 --- a/src/client/graphics/icons/TeamRegularIcon.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { LitElement, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -@customElement("team-regular-icon") -export class TeamRegularIcon extends LitElement { - @property({ type: String }) size = "24"; // Accepts "24", "32", etc. - @property({ type: String }) color = "currentColor"; - - static styles = css` - :host { - display: inline-block; - vertical-align: middle; - } - svg { - display: block; - } - `; - - render() { - return html` - - - - `; - } -} diff --git a/src/client/graphics/icons/TeamSolidIcon.ts b/src/client/graphics/icons/TeamSolidIcon.ts deleted file mode 100644 index d4e021e6a..000000000 --- a/src/client/graphics/icons/TeamSolidIcon.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { LitElement, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -@customElement("team-solid-icon") -export class TeamSolidIcon extends LitElement { - @property({ type: String }) size = "24"; // Accepts "24", "32", etc. - @property({ type: String }) color = "currentColor"; - - static styles = css` - :host { - display: inline-block; - vertical-align: middle; - } - svg { - display: block; - } - `; - - render() { - return html` - - - - `; - } -} diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts index cce6c9c54..8929ede8e 100644 --- a/src/client/graphics/layers/ControlPanel.ts +++ b/src/client/graphics/layers/ControlPanel.ts @@ -1,13 +1,11 @@ import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; -import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg"; import { translateText } from "../../../client/Utils"; import { EventBus } from "../../../core/EventBus"; -import { Gold, UnitType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { AttackRatioEvent } from "../../InputHandler"; import { SendSetTargetTroopRatioEvent } from "../../Transport"; -import { renderNumber, renderTroops } from "../../Utils"; +import { renderTroops } from "../../Utils"; import { UIState } from "../UIState"; import { Layer } from "./Layer"; @@ -29,37 +27,13 @@ export class ControlPanel extends LitElement implements Layer { @state() private _population: number; - @state() - private _maxPopulation: number; - - @state() - private popRate: number; - - @state() - private _troops: number; - - @state() - private _workers: number; - @state() private _isVisible = false; @state() private _manpower: number = 0; - @state() - private _gold: Gold; - - @state() - private _goldPerSecond: Gold; - - @state() - private _factories: number; - private _lastPopulationIncreaseRate: number; - - private _popRateIsIncreasing: boolean = true; - private init_: boolean = false; init() { @@ -118,22 +92,13 @@ export class ControlPanel extends LitElement implements Layer { const popIncreaseRate = player.population() - this._population; if (this.game.ticks() % 5 === 0) { - this._popRateIsIncreasing = - popIncreaseRate >= this._lastPopulationIncreaseRate; this._lastPopulationIncreaseRate = popIncreaseRate; } this._population = player.population(); - this._maxPopulation = this.game.config().maxPopulation(player); - this._gold = player.gold(); - this._troops = player.troops(); - this._workers = player.workers(); - this.popRate = this.game.config().populationIncreaseRate(player) * 10; - this._goldPerSecond = this.game.config().goldAdditionRate(player) * 10n; this.currentTroopRatio = player.troops() / player.population(); this.requestUpdate(); - this._factories = player.units(UnitType.Factory).length; } onAttackRatioChange(newRatio: number) { @@ -209,51 +174,21 @@ export class ControlPanel extends LitElement implements Layer {
e.preventDefault()} > - -
- +
@@ -94,11 +94,20 @@ export class GameLeftSidebar extends LitElement implements Layer {
` : null} -
+
- ${this.isLeaderboardShow - ? html` ` - : html` `} + treeIcon
${this.isTeamGame ? html` @@ -106,9 +115,14 @@ export class GameLeftSidebar extends LitElement implements Layer { class="w-6 h-6 cursor-pointer" @click=${this.toggleTeamLeaderboard} > - ${this.isTeamLeaderboardShow - ? html` ` - : html` `} + treeIcon
` : null} diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts new file mode 100644 index 000000000..7363efc60 --- /dev/null +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -0,0 +1,139 @@ +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import exitIcon from "../../../../resources/images/ExitIconWhite.svg"; +import pauseIcon from "../../../../resources/images/PauseIconWhite.svg"; +import playIcon from "../../../../resources/images/PlayIconWhite.svg"; +import replayRegularIcon from "../../../../resources/images/ReplayRegularIconWhite.svg"; +import replaySolidIcon from "../../../../resources/images/ReplaySolidIconWhite.svg"; +import { EventBus } from "../../../core/EventBus"; +import { GameType } from "../../../core/game/Game"; +import { GameView } from "../../../core/game/GameView"; +import { PauseGameEvent } from "../../Transport"; +import { Layer } from "./Layer"; + +@customElement("game-right-sidebar") +export class GameRightSidebar extends LitElement implements Layer { + public game: GameView; + public eventBus: EventBus; + @state() + private _isSinglePlayer: boolean = false; + + @state() + private _isReplayVisible: boolean = false; + + @state() + private _isVisible: boolean = true; + + @state() + private isPaused: boolean = false; + + @state() + private isExistButtonVisible: boolean = true; + + createRenderRoot() { + return this; + } + + init() { + this._isSinglePlayer = + this.game?.config()?.gameConfig()?.gameType === GameType.Singleplayer || + this.game.config().isReplay(); + this._isVisible = true; + this.game.inSpawnPhase(); + this.requestUpdate(); + } + + tick() { + if (!this.game.inSpawnPhase()) { + this.isExistButtonVisible = false; + } + } + + private toggleReplayPanel(): void { + this._isReplayVisible = !this._isReplayVisible; + } + + private onPauseButtonClick() { + this.isPaused = !this.isPaused; + this.eventBus.emit(new PauseGameEvent(this.isPaused)); + } + + private onExitButtonClick() { + const isAlive = this.game.myPlayer()?.isAlive(); + if (isAlive) { + const isConfirmed = confirm("Are you sure you want to exit the game?"); + if (!isConfirmed) return; + } + // redirect to the home page + window.location.href = "/"; + } + + render() { + return html` + + `; + } +} diff --git a/src/client/graphics/layers/GameTopBar.ts b/src/client/graphics/layers/GameTopBar.ts new file mode 100644 index 000000000..0b2109ac7 --- /dev/null +++ b/src/client/graphics/layers/GameTopBar.ts @@ -0,0 +1,471 @@ +import { html, LitElement } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; +import cityIcon from "../../../../resources/images/CityIconWhite.svg"; +import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg"; +import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; +import exitIcon from "../../../../resources/images/ExitIconWhite.svg"; +import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg"; +import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg"; +import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg"; +import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png"; +import mouseIcon from "../../../../resources/images/MouseIconWhite.svg"; +import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg"; +import populationIcon from "../../../../resources/images/PopulationIconSolidWhite.svg"; +import portIcon from "../../../../resources/images/PortIcon.svg"; +import samLauncherIcon from "../../../../resources/images/SamLauncherUnitWhite.png"; +import settingsIcon from "../../../../resources/images/SettingIconWhite.svg"; +import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg"; +import treeIcon from "../../../../resources/images/TreeIconWhite.svg"; +import troopIcon from "../../../../resources/images/TroopIconWhite.svg"; +import workerIcon from "../../../../resources/images/WorkerIconWhite.svg"; +import { translateText } from "../../../client/Utils"; +import { EventBus } from "../../../core/EventBus"; +import { UnitType } from "../../../core/game/Game"; +import { GameUpdateType } from "../../../core/game/GameUpdates"; +import { GameView } from "../../../core/game/GameView"; +import { UserSettings } from "../../../core/game/UserSettings"; +import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler"; +import { renderNumber, renderTroops } from "../../Utils"; +import { Layer } from "./Layer"; + +@customElement("game-top-bar") +export class GameTopBar extends LitElement implements Layer { + public game: GameView; + public eventBus: EventBus; + private _userSettings: UserSettings = new UserSettings(); + private _population = 0; + private _troops = 0; + private _cities = 0; + private _factories = 0; + private _workers = 0; + private _missileSilo = 0; + private _port = 0; + private _defensePost = 0; + private _samLauncher = 0; + private _lastPopulationIncreaseRate = 0; + private _popRateIsIncreasing = false; + private hasWinner = false; + + @state() + private showSettingsMenu = false; + @state() + private alternateView: boolean = false; + + @state() + private timer: number = 0; + + @query(".settings-container") + private settingsContainer!: HTMLElement; + + createRenderRoot() { + return this; + } + + init() { + this.requestUpdate(); + } + + tick() { + this.updatePopulationIncrease(); + const player = this.game?.myPlayer(); + if (!player) return; + this._troops = player.troops(); + this._workers = player.workers(); + this._cities = player.totalUnitLevels(UnitType.City); + this._missileSilo = player.totalUnitLevels(UnitType.MissileSilo); + this._port = player.totalUnitLevels(UnitType.Port); + this._defensePost = player.totalUnitLevels(UnitType.DefensePost); + this._samLauncher = player.totalUnitLevels(UnitType.SAMLauncher); + this._factories = player.totalUnitLevels(UnitType.Factory); + const updates = this.game.updatesSinceLastTick(); + if (updates) { + this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0; + } + if (this.game.inSpawnPhase()) { + this.timer = 0; + } else if (!this.hasWinner && this.game.ticks() % 10 === 0) { + this.timer++; + } + this.requestUpdate(); + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("click", this.handleOutsideClick, true); + } + + disconnectedCallback() { + window.removeEventListener("click", this.handleOutsideClick, true); + super.disconnectedCallback(); + } + private handleOutsideClick = (event: MouseEvent) => { + if ( + this.showSettingsMenu && + this.settingsContainer && + !this.settingsContainer.contains(event.target as Node) + ) { + this.showSettingsMenu = false; + } + }; + + private onExitButtonClick() { + const isAlive = this.game.myPlayer()?.isAlive(); + if (isAlive) { + const isConfirmed = confirm("Are you sure you want to exit the game?"); + if (!isConfirmed) return; + } + // redirect to the home page + window.location.href = "/"; + } + + private onTerrainButtonClick() { + this.alternateView = !this.alternateView; + this.eventBus.emit(new AlternateViewEvent(this.alternateView)); + this.requestUpdate(); + } + + private onToggleEmojisButtonClick() { + this._userSettings.toggleEmojis(); + this.requestUpdate(); + } + + private onToggleSpecialEffectsButtonClick() { + this._userSettings.toggleFxLayer(); + this.requestUpdate(); + } + + private onToggleDarkModeButtonClick() { + this._userSettings.toggleDarkMode(); + this.requestUpdate(); + this.eventBus.emit(new RefreshGraphicsEvent()); + } + + private onToggleRandomNameModeButtonClick() { + this._userSettings.toggleRandomName(); + } + private onToggleLeftClickOpensMenu() { + this._userSettings.toggleLeftClickOpenMenu(); + } + + private toggleSettingsMenu() { + this.showSettingsMenu = !this.showSettingsMenu; + } + + private updatePopulationIncrease() { + const player = this.game?.myPlayer(); + if (player === null) return; + const popIncreaseRate = player.population() - this._population; + if (this.game.ticks() % 5 === 0) { + this._popRateIsIncreasing = + popIncreaseRate >= this._lastPopulationIncreaseRate; + this._lastPopulationIncreaseRate = popIncreaseRate; + } + } + + private secondsToHms = (d: number): string => { + const h = Math.floor(d / 3600); + const m = Math.floor((d % 3600) / 60); + const s = Math.floor((d % 3600) % 60); + let time = d === 0 ? "-" : `${s}s`; + if (m > 0) time = `${m}m` + time; + if (h > 0) time = `${h}h` + time; + return time; + }; + + render() { + const myPlayer = this.game?.myPlayer(); + if (!this.game || !myPlayer || this.game.inSpawnPhase()) { + return null; + } + + const isAlt = this.game.config().isReplay(); + if (isAlt) { + return html` +
+
+ ${this.secondsToHms(this.timer)} +
+
+ `; + } + const popRate = myPlayer + ? this.game.config().populationIncreaseRate(myPlayer) * 10 + : 0; + const maxPop = myPlayer ? this.game.config().maxPopulation(myPlayer) : 0; + const goldPerSecond = myPlayer + ? this.game.config().goldAdditionRate(myPlayer) * 10n + : 0n; + + return html` +
+
+ ${myPlayer?.isAlive() && !this.game.inSpawnPhase() + ? html` +
+
+
+
+ gold + +${renderNumber(goldPerSecond)} +
+
${renderNumber(myPlayer.gold())}
+
+
+
+ population + + +${renderTroops(popRate)} + +
+
+ ${renderTroops(myPlayer.population())} / + ${renderTroops(maxPop)} +
+
+
+
+
+ troops + ${renderTroops(this._troops)} +
+
+ gold + ${renderTroops(this._workers)} +
+
+
+
+
+ gold + ${renderNumber(this._cities)} +
+
+ gold + ${renderNumber(this._factories)} +
+
+ gold + ${renderNumber(this._port)} +
+
+ gold + ${renderNumber(this._defensePost)} +
+
+ gold + ${renderNumber(this._missileSilo)} +
+
+ gold + ${renderNumber(this._samLauncher)} +
+
+
+
+ ` + : html`
`} +
+
+ ${this.secondsToHms(this.timer)} +
+
+ settings + ${this.showSettingsMenu + ? html` +
+ + + + + + + +
+ ` + : null} +
+
+
+
+ `; + } +} diff --git a/src/client/graphics/layers/HeadsUpMessage.ts b/src/client/graphics/layers/HeadsUpMessage.ts index 5c1e206d9..95881a898 100644 --- a/src/client/graphics/layers/HeadsUpMessage.ts +++ b/src/client/graphics/layers/HeadsUpMessage.ts @@ -34,8 +34,8 @@ export class HeadsUpMessage extends LitElement implements Layer { return html`
e.preventDefault()} diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index ca2c97354..161d40e7f 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -352,11 +352,11 @@ export class PlayerInfoOverlay extends LitElement implements Layer { return html`
e.preventDefault()} >
${this.player !== null ? this.renderPlayerInfo(this.player) : ""} ${this.unit !== null ? this.renderUnitInfo(this.unit) : ""} diff --git a/src/client/graphics/layers/ReplayPanel.ts b/src/client/graphics/layers/ReplayPanel.ts index 622b233ff..0303485c9 100644 --- a/src/client/graphics/layers/ReplayPanel.ts +++ b/src/client/graphics/layers/ReplayPanel.ts @@ -1,7 +1,6 @@ import { html, LitElement } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { EventBus } from "../../../core/EventBus"; -import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { ReplaySpeedChangeEvent } from "../../InputHandler"; import { @@ -16,27 +15,26 @@ export class ReplayPanel extends LitElement implements Layer { public game: GameView | undefined; public eventBus: EventBus | undefined; + @property({ type: Boolean }) + visible: boolean = false; + @state() private _replaySpeedMultiplier: number = defaultReplaySpeedMultiplier; - private _isSinglePlayer: boolean = false; - @state() - private _isVisible = false; + @property({ type: Boolean }) + isSingleplayer = false; - init() { - this._isSinglePlayer = - this.game?.config().gameConfig().gameType === GameType.Singleplayer; - if (this._isSinglePlayer) { - this.setVisible(true); - } + createRenderRoot() { + return this; // Enable Tailwind CSS } - tick() { - if (!this._isVisible && this.game?.config().isReplay()) { - this.setVisible(true); - } + init() {} - this.requestUpdate(); + tick() { + if (!this.visible) return; + if (this.game!.ticks() % 10 === 0) { + this.requestUpdate(); + } } onReplaySpeedChange(value: ReplaySpeedMultiplier) { @@ -44,85 +42,45 @@ export class ReplayPanel extends LitElement implements Layer { this.eventBus?.emit(new ReplaySpeedChangeEvent(value)); } - renderLayer(context: CanvasRenderingContext2D) { - // Render any necessary canvas elements - } - - shouldTransform(): boolean { + renderLayer(_ctx: CanvasRenderingContext2D) {} + shouldTransform() { return false; } - setVisible(visible: boolean) { - this._isVisible = visible; - this.requestUpdate(); - } - render() { - if (!this._isVisible) { - return html``; - } + if (!this.visible) return html``; return html`
e.preventDefault()} + @contextmenu=${(e: Event) => e.preventDefault()} >
- - - - + ${this.renderSpeedButton(ReplaySpeedMultiplier.slow, "×0.5")} + ${this.renderSpeedButton(ReplaySpeedMultiplier.normal, "×1")} + ${this.renderSpeedButton(ReplaySpeedMultiplier.fast, "×2")} + ${this.renderSpeedButton(ReplaySpeedMultiplier.fastest, "max")}
`; } - createRenderRoot() { - return this; // Disable shadow DOM to allow Tailwind styles + private renderSpeedButton(value: ReplaySpeedMultiplier, label: string) { + const isActive = this._replaySpeedMultiplier === value; + return html` + + `; } } diff --git a/src/client/graphics/layers/SpawnTimer.ts b/src/client/graphics/layers/SpawnTimer.ts index 081dbcb0b..c3a5f97cc 100644 --- a/src/client/graphics/layers/SpawnTimer.ts +++ b/src/client/graphics/layers/SpawnTimer.ts @@ -16,8 +16,11 @@ export class SpawnTimer implements Layer { tick() { if (this.game.inSpawnPhase()) { - this.ratios[0] = - this.game.ticks() / this.game.config().numSpawnPhaseTurns(); + // During spawn phase, only one segment filling full width + this.ratios = [ + this.game.ticks() / this.game.config().numSpawnPhaseTurns(), + ]; + this.colors = ["rgba(0, 128, 255, 0.7)"]; return; } @@ -33,18 +36,17 @@ export class SpawnTimer implements Layer { const team = player.team(); if (team === null) throw new Error("Team is null"); const tiles = teamTiles.get(team) ?? 0; - const sum = tiles + player.numTilesOwned(); - teamTiles.set(team, sum); + teamTiles.set(team, tiles + player.numTilesOwned()); } const theme = this.game.config().theme(); const total = sumIterator(teamTiles.values()); if (total === 0) return; + for (const [team, count] of teamTiles) { const ratio = count / total; - const color = theme.teamColor(team).toRgbString(); this.ratios.push(ratio); - this.colors.push(color); + this.colors.push(theme.teamColor(team).toRgbString()); } } @@ -53,12 +55,23 @@ export class SpawnTimer implements Layer { } renderLayer(context: CanvasRenderingContext2D) { - if (this.ratios === null) return; - if (this.ratios.length === 0) return; - if (this.colors.length === 0) return; + if (this.ratios.length === 0 || this.colors.length === 0) return; const barHeight = 10; const barWidth = this.transformHandler.width(); + let yOffset: number; + + if (this.game.inSpawnPhase()) { + // At spawn time, draw at top + yOffset = 0; + } else if (this.game.config().gameConfig().gameMode === GameMode.Team) { + // After spawn, only in team mode, offset based on screen width + const screenW = window.innerWidth; + yOffset = screenW > 1024 ? 80 : 58; + } else { + // Not spawn and not team mode: no bar + return; + } let x = 0; let filledRatio = 0; @@ -67,7 +80,7 @@ export class SpawnTimer implements Layer { const segmentWidth = barWidth * ratio; context.fillStyle = this.colors[i]; - context.fillRect(x, 0, segmentWidth, barHeight); + context.fillRect(x, yOffset, segmentWidth, barHeight); x += segmentWidth; filledRatio += ratio; @@ -76,8 +89,6 @@ export class SpawnTimer implements Layer { } function sumIterator(values: MapIterator) { - // To use reduce, we'd need to allocate an array: - // return Array.from(values).reduce((sum, v) => sum + v, 0); let total = 0; for (const value of values) { total += value; diff --git a/src/client/graphics/layers/TopBar.ts b/src/client/graphics/layers/TopBar.ts deleted file mode 100644 index c6d58536a..000000000 --- a/src/client/graphics/layers/TopBar.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { LitElement, html } from "lit"; -import { customElement } from "lit/decorators.js"; -import { translateText } from "../../../client/Utils"; -import { GameView } from "../../../core/game/GameView"; -import { renderNumber, renderTroops } from "../../Utils"; -import { Layer } from "./Layer"; - -@customElement("top-bar") -export class TopBar extends LitElement implements Layer { - public game: GameView; - private isVisible = false; - private _population = 0; - private _lastPopulationIncreaseRate = 0; - private _popRateIsIncreasing = false; - - createRenderRoot() { - return this; - } - - init() { - this.isVisible = true; - this.requestUpdate(); - } - - tick() { - this.updatePopulationIncrease(); - this.requestUpdate(); - } - - private updatePopulationIncrease() { - const player = this.game?.myPlayer(); - if (player === null) return; - const popIncreaseRate = player.population() - this._population; - if (this.game.ticks() % 5 === 0) { - this._popRateIsIncreasing = - popIncreaseRate >= this._lastPopulationIncreaseRate; - this._lastPopulationIncreaseRate = popIncreaseRate; - } - } - - render() { - if (!this.isVisible) { - return html``; - } - - const myPlayer = this.game?.myPlayer(); - if (!myPlayer?.isAlive() || this.game?.inSpawnPhase()) { - return html``; - } - - const popRate = this.game.config().populationIncreaseRate(myPlayer) * 10; - const maxPop = this.game.config().maxPopulation(myPlayer); - const goldPerSecond = this.game.config().goldAdditionRate(myPlayer) * 10n; - - return html` -
- -
- ${translateText("control_panel.pop")}: - ${renderTroops(myPlayer.population())} / - ${renderTroops(maxPop)} - (+${renderTroops(popRate)}) -
- -
- ${translateText("control_panel.gold")}: - ${renderNumber(myPlayer.gold())} - (+${renderNumber(goldPerSecond)}) -
-
- `; - } -} diff --git a/src/client/index.html b/src/client/index.html index 77c4ece35..d0b166ef2 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -265,7 +265,6 @@
-
-
+
@@ -363,7 +359,8 @@ - + + diff --git a/src/client/styles.css b/src/client/styles.css index e3bc2ab11..05950fe70 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -37,6 +37,14 @@ background: rgba(255, 255, 255, 0.3); } +.hide-scrollbar { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ +} +.hide-scrollbar::-webkit-scrollbar { + display: none; /* Chrome, Safari */ +} + .start-game-button { width: 100%; max-width: 300px; diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index f50bd6c85..284447b51 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -264,10 +264,17 @@ export class PlayerView { targetTroopRatio(): number { return this.data.targetTroopRatio; } + troops(): number { return this.data.troops; } + totalUnitLevels(type: UnitType): number { + return this.units(type) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0); + } + isAlliedWith(other: PlayerView): boolean { return this.data.allies.some((n) => other.smallID() === n); }