diff --git a/build.gradle b/build.gradle index f19fac9c69..0e44959044 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ allprojects { appName = 'Mindustry' gdxVersion = '1.9.8' aiVersion = '1.8.1' - uCoreVersion = '39939f7' + uCoreVersion = '9503bcb' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index e8502e988d..41e3a372f2 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -263,7 +263,7 @@ setting.sensitivity.name=Controller Sensitivity setting.saveinterval.name=Autosave Interval setting.seconds={0} Seconds setting.fullscreen.name=Fullscreen -setting.multithread.name=Multithreading [scarlet](unstable!) +setting.multithread.name=Multithreading setting.fps.name=Show FPS setting.vsync.name=VSync setting.lasers.name=Show Power Lasers diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties new file mode 100644 index 0000000000..0630b6e4bc --- /dev/null +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -0,0 +1,500 @@ +text.about = Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n +text.discord = Приєднуйтесь до нашого Discord! +text.changes = [SCARLET] Увага! \n[] Деякі важливі механіки гри були змінені.\n- [accent] Телепорти [] тепер використовують електроенергію. \n- [accent] Домінна піч [] та [accent] Тиглі [] тепер мають ліміт. \n- [accent] Тиглі [] зараз вимагають вугілля як паливо. +text.gameover = Ядро було зруйновано. +text.highscore = [YELLOW] Новий рекорд! +text.lasted = Ви тримались до хвилі +text.level.highscore = Рекорд: [accent] {0} +text.level.delete.title = Підтвердьте видалення +text.level.delete = Ви впевнені, що хочете видалити карту \"[orange] {0} \"? +text.level.select = Вибір рівня +text.level.mode = Ігровий режим +text.savegame = Зберегти гру +text.loadgame = Завантажити гру +text.joingame = Приєднатися\nдо гри +text.newgame=Нова гра +text.quit = Вийти +text.about.button = Про +text.name = Назва: +text.public = Публічний +text.players = {0} гравців онлайн +text.server.player.host = {0} (host) +text.players.single = {0} гравців онлайн +text.server.mismatch = Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! +text.server.closing = [accent] Закриття сервера ... +text.server.kicked.kick = Ви були вигнані з сервера! +text.server.kicked.invalidPassword = Невірний пароль! +text.server.kicked.clientOutdated = Застарілий клієнт! Оновіть свою гру! +text.server.kicked.serverOutdated = Застарілий сервер! Попросіть хост оновити! +text.server.connected = {0} приєднався. +text.server.disconnected = {0} від'єднано. +text.nohost = Неможливо розмістити сервер на власній карті! +text.hostserver = Хост-сервер +text.host = Хост +text.hosting = [accent] Відкриття сервера ... +text.hosts.refresh = Оновити +text.hosts.discovering = Знайомство з мережевими іграми +text.server.refreshing = Оновити сервери +text.hosts.none = [lightgray] Ніяких ігор у мережі не знайдено! +text.host.invalid = [scarlet] Неможливо підключитися до хосту. +text.server.friendlyfire = Дружній вогонь +text.server.add = Додати сервер +text.server.delete = Ви впевнені, що хочете видалити цей сервер? +text.server.hostname = Хост: {0} +text.server.edit = Редагувати сервер +text.joingame.byip = [] Приєднатися по IP ...[] +text.joingame.title = Приєднатися до гри +text.joingame.ip = IP +text.disconnect = Роз'єднано +text.connecting = [accent] Підключення ... +text.connecting.data = [accent] Завантаження світових даних ... +text.connectfail = [crimson] Не вдалося підключитися до сервера: [orange] {0} +text.server.port = Порт +text.server.addressinuse = Адреса вже використовується! +text.server.invalidport = Недійсний номер порту. +text.server.error = [crimson] Помилка хостингу сервера: [orange] {0} +text.tutorial.back = < Попер. +text.tutorial.next = Далі > +text.save.new = Нове збереження +text.save.overwrite = Ви впевнені, що хочете перезаписати цей слот для збереження? +text.overwrite = Перезаписати +text.save.none = Не знайдено жодних збережень! +text.saveload = [accent] Збереження ... +text.savefail = Не вдалося зберегти гру! +text.save.delete.confirm = Ви впевнені, що хочете видалити це збереження? +text.save.delete = Видалити +text.save.export = Експорт збереження +text.save.import.invalid = [orange] Це збереження недійсне! +text.save.import.fail = [crimson] Не вдалося імпортувати збереження: [orange] {0} +text.save.export.fail = [crimson] Не вдалося експортувати збереження: [orange] {0} +text.save.import = Імпортувати збереження +text.save.newslot = Назва збереження: +text.save.rename = Переіменувати +text.save.rename.text = Нова назва: +text.selectslot = Виберіть збереження. +text.slot = [accent] слот {0} +text.save.corrupted = [orange] Збережений файл пошкоджений або він невірний! +text.empty = <порожньо> +text.on = Увімкнути +text.off = Вимкнути +text.save.autosave = Автозбереження: {0} +text.save.map = Карта +text.save.wave = Хвиля {0} +text.save.difficulty = Складність +text.save.date = Останнє збережено: {0} +text.confirm = Підтвердити +text.delete = Видалити +text.ok = ОК +text.open = Відкрити +text.cancel = Скасувати +text.openlink = Відкрити посилання +text.back = Назад +text.quit.confirm = Ти впевнений що хочеш піти? +text.loading = [accent] Завантаження ... +text.wave = [orange] хвиля {0} +text.wave.waiting = Хвиля через {0} +text.waiting = Очікування… +text.enemies = {0} Вороги +text.enemies.single = Противник +text.loadimage = Завантажити зображення +text.saveimage = Зберегти зображення +text.oregen = Генерація руд +text.editor.badsize = [orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0} +text.editor.errorimageload = Помилка завантаження файлу зображень: [orange] {0} +text.editor.errorimagesave = Помилка збереження файлу зображення: [orange] {0} +text.editor.generate = Генератор +text.editor.resize = Змінити розмір +text.editor.loadmap = // Завантажити карту +text.editor.savemap = Зберегти карту +text.editor.loadimage = Завантажити зображення +text.editor.saveimage = Зберегти зображення +text.editor.unsaved = [scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? +text.editor.brushsize = Розмір пензля: {0} +text.editor.noplayerspawn = Ця карта не має ігрового поля для гравця! +text.editor.manyplayerspawns = Карти не можуть мати більше одного ігрового поля для гравців! +text.editor.manyenemyspawns = Не може бути більше ніж {0} ворожих точок! +text.editor.resizemap = Змінити розмір карти +text.editor.resizebig = [scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними. +text.editor.mapname = Назва карти: +text.editor.overwrite = [accent] Попередження! Це перезаписує існуючу карту. +text.editor.failoverwrite = [crimson] Неможливо перезаписати карту за замовчуванням! +text.editor.selectmap = Виберіть карту для завантаження: +text.width = Ширина +text.height = Висота +text.randomize = Рандомізувати +text.apply = Застосувати +text.update = Оновити +text.menu = Меню +text.play = Відтворити +text.load = Завантаження +text.save = Зберегти +text.language.restart = Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. +text.settings.language = Мова +text.settings = Налаштування +text.tutorial = Навчальний\nпосібник +text.editor = Редактор +text.mapeditor = Редактор карт +text.donate = Підтримати проект +text.settings.reset = Скинути до стандартних +text.settings.controls = Елементи управління +text.settings.game = Гра +text.settings.sound = Звук +text.settings.graphics = Графіка +text.upgrades = Оновлення +text.purchased = [LIME] Створено! +text.weapons = Зброя +text.paused = Пауза +text.respawn = Відновлення за +text.info.title = [accent] інформація +text.error.title = [crimson] Виникла помилка +text.error.crashmessage = [SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com [] +text.error.crashtitle = Виникла помилка +text.mode.break = Режим зносу: {0} +text.mode.place = Режим будівництва: {0} +placemode.hold.name = Лінія +placemode.areadelete.name = Площа +placemode.touchdelete.name = Дотик +placemode.holddelete.name = Утримування. +placemode.none.name = (None) +placemode.touch.name = Дотик +placemode.cursor.name = курсор +text.blocks.extrainfo = [accent] додатковий інформаційний блок: +text.blocks.blockinfo = Блокування інформації +text.blocks.powercapacity = Потужність +text.blocks.powershot = Потужність / постріл +text.blocks.powersecond = Потужність / секунда +text.blocks.powerdraindamage = Потужність дренажу / пошкодження +text.blocks.shieldradius = Радіус щита +text.blocks.itemspeedsecond = Швидкість / секунда +text.blocks.range = Радіус +text.blocks.size = Розмір +text.blocks.powerliquid = Потужність / Рідина +text.blocks.maxliquidsecond = Макс. Рідина / секунда +text.blocks.liquidcapacity = Ємкість рідини +text.blocks.liquidsecond = Рідина / секунда +text.blocks.damageshot = Пошкодження / постріл +text.blocks.ammocapacity = Місткість боєприпасів +text.blocks.ammo = Набої +text.blocks.ammoitem = Боєприпаси / предмет +text.blocks.maxitemssecond = Макс. Елементи / секунду +text.blocks.powerrange = Радіус потужності +text.blocks.lasertilerange = Радіус лазерних плиток +text.blocks.capacity = Ємкість +text.blocks.itemcapacity = Ємкість предмету +text.blocks.maxpowergenerationsecond = Максимальна потужність / секунда +text.blocks.powergenerationsecond = Потужність / секунда +text.blocks.generationsecondsitem = Генерація за секунду / предмет +text.blocks.input = Ввід +text.blocks.inputliquid = Ввід речовини +text.blocks.inputitem = Вхідний матеріал +text.blocks.output = Вивід +text.blocks.secondsitem = Секунда / предмет +text.blocks.maxpowertransfersecond = Максимальна передача потужності / секунда +text.blocks.explosive = Вибухонебезпечний! +text.blocks.repairssecond = Ремонт / секунда +text.blocks.health = Здоров'я +text.blocks.inaccuracy = Неточність +text.blocks.shots = Постріли +text.blocks.shotssecond = Постріли / секунду +text.blocks.fuel = Паливо: +text.blocks.fuelduration = Тривалість палива +text.blocks.maxoutputsecond = Макс. Вихід / секунду +text.blocks.inputcapacity = Вхідна ємність +text.blocks.outputcapacity = Випускна ємність +text.blocks.poweritem = Потужність / виріб +text.placemode = Місцевий режим +text.breakmode = Перерваний режим +text.health = Здоров'я +setting.difficulty.easy = Легкий +setting.difficulty.normal = Нормальний +setting.difficulty.hard = Важкий +setting.difficulty.insane = Божевільний +setting.difficulty.purge = Очистити +setting.difficulty.name = Складність +setting.screenshake.name = Тряска екрана +setting.smoothcam.name = Гладка камера +setting.indicators.name = Індикатори ворога +setting.effects.name = Ефекти відображення +setting.sensitivity.name = Чутливість контролера +setting.saveinterval.name = Інтервал автозбереження +setting.seconds = {0} секунд +setting.fullscreen.name = Повноекранний +setting.multithread.name = Багатопотоковий [scarlet] (нестабільний!) +setting.fps.name = Показати FPS +setting.vsync.name = VSunc +setting.lasers.name = Показати енергетичні лазери +setting.healthbars.name = Показати здоров'я +setting.pixelate.name = Пікселяція екрану +setting.musicvol.name = Гучність музики +setting.mutemusic.name = Вимкнути музику +setting.sfxvol.name = Гучність ефектів +setting.mutesound.name = Вимкнути звук +map.maze.name = Лабіринт +map.fortress.name = Фортеця +map.sinkhole.name = Свердловина +map.caves.name = Печери +map.volcano.name = Вулкан +map.caldera.name = Кальдера +map.scorch.name = Мертва земля +map.desert.name = Пустеля +map.island.name = Острів +map.grassland.name = Пасовища +map.tundra.name = Тундра +map.spiral.name = Спіраль +map.tutorial.name = Навчання +tutorial.intro.text = [yellow] Ласкаво просимо до підручника. [] Для початку натисніть \"далі\". +tutorial.moveDesktop.text = Для переміщення використовуйте клавіші [orange] ​​[[WASD] []. Утримуйте [orange] SHIFT[], для прискорення. Утримуйте [orange] CTRL [], використовуючи [orange] колесо прокручування [] для збільшення або зменшення. +tutorial.shoot.text = Використовуйте мишу, щоб націлитись, утримуйте [orange] ліву кнопку миші [], щоб стріляти. Попрактикуйтесь на [yellow] мішені []. +tutorial.moveAndroid.text = Щоб перетягнути панораму, перетягніть один палець по екрану. Використовуйте два пальця, щоб збільшити чи зменшити маштаб. +tutorial.placeSelect.text = Спробуйте вибрати [yellow] конвеєр [] у меню блоку внизу справа. +tutorial.placeConveyorDesktop.text = Використовуйте [orange] [[колесико миші] [], щоб повернути конвеєр [orange] вперед [], а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[ліву кнопку миші] []. +tutorial.placeConveyorAndroid.text = Використовуйте [orange] [[кнопку оберту] [], щоб обернути конвеєр [оранжевий] вперед [], перетягуйте його одним пальцем, а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[галочка][]. +tutorial.placeConveyorAndroidInfo.text = Крім того, ви можете натиснути піктограму перехрестя внизу ліворуч, щоб переключитися на [orange] [[сенсорний режим]] [], і помістити блоки, натиснувши на екран. У сенсорному режимі блоки можна повертати зі стрілкою внизу ліворуч. Натисніть [yellow] наступний [], щоб спробувати. +tutorial.placeDrill.text = Тепер виберіть та розмістіть [yellow] кам'яне свердло [] у зазначеному місці. +tutorial.blockInfo.text = Якщо ви хочете дізнатись більше про блок, ви можете торкнутися [orange] знак питання [] у верхньому правому куті, щоб прочитати його опис. +tutorial.deselectDesktop.text = Ви можете вимкнути блок, використовуючи [orange] [[клацання правою кнопкою миші] []. +tutorial.deselectAndroid.text = Ви можете скасувати вибір блоку, натиснувши кнопку [orange] X []. +tutorial.drillPlaced.text = Дриль тепер видобуває [yellow] камінь, [] та виведе його на конвеєр, а потім переміщає його в [yellow] ядро []. +tutorial.drillInfo.text = Різні руди потребують різних дрилі. Камінь вимагає кам'яні свердла, залізо вимагає залізні свердла та ін +tutorial.drillPlaced2.text = Переміщення елементів у ядро ​​вказує їх у ваш [yellow] предметний інвентар [] у верхньому лівому куті. Розміщення блоків використовує предмети з вашого інвентарю. +tutorial.moreDrills.text = Ви можете пов'язати багато свердлів і конвеєрів разом в одну гілку конвеєра. +tutorial.deleteBlock.text = Ви можете видалити блоки, натиснувши правою клавішею [orange] правою кнопкою миші [] по блоці, який ви хочете видалити. Спробуйте видалити цей конвеєр. +tutorial.deleteBlockAndroid.text = Ви можете видалити блоки за допомогою [orange], перехрестя [] в меню [mode] зламу [orange] у нижньому лівому куті та натиснувши на блок. Спробуйте видалити цей конвеєр. +tutorial.placeTurret.text = Тепер виділіть та розмістіть [yellow] турель [] у [yellow] позначеному місці []. +tutorial.placedTurretAmmo.text = Ця турель тепер приймає [yellow] боєприпас [] з конвеєра. Ви можете побачити, скільки боєприпасів вона має, натискаючи на неї і перевіряючи [green] зелену полоску []. +tutorial.turretExplanation.text = Турелі будуть автоматично стріляти у найближчого ворога, якщо вони мають достатню кількість боєприпасів. +tutorial.waves.text = Кожні [yellow] 60 [] секунд, хвиля [coral] ворогів [] буде виникати в певних місцях і намагатися знищити ядро. +tutorial.coreDestruction.text = Ваша мета полягає в тому, щоб [yellow] захищати ядро []. Якщо ядро ​​знищено, ви [coral] програєте[]. +tutorial.pausingDesktop.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] паузи [] у верхньому лівому куті або на кнопку [orange] пропуск [], щоб призупинити гру. Ви можете вибрати і розмістити блоки під час призупинення, але не можете переміщатися чи стріляти. +tutorial.pausingAndroid.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] пауза [] у верхньому лівому куті, щоб призупинити гру. Ти можеш ще знищувати та будувати блоки під час призупинення. +tutorial.purchaseWeapons.text = Ви можете придбати нову [yellow] зброю [] для вашого механізму, відкривши меню оновлення в лівому нижньому кутку. +tutorial.switchWeapons.text = Перемикати зброю будь-яким натисканням його піктограми внизу ліворуч або за допомогою цифр [orange] [[1-9] []. +tutorial.spawnWave.text = Ось хвиля зараз. Знищи їх +tutorial.pumpDesc.text = У пізніших хвилях, можливо, доведеться використовувати [yellow] насоси [] для розподілу рідин для генераторів або екстракторів. +tutorial.pumpPlace.text = Насоси працюють аналогічно свердлам, за винятком того, що вони виробляють рідини замість предметів. Спробуйте встановити насос на [yellow] призначене мастило []. +tutorial.conduitUse.text = Тепер покладіть [orange] трубопровід [], віддаляючись від насоса. +tutorial.conduitUse2.text = І ще кілька ... +tutorial.conduitUse3.text = І ще кілька ... +tutorial.generator.text = Тепер, помістіть блок [orange] ​​базовий генератор енергії [] в кінці каналу. +tutorial.generatorExplain.text = Цей генератор тепер створить [yellow] енергію [] від масла. +tutorial.lasers.text = Потужність розподіляється за допомогою [yellow] лазерів потужності []. Поверніть і помістіть його тут. +tutorial.laserExplain.text = Тепер генератор переведе енергію в лазерний блок. Промінь [yellow] непрозорий [] означає, що в даний час він передає потужність, а промінь [yellow] прозорий [] означає, що це не так. +tutorial.laserMore.text = Ви можете перевірити, скільки енергії в блоку, наведіть курсор миші на нього і перевірте [yellow] жовту стрічку [] у верхній частині екрана. +tutorial.healingTurret.text = Цей лазер може бути використаний для живлення турелі для ремонту [lime] []. Помістіть одну тут. +tutorial.healingTurretExplain.text = Поки вона має енергію, ця турель може [lime] відремонтувати блоки. [] Під час гри постарайтеся збудувати одну таку чим швидше! +tutorial.smeltery.text = Для багатьох блоків потрібна [orange] сталь [], для цього потрібна[orange] доминна піч [] . Місце тут. +tutorial.smelterySetup.text = Ця піч буде тепер виробляти [orange] сталь [] із вхідного заліза, використовуючи вугілля як паливо. +tutorial.tunnelExplain.text = Також зауважте, що елементи проходять через [yellow] тунельний блок [] і з'являються з іншого боку, проходячи через кам'яний блок. Майте на увазі, що тунелі можуть проходити лише до 2 блоків. +tutorial.end.text = Ви завершили підручник! Удачі! +text.keybind.title = Ключ перемотки +keybind.move_x.name = move_x +keybind.move_y.name = move_y +keybind.select.name = Вибрати +keybind.break.name = {0}break{/0}{1}; {/1} +keybind.shoot.name = Постріл +keybind.zoom_hold.name = zoom_hold +keybind.zoom.name = Збільшити +keybind.block_info.name = Інформація про блок +keybind.menu.name = Меню +keybind.pause.name = Пауза +keybind.dash.name = Тире +keybind.chat.name = Чат +keybind.player_list.name = Список гравців +keybind.console.name = // Консоль 1 +keybind.rotate_alt.name = rotate_alt +keybind.rotate.name = Повернути +keybind.weapon_1.name = Зброя! +keybind.weapon_2.name = Зброя! +keybind.weapon_3.name = Зброя! +keybind.weapon_4.name = Зброя! +keybind.weapon_5.name = Зброя! +keybind.weapon_6.name = Зброя! +mode.waves.name = Хвилі +mode.sandbox.name = Пісочниця +mode.freebuild.name = Вільний режим +upgrade.standard.name = Стандартний +upgrade.standard.description = Стандартний механ. +upgrade.blaster.name = Бластер +upgrade.blaster.description = Стріляє повільно, слабкі кулі. +upgrade.triblaster.name = Трипластер +upgrade.triblaster.description = Вистрілює 3 кулі в розповсюдженні. +upgrade.clustergun.name = Касетна гармата +upgrade.clustergun.description = Вистрілює неточними вибуховими гранатами. +upgrade.beam.name = Пушечна гармата +upgrade.beam.description = Вистрілює далекобійним,пробірний лазерний промінь. +upgrade.vulcan.name = Вулкан +upgrade.vulcan.description = Вистрілює шквал швидких куль. +upgrade.shockgun.name = Шок-пушка +upgrade.shockgun.description = Стріляє руйнівним вибухом заряженої шрапнелі. +item.stone.name = Камінь +item.iron.name = Залізо +item.coal.name = Вугівалля +item.steel.name = Сталь +item.titanium.name = Титан +item.dirium.name = Дириум +item.uranium.name = Уран +item.sand.name = Пісок +liquid.water.name = Вода +liquid.plasma.name = Плазма +liquid.lava.name = Лава +liquid.oil.name = Нафта +block.weaponfactory.name = Фабрика зброї +block.weaponfactory.fulldescription = Використовується для створення зброї для гравця mech. Натисніть, щоб використати. Автоматично приймає ресурси з основного ядра. +block.air.name = Повітря +block.blockpart.name = Блокчастина +block.deepwater.name = Глибока вода +block.water.name = Вода +block.lava.name = Лава +block.oil.name = Нафта +block.stone.name = Камінь +block.blackstone.name = Чорний камінь +block.iron.name = Залізо +block.coal.name = Вугілля +block.titanium.name = Титан +block.uranium.name = Уран +block.dirt.name = Бруд +block.sand.name = Пісок +block.ice.name = Лід +block.snow.name = Сніг +block.grass.name = Трава +block.sandblock.name = Блок піску +block.snowblock.name = Блок снігу +block.stoneblock.name = Блок камню +block.blackstoneblock.name = Блок чорного камню +block.grassblock.name = Блок бруду +block.mossblock.name = Моссблок +block.shrub.name = Чагарник +block.rock.name = Камень +block.icerock.name = Ледяний камень +block.blackrock.name = Чорний камінь +block.dirtblock.name = Блок землі +block.stonewall.name = Кам'яна стіна +block.stonewall.fulldescription = Недорогий захисний блок. Корисно для захисту ядра та турелі в перші кілька хвиль. +block.ironwall.name = Залізна стіна +block.ironwall.fulldescription = Основний захисний блок. Забезпечує захист від ворогів. +block.steelwall.name = Сталева стіна +block.steelwall.fulldescription = Стандартний захисний блок. адекватний захист від ворогів. +block.titaniumwall.name = Титанова стіна +block.titaniumwall.fulldescription = Сильний захисний блок. Забезпечує захист від ворогів. +block.duriumwall.name = Діріумова стіна +block.duriumwall.fulldescription = Дуже сильний захисний блок. Забезпечує захист від ворогів. +block.compositewall.name = Композитна стіна +block.steelwall-large.name = Велика сталева стіна +block.steelwall-large.fulldescription = Стандартний захисний блок. Поєднує в собі кілька блоків. +block.titaniumwall-large.name = Велика титанова стіна +block.titaniumwall-large.fulldescription = Сильний захисний блок. Поєднує в собі кілька блоків. +block.duriumwall-large.name = Велика дирмітова стіна +block.duriumwall-large.fulldescription = Дуже сильний захисний блок.Поєднує в собі кілька блоків. +block.titaniumshieldwall.name = Стіна з щитом +block.titaniumshieldwall.fulldescription = Сильний захисний блок з додатковим вбудованим щитом. Потрібна енергія. Використовує енергію для поглинання ворожих куль. Рекомендується використовувати силові пристосування для забезпечення енергії цього блоку. +block.repairturret.name = Ремонтна турель +block.repairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Повільний темп. Використовує невелику кількість енергії. +block.megarepairturret.name = Ремонтна турель II +block.megarepairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Збільшений радіус та швидший темп ремонту . Використовує багато енергії. +block.shieldgenerator.name = Генератор щиту +block.shieldgenerator.fulldescription = Передовий захисний блок. Захищає всі блоки в радіусі від нападу. Не вкористовує енергію при бездіяльності, але швидко витрачає енергію на захист від куль. +block.door.name = Двері +block.door.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. +block.door-large.name = Великі двері +block.door-large.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. +block.conduit.name = Трубопровід +block.conduit.fulldescription = Основний транспортний блок. Працює як конвеєр, але з рідинами. Найкраще використовується з насосами або іншими трубопроводами. Може використовуватися як міст через рідини для ворогів та гравців. +block.pulseconduit.name = Імпульсний канал +block.pulseconduit.fulldescription = Покращенний блок перевезення рідин. Транспортує рідини швидше і зберігає більше стандартних каналів. +block.liquidrouter.name = маршрутизатор для рідини +block.liquidrouter.fulldescription = Працює аналогічно маршрутизатору. Приймає рідину ввід з одного боку і виводить його на інші сторони. Корисний для розщеплення рідини з одного каналу на кілька інших трубопроводів. +block.conveyor.name = Конвеєр +block.conveyor.fulldescription = Базовий транспортний блок. Переміщує предмети вперед і автоматично вкладає їх у турелі або ремісники. Поворотний Може використовуватися як міст через рідину для ворогів та гравців. +block.steelconveyor.name = Сталевий конвеєр +block.steelconveyor.fulldescription = Розширений блок транспортування предметів. Переміщення елементів швидше, ніж стандартні конвеєри. +block.poweredconveyor.name = Імпульсний конвеєр +block.poweredconveyor.fulldescription = Кінцевий транспортний блок. Переміщення елементів швидше, ніж сталеві конвеєри. +block.router.name = Маршрутизатор +block.router.fulldescription = Приймає елементи з одного напрямку і виводить їх на 3 інших напрямках. Можна також зберігати певну кількість предметів. Використовується для розщеплення матеріалів з одного свердла на декілька башточок. +block.junction.name = Міст +block.junction.fulldescription = Виступає як міст для двох перехресних конвеєрних стрічок. Корисне у ситуаціях з двома різними конвеєрами, що несуть різні матеріали в різних місцях. +block.conveyortunnel.name = Конвеєрний тунель +block.conveyortunnel.fulldescription = Транспортує предмети під блоками. Щоб використати, помістіть один тунель, що веде у блок, щоб бути підсвіченим, а один - з іншого боку. Переконайтеся, що обидва тунелі стикаються з протилежними напрямками, тобто до блоків, які вони вводять або виводять. +block.liquidjunction.name = Міст для рідини +block.liquidjunction.fulldescription = Діє як міст для двох перехресних трубопроводів. Корисно в ситуаціях з двома різними трубами, що несуть різні рідини в різних місцях. +block.liquiditemjunction.name = Перехрестя рідкого пункту +block.liquiditemjunction.fulldescription = Виступає як міст для перетину трубопроводів і конвеєрів. +block.powerbooster.name = Підсилювач потужності +block.powerbooster.fulldescription = Поширює енергію на всі блоки в межах його радіуса. +block.powerlaser.name = Енергетичний лазер +block.powerlaser.fulldescription = Створює лазер, який передає енергію блоку перед ним. Не створює жодної сили сама. Найкраще використовується з генераторами або іншими лазерами. +block.powerlaserrouter.name = Лазерний маршрутизатор +block.powerlaserrouter.fulldescription = Лазер, який розподіляє енергію у три напрямки одночасно. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора. +block.powerlasercorner.name = Лазерний кут +block.powerlasercorner.fulldescription = Лазер, який розподіляє енергію одночасно на два напрямки. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора, а маршрутизатор неточний. +block.teleporter.name = Телепорт +block.teleporter.fulldescription = Продвинутий блок транспортування предметів.Щоб телепортувати предмети з одного місця в інше потрібно збудувати 2 телепорти і назначити на них одинаковий колір. Використовує енергію. Натисніть, щоб змінити колір. +block.sorter.name = Сортувальник +block.sorter.fulldescription = Сортує предмети за типом матеріалу. Матеріал для прийняття позначається кольором у блоці. Всі елементи, що відповідають матеріалу сортування, виводяться вперед, а все інше виводить ліворуч і праворуч. +block.core.name = Ядро +block.pump.name = Насос +block.pump.fulldescription = Насоси рідини з вихідного блоку - зазвичай вода, лава чи олія. Виводить рідину в сусідні трубопроводи. +block.fluxpump.name = Флюсовий насос +block.fluxpump.fulldescription = Розширений варіант насоса. Зберігає більше рідини та перекачує швидше. +block.smelter.name = Плавильня +block.smelter.fulldescription = Основний ремісничий блок. Коли вводиться 1 залізо та 1 вугілля в якості палива, виводить одну сталь. Рекомендується вводити залізо та вугілля на різних поясах, щоб запобігти засміченню. +block.crucible.name = Тигель +block.crucible.fulldescription = Розширений блок обробки. При введенні 1 титану, 1 сталі та 1 вугілля в якості пального, виводить один дирний. Рекомендується вводити вугілля, сталь та титан на різних поясах, щоб запобігти засміченню. +block.coalpurifier.name = вугільний екстрактор +block.coalpurifier.fulldescription = Основний екстрактор. Виходить вугілля при постачанні великої кількості води та каменю. +block.titaniumpurifier.name = Титановий екстрактор +block.titaniumpurifier.fulldescription = Стандартний блок екстрактора. Виходить титан при постачанні великої кількості води та заліза. +block.oilrefinery.name = Нафтопереробний завод +block.oilrefinery.fulldescription = Очищує велику кількість нафти і перетворює на вугілля. Корисний для заправки вугільних башточок, коли вугільні родовища є дефіцитними. +block.stoneformer.name = Кам'янний екстрактор +block.stoneformer.fulldescription = Здавлюється рідка лава в камінь. Корисно для виготовлення великої кількості каменю для очищувачів вугілля. +block.lavasmelter.name = Лавовий завод +block.lavasmelter.fulldescription = Використовує лаву для перетворення залізо на сталь. Альтернатива плавильні. Корисно в ситуаціях, коли вугілля є дефіцитним. +block.stonedrill.name = Кам'янна свердловина +block.stonedrill.fulldescription = Основна свердловина.Розміщюється на кам'яній плитці виводить камінь повільними темпами. +block.irondrill.name = Залізна свердловина +block.irondrill.fulldescription = Базова свердловина.Розміщюється на родовищі залізної руди, випускає залізо в повільному темпі. +block.coaldrill.name = Вугільна свердловина +block.coaldrill.fulldescription = Базова свердловина.Розміщюється на родовищі вугільної руди ,видобуває вугілля повільними темпами. +block.uraniumdrill.name = Уранова свердловина +block.uraniumdrill.fulldescription = Продвинута свердловина. Розміщюється на родовищі уранової руди.Видобуток урану відбувається повільними темпами. +block.titaniumdrill.name = Титанова свердловина +block.titaniumdrill.fulldescription = Продвинута свердловина.Розміщюється на родовищі титанової руди, Видобуток титану відбувається повільним темпом. +block.omnidrill.name = Убер свердловина +block.omnidrill.fulldescription = Кінцева свердловина.Дуже швидко видобуває будь-який вид руди. +block.coalgenerator.name = Вугільний генератор +block.coalgenerator.fulldescription = Основний генератор. Генерує енергію з вугілля. Виводиться потужність лазерів на 4 сторони. +block.thermalgenerator.name = Теплогенератор +block.thermalgenerator.fulldescription = Генерує енергію від лави. Виводиться потужність лазерів на 4 сторони. +block.combustiongenerator.name = Генератор горіння +block.combustiongenerator.fulldescription = Генерує енергію з нафти. Виводиться потужність лазерів на 4 сторони. +block.rtgenerator.name = RTG генератор +block.rtgenerator.fulldescription = Генерує невелику кількість енергії з радіоактивного розпаду урану. Виводиться потужність лазерів на 4 сторони. +block.nuclearreactor.name = Ядерний реактор +block.nuclearreactor.fulldescription = Розширений варіант RTG Generator і кінцевий генератор електроенергії. Генерує енергію з урану. Потребує постійного водяного охолодження.Сильно вибухне якщо не буде постачання води у великії кількості +block.turret.name = Турель +block.turret.fulldescription = Базова, дешева турель. Використовує камінь для боєприпасів. Має трохи більше діапазону, ніж подвійна турель. +block.doubleturret.name = Подвійна турель +block.doubleturret.fulldescription = Дещо потужна версія турель. Використовує камінь для боєприпасів. Значно більший урон, але менший діапазон. Вистрілює дві кулі. +block.machineturret.name = Кулеметна турель +block.machineturret.fulldescription = Стандартна всеосяжна турель. Використовує залізо для боєприпасів. Має швидку швидкість пострілу і гідну шкоду. +block.shotgunturret.name = Розріджуюча турель +block.shotgunturret.fulldescription = Стандартна турель. Використовує залізо для боєприпасів. Вистрілює 7 куль навколо себе.Наносить значних ушкоджень,звісно якщо поцілить :) +block.flameturret.name = Вогнемет +block.flameturret.fulldescription = Продвинута турель ближнього діапазону. Використовує вугілля для боєприпасів. Має дуже низький радіус, але дуже високий збиток. Добре для близьких дистанцій. Рекомендується використовувати за стінами. +block.sniperturret.name = Лазерна турель. +block.sniperturret.fulldescription = Продвинута далекобійна турель. Використовує сталь для боєприпасів. Дуже високий збиток, але низький рівень урону. Дорогі для використання, але можуть бути розташовані далеко від ліній ворога через його радіус. +block.mortarturret.name = Флак турель +block.mortarturret.fulldescription = Продвинута,неточна турель. Використовує вугілля для боєприпасів. Стріляє кулями, що вибухають у шрапнеллв. Корисне для великих натовпів ворогів. +block.laserturret.name = Лазерна турель +block.laserturret.fulldescription = Продвинута однопушечна турель. Використовує енергію. Хороша на середніх дистанціях. Ніколи не пропускає. +block.waveturret.name = Тесла +block.waveturret.fulldescription = Передова багатоцільова турель. Використовує енергію. Середній радіус. Ніколи не пропускає. Активно знижує, але може вражати декількох ворогів одночасно з ланцюговим освітленням. +block.plasmaturret.name = Плазмова турель +block.plasmaturret.fulldescription = Дуже продвинута версія Вогнеметної турелі. Використовує вугілля як боєприпаси. Дуже високий урон, від близької до середньої дистанції. +block.chainturret.name = Уранова турель +block.chainturret.fulldescription = Остаточна швидкістна вежа. Використовує уран як боєприпаси. Вистрілює великі кулі при високій швидкості вогню. Середній радіус. Промінь кілька плиток. Надзвичайно жорсткий. +block.titancannon.name = Титанова гармата +block.titancannon.fulldescription = Найбільш далекобійна турель. Використовує уран як боєприпаси. Вистрілює великі снаряди. Далекобійний. Промінь кілька плиток. Надзвичайно жорсткий. +block.playerspawn.name = Спавн Гравця +block.enemyspawn.name = Спавн ворогів diff --git a/core/assets/ui/square.fnt b/core/assets/ui/square.fnt index 85e2b5833c..3ab33bd043 100644 --- a/core/assets/ui/square.fnt +++ b/core/assets/ui/square.fnt @@ -70,10 +70,13 @@ char id=98 x=204 y=102 width=22 height=22 xoffset=-1 yoffset=15 x char id=99 x=226 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=100 x=248 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=101 x=270 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 +char id=1108 x=270 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=102 x=292 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=103 x=314 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=104 x=336 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=105 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0 +char id=1110 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0 +char id=1111 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0 char id=106 x=372 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=107 x=394 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 char id=108 x=416 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0 diff --git a/core/assets/version.properties b/core/assets/version.properties index 24992b3c1c..795149e41d 100644 --- a/core/assets/version.properties +++ b/core/assets/version.properties @@ -1,7 +1,7 @@ #Autogenerated file. Do not modify. -#Fri Mar 16 22:39:48 EDT 2018 +#Mon Mar 19 20:46:00 EDT 2018 version=release -androidBuildCode=446 +androidBuildCode=449 name=Mindustry code=3.4 build=custom build diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 5cb3b2b5b1..5faeeae2ce 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -92,7 +92,7 @@ public class Vars{ public static final int tilesize = 8; - public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("pl", "PL"), + public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl", "PL"), new Locale("de"), new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")}; public static final Color[] playerColors = { diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 317c3d9c31..29040420b2 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -122,6 +122,8 @@ public class Control extends Module{ "lastBuild", 0 ); + Log.info("{0}", (int)'ї'); + KeyBinds.load(); for(Map map : world.maps().list()){ diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index bca30b9f46..da9f56964a 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -12,6 +12,7 @@ import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Packets.*; +import io.anuke.mindustry.net.TraceInfo; import io.anuke.mindustry.resource.*; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Placement; @@ -65,8 +66,7 @@ public class NetServer extends Module{ String ip = Net.getConnection(id).address; - admins.setKnownName(ip, packet.name); - admins.setKnownIP(uuid, ip); + admins.updatePlayerJoined(uuid, ip, packet.name); admins.getTrace(ip).uuid = uuid; admins.getTrace(ip).android = packet.android; @@ -150,6 +150,7 @@ public class NetServer extends Module{ Net.send(dc, SendMode.tcp); Platform.instance.updateRPC(); + admins.save(); }); Net.handleServer(PositionPacket.class, (id, packet) -> { @@ -161,6 +162,22 @@ public class NetServer extends Module{ }); Net.handleServer(ShootPacket.class, (id, packet) -> { + TraceInfo info = admins.getTrace(Net.getConnection(id).address); + Weapon weapon = (Weapon)Upgrade.getByID(packet.weaponid); + + float wtrc = 45f; + + if(!Timers.get(info.ip + "-weapontrace", wtrc)){ + info.fastShots ++; + }else{ + + if(info.fastShots - 2 > (int)(wtrc / (weapon.getReload() / 2f))){ + Net.kickConnection(id, KickReason.kick); + } + + info.fastShots = 0; + } + packet.playerid = connections.get(id).id; Net.sendExcept(id, packet, SendMode.udp); }); @@ -182,6 +199,7 @@ public class NetServer extends Module{ admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block; admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++; + admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlockPlaced ++; Net.send(packet, SendMode.tcp); }); @@ -196,6 +214,7 @@ public class NetServer extends Module{ if(block != null) { admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block; admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++; + admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlocksBroken ++; if (block.update || block.destructible) admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++; } diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index a37db3aefd..b53ad196af 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -7,177 +7,224 @@ import io.anuke.ucore.core.Settings; public class Administration { private Json json = new Json(); + /**All player info. Maps UUIDs to info. This persists throughout restarts.*/ + private ObjectMap playerInfo = new ObjectMap<>(); + /**Maps UUIDs to trace infos. This is wiped when a player logs off.*/ + private ObjectMap traceInfo = new ObjectMap<>(); private Array bannedIPs = new Array<>(); - private Array bannedIDs = new Array<>(); - private Array admins = new Array<>(); - private ObjectMap ipNames = new ObjectMap<>(); - private ObjectMap idIPs = new ObjectMap<>(); - private ObjectMap traces = new ObjectMap<>(); public Administration(){ - Settings.defaultList( - "bans", "{}", - "bannedIDs", "{}", - "admins", "{}", - "knownIPs", "{}", - "knownIDs", "{}" - ); + Settings.defaults("playerInfo", "{}"); + Settings.defaults("bannedIPs", "{}"); load(); } - public TraceInfo getTrace(String ip){ - if(!traces.containsKey(ip)) traces.put(ip, new TraceInfo(ip)); + /**Call when a player joins to update their information here.*/ + public void updatePlayerJoined(String id, String ip, String name){ + PlayerInfo info = getCreateInfo(id); + info.lastName = name; + info.lastIP = ip; + info.timesJoined ++; + if(!info.names.contains(name, false)) info.names.add(name); + if(!info.ips.contains(ip, false)) info.ips.add(ip); + } - return traces.get(ip); + /**Returns trace info by IP.*/ + public TraceInfo getTrace(String ip){ + if(!traceInfo.containsKey(ip)) traceInfo.put(ip, new TraceInfo(ip)); + + return traceInfo.get(ip); } public void clearTraces(){ - traces.clear(); + traceInfo.clear(); } - /**Sets last known name for an IP.*/ - public void setKnownName(String ip, String name){ - ipNames.put(ip, name); - saveKnown(); - } - - /**Sets last known UUID for an IP.*/ - public void setKnownIP(String id, String ip){ - idIPs.put(id, ip); - saveKnown(); - } - - /**Returns the last known name for an IP. Returns 'unknown' if this IP has an unknown username.*/ - public String getLastName(String ip){ - return ipNames.get(ip, "unknown"); - } - - /**Returns the last known IP for a UUID. Returns 'unknown' if this IP has an unknown IP.*/ - public String getLastIP(String id){ - return idIPs.get(id, "unknown"); - } - - /**Return the last known device ID associated with an IP. Returns 'unknown' if this IP has an unknown device.*/ - public String getLastID(String ip){ - for(String id : idIPs.keys()){ - if(idIPs.get(id).equals(ip)){ - return id; - } - } - return "unknown"; - } - - /**Returns list of banned IPs.*/ - public Array getBanned(){ - return bannedIPs; - } - - /**Returns list of banned IDs.*/ - public Array getBannedIDs(){ - return bannedIDs; - } - - /**Bans a player by IP; returns whether this player was already banned.*/ + /**Bans a player by IP; returns whether this player was already banned. + * If there are players who at any point had this IP, they will be UUID banned as well.*/ public boolean banPlayerIP(String ip){ if(bannedIPs.contains(ip, false)) return false; + + for(PlayerInfo info : playerInfo.values()){ + if(info.ips.contains(ip, false)){ + info.banned = true; + } + } + bannedIPs.add(ip); - saveBans(); + save(); return true; } - /**Bans a player by UUID.*/ + /**Bans a player by UUID; returns whether this player was already banned.*/ public boolean banPlayerID(String id){ - if(bannedIDs.contains(id, false)) + if(playerInfo.containsKey(id) && playerInfo.get(id).banned) return false; - bannedIDs.add(id); - saveBans(); + + getCreateInfo(id).banned = true; + + save(); return true; } - /**Unbans a player by IP; returns whether this player was banned in the first place..*/ + /**Unbans a player by IP; returns whether this player was banned in the first place. + * This method also unbans any player that was banned and had this IP.*/ public boolean unbanPlayerIP(String ip){ - if(!bannedIPs.contains(ip, false)) - return false; + boolean found = bannedIPs.contains(ip, false); + + for(PlayerInfo info : playerInfo.values()){ + if(info.ips.contains(ip, false)){ + info.banned = false; + found = true; + } + } + bannedIPs.removeValue(ip, false); - saveBans(); - return true; + if(found) save(); + + return found; } - /**Unbans a player by IP; returns whether this player was banned in the first place..*/ - public boolean unbanPlayerID(String ip){ - if(!bannedIDs.contains(ip, false)) + /**Unbans a player by ID; returns whether this player was banned in the first place. + * This also unbans all IPs the player used.*/ + public boolean unbanPlayerID(String id){ + PlayerInfo info = getCreateInfo(id); + + if(!info.banned) return false; - bannedIDs.removeValue(ip, false); - saveBans(); + + info.banned = false; + bannedIPs.removeAll(info.ips, false); + save(); return true; } - /**Returns list of banned IPs.*/ - public Array getAdmins(){ - return admins; + /**Returns list of all players with admin status*/ + public Array getAdmins(){ + Array result = new Array<>(); + for(PlayerInfo info : playerInfo.values()){ + if(info.admin){ + result.add(info); + } + } + return result; + } + + /**Returns list of all players with admin status*/ + public Array getBanned(){ + Array result = new Array<>(); + for(PlayerInfo info : playerInfo.values()){ + if(info.banned){ + result.add(info); + } + } + return result; + } + + /**Returns all banned IPs. This does not include the IPs of ID-banned players.*/ + public Array getBannedIPs(){ + return bannedIPs; } /**Makes a player an admin. Returns whether this player was already an admin.*/ - public boolean adminPlayer(String ip){ - if(admins.contains(ip, false)) + public boolean adminPlayer(String id){ + PlayerInfo info = getCreateInfo(id); + + if(info.admin) return false; - admins.add(ip); - saveAdmins(); + + info.admin = true; + save(); return true; } /**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/ - public boolean unAdminPlayer(String ip){ - if(!admins.contains(ip, false)) + public boolean unAdminPlayer(String id){ + PlayerInfo info = getCreateInfo(id); + + if(!info.admin) return false; - admins.removeValue(ip, false); - saveAdmins(); + + info.admin = false; + save(); return true; } public boolean isIPBanned(String ip){ - return bannedIPs.contains(ip, false); + return bannedIPs.contains(ip, false) || (findByIP(ip) != null && findByIP(ip).banned); } public boolean isIDBanned(String uuid){ - return bannedIDs.contains(uuid, false); + return getCreateInfo(uuid).banned; } - public boolean isAdmin(String ip){ - return admins.contains(ip, false); + public boolean isAdmin(String id){ + return getCreateInfo(id).admin; } - private void saveKnown(){ - Settings.putString("knownIPs", json.toJson(ipNames)); - Settings.putString("knownIDs", json.toJson(idIPs)); - Settings.save(); + public PlayerInfo getInfo(String id){ + return getCreateInfo(id); } - private void saveBans(){ - Settings.putString("bans", json.toJson(bannedIPs)); - Settings.putString("bannedIDs", json.toJson(bannedIDs)); - Settings.save(); + public PlayerInfo getInfoOptional(String id){ + return playerInfo.get(id); } - private void saveAdmins(){ - Settings.putString("admins", json.toJson(admins)); + public PlayerInfo findByIP(String ip){ + for(PlayerInfo info : playerInfo.values()){ + if(info.ips.contains(ip, false)){ + return info; + } + } + return null; + } + + private PlayerInfo getCreateInfo(String id){ + if(playerInfo.containsKey(id)){ + return playerInfo.get(id); + }else{ + PlayerInfo info = new PlayerInfo(id); + playerInfo.put(id, info); + save(); + return info; + } + } + + public void save(){ + Settings.putString("playerInfo", json.toJson(playerInfo)); + Settings.putString("bannedIPs", json.toJson(bannedIPs)); Settings.save(); } private void load(){ - bannedIPs = json.fromJson(Array.class, Settings.getString("bans")); - bannedIDs = json.fromJson(Array.class, Settings.getString("bannedIDs")); - admins = json.fromJson(Array.class, Settings.getString("admins")); - ipNames = json.fromJson(ObjectMap.class, Settings.getString("knownIPs")); - idIPs = json.fromJson(ObjectMap.class, Settings.getString("knownIDs")); + playerInfo = json.fromJson(ObjectMap.class, Settings.getString("playerInfo")); + bannedIPs = json.fromJson(Array.class, Settings.getString("bannedIPs")); + } + + public static class PlayerInfo{ + public String id; + public String lastName = "", lastIP = ""; + public Array ips = new Array<>(); + public Array names = new Array<>(); + public int timesKicked; //TODO not implemented! + public int timesJoined; + public int totalBlockPlaced; + public int totalBlocksBroken; + public boolean banned, admin; + + PlayerInfo(String id){ + this.id = id; + } + + private PlayerInfo(){} } } diff --git a/core/src/io/anuke/mindustry/net/TraceInfo.java b/core/src/io/anuke/mindustry/net/TraceInfo.java index 2f37a99efa..940f98726f 100644 --- a/core/src/io/anuke/mindustry/net/TraceInfo.java +++ b/core/src/io/anuke/mindustry/net/TraceInfo.java @@ -9,6 +9,8 @@ public class TraceInfo { public boolean modclient; public boolean android; + public int fastShots; + public int totalBlocksBroken; public int structureBlocksBroken; public Block lastBlockBroken = Blocks.air; diff --git a/core/src/io/anuke/mindustry/resource/Weapon.java b/core/src/io/anuke/mindustry/resource/Weapon.java index 76b3193a09..e6f2c19e2f 100644 --- a/core/src/io/anuke/mindustry/resource/Weapon.java +++ b/core/src/io/anuke/mindustry/resource/Weapon.java @@ -117,6 +117,10 @@ public class Weapon extends Upgrade{ Effects.sound(shootsound, x, y); } + public float getReload(){ + return reload; + } + public void shoot(Player p, float x, float y, float angle){ shootInternal(p, x, y, angle); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java index c23348a91a..b1986af887 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.net.Administration.PlayerInfo; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetEvents; @@ -36,15 +37,15 @@ public class AdminsDialog extends FloatingDialog { table.add("$text.server.admins.none"); } - for(String ip : netServer.admins.getAdmins()){ + for(PlayerInfo info : netServer.admins.getAdmins()){ Table res = new Table("button"); res.margin(14f); - res.labelWrap("[LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f); + res.labelWrap("[LIGHT_GRAY]" + info.lastName).width(w - h - 24f); res.add().growX(); res.addImageButton("icon-cancel", 14*3, () -> { ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> { - netServer.admins.unAdminPlayer(ip); + netServer.admins.unAdminPlayer(info.id); for(Player player : playerGroup.all()){ NetConnection c = Net.getConnection(player.clientid); if(c != null){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java index bb6633bef5..d8165c0ae6 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/BansDialog.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.ui.dialogs; +import io.anuke.mindustry.net.Administration.PlayerInfo; import io.anuke.ucore.scene.ui.ScrollPane; import io.anuke.ucore.scene.ui.layout.Table; @@ -33,15 +34,15 @@ public class BansDialog extends FloatingDialog { table.add("$text.server.bans.none"); } - for(String ip : netServer.admins.getBanned()){ + for(PlayerInfo info : netServer.admins.getBanned()){ Table res = new Table("button"); res.margin(14f); - res.labelWrap("IP: [LIGHT_GRAY]" + ip + "\n[]Name: [LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f); + res.labelWrap("IP: [LIGHT_GRAY]" + info.lastIP + "\n[]Name: [LIGHT_GRAY]" + info.lastName).width(w - h - 24f); res.add().growX(); res.addImageButton("icon-cancel", 14*3, () -> { ui.showConfirm("$text.confirm", "$text.confirmunban", () -> { - netServer.admins.unbanPlayerIP(ip); + netServer.admins.unbanPlayerID(info.id); setup(); }); }).size(h).pad(-14f); diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 7299696239..ce21bdaf29 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.game.EventType.GameOverEvent; import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.io.Version; +import io.anuke.mindustry.net.Administration.PlayerInfo; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetEvents; @@ -126,7 +127,7 @@ public class ServerControl extends Module { Log.info("Stopped server."); }); - handler.register("host", " ", "Open the server with a specific map.", arg -> { + handler.register("host", " [mode]", "Open the server with a specific map.", arg -> { if(state.is(State.playing)){ err("Already hosting. Type 'stop' to stop hosting first."); return; @@ -146,7 +147,7 @@ public class ServerControl extends Module { GameMode mode = null; try{ - mode = GameMode.valueOf(arg[1]); + mode = arg.length < 2 ? GameMode.waves : GameMode.valueOf(arg[1]); }catch (IllegalArgumentException e){ err("No gamemode '{0}' found.", arg[1]); return; @@ -306,26 +307,26 @@ public class ServerControl extends Module { }); handler.register("bans", "List all banned IPs and IDs.", arg -> { - Array bans = netServer.admins.getBanned(); + Array bans = netServer.admins.getBanned(); if(bans.size == 0){ - Log.info("No IP-banned players have been found."); + Log.info("No ID-banned players have been found."); }else{ - Log.info("&lyBanned players [IP]:"); - for(String string : bans){ - Log.info(" &ly {0} / Last known name: '{1}'", string, netServer.admins.getLastName(string)); + Log.info("&lyBanned players [ID]:"); + for(PlayerInfo info : bans){ + Log.info(" &ly {0} / Last known name: '{1}'", info.id, info.lastName); } } - Array idbans = netServer.admins.getBannedIDs(); + Array ipbans = netServer.admins.getBannedIPs(); - if(idbans.size == 0){ - Log.info("No ID-banned players have been found."); + if(ipbans.size == 0){ + Log.info("No IP-banned players have been found."); }else{ - Log.info("&lmBanned players [ID]:"); - for(String string : idbans){ - Log.info(" &lm '{0}' / Last known name: '{1}' / Last known IP: '{2}'", string, - netServer.admins.getLastName(netServer.admins.getLastIP(string)), netServer.admins.getLastIP(string)); + Log.info("&lmBanned players [IP]:"); + for(String string : ipbans){ + PlayerInfo info = netServer.admins.findByIP(string); + Log.info(" &lm '{0}' / Last known name: '{1}' / ID: '{2}'", string, info.lastName, info.id); } } }); @@ -363,12 +364,6 @@ public class ServerControl extends Module { handler.register("unbanip", "", "Completely unban a person by IP.", arg -> { if(netServer.admins.unbanPlayerIP(arg[0])) { info("Unbanned player by IP: {0}.", arg[0]); - for(String s : netServer.admins.getBannedIDs()){ - if(netServer.admins.getLastIP(s).equals(arg[0])){ - netServer.admins.unbanPlayerID(s); - Log.info("Also unbanned UUID '{0}' as it corresponds to this IP.", s); - } - } }else{ err("That IP is not banned!"); } @@ -377,11 +372,6 @@ public class ServerControl extends Module { handler.register("unbanid", "", "Completely unban a person by ID.", arg -> { if(netServer.admins.unbanPlayerID(arg[0])) { info("&lmUnbanned player by ID: {0}.", arg[0]); - String ip = netServer.admins.getLastIP(arg[0]); - if(!ip.equals("unknown")) { - netServer.admins.unbanPlayerIP(ip); - Log.info("Also unbanned IP '{0}' as it corresponds to this ID.", ip); - } }else{ err("That IP is not banned!"); } @@ -403,10 +393,10 @@ public class ServerControl extends Module { } if(target != null){ - String ip = Net.getConnection(target.clientid).address; - netServer.admins.adminPlayer(ip); + String id = netServer.admins.getTrace(Net.getConnection(target.clientid).address).uuid; + netServer.admins.adminPlayer(id); NetEvents.handleAdminSet(target, true); - info("Admin-ed player by IP: {0} / {1}", ip, arg[0]); + info("Admin-ed player by ID: {0} / {1}", id, arg[0]); }else{ info("Nobody with that name could be found."); } @@ -428,24 +418,24 @@ public class ServerControl extends Module { } if(target != null){ - String ip = Net.getConnection(target.clientid).address; - netServer.admins.unAdminPlayer(ip); + String id = netServer.admins.getTrace(Net.getConnection(target.clientid).address).uuid; + netServer.admins.unAdminPlayer(id); NetEvents.handleAdminSet(target, false); - info("Un-admin-ed player by IP: {0} / {1}", ip, arg[0]); + info("Un-admin-ed player by ID: {0} / {1}", id, arg[0]); }else{ info("Nobody with that name could be found."); } }); handler.register("admins", "List all admins.", arg -> { - Array admins = netServer.admins.getAdmins(); + Array admins = netServer.admins.getAdmins(); if(admins.size == 0){ Log.info("No admins have been found."); }else{ Log.info("&lyAdmins:"); - for(String string : admins){ - Log.info(" &luy {0} / Name: '{1}'", string, netServer.admins.getLastName(string)); + for(PlayerInfo info : admins){ + Log.info(" &luy {0} / ID: '{1}' / IP: '{2}'", info.lastName, info.id, info.lastIP); } } }); @@ -509,7 +499,7 @@ public class ServerControl extends Module { info("Core destroyed."); }); - handler.register("info", "Print debug info", arg -> { + handler.register("debug", "Print debug info", arg -> { info(DebugFragment.debugInfo()); }); @@ -540,6 +530,23 @@ public class ServerControl extends Module { } }); + handler.register("info", "", "Get global info for a player's UUID.", arg -> { + PlayerInfo info = netServer.admins.getInfoOptional(arg[0]); + + if(info != null){ + Log.info("&lcTrace info for player '{0}':", info.lastName); + Log.info(" &lyall names used: {0}", info.names); + Log.info(" &lyIP: {0}", info.lastIP); + Log.info(" &lyall IPs used: {0}", info.ips); + Log.info(" &lytimes joined: {0}", info.timesJoined); + Log.info(""); + Log.info(" &lytotal blocks broken: {0}", info.totalBlocksBroken); + Log.info(" &lytotal blocks placed: {0}", info.totalBlockPlaced); + }else{ + info("Nobody with that UUID could be found."); + } + }); + handler.register("trace", "", "Trace a player's actions", arg -> { if(!state.is(State.playing)) { err("Open the server first.");