Merge branch 'master' of https://github.com/Anuken/Mindustry into 4.0

This commit is contained in:
Anuken
2018-03-23 22:48:51 -04:00
24 changed files with 1535 additions and 244 deletions
+2 -1
View File
@@ -38,6 +38,7 @@ text.server.kicked.invalidPassword=Invalid password!
text.server.kicked.clientOutdated=Outdated client! Update your game!
text.server.kicked.serverOutdated=Outdated server! Ask the host to update!
text.server.kicked.banned=You are banned on this server.
text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again.
text.server.connected={0} has joined.
text.server.disconnected={0} has disconnected.
text.nohost=Can't host server on a custom map!
@@ -262,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
+500
View File
@@ -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 = Спавн ворогів
+3
View File
@@ -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
+1 -1
View File
@@ -96,7 +96,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 = {
+67 -18
View File
@@ -8,10 +8,9 @@ import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.game.EventType.GameOverEvent;
import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Administration;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Administration.PlayerInfo;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.*;
import io.anuke.mindustry.world.Block;
@@ -32,7 +31,7 @@ import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.*;
public class NetServer extends Module{
private final static float serverSyncTime = 4, itemSyncTime = 10;
private final static float serverSyncTime = 4, itemSyncTime = 10, kickDuration = 30 * 1000;
private final static int timerEntitySync = 0;
private final static int timerStateSync = 1;
@@ -52,40 +51,48 @@ public class NetServer extends Module{
Net.handleServer(Connect.class, (id, connect) -> {
if(admins.isIPBanned(connect.addressTCP)){
Net.kickConnection(id, KickReason.banned);
kick(id, KickReason.banned);
}
});
Net.handleServer(ConnectPacket.class, (id, packet) -> {
String uuid = new String(Base64Coder.encode(packet.uuid));
if(Net.getConnection(id) == null ||
admins.isIPBanned(Net.getConnection(id).address)) return;
TraceInfo trace = admins.getTrace(Net.getConnection(id).address);
PlayerInfo info = admins.getInfo(uuid);
if(admins.isIDBanned(uuid)){
Net.kickConnection(id, KickReason.banned);
kick(id, KickReason.banned);
return;
}
if(TimeUtils.millis() - info.lastKicked < kickDuration){
kick(id, KickReason.recentKick);
return;
}
String ip = Net.getConnection(id).address;
admins.setKnownName(ip, packet.name);
admins.setKnownIP(uuid, ip);
admins.getTrace(ip).uuid = uuid;
admins.getTrace(ip).android = packet.android;
admins.updatePlayerJoined(uuid, ip, packet.name);
trace.uuid = uuid;
trace.android = packet.android;
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
Net.kickConnection(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
kick(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
return;
}
if(packet.version == -1){
admins.getTrace(ip).modclient = true;
trace.modclient = true;
}
Log.info("Sending data to player '{0}' / {1}", packet.name, id);
Player player = new Player();
player.isAdmin = admins.isAdmin(Net.getConnection(id).address);
player.isAdmin = admins.isAdmin(uuid, ip);
player.clientid = id;
player.name = packet.name;
player.mech = packet.android ? Mech.standardShip : Mech.standard;
@@ -95,7 +102,7 @@ public class NetServer extends Module{
player.color.set(packet.color);
connections.put(id, player);
admins.getTrace(ip).playerid = player.id;
trace.playerid = player.id;
//TODO try DeflaterOutputStream
ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -137,6 +144,7 @@ public class NetServer extends Module{
Net.send(dc, SendMode.tcp);
Platform.instance.updateRPC();
admins.save();
});
Net.handleServer(PositionPacket.class, (id, packet) -> {
@@ -149,9 +157,23 @@ public class NetServer extends Module{
BulletType type = BaseBulletType.getByID(packet.bulletid);
player.onRemoteShoot(type, packet.x, packet.y, packet.rotation, packet.data);
TraceInfo info = admins.getTrace(Net.getConnection(id).address);
Weapon weapon = Upgrade.getByID((byte)packet.data);
packet.entityid = player.id;
packet.groupid = (byte)player.getGroup().getID();
float wtrc = 40f;
if(!Timers.get(info.ip + "-weapontrace", wtrc)){
info.fastShots ++;
}else{
if(info.fastShots - 2 > (int)(wtrc / (weapon.getReload() / 2f))){
kick(id, KickReason.kick);
}
info.fastShots = 0;
}
packet.entityid = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.udp);
});
@@ -173,6 +195,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);
});
@@ -188,6 +211,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++;
}
@@ -270,10 +294,10 @@ public class NetServer extends Module{
if(packet.action == AdminAction.ban){
admins.banPlayerIP(ip);
Net.kickConnection(other.clientid, KickReason.banned);
kick(other.clientid, KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(packet.action == AdminAction.kick){
Net.kickConnection(other.clientid, KickReason.kick);
kick(other.clientid, KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(packet.action == AdminAction.trace){
TracePacket trace = new TracePacket();
@@ -306,6 +330,31 @@ public class NetServer extends Module{
admins.clearTraces();
}
public void kick(int connection, KickReason reason){
NetConnection con = Net.getConnection(connection);
if(con == null){
Log.err("Cannot kick unknown player!");
return;
}else{
Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason);
}
PlayerInfo info = admins.getInfo(admins.getTrace(con.address).uuid);
if(reason == KickReason.kick || reason == KickReason.banned){
info.timesKicked ++;
info.lastKicked = TimeUtils.millis();
}
KickPacket p = new KickPacket();
p.reason = reason;
con.send(p, SendMode.tcp);
Timers.runTask(2f, con::close);
admins.save();
}
void sync(){
if(timer.get(timerEntitySync, serverSyncTime)){
+1 -1
View File
@@ -156,12 +156,12 @@ public class UI extends SceneModule{
language = new LanguageDialog();
settings = new SettingsMenuDialog();
paused = new PausedDialog();
changelog = new ChangelogDialog();
about = new AboutDialog();
host = new HostDialog();
bans = new BansDialog();
admins = new AdminsDialog();
traces = new TraceDialog();
changelog = new ChangelogDialog();
build.begin(scene);
@@ -26,6 +26,8 @@ public class DefaultKeybinds {
"block_info", Input.CONTROL_LEFT,
"player_list", Input.TAB,
"chat", Input.ENTER,
"chat_scroll_up", Input.UP,
"chat_scroll_down", Input.DOWN,
"console", Input.GRAVE,
"weapon_1", Input.NUM_1,
"weapon_2", Input.NUM_2,
@@ -53,6 +55,8 @@ public class DefaultKeybinds {
"rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B),
"player_list", Input.CONTROLLER_START,
"chat", Input.ENTER,
"chat_scroll_up", Input.UP,
"chat_scroll_down", Input.DOWN,
"console", Input.GRAVE,
"weapon_1", Input.NUM_1,
"weapon_2", Input.NUM_2,
@@ -107,6 +107,10 @@ public class DesktopInput extends InputHandler{
Cursors.restoreCursor();
}
}
if(target != null && target.block().isConfigurable(target)){
showCursor = true;
}
if(target != null && Inputs.keyTap("select") && !ui.hasMouse()){
if(target.block().isConfigurable(target)){
@@ -116,19 +116,23 @@ public abstract class InputHandler extends InputAdapter{
}
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
if(!Net.client()){
if(!Net.client()){ //is server or singleplayer
Placement.placeBlock(player.team, x, y, result, rotation, effects, sound);
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
}
if(Net.active()){
NetEvents.handlePlace(x, y, result, rotation);
}
if(!Net.client()){
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
}
}
public void breakBlock(int x, int y, boolean sound){
if(!Net.client()) Placement.breakBlock(player.team, x, y, true, sound);
if(!Net.client())
Placement.breakBlock(player.team, x, y, true, sound);
if(Net.active()){
NetEvents.handleBreak(x, y);
@@ -7,177 +7,228 @@ 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<String, PlayerInfo> playerInfo = new ObjectMap<>();
/**Maps UUIDs to trace infos. This is wiped when a player logs off.*/
private ObjectMap<String, TraceInfo> traceInfo = new ObjectMap<>();
private Array<String> bannedIPs = new Array<>();
private Array<String> bannedIDs = new Array<>();
private Array<String> admins = new Array<>();
private ObjectMap<String, String> ipNames = new ObjectMap<>();
private ObjectMap<String, String> idIPs = new ObjectMap<>();
private ObjectMap<String, TraceInfo> 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<String> getBanned(){
return bannedIPs;
}
/**Returns list of banned IDs.*/
public Array<String> 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<String> getAdmins(){
return admins;
/**Returns list of all players with admin status*/
public Array<PlayerInfo> getAdmins(){
Array<PlayerInfo> 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<PlayerInfo> getBanned(){
Array<PlayerInfo> 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<String> 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, String ip){
PlayerInfo info = getCreateInfo(id);
if(info.admin)
return false;
admins.add(ip);
saveAdmins();
info.validAdminIP = ip;
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, String ip){
PlayerInfo info = getCreateInfo(id);
return info.admin && ip.equals(info.validAdminIP);
}
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 = "<unknown>", lastIP = "<unknown>";
public String validAdminIP;
public Array<String> ips = new Array<>();
public Array<String> names = new Array<>();
public int timesKicked; //TODO not implemented!
public int timesJoined;
public int totalBlockPlaced;
public int totalBlocksBroken;
public boolean banned, admin;
public long lastKicked; //last kicked timestamp
PlayerInfo(String id){
this.id = id;
}
private PlayerInfo(){}
}
}
-10
View File
@@ -12,7 +12,6 @@ import com.badlogic.gdx.utils.reflect.ClassReflection;
import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.net.Packet.ImportantPacket;
import io.anuke.mindustry.net.Packet.UnimportantPacket;
import io.anuke.mindustry.net.Packets.KickReason;
import io.anuke.mindustry.net.Streamable.StreamBegin;
import io.anuke.mindustry.net.Streamable.StreamBuilder;
import io.anuke.mindustry.net.Streamable.StreamChunk;
@@ -101,11 +100,6 @@ public class Net{
clientProvider.discover(cons);
}
/**Kick a specified connection from the server.*/
public static void kickConnection(int id, KickReason reason){
serverProvider.kick(id, reason);
}
/**Returns a list of all connections IDs.*/
public static Array<NetConnection> getConnections(){
return (Array<NetConnection>)serverProvider.getConnections();
@@ -307,10 +301,6 @@ public class Net{
Array<? extends NetConnection> getConnections();
/**Returns a connection by ID.*/
NetConnection getByID(int id);
/**Kick a certain connection.*/
void kick(int connection, KickReason reason);
/**Returns the ping for a certain connection.*/
int getPingFor(NetConnection connection);
/**Close all connections.*/
void dispose();
}
+1 -1
View File
@@ -350,7 +350,7 @@ public class Packets {
}
public enum KickReason{
kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true);
kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick;
public final boolean quiet;
KickReason(){ quiet = false; }
@@ -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;
@@ -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);
@@ -2,7 +2,6 @@ package io.anuke.mindustry.ui.dialogs;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.ui.Links;
import io.anuke.mindustry.ui.Links.LinkEntry;
import io.anuke.ucore.core.Core;
@@ -10,6 +9,8 @@ import io.anuke.ucore.core.Timers;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.ui;
public class AboutDialog extends FloatingDialog {
public AboutDialog(){
@@ -45,7 +46,7 @@ public class AboutDialog extends FloatingDialog {
table.addImageButton("icon-link", 14*3, () -> {
if(!Gdx.net.openURI(link.link)){
Vars.ui.showError("$text.linkfail");
ui.showError("$text.linkfail");
Gdx.app.getClipboard().setContents(link.link);
}
}).size(h-5, h);
@@ -58,6 +59,7 @@ public class AboutDialog extends FloatingDialog {
content().add(pane).growX();
buttons().addButton("$text.credits", this::showCredits).size(200f, 64f);
buttons().addButton("$text.changelog.title", ui.changelog::show).size(200f, 64f);
}
private void showCredits(){
@@ -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){
@@ -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);
@@ -31,7 +31,7 @@ public class BackgroundFragment implements Fragment {
float logoh = logo.getRegionHeight()*logoscl;
Draw.color();
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15 - Unit.dp.scl(portrait ? 30f : 30), logow, logoh);
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15 - Unit.dp.scl(portrait ? 30f : 0), logow, logoh);
}).visible(() -> state.is(State.menu)).grow();
}
}
@@ -20,6 +20,7 @@ import io.anuke.ucore.scene.ui.TextField;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.scene.ui.layout.Unit;
import io.anuke.ucore.util.Log;
import java.util.Arrays;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.ucore.core.Core.scene;
@@ -39,6 +40,8 @@ public class ChatFragment extends Table implements Fragment{
private float textWidth = Unit.dp.scl(600);
private Color shadowColor = new Color(0, 0, 0, 0.4f);
private float textspacing = Unit.dp.scl(10);
private Array<String> history = new Array<String>();
private int historyPos = 0;
public ChatFragment(){
super();
@@ -57,8 +60,21 @@ public class ChatFragment extends Table implements Fragment{
if(Net.active() && Inputs.keyTap("chat")){
toggle();
}
if (chatOpen) {
if (Inputs.keyTap("chat_scroll_up") && historyPos < history.size - 1) {
if (historyPos == 0) history.set(0, chatfield.getText());
historyPos++;
updateChat();
}
if (Inputs.keyTap("chat_scroll_down") && historyPos > 0) {
historyPos--;
updateChat();
}
}
});
history.insert(0, "");
setup();
}
@@ -69,6 +85,8 @@ public class ChatFragment extends Table implements Fragment{
public void clearMessages(){
messages.clear();
history.clear();
history.insert(0, "");
}
private void setup(){
@@ -144,7 +162,8 @@ public class ChatFragment extends Table implements Fragment{
private void sendMessage(){
String message = chatfield.getText();
chatfield.clearText();
clearChatInput();
history.insert(1, message);
if(message.replaceAll(" ", "").isEmpty()) return;
@@ -170,6 +189,17 @@ public class ChatFragment extends Table implements Fragment{
public void hide(){
scene.setKeyboardFocus(null);
chatOpen = false;
clearChatInput();
}
public void updateChat() {
chatfield.setText(history.get(historyPos));
chatfield.setCursorPosition(chatfield.getText().length());
}
public void clearChatInput() {
historyPos = 0;
history.set(0, "");
chatfield.setText("");
}
@@ -128,7 +128,7 @@ public class PlayerListFragment implements Fragment{
ui.showConfirm("$text.confirm", "$text.confirmban", () -> {
if(Net.server()) {
netServer.admins.banPlayerIP(connection.address);
Net.kickConnection(player.clientid, KickReason.banned);
netServer.kick(player.clientid, KickReason.banned);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.ban);
}
@@ -137,7 +137,7 @@ public class PlayerListFragment implements Fragment{
t.addImageButton("icon-cancel", 14*2, () -> {
if(Net.server()) {
Net.kickConnection(player.clientid, KickReason.kick);
netServer.kick(player.clientid, KickReason.kick);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.kick);
}
@@ -148,14 +148,16 @@ public class PlayerListFragment implements Fragment{
t.addImageButton("icon-admin", "toggle", 14*2, () -> {
if(Net.client()) return;
if(netServer.admins.isAdmin(connection.address)){
String id = netServer.admins.getTrace(connection.address).uuid;
if(netServer.admins.isAdmin(id, connection.address)){
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
netServer.admins.unAdminPlayer(connection.address);
netServer.admins.unAdminPlayer(id);
NetEvents.handleAdminSet(player, false);
});
}else{
ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> {
netServer.admins.adminPlayer(connection.address);
netServer.admins.adminPlayer(id, connection.address);
NetEvents.handleAdminSet(player, true);
});
}
@@ -74,7 +74,7 @@ public class Teleporter extends PowerBlock{
@Override
public void placed(Tile tile){
tile.<TeleporterEntity>entity().color = lastColor;
Timers.run(1f, () -> setConfigure(tile, lastColor));
setConfigure(tile, lastColor);
}
@Override