mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-24 03:13:36 +00:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # src/client/HostLobbyModal.ts # src/client/JoinPrivateLobbyModal.ts # src/client/SinglePlayerModal.ts
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="svg4" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" viewBox="0 0 24 24">
|
||||
<!-- Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.st0, .st1 {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #ececec;
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<sodipodi:namedview id="namedview6" bordercolor="#666666" borderopacity="1.0" inkscape:current-layer="svg4" inkscape:cx="11.986254" inkscape:cy="11.931271" inkscape:pagecheckerboard="0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="1653" inkscape:window-maximized="1" inkscape:window-width="3072" inkscape:window-x="0" inkscape:window-y="38" inkscape:zoom="36.375" pagecolor="#ffffff" showgrid="false"/>
|
||||
<path id="path2" d="M12,0c-3.4,2.9-5.5,3-9,3v11.5c0,4.6,3.2,5.8,9,9.5,5.8-3.7,9-4.9,9-9.5V3c-3.5,0-5.6,0-9-3ZM12,2.5c2.5,1.8,4.6,2.3,7,2.4v9.6c0,3-1.7,3.8-7,7.1-5.3-3.3-7-4.1-7-7.1V5c2.4,0,4.5-.6,7-2.4h0ZM13.2,6.1h0ZM16.6,7.1h0ZM16.9,13.8h0ZM13.5,14h0Z"/>
|
||||
<path id="path862" class="st1" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
|
||||
<path id="path1012" class="st0" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
|
||||
<path id="path1162" class="st0" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
|
||||
<path id="path1201" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.6c.4,2.2,1.4,3.5,4.2,5.3.7.4,1,.6,2.8,1.8.6.4,1.3.8,1.6,1s.4.2.4.2c0,0,.3-.2.6-.3.3-.2,1.1-.7,1.7-1.1,3-1.8,3.8-2.4,4.7-3.3s1.6-2.1,1.9-3.6V3h-.7c-1.9,0-2.7,0-3.6-.4-1.4-.4-2.7-1-4.3-2.3l-.4-.4h.1ZM12,2.5l.3.2c1.5,1,2.9,1.7,4.5,2,.7,0,1,.2,2,.2h.2v10.7c-.3,1.5-1.1,2.4-3.8,4.1-.4.2-1.2.8-1.9,1.2-.7.4-1.3.8-1.3.8,0,0-2.8-1.7-3.3-2-2.5-1.6-3.3-2.5-3.6-4V5h.6c.3,0,1.1,0,1.4-.2,1.7-.3,3.1-.9,4.6-1.9l.4-.3h0Z"/>
|
||||
<path id="path3032" class="st2" d="M12,0l-.4.3c-2.1,1.7-3.7,2.4-6.1,2.6h-2.5v12.2c0,.4,0,.8.2,1.2.4,1.7,1.5,3,4,4.6.7.4.9.6,3.3,2,.6.4,1.2.8,1.3.8h.2c0,.1.2,0,.5-.2.3-.2.9-.6,1.5-.9,3.4-2.1,4.2-2.6,5.2-3.6s1.7-2.4,1.9-4V3h-2.5c-2.4-.2-4-.9-6.1-2.6l-.4-.3h-.1ZM12,2.5l.4.3c1.8,1.2,3.4,1.8,5.5,2.1h1.1v10.7c-.2,1.2-.9,2.1-2.3,3.1-.6.5-1.5,1-3.6,2.3-.6.3-1,.6-1,.6,0,0-1.7-1-2.5-1.5-1.7-1.1-2.6-1.7-3.2-2.3-.7-.7-1.1-1.3-1.3-2.3V4.8h.4c2.5-.2,4.2-.8,6.2-2.1l.4-.3h-.1Z"/>
|
||||
<path id="path1787" class="st2" d="M8.6,17.8c-.2-.1-.2-.3-.2-.5h0c0-.1.5-1,.5-1,.5-1,.6-1,.7-1h.5c.1,0,.2,0,.2.2v.2q0,.2-.5,1.2l-.5.9h0c-.2.2-.5.2-.7,0h0Z"/>
|
||||
<path id="path1826" class="st2" d="M7.7,13.2c-.2-.3-.2-.7,0-.8,0,0,.7-.4,1.5-.9l1.4-.8,1-1.7c.9-1.6,1-1.8,1.1-1.9s.2-.2,1.1-.6c.6-.3,1-.5,1.1-.5.3,0,.6.2.7.4,0,0,.2,2.2.2,2.4h0c0,.1-1,1.9-1,1.9l-1,1.8v3.5c0,.3-.3.5-.6.5h-.1l-2.7-1.5-2.7-1.8Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"main": {
|
||||
"join_discord": "Влизане в Дискорд!",
|
||||
"create_lobby": "Създаване на Лоби",
|
||||
"join_lobby": "Влизане в Лоби",
|
||||
"single_player": "Самостоятелна Игра",
|
||||
"instructions": "Инструкции",
|
||||
"how_to_play": "Как се играе",
|
||||
"wiki": "Уики"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Бързи клавиши",
|
||||
|
||||
"table_key": "Бутон",
|
||||
|
||||
"table_action": "Действие",
|
||||
|
||||
"action_alt_view": "Алтернативен изглед (терен/държави)",
|
||||
"action_attack_altclick": "Атака (когато левият бутон е зададен за отваряне на меню)",
|
||||
"action_build": "Отваряне на меню за строене",
|
||||
"action_center": "Центриране на камера върху играч",
|
||||
"action_zoom": "Променяне на мащаба навън/навътре",
|
||||
"action_move_camera": "Преместване на камера",
|
||||
"action_ratio_change": "Намаляване/Увеличаване на съотношение на атака",
|
||||
"action_reset_gfx": "Анулиране на графики",
|
||||
|
||||
"ui_section": "Потребителски интерфейс на играта",
|
||||
"ui_leaderboard": "Класация",
|
||||
"ui_leaderboard_desc": "Показва топ играчите на играта и техните имена, % притежавана земя и злато.",
|
||||
"ui_control": "Контролен панел",
|
||||
"ui_control_desc": "Контролният панел съдържа следните елементи:",
|
||||
"ui_pop": "Поп - Количеството хора, които имате, максималната Ви популация и скоростта, с която тя се увеличава.",
|
||||
"ui_gold": "Gold - Количеството злато, което имате и скоростта, с която го получавате.",
|
||||
"ui_troops_workers": "Войници и Работници - Количеството разпределени войници и работници. Войниците се използват за атакуване или защитаване срещу атаки. Работниците се използват за генериране на злато. Можете да коригирате количеството войници и работници, използвайки плъзгача.",
|
||||
"ui_attack_ratio": "Съотношение на атака - Количеството войници, които ще се използват при атака. Можете да коригирате съотношението на атака, използвайки плъзгача.",
|
||||
|
||||
"ui_options": "Опции",
|
||||
"ui_options_desc": "Следните елементи могат да бъдат намерени вътре:",
|
||||
"option_pause": "Поставяне на пауза/Премахване на пауза на играта - Налични само в самостоятелна игра.",
|
||||
"option_timer": "Таймер - Изминалото време от началото на играта.",
|
||||
"option_exit": "Бутон за изход.",
|
||||
"option_settings": "Настройки - Отваряне на менюто за настройки. Вътре можете да превключите Алтернативния изглед, Тъмния режим, Емоджитата и действие при щракване на левия бутон.",
|
||||
|
||||
"radial_title": "Радиално меню",
|
||||
"radial_desc": "Натискане на десния бутон (или докосване при мобилни устройства) отваря радиалното меню. Оттам можете да:",
|
||||
"radial_build": "Отворите менюто за строене.",
|
||||
"radial_info": "Отворите информационното меню.",
|
||||
"radial_boat": "Изпратите лодка да атакува на посочената локация (налично само, ако имате достъп до вода).",
|
||||
"radial_close": "Затворите менюто.",
|
||||
|
||||
"info_title": "Информационно меню",
|
||||
"info_enemy_desc": "Съдържа информация като името на селектирания играч, злато, войници, и дали играчът е предател. Предател е играч, който е предал и атакувал друг играч, който е бил в съюзничество с него. Иконките отдолу представляват следните взаимодействия:",
|
||||
"info_target": "Поставяне на целева маркировка върху играча, маркирайки го за всички съюзници, използвано за координиране на атаки.",
|
||||
"info_alliance": "Изпращане на заявка за съюзничество до играча. Съюзници могат да споделят ресурси и войски, но не могат да се атакуват взаимно.",
|
||||
"info_emoji": "Изпращане на емоджи до играча.",
|
||||
|
||||
"info_ally_panel": "Информационно меню за съюзници",
|
||||
"info_ally_desc": "Когато се съюзите с играч, следните иконки стават налични:",
|
||||
"ally_betray": "Предаване на съюзника Ви, прекратявайки съюзничеството. Сега ще имате перманентна иконка, заседнала до името Ви. Има по-малка вероятност ботове да се съюзят с Вас, а играчи ще мислят два пъти преди да го направят.",
|
||||
"ally_donate": "Даряване на част от войниците си на съюзника Ви. Използвано, когато той е с малко войници и бива атакуван или когато му е нужна тази допълнителна сила, за да размаже противника.",
|
||||
|
||||
"build_menu_title": "Меню за строене",
|
||||
"build_name": "Име",
|
||||
"build_icon": "Иконка",
|
||||
"build_desc": "Описание",
|
||||
|
||||
"build_city": "Град",
|
||||
"build_city_desc": "Увеличава максималната Ви популация. Полезно, когато не можете да разширите територията си или сте на път да достигнете лимита на популацията си.",
|
||||
"build_defense": "Отбранителен пост",
|
||||
"build_defense_desc": "Увеличава защитата около близките граници. Атаките от врагове са по-бавни и имат повече жертви.",
|
||||
"build_port": "Пристанище",
|
||||
"build_port_desc": "Автоматично изпраща търговски кораби между пристанищата на вашата страна и други държави (освен ако сте щракнали върху \"спиране на търговията\" върху тях или те са щракнали върху \"спиране на търговията с вас\"), давайки злато и на двете страни. Позволява изграждането на бойни кораби. Може да се строи само близо до вода.",
|
||||
"build_warship": "Боен кораб",
|
||||
"build_warship_desc": "Патрулира в даден район, превземайки търговски кораби и унищожавайки вражески военни кораби и лодки. Поражда се от най-близкото пристанище и патрулира района, в който първо сте щракнали, за да го построите.",
|
||||
"build_silo": "Ракетен силоз",
|
||||
"build_silo_desc": "Позволява изстрелване на ракети.",
|
||||
"build_sam": "Ракетна установка земя-въздух SAM",
|
||||
"build_sam_desc": "Има 75% шанс да прихване вражески ракети в своя обхват от 100 пиксела. Ракетната установка земя-въздух SAM има 7,5 секунди охлаждане и не може да прихване МИРВ-ове.",
|
||||
"build_atom": "Атомна бомба",
|
||||
"build_atom_desc": "Малка експлозивна бомба, която унищожава територия, сгради, кораби и лодки. Поражда се от най-близкия ракетен силоз и се приземява в областта, в която първо сте щракнали, за да я построите.",
|
||||
"build_hydrogen": "Водородна бомба",
|
||||
"build_hydrogen_desc": "Голяма експлозивна бомба. Поражда се от най-близкия ракетен силоз и се приземява в областта, в която първо сте щракнали, за да я построите.",
|
||||
"build_mirv": "МИРВ",
|
||||
"build_mirv_desc": "Най-мощната бомба в играта. Разделя се на по-малки бомби, които покриват огромна територия. Поврежда само играча, върху който първо сте щракнали, за да го построите. Поражда се от най-близкия ракетен силоз и се приземява в областта, в която първо сте щракнали, за да го построите.",
|
||||
|
||||
"player_icons": "Иконки на играча",
|
||||
"icon_desc": "Примери за някои от иконките в играта, които ще срещнете и какво означават:",
|
||||
"icon_crown": "Корона - Това е играч номер 1 в класацията",
|
||||
"icon_traitor": "Кръстосани мечове - Предател. Този играч е атакувал съюзник.",
|
||||
"icon_ally": "Ръкостискане - Съюзник. Този играч е Ваш съюзник."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Самостоятелна Игра",
|
||||
"map": "Карта",
|
||||
"difficulty": "Трудност",
|
||||
"allow_alliances": "Позволяване на съюзничества",
|
||||
"options_title": "Опции",
|
||||
"bots": "Ботове: ",
|
||||
"bots_disabled": "Изключено",
|
||||
"disable_nations": "Изключване на нации",
|
||||
"instant_build": "Незабавно изграждане",
|
||||
"infinite_gold": "Безкрайно злато",
|
||||
"infinite_troops": "Безкрайни войници",
|
||||
"disable_nukes": "Изключване на ядрени оръжия",
|
||||
"start": "Започване на игра"
|
||||
},
|
||||
"map": {
|
||||
"world": "Свят",
|
||||
"europe": "Европа",
|
||||
"mena": "МЕНА",
|
||||
"northamerica": "Северна Америка",
|
||||
"oceania": "Океания",
|
||||
"blacksea": "Черно Море",
|
||||
"africa": "Африка",
|
||||
"asia": "Азия",
|
||||
"mars": "Марс",
|
||||
"southamerica": "Южна Америка",
|
||||
"britannia": "Британия",
|
||||
"gatewaytotheatlantic": "Порта към Атлантика",
|
||||
"australia": "Австралия",
|
||||
"random": "Произволна"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Влизане в частно лоби",
|
||||
"enter_id": "Въведете ID на лобито",
|
||||
"player": "Играч",
|
||||
"players": "Играчи",
|
||||
"join_lobby": "Влизане в лоби",
|
||||
"checking": "Проверяване на лоби...",
|
||||
"not_found": "Не е намерено лобито. Моля, проверете ID-то и опитайте отново.",
|
||||
"error": "Възникна грешка. Моля, опитайте отново.",
|
||||
"joined_waiting": "Влизането е успешно! Чакане за започване на играта..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Влизане в следващата игра",
|
||||
"waiting": "чакащи играчи"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Въведете потребителското си име",
|
||||
"not_string": "Потребителското име трябва да е символен низ.",
|
||||
"too_short": "Потребителското име трябва да е дълго поне {min} символа.",
|
||||
"too_long": "Потребителското име не трябва да надвишава {max} символа.",
|
||||
"invalid_chars": "Потребителското име може да съдържа само букви, цифри, интервали, долни черти и [квадратни скоби]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Частно лоби",
|
||||
"map": "Карта",
|
||||
"difficulty": "Трудност",
|
||||
"options_title": "Опции",
|
||||
"bots": "Ботове: ",
|
||||
"bots_disabled": "Изключено",
|
||||
"disable_nations": "Изключване на нации",
|
||||
"instant_build": "Незабавно изграждане",
|
||||
"infinite_gold": "Безкрайно злато",
|
||||
"infinite_troops": "Безкрайни войници",
|
||||
"disable_nukes": "Изключване на ядрени оръжия",
|
||||
"player": "Играч",
|
||||
"players": "Играчи",
|
||||
"waiting": "Изчакване на играчи...",
|
||||
"start": "Започване на игра"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Релаксирано",
|
||||
"Balanced": "Балансирано",
|
||||
"Intense": "Интензивно",
|
||||
"Impossible": "Невъзможно"
|
||||
}
|
||||
}
|
||||
@@ -88,5 +88,82 @@
|
||||
"icon_crown": "Crown - This is the number 1 player in the leaderboard",
|
||||
"icon_traitor": "Crossed swords - Traitor. This player attacked an ally.",
|
||||
"icon_ally": "Handshake - Ally. This player is your ally."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Single Player",
|
||||
"map": "Map",
|
||||
"difficulty": "difficulty",
|
||||
"allow_alliances": "Allow alliances",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Disabled",
|
||||
"disable_nations": "Disable Nations",
|
||||
"instant_build": "Instant build",
|
||||
"infinite_gold": "Infinite gold",
|
||||
"infinite_troops": "Infinite troops",
|
||||
"disable_nukes": "Disable Nukes",
|
||||
"start": "Start Game"
|
||||
},
|
||||
"map": {
|
||||
"world": "World",
|
||||
"europe": "Europe",
|
||||
"mena": "MENA",
|
||||
"northamerica": "North America",
|
||||
"oceania": "Oceania",
|
||||
"blacksea": "Black Sea",
|
||||
"africa": "Africa",
|
||||
"asia": "Asia",
|
||||
"mars": "Mars",
|
||||
"southamerica": "South America",
|
||||
"britannia": "Britannia",
|
||||
"gatewaytotheatlantic": "Gateway to the Atlantic",
|
||||
"australia": "Australia",
|
||||
"iceland": "Iceland",
|
||||
"random": "Random"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Join Private Lobby",
|
||||
"enter_id": "Enter Lobby ID",
|
||||
"player": "Player",
|
||||
"players": "Players",
|
||||
"join_lobby": "Join Lobby",
|
||||
"checking": "Checking lobby...",
|
||||
"not_found": "Lobby not found. Please check the ID and try again.",
|
||||
"error": "An error occurred. Please try again.",
|
||||
"joined_waiting": "Joined successfully! Waiting for game to start..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Join next Game",
|
||||
"waiting": "players waiting"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Enter your username",
|
||||
"not_string": "Username must be a string.",
|
||||
"too_short": "Username must be at least {min} characters long.",
|
||||
"too_long": "Username must not exceed {max} characters.",
|
||||
"invalid_chars": "Username can only contain letters, numbers, spaces, underscores, and [square brackets]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Private Lobby",
|
||||
"map": "Map",
|
||||
"difficulty": "Difficulty",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Disabled",
|
||||
"disable_nations": "Disable Nations",
|
||||
"instant_build": "Instant build",
|
||||
"infinite_gold": "Infinite gold",
|
||||
"infinite_troops": "Infinite troops",
|
||||
"disable_nukes": "Disable Nukes",
|
||||
"player": "Player",
|
||||
"players": "Players",
|
||||
"waiting": "Waiting for players...",
|
||||
"start": "Start Game"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Relaxed",
|
||||
"Balanced": "Balanced",
|
||||
"Intense": "Intense",
|
||||
"Impossible": "Impossible"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"main": {
|
||||
"join_discord": "Rejoignez le Discord !",
|
||||
"create_lobby": "Créer un Salon",
|
||||
"join_lobby": "Rejoindre un Salon",
|
||||
"single_player": "Jouer seul",
|
||||
"instructions": "Instructions",
|
||||
"how_to_play": "Comment jouer ?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Raccourcis clavier",
|
||||
|
||||
"table_key": "Touche",
|
||||
|
||||
"table_action": "Action",
|
||||
|
||||
"action_alt_view": "Vue alternative (terrain/pays)",
|
||||
"action_attack_altclick": "Attaquer (quand le clic gauche est configuré pour ouvrir le menu)",
|
||||
"action_build": "Ouvrir le menu de construction",
|
||||
"action_center": "Recentrer la caméra",
|
||||
"action_zoom": "Zoom avant/arrière",
|
||||
"action_move_camera": "Déplacer la caméra",
|
||||
"action_ratio_change": "Diminuer/Augmenter le ratio d'attaque",
|
||||
"action_reset_gfx": "Réinitialiser les graphismes",
|
||||
|
||||
"ui_section": "Interface utilisateur du jeu",
|
||||
"ui_leaderboard": "Classement",
|
||||
"ui_leaderboard_desc": "Affiche les meilleurs joueurs du jeu avec leurs noms, le % de territoire possédé et l'or.",
|
||||
"ui_control": "Panneau de contrôle",
|
||||
"ui_control_desc": "Le panneau de contrôle contient les éléments suivants :",
|
||||
"ui_pop": "Pop - Le nombre d'unités que vous avez, votre population maximale et le taux auquel vous les obtenez.",
|
||||
"ui_gold": "Or - La quantité d'or que vous avez et le taux auquel vous l'obtenez.",
|
||||
"ui_troops_workers": "Troupes et Ouvriers - Le nombre de troupes et d'ouvriers alloués. Les troupes sont utilisées pour attaquer ou se défendre. Les ouvriers sont utilisés pour générer de l'or. Vous pouvez ajuster le nombre de troupes et d'ouvriers en utilisant le curseur.",
|
||||
"ui_attack_ratio": "Ratio d'attaque - Le nombre de troupes qui seront utilisées lorsque vous attaquez. Vous pouvez ajuster le ratio d'attaque en utilisant le curseur.",
|
||||
|
||||
"ui_options": "Options",
|
||||
"ui_options_desc": "Les éléments suivants peuvent être trouvés à l'intérieur :",
|
||||
"option_pause": "Mettre en pause/Reprendre le jeu - Disponible uniquement en mode solo.",
|
||||
"option_timer": "Chronomètre - Temps écoulé depuis le début du jeu.",
|
||||
"option_exit": "Bouton de sortie.",
|
||||
"option_settings": "Paramètres - Ouvrir le menu des paramètres. Vous pouvez y activer la Vue Alternative, le Mode Sombre, les Emojis et l'action sur le clic gauche.",
|
||||
|
||||
"radial_title": "Menu radial",
|
||||
"radial_desc": "Un clic droit (ou un toucher sur mobile) ouvre le menu radial. De là, vous pouvez :",
|
||||
"radial_build": "Ouvrir le menu de construction.",
|
||||
"radial_info": "Ouvrir le menu d'informations.",
|
||||
"radial_boat": "Envoyer un bateau pour attaquer à l'emplacement sélectionné (disponible uniquement si vous avez accès à l'eau).",
|
||||
"radial_close": "Fermer le menu.",
|
||||
|
||||
"info_title": "Menu d'informations",
|
||||
"info_enemy_desc": "Contient des informations telles que le nom du joueur sélectionné, l'or, les troupes, et si le joueur est un traître. Un traître est un joueur qui a trahi et attaqué un joueur qui était en alliance avec lui. Les icônes ci-dessous représentent les interactions suivantes :",
|
||||
"info_target": "Placer une marque cible sur le joueur, le marquant pour tous les alliés, utilisée pour coordonner les attaques.",
|
||||
"info_alliance": "Envoyer une demande d'alliance au joueur. Les alliés peuvent partager des ressources et des troupes, mais ne peuvent pas s'attaquer entre eux.",
|
||||
"info_emoji": "Envoyer un emoji au joueur.",
|
||||
|
||||
"info_ally_panel": "Panneau d'informations des alliés",
|
||||
"info_ally_desc": "Lorsque vous vous alliez avec un joueur, les nouvelles icônes suivantes deviennent disponibles :",
|
||||
"ally_betray": "Trahir un allié, mettant fin à l'alliance. Vous aurez alors une icône permanente à côté de votre nom. Les bots seront moins susceptibles de s'allier avec vous et les joueurs, à qui vous demanderez alliance feront plus attention.",
|
||||
"ally_donate": "Donner une partie de vos troupes à un allié. Utilisé lorsqu'ils manquent de troupes et sont attaqués, ou lorsqu'ils ont besoin de puissance supplémentaire pour écraser un ennemi.",
|
||||
|
||||
"build_menu_title": "Menu de construction",
|
||||
"build_name": "Nom",
|
||||
"build_icon": "Icône",
|
||||
"build_desc": "Description",
|
||||
|
||||
"build_city": "Ville",
|
||||
"build_city_desc": "Utile quand vous ne pouvez pas agrandir votre territoire ou quand vous atteignez votre limite de population.",
|
||||
"build_defense": "Poste de défense",
|
||||
"build_defense_desc": "Augmente les défenses autour des frontières proches. Les attaques des ennemis sont plus lentes et causent plus de pertes.",
|
||||
"build_port": "Port",
|
||||
"build_port_desc": "Envoie automatiquement des navires marchands en partant de vos ports vers d'autres pays (sauf si vous avez cliqué sur \"arrêter le commerce\" sur un joueur, ou s'ils ont cliqué sur \"arrêter le commerce\" sur vous), donnant de l'or aux deux côtés. Permet de construire des navires de guerre. Ne peut être construit qu'à proximité de l'eau.",
|
||||
"build_warship": "Navire de guerre",
|
||||
"build_warship_desc": "Patrouille dans une zone, capturant des navires marchands et détruisant des navires de guerre et des bateaux ennemis. Apparaît dans le port le plus proche et patrouille dans la zone cliquée.",
|
||||
"build_silo": "Silo à missiles",
|
||||
"build_silo_desc": "Permet de lancer des missiles.",
|
||||
"build_sam": "Lanceur SAM",
|
||||
"build_sam_desc": "A 75 % de chance d'intercepter les missiles ennemis dans sa portée de 100 pixels. Le SAM a un temps de recharge de 7,5 secondes et ne peut pas intercepter les MIRV.",
|
||||
"build_atom": "Bombe atomique",
|
||||
"build_atom_desc": "Petite bombe explosive qui détruit le territoire, les bâtiments, les navires et les bateaux. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
"build_hydrogen": "Bombe à hydrogène",
|
||||
"build_hydrogen_desc": "Grande bombe explosive. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
"build_mirv": "MIRV",
|
||||
"build_mirv_desc": "La bombe la plus puissante du jeu. Se divise en plus petites bombes qui couvriront une vaste zone de territoire. Ne fait des dégâts qu'au joueur sur lequel vous avez cliqué pour la construire. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
|
||||
"player_icons": "Icônes des joueurs",
|
||||
"icon_desc": "Exemples de certaines icônes du jeu que vous rencontrerez et leur signification :",
|
||||
"icon_crown": "Couronne - Ce joueur est le numéro 1 du classement",
|
||||
"icon_traitor": "Épées croisées - Traître. Ce joueur a attaqué un allié.",
|
||||
"icon_ally": "Poignée de main - Allié. Ce joueur est votre allié."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Joueur seul",
|
||||
"map": "Carte",
|
||||
"difficulty": "Difficulté",
|
||||
"allow_alliances": "Autoriser les alliances",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots : ",
|
||||
"bots_disabled": "Désactivé",
|
||||
"disable_nations": "Désactiver les nations",
|
||||
"instant_build": "Construction instantanée",
|
||||
"infinite_gold": "Or infini",
|
||||
"infinite_troops": "Troupes infinies",
|
||||
"disable_nukes": "Désactiver les armes nucléaires",
|
||||
"start": "Commencer la partie"
|
||||
},
|
||||
"map": {
|
||||
"world": "Monde",
|
||||
"europe": "Europe",
|
||||
"mena": "MENA",
|
||||
"northamerica": "Amérique du Nord",
|
||||
"oceania": "Océanie",
|
||||
"blacksea": "Mer Noire",
|
||||
"africa": "Afrique",
|
||||
"asia": "Asie",
|
||||
"mars": "Mars",
|
||||
"southamerica": "Amérique du Sud",
|
||||
"britannia": " Grande-Bretagne",
|
||||
"gatewaytotheatlantic": "Porte de l'Atlantique",
|
||||
"australia": "Australie",
|
||||
"random": "Aléatoire"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Rejoindre un salon privé",
|
||||
"enter_id": "Entrez l'ID du salon",
|
||||
"player": "Joueur",
|
||||
"players": "Joueurs",
|
||||
"join_lobby": "Rejoindre le salon",
|
||||
"checking": "Vérification du salon...",
|
||||
"not_found": "Salon introuvable. Veuillez vérifier l'ID et réessayer.",
|
||||
"error": "Une erreur est survenue. Veuillez réessayer.",
|
||||
"joined_waiting": "Rejoint avec succès ! En attente du début de la partie..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Rejoindre la prochaine partie",
|
||||
"waiting": "joueurs en attente"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Entrez votre nom d'utilisateur",
|
||||
"not_string": "Le nom d'utilisateur doit être une chaîne de caractères.",
|
||||
"too_short": "Le nom d'utilisateur doit comporter au moins {min} caractères.",
|
||||
"too_long": "Le nom d'utilisateur ne doit pas dépasser {max} caractères.",
|
||||
"invalid_chars": "Le nom d'utilisateur ne peut contenir que des lettres, des chiffres, des espaces, des tirets bas et des [crochets]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Salon privé",
|
||||
"map": "Carte",
|
||||
"difficulty": "Difficulté",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots : ",
|
||||
"bots_disabled": "Désactivé",
|
||||
"disable_nations": "Désactiver les nations",
|
||||
"instant_build": "Construction instantanée",
|
||||
"infinite_gold": "Or infini",
|
||||
"infinite_troops": "Troupes infinies",
|
||||
"disable_nukes": "Désactiver les armes nucléaires",
|
||||
"player": "Joueur",
|
||||
"players": "Joueurs",
|
||||
"waiting": "En attente de joueurs...",
|
||||
"start": "Commencer la partie"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Détendu",
|
||||
"Balanced": "Équilibré",
|
||||
"Intense": "Intense",
|
||||
"Impossible": "Impossible"
|
||||
}
|
||||
}
|
||||
@@ -77,5 +77,83 @@
|
||||
"icon_crown": "王冠 - リーダーボード1位のプレイヤー",
|
||||
"icon_traitor": "交差した剣 - 裏切り者(同盟を攻撃)",
|
||||
"icon_ally": "握手 - 味方(同盟関係)"
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "シングルプレイヤー",
|
||||
"map": "マップ",
|
||||
"difficulty": "難易度",
|
||||
"allow_alliances": "同盟を許可",
|
||||
"options_title": "オプション",
|
||||
"bots": "ボット数: ",
|
||||
"bots_disabled": "無効",
|
||||
"disable_nations": "国家を無効化",
|
||||
"instant_build": "即時建設",
|
||||
"infinite_gold": "資金無限",
|
||||
"infinite_troops": "兵士無限",
|
||||
"disable_nukes": "核兵器使用禁止",
|
||||
"start": "ゲーム開始"
|
||||
},
|
||||
"map": {
|
||||
"world": "世界",
|
||||
"europe": "ヨーロッパ",
|
||||
"mena": "中東・北アフリカ",
|
||||
"northamerica": "北アメリカ",
|
||||
"oceania": "オセアニア",
|
||||
"blacksea": "黒海",
|
||||
"africa": "アフリカ",
|
||||
"asia": "アジア",
|
||||
"mars": "火星",
|
||||
"southamerica": "南アメリカ",
|
||||
"britannia": "ブリタニア",
|
||||
"gatewaytotheatlantic": "西ヨーロッパ",
|
||||
"australia": "オーストラリア",
|
||||
"iceland": "アイスランド",
|
||||
"random": "ランダム"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "プライベートゲームに参加",
|
||||
"enter_id": "ロビーIDを入力",
|
||||
"player": "人のプレイヤー",
|
||||
"players": "人のプレイヤー",
|
||||
"join_lobby": "ロビーに参加",
|
||||
"checking": "ロビーを確認中...",
|
||||
"not_found": "ロビーが見つかりません。IDを確認してもう一度お試しください。",
|
||||
"error": "エラーが発生しました。もう一度お試しください。",
|
||||
"joined_waiting": "参加に成功しました!ゲーム開始をお待ちください..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "次のゲームに参加",
|
||||
"waiting": "人が参加しています..."
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "ユーザー名を入力",
|
||||
"not_string": "ユーザー名は文字列で入力してください。",
|
||||
"too_short": "ユーザー名は{min}文字より長い必要があります。",
|
||||
"too_long": "ユーザー名は{max}文字より短い必要があります",
|
||||
"invalid_chars": "ユーザー名には英字、数字、スペース、アンダースコア、および [角括弧] のみ使用できます。"
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "プライベートロビー",
|
||||
"map": "マップ",
|
||||
"difficulty": "難易度",
|
||||
"allow_alliances": "同盟を許可",
|
||||
"options_title": "オプション",
|
||||
"bots": "ボット数: ",
|
||||
"bots_disabled": "無効",
|
||||
"disable_nations": "実在する国家を無効化",
|
||||
"instant_build": "即時建設",
|
||||
"infinite_gold": "資金無限",
|
||||
"infinite_troops": "兵士無限",
|
||||
"disable_nukes": "核兵器使用禁止",
|
||||
"player": "プレイヤー",
|
||||
"players": "プレイヤー",
|
||||
"waiting": "他のプレイヤーの参加を待っています...",
|
||||
"start": "ゲーム開始"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "簡単",
|
||||
"Balanced": "普通",
|
||||
"Intense": "難しい",
|
||||
"Impossible": "不可能"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"main": {
|
||||
"join_discord": "Word lid van de Discord!",
|
||||
"create_lobby": "Maak een Lobby aan",
|
||||
"join_lobby": "Treed een Lobby toe",
|
||||
"single_player": "Enkelspeler",
|
||||
"instructions": "Instructies",
|
||||
"how_to_play": "Hoe spelen?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Sneltoetsen",
|
||||
|
||||
"table_key": "Toets",
|
||||
|
||||
"table_action": "Actie",
|
||||
|
||||
"action_alt_view": "Alternatieve weergave (terrein/landen)",
|
||||
"action_attack_altclick": "Aanvallen (wanneer linkerklik is ingesteld om menu te openen)",
|
||||
"action_build": "Open bouwmenu",
|
||||
"action_center": "Camera centreren op de speler",
|
||||
"action_zoom": "In-/uitzoomen",
|
||||
"action_move_camera": "Camera bewegen",
|
||||
"action_ratio_change": "Aanvalsverhouding verlagen/verhogen",
|
||||
"action_reset_gfx": "Grafische instellingen herstellen",
|
||||
|
||||
"ui_section": "Spel-UI",
|
||||
"ui_leaderboard": "Leaderboard",
|
||||
"ui_leaderboard_desc": "Toont de top spelers van het spel en hun namen, % bezette land en goud.",
|
||||
"ui_control": "Controlepaneel",
|
||||
"ui_control_desc": "Het controlepaneel bevat de volgende elementen:",
|
||||
"ui_pop": "Pop - Jouw totale bevolking, jouw maximale bevolking en de snelheid waarmee je ze verwerft.",
|
||||
"ui_gold": "Goud - Het goud dat je hebt en de snelheid waarmee je het verwerft.",
|
||||
"ui_troops_workers": "Troepen en Werkers - Het aantal toegewezen troepen en werkers. Troepen worden gebruikt om aan te vallen of verdedigen. Werkers worden gebruikt om goud te genereren. Je kunt het aantal troepen en werkers aanpassen met de schuifbalk.",
|
||||
"ui_attack_ratio": "Aanvalsverhouding - Het aantal troepen dat wordt gebruikt bij een aanval. Je kunt de aanvalsverhouding aanpassen met de schuifbalk.",
|
||||
|
||||
"ui_options": "Opties",
|
||||
"ui_options_desc": "De volgende elementen zijn hierin te vinden:",
|
||||
"option_pause": "Spel pauzeren/hervatten - Alleen beschikbaar in enkelspelermodus.",
|
||||
"option_timer": "Timer - Tijd verstreken sinds het begin van het spel.",
|
||||
"option_exit": "Afsluitknop.",
|
||||
"option_settings": "Instellingen - Open het instellingenmenu. Daarin kun je Alternatieve Weergave, Donkere Modus, Emoji's en actie bij linkerklikken aan- of uitzetten.",
|
||||
|
||||
"radial_title": "Radiaal menu",
|
||||
"radial_desc": "Met rechtermuisklik (of tikken op mobiel) opent je het radiale menu. Van daaruit kun je:",
|
||||
"radial_build": "Het bouwmenu openen.",
|
||||
"radial_info": "Het Infomenu openen.",
|
||||
"radial_boat": "Een boot sturen om de geselecteerde locatie aan te vallen (alleen beschikbaar als je toegang hebt tot water).",
|
||||
"radial_close": "Het menu sluiten.",
|
||||
|
||||
"info_title": "Infomenu",
|
||||
"info_enemy_desc": "Bevat informatie zoals de naam van de geselecteerde speler, goud, troepen en of de speler een verrader is. Een verrader is een speler die een bondgenoot heeft aangevallen. De iconen hieronder staan voor de volgende interacties:",
|
||||
"info_target": "Plaats een doelmarkering op de speler, zichtbaar voor alle bondgenoten, wordt gebruikt om aanvallen te coördineren.",
|
||||
"info_alliance": "Stuur een bondgenootschapsverzoek naar de speler. Bondgenoten kunnen goud en troepen delen, maar kunnen elkaar niet aanvallen.",
|
||||
"info_emoji": "Stuur een emoji naar de speler.",
|
||||
|
||||
"info_ally_panel": "Bondgenoot-informatiepaneel",
|
||||
"info_ally_desc": "Wanneer je een bondgenootschap sluit met een speler, worden de volgende nieuwe iconen beschikbaar:",
|
||||
"ally_betray": "Verraad je bondgenoot, beëindig het bondgenootschap. Je krijgt dan een permanent icoon naast je naam. Bots zijn minder geneigd om met je een bondgenootschap aan te gaan en spelers zullen er tweemaal over nadenken.",
|
||||
"ally_donate": "Geef een deel van je troepen af aan een bondgenoot. Gebruikt wanneer ze weinig troepen hebben en worden aangevallen, of wanneer ze extra kracht nodig hebben om een vijand te verslaan.",
|
||||
|
||||
"build_menu_title": "Bouwmenu",
|
||||
"build_name": "Naam",
|
||||
"build_icon": "Icoon",
|
||||
"build_desc": "Beschrijving",
|
||||
|
||||
"build_city": "Stad",
|
||||
"build_city_desc": "Verhoogt je maximale bevolking. Handig wanneer je je gebied niet kunt uitbreiden of bijna je bevolkingslimiet heb bereikt.",
|
||||
"build_defense": "Verdedigingspost",
|
||||
"build_defense_desc": "Versterkt verdediging rond nabijgelegen grenzen. Aanvallen van vijanden zijn trager en hebben meer slachtoffers.",
|
||||
"build_port": "Haven",
|
||||
"build_port_desc": "Stuurt automatisch handelsschepen tussen havens van je land en andere landen (tenzij je \"stop handel\" hebt geklikt op hen of zij \"stop handel\" op jou hebben geklikt), wat goud oplevert voor beide partijen. Maakt het bouwen van Oorlogsschepen mogelijk. Kan alleen in de nabijheid van water worden gebouwd.",
|
||||
"build_warship": "Oorlogsschip",
|
||||
"build_warship_desc": "Patrouilleert in een gebied, vangt handelsschepen en vernietigt vijandelijke Oorlogsschepen en Boten. Spawnt vanuit de dichtstbijzijnde Haven en patrouilleert in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_silo": "Raketsilo",
|
||||
"build_silo_desc": "Maakt het lanceren van raketten mogelijk.",
|
||||
"build_sam": "SAM-lanceerder",
|
||||
"build_sam_desc": "Heeft een 75% kans om vijandelijke raketten binnen een straal van 100 pixels te onderscheppen. De SAM heeft een afkoeltijd van 7,5 seconden en kan geen MIRV's onderscheppen.",
|
||||
"build_atom": "Atoombom",
|
||||
"build_atom_desc": "Kleine explosieve bom die gebied, gebouwen, schepen en boten vernietigt. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_hydrogen": "Waterstofbom",
|
||||
"build_hydrogen_desc": "Grote explosieve bom. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_mirv": "MIRV",
|
||||
"build_mirv_desc": "De krachtigste bom in het spel. Splitst zich op in kleinere bommen die een enorm gebied dekken. Schaadt alleen de speler waar je voor het eerst op hebt geklikt om het te bouwen. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
|
||||
"player_icons": "Spelericonen",
|
||||
"icon_desc": "Voorbeelden van enkele ingame-iconen die je zult tegenkomen en wat ze betekenen:",
|
||||
"icon_crown": "Kroon - Dit is de nummer 1 speler op de leaderboard",
|
||||
"icon_traitor": "Gekruiste zwaarden - Verrader. Deze speler heeft een bondgenoot aangevallen.",
|
||||
"icon_ally": "Handdruk - Bondgenoot. Deze speler is je bondgenoot."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Enkelspeler",
|
||||
"map": "Kaart",
|
||||
"difficulty": "Moeilijkheidsgraad",
|
||||
"allow_alliances": "Bondgenootschappen toestaan",
|
||||
"options_title": "Opties",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Uitgeschakeld",
|
||||
"disable_nations": "Naties uitschakelen",
|
||||
"instant_build": "Bouw wachttijd afschaffen",
|
||||
"infinite_gold": "Oneindig goud",
|
||||
"infinite_troops": "Oneindig troepen",
|
||||
"disable_nukes": "Kernwapens uitschakelen",
|
||||
"start": "Start Spel"
|
||||
},
|
||||
"map": {
|
||||
"world": "Wereld",
|
||||
"europe": "Europa",
|
||||
"mena": "MENA",
|
||||
"northamerica": "Noord-Amerika",
|
||||
"oceania": "Oceanië",
|
||||
"blacksea": "Zwarte Zee",
|
||||
"africa": "Afrika",
|
||||
"asia": "Azië",
|
||||
"mars": "Mars",
|
||||
"southamerica": "Zuid-Amerika",
|
||||
"britannia": "Groot-Brittanië",
|
||||
"gatewaytotheatlantic": "Poort van de Atlantische Oceaan",
|
||||
"australia": "Australië",
|
||||
"random": "Willekeurig"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Treed een Privé Lobby toe",
|
||||
"enter_id": "Voer Lobby-ID in",
|
||||
"player": "Speler",
|
||||
"players": "Spelers",
|
||||
"join_lobby": "Treed een Lobby toe",
|
||||
"checking": "Lobby controleren...",
|
||||
"not_found": "Lobby niet gevonden. Controleer de ID en probeer het opnieuw.",
|
||||
"error": "Er is een fout opgetreden. Probeer het opnieuw.",
|
||||
"joined_waiting": "Succesvol toegetreden! Wachten tot het spel begint..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Treed de volgende spel binnen",
|
||||
"waiting": "spelers wachten"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Voer je gebruikersnaam in",
|
||||
"not_string": "Gebruikersnaam moet een tekenreeks zijn.",
|
||||
"too_short": "Gebruikersnaam moet minstens {min} tekens lang zijn.",
|
||||
"too_long": "Gebruikersnaam mag niet langer zijn dan {max} tekens.",
|
||||
"invalid_chars": "Gebruikersnaam mag alleen letters, cijfers, spaties, underscores en [vierkante haken] bevatten."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Privé Lobby",
|
||||
"map": "Kaart",
|
||||
"difficulty": "Moeilijkheidsgraad",
|
||||
"options_title": "Opties",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Uitgeschakeld",
|
||||
"disable_nations": "Naties uitschakelen",
|
||||
"instant_build": "Direct bouwen",
|
||||
"infinite_gold": "Oneindig goud",
|
||||
"infinite_troops": "Oneindig troepen",
|
||||
"disable_nukes": "Kernwapens uitschakelen",
|
||||
"player": "Speler",
|
||||
"players": "Spelers",
|
||||
"waiting": "Wachten op spelers...",
|
||||
"start": "Start Spel"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Ontspannen",
|
||||
"Balanced": "Gebalanceerd",
|
||||
"Intense": "Intens",
|
||||
"Impossible": "Onmogelijk"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "Iceland",
|
||||
"width": 2000,
|
||||
"height": 1500,
|
||||
"nations": [
|
||||
{
|
||||
"coordinates": [455, 1115],
|
||||
"name": "Southern Peninsula",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [550, 1050],
|
||||
"name": "Capital Region",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [370, 810],
|
||||
"name": "Western Region",
|
||||
"strength": 1,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [290, 560],
|
||||
"name": "Westfjords",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [790, 555],
|
||||
"name": "Northwestern Region",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [1050, 1235],
|
||||
"name": "Southern Region",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [1240, 660],
|
||||
"name": "Northeastern Region",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
},
|
||||
{
|
||||
"coordinates": [1740, 740],
|
||||
"name": "Eastern Region",
|
||||
"strength": 2,
|
||||
"flag": "is"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 662 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 367 KiB |
+156
-125
@@ -11,6 +11,7 @@ import randomMap from "../../resources/images/RandomMap.png";
|
||||
import { generateID } from "../core/Util";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("host-lobby-modal")
|
||||
export class HostLobbyModal extends LitElement {
|
||||
@@ -37,7 +38,7 @@ export class HostLobbyModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Private lobby">
|
||||
<o-modal title=${translateText("host_modal.title")}>
|
||||
<div class="lobby-id-box">
|
||||
<button
|
||||
class="lobby-id-button"
|
||||
@@ -45,30 +46,32 @@ export class HostLobbyModal extends LitElement {
|
||||
?disabled=${this.copySuccess}
|
||||
>
|
||||
<span class="lobby-id">${this.lobbyId}</span>
|
||||
${this.copySuccess
|
||||
? html`<span class="copy-success-icon">✓</span>`
|
||||
: html`
|
||||
<svg
|
||||
class="clipboard-icon"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 512 512"
|
||||
height="18px"
|
||||
width="18px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M296 48H176.5C154.4 48 136 65.4 136 87.5V96h-7.5C106.4 96 88 113.4 88 135.5v288c0 22.1 18.4 40.5 40.5 40.5h208c22.1 0 39.5-18.4 39.5-40.5V416h8.5c22.1 0 39.5-18.4 39.5-40.5V176L296 48zm0 44.6l83.4 83.4H296V92.6zm48 330.9c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5h7.5v255.5c0 22.1 10.4 32.5 32.5 32.5H344v7.5zm48-48c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5H264v128h128v167.5z"
|
||||
></path>
|
||||
</svg>
|
||||
`}
|
||||
${
|
||||
this.copySuccess
|
||||
? html`<span class="copy-success-icon">✓</span>`
|
||||
: html`
|
||||
<svg
|
||||
class="clipboard-icon"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 512 512"
|
||||
height="18px"
|
||||
width="18px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M296 48H176.5C154.4 48 136 65.4 136 87.5V96h-7.5C106.4 96 88 113.4 88 135.5v288c0 22.1 18.4 40.5 40.5 40.5h208c22.1 0 39.5-18.4 39.5-40.5V416h8.5c22.1 0 39.5-18.4 39.5-40.5V176L296 48zm0 44.6l83.4 83.4H296V92.6zm48 330.9c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5h7.5v255.5c0 22.1 10.4 32.5 32.5 32.5H344v7.5zm48-48c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5H264v128h128v167.5z"
|
||||
></path>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div class="options-layout">
|
||||
<!-- Map Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Map</div>
|
||||
<div class="option-title">${translateText("host_modal.map")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(GameMapType)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -79,14 +82,17 @@ export class HostLobbyModal extends LitElement {
|
||||
.mapKey=${key}
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === value}
|
||||
.translation=${translateText(
|
||||
`map.${key.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
<div
|
||||
class="option-card random-map ${this.useRandomMap
|
||||
? "selected"
|
||||
: ""}"
|
||||
class="option-card random-map ${
|
||||
this.useRandomMap ? "selected" : ""
|
||||
}"
|
||||
@click=${this.handleRandomMapToggle}
|
||||
>
|
||||
<div class="option-image">
|
||||
@@ -96,14 +102,14 @@ export class HostLobbyModal extends LitElement {
|
||||
style="width:100%; aspect-ratio: 4/2; object-fit:cover; border-radius:8px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="option-card-title">Random</div>
|
||||
<div class="option-card-title">${translateText("map.random")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Difficulty Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Difficulty</div>
|
||||
<div class="option-title">${translateText("host_modal.difficulty")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(Difficulty)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -119,7 +125,9 @@ export class HostLobbyModal extends LitElement {
|
||||
.difficultyKey=${key}
|
||||
></difficulty-display>
|
||||
<p class="option-card-title">
|
||||
${DifficultyDescription[key]}
|
||||
${translateText(
|
||||
`difficulty.${DifficultyDescription[key]}`,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
@@ -127,103 +135,125 @@ export class HostLobbyModal extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Options</div>
|
||||
<div class="option-cards">
|
||||
<label for="private-lobby-bots-count" class="option-card">
|
||||
<input
|
||||
type="range"
|
||||
id="private-lobby-bots-count"
|
||||
min="0"
|
||||
max="400"
|
||||
step="1"
|
||||
@input=${this.handleBotsChange}
|
||||
@change=${this.handleBotsChange}
|
||||
.value="${String(this.bots)}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
Bots: ${this.bots == 0 ? "Disabled" : this.bots}
|
||||
</div>
|
||||
</label>
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${translateText("host_modal.options_title")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
<label for="bots-count" class="option-card">
|
||||
<input
|
||||
type="range"
|
||||
id="bots-count"
|
||||
min="0"
|
||||
max="400"
|
||||
step="1"
|
||||
@input=${this.handleBotsChange}
|
||||
@change=${this.handleBotsChange}
|
||||
.value="${String(this.bots)}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
<span>${translateText("host_modal.bots")}</span>${
|
||||
this.bots == 0
|
||||
? translateText("host_modal.bots_disabled")
|
||||
: this.bots
|
||||
}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-disable-npcd"
|
||||
class="option-card ${this.disableNPCs ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-disable-npcd"
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nations</div>
|
||||
</label>
|
||||
<label
|
||||
for="disable-npcs"
|
||||
class="option-card ${this.disableNPCs ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-npcs"
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.disable_nations")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-instant-build"
|
||||
class="option-card ${this.instantBuild ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-instant-build"
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">Instant build</div>
|
||||
</label>
|
||||
<label
|
||||
for="instant-build"
|
||||
class="option-card ${this.instantBuild ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="instant-build"
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.instant_build")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-infinite-gold"
|
||||
class="option-card ${this.infiniteGold ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-infinite-gold"
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">Infinite gold</div>
|
||||
</label>
|
||||
<label
|
||||
for="infinite-gold"
|
||||
class="option-card ${this.infiniteGold ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="infinite-gold"
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.infinite_gold")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-infinite-troops"
|
||||
class="option-card ${this.infiniteTroops ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-infinite-troops"
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">Infinite troops</div>
|
||||
</label>
|
||||
<label
|
||||
for="private-lobby-disable-nukes"
|
||||
class="option-card ${this.disableNukes ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-nukes"
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nukes</div>
|
||||
</label>
|
||||
<label
|
||||
for="infinite-troops"
|
||||
class="option-card ${this.infiniteTroops ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="infinite-troops"
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.infinite_troops")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="disable-nukes"
|
||||
class="option-card ${this.disableNukes ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-nukes"
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.disable_nukes")}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lobby Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${this.players.length === 1 ? "Player" : "Players"}
|
||||
</div>
|
||||
<!-- Lobby Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${
|
||||
this.players.length === 1
|
||||
? translateText("host_modal.player")
|
||||
: translateText("host_modal.players")
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="players-list">
|
||||
${this.players.map(
|
||||
@@ -231,17 +261,18 @@ export class HostLobbyModal extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<o-button
|
||||
.title=${this.players.length === 1
|
||||
? "Waiting for players..."
|
||||
: "Start Game"}
|
||||
?disable=${this.players.length < 2}
|
||||
|
||||
<button
|
||||
@click=${this.startGame}
|
||||
block
|
||||
?disabled=${this.players.length < 2}
|
||||
class="start-game-button"
|
||||
>
|
||||
</o-button>
|
||||
${
|
||||
this.players.length === 1
|
||||
? translateText("host_modal.waiting")
|
||||
: translateText("host_modal.start")
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</o-modal>
|
||||
`;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { consolex } from "../core/Consolex";
|
||||
import { GameInfo, GameRecord } from "../core/Schemas";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
import "./components/baseComponents/Modal";
|
||||
import "./components/baseComponents/Button";
|
||||
|
||||
@@ -22,12 +23,12 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Join Private Lobby">
|
||||
<o-modal title=${translateText("private_lobby.title")}>
|
||||
<div class="lobby-id-box">
|
||||
<input
|
||||
type="text"
|
||||
id="lobbyIdInput"
|
||||
placeholder="Enter Lobby ID"
|
||||
placeholder=${translateText("private_lobby.enter_id")}
|
||||
@keyup=${this.handleChange}
|
||||
/>
|
||||
<button
|
||||
@@ -58,7 +59,9 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
? html` <div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${this.players.length === 1 ? "Player" : "Players"}
|
||||
${this.players.length === 1
|
||||
? translateText("private_lobby.player")
|
||||
: translateText("private_lobby.players")}
|
||||
</div>
|
||||
|
||||
<div class="players-list">
|
||||
@@ -72,7 +75,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
<div class="flex justify-center">
|
||||
${!this.hasJoined
|
||||
? html` <o-button
|
||||
title="Join Lobby"
|
||||
title=${translateText("private_lobby.join_lobby")}
|
||||
block
|
||||
@click=${this.joinLobby}
|
||||
></o-button>`
|
||||
@@ -149,7 +152,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
private async joinLobby(): Promise<void> {
|
||||
const lobbyId = this.lobbyIdInput.value;
|
||||
consolex.log(`Joining lobby with ID: ${lobbyId}`);
|
||||
this.message = "Checking lobby...";
|
||||
this.message = `${translateText("private_lobby.checking")}`;
|
||||
|
||||
try {
|
||||
// First, check if the game exists in active lobbies
|
||||
@@ -160,10 +163,10 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
const archivedGame = await this.checkArchivedGame(lobbyId);
|
||||
if (archivedGame) return;
|
||||
|
||||
this.message = "Lobby not found. Please check the ID and try again.";
|
||||
this.message = `${translateText("private_lobby.not_found")}`;
|
||||
} catch (error) {
|
||||
consolex.error("Error checking lobby existence:", error);
|
||||
this.message = "An error occurred. Please try again.";
|
||||
this.message = `${translateText("private_lobby.error")}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
const gameInfo = await response.json();
|
||||
|
||||
if (gameInfo.exists) {
|
||||
this.message = "Joined successfully! Waiting for game to start...";
|
||||
this.message = translateText("private_lobby.joined_waiting");
|
||||
this.hasJoined = true;
|
||||
|
||||
this.dispatchEvent(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Difficulty, GameMapType, GameType } from "../core/game/Game";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import { getMapsImage } from "./utilities/Maps";
|
||||
import { GameID, GameInfo } from "../core/Schemas";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("public-lobby")
|
||||
export class PublicLobby extends LitElement {
|
||||
@@ -102,7 +103,9 @@ export class PublicLobby extends LitElement {
|
||||
? "opacity-70 cursor-not-allowed"
|
||||
: ""}"
|
||||
>
|
||||
<div class="text-lg md:text-2xl font-semibold mb-2">Join next Game</div>
|
||||
<div class="text-lg md:text-2xl font-semibold mb-2">
|
||||
${translateText("public_lobby.join")}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<img
|
||||
src="${getMapsImage(lobby.gameConfig.gameMap)}"
|
||||
@@ -115,13 +118,16 @@ export class PublicLobby extends LitElement {
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.gameConfig.gameMap}
|
||||
<!-- ${lobby.gameConfig.gameMap} -->
|
||||
${translateText(
|
||||
`map.${lobby.gameConfig.gameMap.toLowerCase().replace(/\s+/g, "")}`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers} players
|
||||
waiting
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
|
||||
${translateText("public_lobby.waiting")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
|
||||
@@ -11,6 +11,7 @@ import "./components/Maps";
|
||||
import randomMap from "../../resources/images/RandomMap.png";
|
||||
import { GameInfo } from "../core/Schemas";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("single-player-modal")
|
||||
export class SinglePlayerModal extends LitElement {
|
||||
@@ -30,11 +31,11 @@ export class SinglePlayerModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Single Player">
|
||||
<o-modal title=${translateText("single_modal.title")}>
|
||||
<div class="options-layout">
|
||||
<!-- Map Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Map</div>
|
||||
<div class="option-title">${translateText("single_modal.map")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(GameMapType)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -49,6 +50,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
.mapKey=${key}
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === value}
|
||||
.translation=${translateText(
|
||||
`map.${key.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
`,
|
||||
@@ -66,14 +70,18 @@ export class SinglePlayerModal extends LitElement {
|
||||
style="width:100%; aspect-ratio: 4/2; object-fit:cover; border-radius:8px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="option-card-title">Random</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("map.random")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Difficulty Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Difficulty</div>
|
||||
<div class="option-title">
|
||||
${translateText("single_modal.difficulty")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(Difficulty)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -89,7 +97,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
.difficultyKey=${key}
|
||||
></difficulty-display>
|
||||
<p class="option-card-title">
|
||||
${DifficultyDescription[key]}
|
||||
${translateText(
|
||||
`difficulty.${DifficultyDescription[key]}`,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
@@ -99,7 +109,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Options</div>
|
||||
<div class="option-title">
|
||||
${translateText("single_modal.options_title")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
<label for="bots-count" class="option-card">
|
||||
<input
|
||||
@@ -113,7 +125,10 @@ export class SinglePlayerModal extends LitElement {
|
||||
.value="${this.bots}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
Bots: ${this.bots == 0 ? "Disabled" : this.bots}
|
||||
<span>${translateText("single_modal.bots")}</span>${this
|
||||
.bots == 0
|
||||
? translateText("single_modal.bots_disabled")
|
||||
: this.bots}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -128,7 +143,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nations</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.disable_nations")}
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
for="instant-build"
|
||||
@@ -141,7 +158,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">Instant build</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.instant_build")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -155,7 +174,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">Infinite gold</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.infinite_gold")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -169,7 +190,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">Infinite troops</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.infinite_troops")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -183,14 +206,16 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nukes</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.disable_nukes")}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<o-button
|
||||
title="Start Game"
|
||||
title=${translateText("single_modal.start")}
|
||||
@click=${this.startGame}
|
||||
blockDesktop
|
||||
></o-button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateUsername,
|
||||
} from "../core/validations/username";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
const usernameKey: string = "username";
|
||||
|
||||
@@ -40,7 +41,7 @@ export class UsernameInput extends LitElement {
|
||||
.value=${this.username}
|
||||
@input=${this.handleChange}
|
||||
@change=${this.handleChange}
|
||||
placeholder="Enter your username"
|
||||
placeholder="${translateText("username.enter_username")}"
|
||||
maxlength="${MAX_USERNAME_LENGTH}"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
|
||||
@@ -70,3 +70,30 @@ export function generateCryptoRandomUUID(): string {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function translateText(
|
||||
key: string,
|
||||
params: Record<string, string | number> = {},
|
||||
): string {
|
||||
const keys = key.split(".");
|
||||
let text: any = (window as any).translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
|
||||
if (!text && (window as any).defaultTranslations) {
|
||||
text = (window as any).defaultTranslations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) return key;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
text = text.replace(`{${param}}`, String(value));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
|
||||
Britannia: "Britannia",
|
||||
GatewayToTheAtlantic: "Gateway to the Atlantic",
|
||||
Australia: "Australia",
|
||||
Iceland: "Iceland",
|
||||
};
|
||||
|
||||
@customElement("map-display")
|
||||
export class MapDisplay extends LitElement {
|
||||
@property({ type: String }) mapKey = "";
|
||||
@property({ type: Boolean }) selected = false;
|
||||
@property({ type: String }) translation: string = "";
|
||||
|
||||
static styles = css`
|
||||
.option-card {
|
||||
@@ -89,7 +91,9 @@ export class MapDisplay extends LitElement {
|
||||
<p>${this.mapKey}</p>
|
||||
</div>`}
|
||||
<div class="option-card-title">
|
||||
${MapDescription[this.mapKey as keyof typeof GameMapType]}
|
||||
<!-- ${MapDescription[this.mapKey as keyof typeof GameMapType]}-->
|
||||
${this.translation ||
|
||||
MapDescription[this.mapKey as keyof typeof GameMapType]}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1581,7 +1581,7 @@
|
||||
"name": "Washington"
|
||||
},
|
||||
{
|
||||
"code": "West Virginia",
|
||||
"code": "West_Virginia",
|
||||
"name": "West Virginia"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
|
||||
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
|
||||
import samlauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg";
|
||||
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||
import { renderNumber } from "../../Utils";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
@@ -71,7 +72,7 @@ const buildTable: BuildItemDisplay[][] = [
|
||||
// needs new icon
|
||||
{
|
||||
unitType: UnitType.SAMLauncher,
|
||||
icon: shieldIcon,
|
||||
icon: samlauncherIcon,
|
||||
description: "Defends against incoming nukes",
|
||||
countable: true,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { PauseGameEvent } from "../../Transport";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
@@ -30,6 +30,16 @@ const button = ({
|
||||
</button>
|
||||
`;
|
||||
|
||||
const 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;
|
||||
};
|
||||
|
||||
@customElement("options-menu")
|
||||
export class OptionsMenu extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
@@ -141,11 +151,11 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
children: this.isPaused ? "▶️" : "⏸",
|
||||
})}
|
||||
<div
|
||||
class="w-14 h-8 lg:w-20 lg:h-10 flex items-center justify-center
|
||||
class="w-15 h-8 lg:w-24 lg:h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
|
||||
rounded text-sm lg:text-xl"
|
||||
>
|
||||
${this.timer}
|
||||
${secondsToHms(this.timer)}
|
||||
</div>
|
||||
${button({
|
||||
onClick: this.onExitButtonClick,
|
||||
@@ -161,7 +171,7 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="options-menu flex flex-wrap justify-around gap-y-3 mt-2 bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md ${!this
|
||||
class="options-menu flex flex-col justify-around gap-y-3 mt-2 bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md ${!this
|
||||
.showSettings
|
||||
? "hidden"
|
||||
: ""}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { Layer } from "./Layer";
|
||||
import {
|
||||
@@ -198,10 +198,16 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
return html`
|
||||
<div class="p-2">
|
||||
<div
|
||||
class="text-bold text-sm lg:text-lg font-bold mb-1 ${isAlly
|
||||
class="text-bold text-sm lg:text-lg font-bold mb-1 inline-flex ${isAlly
|
||||
? "text-green-500"
|
||||
: "text-white"}"
|
||||
>
|
||||
${player.flag()
|
||||
? html`<img
|
||||
class="h-8 mr-1 aspect-[3/4]"
|
||||
src=${"/flags/" + player.flag() + ".svg"}
|
||||
/>`
|
||||
: ""}
|
||||
${player.name()}
|
||||
</div>
|
||||
<div class="text-sm opacity-80">Type: ${playerType}</div>
|
||||
@@ -269,11 +275,11 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="flex w-full z-50 flex flex-col"
|
||||
class="flex w-full z-50 flex-col"
|
||||
@contextmenu=${(e) => e.preventDefault()}
|
||||
>
|
||||
<div
|
||||
class="bg-opacity-70 bg-gray-900 rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 text-white text-lg md:text-base ${containerClasses}"
|
||||
class="bg-opacity-60 bg-gray-900 rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 text-white text-lg md:text-base ${containerClasses}"
|
||||
>
|
||||
${this.player != null ? this.renderPlayerInfo(this.player) : ""}
|
||||
${this.unit != null ? this.renderUnitInfo(this.unit) : ""}
|
||||
|
||||
@@ -12,15 +12,30 @@ import cityIcon from "../../../../resources/images/buildings/cityAlt1.png";
|
||||
import { GameView, UnitView } from "../../../core/game/GameView";
|
||||
import { Cell, UnitType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { euclDistFN } from "../../../core/game/GameMap";
|
||||
import {
|
||||
euclDistFN,
|
||||
manhattanDistFN,
|
||||
rectDistFN,
|
||||
hexDistFN,
|
||||
} from "../../../core/game/GameMap";
|
||||
|
||||
const underConstructionColor = colord({ r: 150, g: 150, b: 150 });
|
||||
const reloadingColor = colord({ r: 255, g: 0, b: 0 });
|
||||
|
||||
type DistanceFunction = typeof euclDistFN;
|
||||
|
||||
enum UnitBorderType {
|
||||
Round,
|
||||
Diamond,
|
||||
Square,
|
||||
Hexagon,
|
||||
}
|
||||
|
||||
interface UnitRenderConfig {
|
||||
icon: string;
|
||||
borderRadius: number;
|
||||
territoryRadius: number;
|
||||
borderType: UnitBorderType;
|
||||
}
|
||||
|
||||
export class StructureLayer implements Layer {
|
||||
@@ -35,26 +50,31 @@ export class StructureLayer implements Layer {
|
||||
icon: anchorIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Round,
|
||||
},
|
||||
[UnitType.City]: {
|
||||
icon: cityIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Round,
|
||||
},
|
||||
[UnitType.MissileSilo]: {
|
||||
icon: missileSiloIcon,
|
||||
borderRadius: 9.5,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
},
|
||||
[UnitType.DefensePost]: {
|
||||
icon: shieldIcon,
|
||||
borderRadius: 9.5,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Hexagon,
|
||||
},
|
||||
[UnitType.SAMLauncher]: {
|
||||
icon: SAMMissileIcon,
|
||||
borderRadius: 10,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -68,6 +88,7 @@ export class StructureLayer implements Layer {
|
||||
icon: SAMMissileReloadingIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,7 +112,7 @@ export class StructureLayer implements Layer {
|
||||
);
|
||||
this.unitIcons.set(unitType, iconData);
|
||||
console.log(
|
||||
`icond data width height: ${iconData.width}, ${iconData.height}`,
|
||||
`icon data width height: ${iconData.width}, ${iconData.height}`,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -141,6 +162,51 @@ export class StructureLayer implements Layer {
|
||||
return unitType in this.unitConfigs;
|
||||
}
|
||||
|
||||
private drawBorder(
|
||||
unit: UnitView,
|
||||
borderColor: Colord,
|
||||
config: UnitRenderConfig,
|
||||
distanceFN: DistanceFunction,
|
||||
) {
|
||||
// Draw border and territory
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
distanceFN(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
borderColor,
|
||||
255,
|
||||
);
|
||||
}
|
||||
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
distanceFN(unit.tile(), config.territoryRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
unit.type() == UnitType.Construction
|
||||
? underConstructionColor
|
||||
: this.theme.territoryColor(unit.owner().info()),
|
||||
130,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getDrawFN(type: UnitBorderType) {
|
||||
switch (type) {
|
||||
case UnitBorderType.Round:
|
||||
return euclDistFN;
|
||||
case UnitBorderType.Diamond:
|
||||
return manhattanDistFN;
|
||||
case UnitBorderType.Square:
|
||||
return rectDistFN;
|
||||
case UnitBorderType.Hexagon:
|
||||
return hexDistFN;
|
||||
}
|
||||
}
|
||||
|
||||
private handleUnitRendering(unit: UnitView) {
|
||||
const unitType = unit.constructionType() ?? unit.type();
|
||||
let iconType = unitType;
|
||||
@@ -157,17 +223,16 @@ export class StructureLayer implements Layer {
|
||||
|
||||
if (!config || !icon) return;
|
||||
|
||||
const drawFunction = this.getDrawFN(config.borderType);
|
||||
// Clear previous rendering
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.borderRadius, true),
|
||||
drawFunction(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
|
||||
}
|
||||
|
||||
if (!unit.isActive()) {
|
||||
return;
|
||||
}
|
||||
if (!unit.isActive()) return;
|
||||
|
||||
let borderColor = this.theme.borderColor(unit.owner().info());
|
||||
if (unitType == UnitType.SAMLauncher && unit.isSamCooldown()) {
|
||||
@@ -176,30 +241,7 @@ export class StructureLayer implements Layer {
|
||||
borderColor = underConstructionColor;
|
||||
}
|
||||
|
||||
// Draw border and territory
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
borderColor,
|
||||
255,
|
||||
);
|
||||
}
|
||||
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.territoryRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
unit.type() == UnitType.Construction
|
||||
? underConstructionColor
|
||||
: this.theme.territoryColor(unit.owner().info()),
|
||||
130,
|
||||
);
|
||||
}
|
||||
this.drawBorder(unit, borderColor, config, drawFunction);
|
||||
|
||||
const startX = this.game.x(unit.tile()) - Math.floor(icon.width / 2);
|
||||
const startY = this.game.y(unit.tile()) - Math.floor(icon.height / 2);
|
||||
|
||||
+97
-3
@@ -218,7 +218,7 @@
|
||||
</g>
|
||||
</svg>
|
||||
<div class="flex justify-center text-sm font-bold mt-[-5px] logo-version">
|
||||
v0.20.0
|
||||
v0.21.0
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -304,7 +304,10 @@
|
||||
class="text-center appearance-none w-full bg-blue-100 hover:bg-blue-200 text-blue-900 p-3 sm:p-4 lg:p-5 font-medium text-sm sm:text-base lg:text-lg rounded-md border-none cursor-pointer transition-colors duration-300"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="bg">Български</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -331,7 +334,7 @@
|
||||
<help-modal></help-modal>
|
||||
|
||||
<div
|
||||
class="flex flex-column gap-2 fixed right-0 top-0 z-50 flex flex-col w-32 sm:w-32 lg:w-48"
|
||||
class="flex flex-column gap-2 fixed right-[10px] top-[10px] z-50 flex flex-col w-32 sm:w-32 lg:w-48"
|
||||
>
|
||||
<options-menu></options-menu>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
@@ -394,7 +397,98 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="lang.js"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const locale = new Intl.Locale(navigator.language);
|
||||
const defaultLang = locale.language;
|
||||
const userLang = localStorage.getItem("lang") || defaultLang;
|
||||
async function loadLanguage(lang) {
|
||||
try {
|
||||
const response = await fetch(`/lang/${lang}.json`);
|
||||
if (!response.ok)
|
||||
throw new Error(`Language file not found: ${lang}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("🚨 Translation load error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
function applyTranslation(translations) {
|
||||
const components = [
|
||||
"single-player-modal",
|
||||
"host-lobby-modal",
|
||||
"join-private-lobby-modal",
|
||||
"emoji-table",
|
||||
"leader-board",
|
||||
"build-menu",
|
||||
"win-modal",
|
||||
"game-starting-modal",
|
||||
"top-bar",
|
||||
"player-panel",
|
||||
"help-modal",
|
||||
"username-input",
|
||||
"public-lobby",
|
||||
];
|
||||
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
if (!text && window.defaultTranslations) {
|
||||
let fallback = window.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
fallback = fallback?.[k];
|
||||
if (!fallback) break;
|
||||
}
|
||||
text = fallback;
|
||||
}
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
components.forEach((tagName) => {
|
||||
const el = document.querySelector(tagName);
|
||||
if (el && typeof el.requestUpdate === "function") {
|
||||
el.requestUpdate();
|
||||
} else {
|
||||
console.warn(
|
||||
`requestUpdate() not available on <${tagName}> or element not found.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function changeLanguage(lang) {
|
||||
// console.log(`Changing language to: ${lang}`);
|
||||
localStorage.setItem("lang", lang);
|
||||
const translations = await loadLanguage(lang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
}
|
||||
const defaultTranslations = await loadLanguage("en");
|
||||
window.defaultTranslations = defaultTranslations;
|
||||
const translations = await loadLanguage(userLang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
|
||||
const langSelector = document.getElementById("lang-selector");
|
||||
if (langSelector) {
|
||||
langSelector.value = userLang;
|
||||
}
|
||||
document
|
||||
.getElementById("lang-selector")
|
||||
.addEventListener("change", function (event) {
|
||||
changeLanguage(event.target.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const defaultLang = navigator.language.startsWith("ja") ? "ja" : "en";
|
||||
const userLang = localStorage.getItem("lang") || defaultLang;
|
||||
|
||||
async function loadLanguage(lang) {
|
||||
try {
|
||||
const response = await fetch(`/lang/${lang}.json`);
|
||||
if (!response.ok) throw new Error(`Language file not found: ${lang}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("🚨 Translation load error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslation(translations) {
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function changeLanguage(lang) {
|
||||
// console.log(`Changing language to: ${lang}`);
|
||||
localStorage.setItem("lang", lang);
|
||||
const translations = await loadLanguage(lang);
|
||||
applyTranslation(translations);
|
||||
}
|
||||
|
||||
const translations = await loadLanguage(userLang);
|
||||
applyTranslation(translations);
|
||||
|
||||
const langSelector = document.getElementById("lang-selector");
|
||||
if (langSelector) {
|
||||
langSelector.value = userLang;
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("lang-selector")
|
||||
.addEventListener("change", function (event) {
|
||||
changeLanguage(event.target.value);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import mars from "../../../resources/maps/MarsThumb.png";
|
||||
import britannia from "../../../resources/maps/BritanniaThumb.png";
|
||||
import gatewayToTheAtlantic from "../../../resources/maps/GatewayToTheAtlanticThumb.png";
|
||||
import australia from "../../../resources/maps/AustraliaThumb.png";
|
||||
import iceland from "../../../resources/maps/IcelandThumb.png";
|
||||
|
||||
import { GameMapType } from "../../core/game/Game";
|
||||
|
||||
@@ -42,6 +43,8 @@ export function getMapsImage(map: GameMapType): string {
|
||||
return gatewayToTheAtlantic;
|
||||
case GameMapType.Australia:
|
||||
return australia;
|
||||
case GameMapType.Iceland:
|
||||
return iceland;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export enum GameMapType {
|
||||
Britannia = "Britannia",
|
||||
GatewayToTheAtlantic = "Gateway to the Atlantic",
|
||||
Australia = "Australia",
|
||||
Iceland = "Iceland",
|
||||
}
|
||||
|
||||
export enum GameType {
|
||||
|
||||
@@ -320,7 +320,7 @@ export class GameMapImpl implements GameMap {
|
||||
export function euclDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => gm.euclideanDist(root, n) <= dist;
|
||||
@@ -341,8 +341,63 @@ export function euclDistFN(
|
||||
export function manhattanDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx + dy <= dist;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function rectDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const dx = Math.abs(gm.x(n) - gm.x(root));
|
||||
const dy = Math.abs(gm.y(n) - gm.y(root));
|
||||
return dx <= dist && dy <= dist;
|
||||
};
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx <= dist && dy <= dist;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function hexDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const dx = Math.abs(gm.x(n) - gm.x(root));
|
||||
const dy = Math.abs(gm.y(n) - gm.y(root));
|
||||
return dx <= dist && dy <= dist && dx + dy <= dist * 1.5;
|
||||
};
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx <= dist && dy <= dist && dx + dy <= dist * 1.5;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function andFN(
|
||||
|
||||
@@ -37,6 +37,7 @@ const MAP_FILE_NAMES: Record<GameMapType, string> = {
|
||||
[GameMapType.Britannia]: "Britannia",
|
||||
[GameMapType.GatewayToTheAtlantic]: "GatewayToTheAtlantic",
|
||||
[GameMapType.Australia]: "Australia",
|
||||
[GameMapType.Iceland]: "Iceland",
|
||||
};
|
||||
|
||||
class GameMapLoader {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
englishRecommendedTransformers,
|
||||
} from "obscenity";
|
||||
import { simpleHash } from "../Util";
|
||||
import { translateText } from "../../client/Utils";
|
||||
|
||||
const matcher = new RegExpMatcher({
|
||||
...englishDataset.build(),
|
||||
@@ -41,28 +42,33 @@ export function validateUsername(username: string): {
|
||||
error?: string;
|
||||
} {
|
||||
if (typeof username !== "string") {
|
||||
return { isValid: false, error: "Username must be a string." };
|
||||
return { isValid: false, error: translateText("username.not_string") };
|
||||
}
|
||||
|
||||
if (username.length < MIN_USERNAME_LENGTH) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Username must be at least ${MIN_USERNAME_LENGTH} characters long.`,
|
||||
error: translateText("username.too_short", {
|
||||
min: MIN_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (username.length > MAX_USERNAME_LENGTH) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Username must not exceed ${MAX_USERNAME_LENGTH} characters.`,
|
||||
error: translateText("username.too_long", {
|
||||
max: MAX_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!validPattern.test(username)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error:
|
||||
"Username can only contain letters, numbers, spaces, underscores, and [square brackets].",
|
||||
error: translateText("username.invalid_chars", {
|
||||
max: MAX_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { decodePNGFromStream, Bitmap } from "pureimage";
|
||||
import { Readable } from "stream";
|
||||
|
||||
const min_island_size = 30;
|
||||
const min_lake_size = 200;
|
||||
|
||||
interface Coord {
|
||||
x: number;
|
||||
@@ -26,10 +27,12 @@ class Terrain {
|
||||
export async function generateMap(
|
||||
imageBuffer: Buffer,
|
||||
removeSmall = true,
|
||||
name: string = "",
|
||||
): Promise<{ map: Uint8Array; miniMap: Uint8Array; thumb: Bitmap }> {
|
||||
const stream = Readable.from(imageBuffer);
|
||||
const img = await decodePNGFromStream(stream);
|
||||
|
||||
console.log(`Processing Map: ${name}`);
|
||||
console.log("Image loaded successfully");
|
||||
console.log("Image dimensions:", img.width, "x", img.height);
|
||||
|
||||
@@ -57,13 +60,8 @@ export async function generateMap(
|
||||
}
|
||||
}
|
||||
|
||||
if (removeSmall) {
|
||||
removeSmallIslands(terrain);
|
||||
removeSmallLakes(terrain);
|
||||
}
|
||||
const shorelineWaters = processShore(terrain);
|
||||
processDistToLand(shorelineWaters, terrain);
|
||||
processOcean(terrain);
|
||||
removeSmallIslands(terrain, removeSmall);
|
||||
processWater(terrain, removeSmall);
|
||||
|
||||
const miniTerrain = await createMiniMap(terrain);
|
||||
const thumb = await createMapThumbnail(miniTerrain);
|
||||
@@ -75,7 +73,7 @@ export async function generateMap(
|
||||
};
|
||||
}
|
||||
|
||||
export async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
|
||||
async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
|
||||
// Create 2D array properly with correct dimensions
|
||||
const miniMap: Terrain[][] = Array(Math.floor(tm.length / 2))
|
||||
.fill(null)
|
||||
@@ -100,18 +98,19 @@ export async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
|
||||
}
|
||||
|
||||
function processShore(map: Terrain[][]): Coord[] {
|
||||
console.log("Identifying shorelines");
|
||||
const shorelineWaters: Coord[] = [];
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
const terrain = map[x][y];
|
||||
const tile = map[x][y];
|
||||
const ns = neighbors(x, y, map);
|
||||
if (terrain.type == TerrainType.Land) {
|
||||
if (tile.type == TerrainType.Land) {
|
||||
if (ns.filter((t) => t.type == TerrainType.Water).length > 0) {
|
||||
terrain.shoreline = true;
|
||||
tile.shoreline = true;
|
||||
}
|
||||
} else {
|
||||
if (ns.filter((t) => t.type == TerrainType.Land).length > 0) {
|
||||
terrain.shoreline = true;
|
||||
tile.shoreline = true;
|
||||
shorelineWaters.push({ x, y });
|
||||
}
|
||||
}
|
||||
@@ -121,24 +120,20 @@ function processShore(map: Terrain[][]): Coord[] {
|
||||
}
|
||||
|
||||
function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
|
||||
const queue: [Coord, number][] = shorelineWaters.map((coord) => [coord, 0]);
|
||||
const visited = new Set<string>();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const [coord, distance] = queue.shift()!;
|
||||
const key = `${coord.x},${coord.y}`;
|
||||
|
||||
if (visited.has(key)) continue;
|
||||
visited.add(key);
|
||||
|
||||
const terrain = map[coord.x][coord.y];
|
||||
if (terrain.type === TerrainType.Water) {
|
||||
terrain.magnitude = distance;
|
||||
|
||||
const nCoords: Coord[] = getNeighborCoords(coord.x, coord.y, map);
|
||||
nCoords.forEach((nCoord) => {
|
||||
queue.push([{ x: nCoord.x, y: nCoord.y }, distance + 1]);
|
||||
});
|
||||
console.log("Setting Water tiles magnitude = distance from land");
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
const tile = map[x][y];
|
||||
if (tile.type == TerrainType.Water) {
|
||||
if (shorelineWaters.some((coord) => coord.x == x && coord.y == y)) {
|
||||
tile.magnitude = 0;
|
||||
} else {
|
||||
const dist = shorelineWaters.map(
|
||||
(coord) => Math.abs(x - coord.x) + Math.abs(y - coord.y),
|
||||
);
|
||||
tile.magnitude = Math.min(...dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,14 +141,14 @@ function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
|
||||
function neighbors(x: number, y: number, map: Terrain[][]): Terrain[] {
|
||||
const nCoords: Coord[] = getNeighborCoords(x, y, map);
|
||||
const ns: Terrain[] = [];
|
||||
nCoords.forEach((nCoord) => {
|
||||
for (const nCoord of nCoords) {
|
||||
ns.push(map[nCoord.x][nCoord.y]);
|
||||
});
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
// Improved processOcean function that identifies the largest body of water
|
||||
function processOcean(map: Terrain[][]) {
|
||||
function processWater(map: Terrain[][], removeSmall: boolean) {
|
||||
console.log("Processing water bodies");
|
||||
const visited = new Set<string>();
|
||||
const waterBodies: { coords: Coord[]; size: number }[] = [];
|
||||
|
||||
@@ -176,16 +171,37 @@ function processOcean(map: Terrain[][]) {
|
||||
// Sort water bodies by size (largest first)
|
||||
waterBodies.sort((a, b) => b.size - a.size);
|
||||
|
||||
// Mark the largest water body as ocean
|
||||
if (waterBodies.length > 0) {
|
||||
const largestWaterBody = waterBodies[0];
|
||||
let smallLakes = 0;
|
||||
|
||||
// Mark all tiles in the largest water body as ocean
|
||||
if (waterBodies.length > 0) {
|
||||
// Mark the largest water body as ocean
|
||||
const largestWaterBody = waterBodies[0];
|
||||
for (const coord of largestWaterBody.coords) {
|
||||
map[coord.x][coord.y].ocean = true;
|
||||
}
|
||||
|
||||
console.log(`Identified ocean with ${largestWaterBody.size} water tiles`);
|
||||
|
||||
if (removeSmall) {
|
||||
// Assess size of the other water bodies and remove those smaller than min_lake_size
|
||||
console.log("Searching for small water bodies for removal");
|
||||
for (let w = 1; w < waterBodies.length; w++) {
|
||||
if (waterBodies[w].size < min_lake_size) {
|
||||
smallLakes++;
|
||||
for (const coord of waterBodies[w].coords) {
|
||||
map[coord.x][coord.y].type = TerrainType.Land;
|
||||
map[coord.x][coord.y].magnitude = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
`Identified and removed ${smallLakes} bodies of water smaller than ${min_lake_size} tiles`,
|
||||
);
|
||||
}
|
||||
|
||||
//Identify shoreline tiles, get array of shoreline water tiles
|
||||
const shorelineWaters = processShore(map);
|
||||
//Adjust water tile magnitudes to reflect distance from land
|
||||
processDistToLand(shorelineWaters, map);
|
||||
} else {
|
||||
console.log("No water bodies found in the map");
|
||||
}
|
||||
@@ -204,25 +220,25 @@ function packTerrain(map: Terrain[][]): Uint8Array {
|
||||
|
||||
for (let x = 0; x < width; x++) {
|
||||
for (let y = 0; y < height; y++) {
|
||||
const terrain = map[x][y];
|
||||
const tile = map[x][y];
|
||||
let packedByte = 0;
|
||||
if (terrain == null) {
|
||||
if (tile == null) {
|
||||
throw new Error(`terrain null at ${x}:${y}`);
|
||||
}
|
||||
|
||||
if (terrain.type === TerrainType.Land) {
|
||||
if (tile.type === TerrainType.Land) {
|
||||
packedByte |= 0b10000000;
|
||||
}
|
||||
if (terrain.shoreline) {
|
||||
if (tile.shoreline) {
|
||||
packedByte |= 0b01000000;
|
||||
}
|
||||
if (terrain.ocean) {
|
||||
if (tile.ocean) {
|
||||
packedByte |= 0b00100000;
|
||||
}
|
||||
if (terrain.type == TerrainType.Land) {
|
||||
packedByte |= Math.min(Math.ceil(terrain.magnitude), 31);
|
||||
if (tile.type == TerrainType.Land) {
|
||||
packedByte |= Math.min(Math.ceil(tile.magnitude), 31);
|
||||
} else {
|
||||
packedByte |= Math.min(Math.ceil(terrain.magnitude / 2), 31);
|
||||
packedByte |= Math.min(Math.ceil(tile.magnitude / 2), 31);
|
||||
}
|
||||
|
||||
packedData[4 + y * width + x] = packedByte;
|
||||
@@ -253,56 +269,49 @@ function getArea(
|
||||
area.push({ x: coord.x, y: coord.y });
|
||||
|
||||
const nCoords: Coord[] = getNeighborCoords(coord.x, coord.y, map);
|
||||
nCoords.forEach((nCoord) => {
|
||||
for (const nCoord of nCoords) {
|
||||
queue.push({ x: nCoord.x, y: nCoord.y });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
function removeSmallIslands(map: Terrain[][]) {
|
||||
function removeSmallIslands(map: Terrain[][], removeSmall: boolean) {
|
||||
if (!removeSmall) return;
|
||||
const visited = new Set<string>();
|
||||
const landBodies: { coords: Coord[]; size: number }[] = [];
|
||||
|
||||
// Find all distinct land bodies
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
if (map[x][y].type === TerrainType.Land) {
|
||||
const key = `${x},${y}`;
|
||||
if (visited.has(key)) continue;
|
||||
|
||||
const island = getArea(x, y, map, visited);
|
||||
if (island.length < min_island_size) {
|
||||
island.forEach((coord) => {
|
||||
map[coord.x][coord.y].type = TerrainType.Water;
|
||||
});
|
||||
}
|
||||
const landBody: Coord[] = getArea(x, y, map, visited);
|
||||
landBodies.push({
|
||||
coords: landBody,
|
||||
size: landBody.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeSmallLakes(map: Terrain[][]) {
|
||||
const visited = new Set<string>();
|
||||
const min_lake_size = 200;
|
||||
let smallIslands = 0;
|
||||
|
||||
console.log(`removing small lakes ${map.length}, ${map[0].length}`);
|
||||
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
if (map[x][y].type === TerrainType.Water) {
|
||||
const key = `${x},${y}`;
|
||||
if (visited.has(key)) continue;
|
||||
|
||||
const lake = getArea(x, y, map, visited);
|
||||
if (lake.length < min_lake_size) {
|
||||
lake.forEach((coord) => {
|
||||
map[coord.x][coord.y].type = TerrainType.Land;
|
||||
map[coord.x][coord.y].magnitude = 0;
|
||||
});
|
||||
}
|
||||
for (let b = 0; b < landBodies.length; b++) {
|
||||
if (landBodies[b].size < min_island_size) {
|
||||
smallIslands++;
|
||||
for (const coord of landBodies[b].coords) {
|
||||
map[coord.x][coord.y].type = TerrainType.Water;
|
||||
map[coord.x][coord.y].magnitude = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
`Identified and removed ${smallIslands} islands smaller than ${min_island_size} tiles`,
|
||||
);
|
||||
}
|
||||
|
||||
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
|
||||
|
||||
@@ -18,8 +18,11 @@ const maps = [
|
||||
"Britannia",
|
||||
"GatewayToTheAtlantic",
|
||||
"Australia",
|
||||
"Iceland",
|
||||
];
|
||||
|
||||
const removeSmall = true;
|
||||
|
||||
async function loadTerrainMaps() {
|
||||
await Promise.all(
|
||||
maps.map(async (map) => {
|
||||
@@ -30,7 +33,11 @@ async function loadTerrainMaps() {
|
||||
map + ".png",
|
||||
);
|
||||
const imageBuffer = await fs.readFile(mapPath);
|
||||
const { map: mainMap, miniMap, thumb } = await generateMap(imageBuffer);
|
||||
const {
|
||||
map: mainMap,
|
||||
miniMap,
|
||||
thumb,
|
||||
} = await generateMap(imageBuffer, removeSmall, map);
|
||||
|
||||
const outputPath = path.join(
|
||||
process.cwd(),
|
||||
|
||||
@@ -287,6 +287,7 @@ function getNextMap(): GameMapType {
|
||||
Mars: 2,
|
||||
Britannia: 2,
|
||||
GatewayToTheAtlantic: 2,
|
||||
Iceland: 2,
|
||||
};
|
||||
|
||||
Object.keys(GameMapType).forEach((key) => {
|
||||
|
||||
Reference in New Issue
Block a user