Merge branches 'master' and 'mods' of https://github.com/Anuken/Mindustry

# Conflicts:
#	core/src/io/anuke/mindustry/entities/type/FlyingUnit.java
#	gradle.properties
This commit is contained in:
Anuken
2019-09-29 17:03:28 -04:00
58 changed files with 1012 additions and 273 deletions

View File

@@ -22,20 +22,6 @@ public class Annotations{
public @interface OverrideCallSuper { public @interface OverrideCallSuper {
} }
/** Indicates that a method return or field can be null.*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Nullable{
}
/** Indicates that a method return or field cannot be null.*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface NonNull{
}
/** Marks a class as serializable. */ /** Marks a class as serializable. */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)

View File

@@ -22,6 +22,7 @@ load.map = Maps
load.image = Images load.image = Images
load.content = Content load.content = Content
load.system = System load.system = System
load.mod = Mods
stat.wave = Waves Defeated:[accent] {0} stat.wave = Waves Defeated:[accent] {0}
stat.enemiesDestroyed = Enemies Destroyed:[accent] {0} stat.enemiesDestroyed = Enemies Destroyed:[accent] {0}
@@ -64,6 +65,17 @@ uploadingpreviewfile = Uploading Preview File
committingchanges = Comitting Changes committingchanges = Comitting Changes
done = Done done = Done
mods.alphainfo = Keep in mind that mods are in alpha, and[scarlet] may be very buggy[].\nReport any issues you find to the Mindustry Github or Discord.
mods.alpha = [scarlet](Alpha)
mods = Mods
mods.none = [LIGHT_GRAY]No mods found!
mod.enabled = [lightgray]Enabled
mod.disabled = [scarlet]Disabled
mod.requiresrestart = The game will now close to apply the mod changes.
mod.import = Import Mod
mod.remove.confirm = This mod will be deleted.
mod.author = [LIGHT_GRAY]Author:[] {0}
about.button = About about.button = About
name = Name: name = Name:
noname = Pick a[accent] player name[] first. noname = Pick a[accent] player name[] first.

View File

@@ -48,7 +48,7 @@ minimap = Мінімапа
close = Закрити close = Закрити
website = Веб-сайт website = Веб-сайт
quit = Вихід quit = Вихід
save.quit = Save & Quit save.quit = Зберегти & Вийти
maps = Мапи maps = Мапи
maps.browse = Перегляд мап maps.browse = Перегляд мап
continue = Продовжити continue = Продовжити
@@ -98,7 +98,7 @@ host = Сервер
hosting = [accent]Відкриття сервера… hosting = [accent]Відкриття сервера…
hosts.refresh = Оновити hosts.refresh = Оновити
hosts.discovering = Пошук локальних ігор hosts.discovering = Пошук локальних ігор
hosts.discovering.any = Discovering games hosts.discovering.any = Пошук ігор
server.refreshing = Оновлення сервера server.refreshing = Оновлення сервера
hosts.none = [lightgray]Локальних ігр не знайдено hosts.none = [lightgray]Локальних ігр не знайдено
host.invalid = [scarlet]Не вдалося підключитися до сервера. host.invalid = [scarlet]Не вдалося підключитися до сервера.
@@ -122,16 +122,16 @@ server.version = [lightgray]Версія: {0}
server.custombuild = [yellow]Користувацька збірка server.custombuild = [yellow]Користувацька збірка
confirmban = Ви дійсно хочете заблокувати цього гравця? confirmban = Ви дійсно хочете заблокувати цього гравця?
confirmkick = Ви дійсно хочете викинути цього гравця? confirmkick = Ви дійсно хочете викинути цього гравця?
confirmvotekick = Are you sure you want to vote-kick this player? confirmvotekick = Ви дійсно хочете вигнати цього гравця за допомогою голосуванняr?
confirmunban = Ви дійсно хочете розблокувати цього гравця? confirmunban = Ви дійсно хочете розблокувати цього гравця?
confirmadmin = Ви дійсно хочете зробити цього гравця адміністратором? confirmadmin = Ви дійсно хочете зробити цього гравця адміністратором?
confirmunadmin = Ви дійсно хочете видалити статус адміністратора з цього гравця? confirmunadmin = Ви дійсно хочете видалити статус адміністратора з цього гравця?
joingame.title = Приєднатися до гри joingame.title = Приєднатися до гри
joingame.ip = IP: joingame.ip = IP:
disconnect = Відключено. disconnect = Відключено.
disconnect.error = Connection error. disconnect.error = Помилка з’єднання.
disconnect.closed = Connection closed. disconnect.closed = З'єднання закрито.
disconnect.timeout = Timed out. disconnect.timeout = Час вийшов.
disconnect.data = Не вдалося завантажити дані світу! disconnect.data = Не вдалося завантажити дані світу!
cantconnect = Не вдалося під’єднатися до гри ([accent]{0}[]). cantconnect = Не вдалося під’єднатися до гри ([accent]{0}[]).
connecting = [accent]Підключення… connecting = [accent]Підключення…
@@ -159,7 +159,7 @@ save.rename = Перейменувати
save.rename.text = Нова назва: save.rename.text = Нова назва:
selectslot = Виберіть збереження. selectslot = Виберіть збереження.
slot = [accent]Слот {0} slot = [accent]Слот {0}
editmessage = Edit Message editmessage = Редагувати повідомлення
save.corrupted = [accent]Збережений файл пошкоджено або недійсний! \nЯкщо ви щойно оновили свою гру, це, мабуть, є зміною формату збереження та [scarlet] не є[] помилкою. save.corrupted = [accent]Збережений файл пошкоджено або недійсний! \nЯкщо ви щойно оновили свою гру, це, мабуть, є зміною формату збереження та [scarlet] не є[] помилкою.
empty = <Порожньо> empty = <Порожньо>
on = Увімкнено on = Увімкнено
@@ -167,13 +167,14 @@ off = Вимкнено
save.autosave = Автозбереження: {0} save.autosave = Автозбереження: {0}
save.map = Мапа: {0} save.map = Мапа: {0}
save.wave = Хвиля {0} save.wave = Хвиля {0}
save.mode = Gamemode: {0} save.mode = Режим гри: {0}
save.date = Останнє збереження save.date = Останнє збереження
save.playtime = Час гри: {0} save.playtime = Час гри: {0}
warning = Попередження warning = Попередження
confirm = Підтвердження confirm = Підтвердження
delete = Видалити delete = Видалити
view.workshop = Переглянути в Майстерні view.workshop = Переглянути в Майстерні
workshop.listing = Редагувати список Майстерні
ok = ОК ok = ОК
open = Відкрити open = Відкрити
customize = Налаштувати правила customize = Налаштувати правила
@@ -211,10 +212,15 @@ map.nospawn.pvp = У цієї мапи немає ворожих ядер, в я
map.nospawn.attack = У цієї мапи немає ворожих ядер, в яких гравець може з’явитися! Додайте [SCARLET]червоне[] ядро до цієї мапи в редакторі. map.nospawn.attack = У цієї мапи немає ворожих ядер, в яких гравець може з’явитися! Додайте [SCARLET]червоне[] ядро до цієї мапи в редакторі.
map.invalid = Помилка завантаження мапи: пошкоджений або невірний файл мапи. map.invalid = Помилка завантаження мапи: пошкоджений або невірний файл мапи.
map.publish.error = Помилка при опублікуванні мапи: {0} map.publish.error = Помилка при опублікуванні мапи: {0}
map.update = Оновити мапу
map.load.error = Помилка отримання даних з Майстерню: {0}
map.missing = Цю карту було видалено або переміщено.\n[lightgray]Перелік у Майстерні автоматично від’єднано від мапи.
map.menu = Виберіть, що ви хочете зробити з цією мапою.
map.changelog = Список змік (необов’язково):
map.publish.confirm = Ви дійсно хочете опублікувати цю мапу?\n\n[lightgray]Переконайтеся, що спершу ви згодні з Ліцензійною угодою Steam, або ваші мапи не з’являться! map.publish.confirm = Ви дійсно хочете опублікувати цю мапу?\n\n[lightgray]Переконайтеся, що спершу ви згодні з Ліцензійною угодою Steam, або ваші мапи не з’являться!
eula = Ліцензійна угода eula = Ліцензійна угода
map.publish = Map published. map.publish = Мапа опублікована.
map.publishing = [accent]Publishing map... map.publishing = [accent]Публікації мапи...
editor.brush = Пензлик editor.brush = Пензлик
editor.openin = Відкрити в редакторі editor.openin = Відкрити в редакторі
editor.oregen = Генерація руд editor.oregen = Генерація руд
@@ -222,7 +228,7 @@ editor.oregen.info = Генерація руд:
editor.mapinfo = Інформація про мапу editor.mapinfo = Інформація про мапу
editor.author = Автор: editor.author = Автор:
editor.description = Опис: editor.description = Опис:
editor.nodescription = A map must have a description of at least 4 characters before being published. editor.nodescription = Мапа повинна мати щонаймеше 4 символи для публікації.
editor.waves = Хвилі: editor.waves = Хвилі:
editor.rules = Правила: editor.rules = Правила:
editor.generation = Генерація: editor.generation = Генерація:
@@ -246,7 +252,7 @@ waves.invalid = Недійсні хвилі у буфері обміну.
waves.copied = Хвилі скопійовані. waves.copied = Хвилі скопійовані.
waves.none = Вороги не були встановлені.\nЗазначимо, що пусті хвилі будуть автоматично замінені звичайною хвилею. waves.none = Вороги не були встановлені.\nЗазначимо, що пусті хвилі будуть автоматично замінені звичайною хвилею.
editor.default = [lightgray]<За замовчуванням> editor.default = [lightgray]<За замовчуванням>
details = Details... details = Деталі...
edit = Редагувати… edit = Редагувати…
editor.name = Назва: editor.name = Назва:
editor.spawn = Створити бойову одиницю editor.spawn = Створити бойову одиницю
@@ -256,7 +262,7 @@ editor.errorload = Помилка завантаження зображення:
editor.errorsave = Помилка збереження зображення:\n[accent]{0} editor.errorsave = Помилка збереження зображення:\n[accent]{0}
editor.errorimage = Це зображення, а не мапа. Не змінюйте розширення, очікуючи, що це запрацює.\n\nЯкщо Ви хочете імпортувати застарілку мапу, то використовуйте кнопку «Імпортувати застаріле зображення» у редакторі. editor.errorimage = Це зображення, а не мапа. Не змінюйте розширення, очікуючи, що це запрацює.\n\nЯкщо Ви хочете імпортувати застарілку мапу, то використовуйте кнопку «Імпортувати застаріле зображення» у редакторі.
editor.errorlegacy = Ця мапа занадто стара і використовує попередній формат мапи, який більше не підтримується. editor.errorlegacy = Ця мапа занадто стара і використовує попередній формат мапи, який більше не підтримується.
editor.errornot = This is not a map file. editor.errornot = Це не мапа.
editor.errorheader = Цей файл мапи недійсний або пошкоджений. editor.errorheader = Цей файл мапи недійсний або пошкоджений.
editor.errorname = Мапа не має імені. Може Ви намагаєтеся завантажити збереження? editor.errorname = Мапа не має імені. Може Ви намагаєтеся завантажити збереження?
editor.update = Оновити editor.update = Оновити
@@ -289,7 +295,7 @@ editor.resizemap = Змінити розмір мапи
editor.mapname = Назва мапи: editor.mapname = Назва мапи:
editor.overwrite = [accent]Попередження!\nЦе перезаписує існуючу мапу. editor.overwrite = [accent]Попередження!\nЦе перезаписує існуючу мапу.
editor.overwrite.confirm = [scarlet]Попередження![] Мапа з такою назвою вже існує. Ви впевнені, що хочете переписати її? editor.overwrite.confirm = [scarlet]Попередження![] Мапа з такою назвою вже існує. Ви впевнені, що хочете переписати її?
editor.exists = A map with this name already exists. editor.exists = Мапа за такою назвою вже існує.
editor.selectmap = Виберіть мапу для завантаження: editor.selectmap = Виберіть мапу для завантаження:
toolmode.replace = Замінити toolmode.replace = Замінити
toolmode.replace.description = Малює тільки\nна суцільних блоках. toolmode.replace.description = Малює тільки\nна суцільних блоках.
@@ -369,7 +375,7 @@ launch.skip.confirm = Якщо Ви пропустите зараз, Ви не
uncover = Розкрити uncover = Розкрити
configure = Вивантажити конфігурацію configure = Вивантажити конфігурацію
configure.locked = [lightgray]Можливість розблокувати вивантаження ресурсів буде доступна на {0}-тій хвилі. configure.locked = [lightgray]Можливість розблокувати вивантаження ресурсів буде доступна на {0}-тій хвилі.
configure.invalid = Amount must be a number between 0 and {0}. configure.invalid = Кількість повинна бути числом між 0 та {0}.
zone.unlocked = Зона «[lightgray]{0}» тепер розблокована. zone.unlocked = Зона «[lightgray]{0}» тепер розблокована.
zone.requirement.complete = Ви досягли {0}-тої хвилі,\nВимоги до зони «{1}» виконані. zone.requirement.complete = Ви досягли {0}-тої хвилі,\nВимоги до зони «{1}» виконані.
zone.config.complete = Ви досягли {0}-тої хвилі.\nМожливість вивантаження ресурсів тепер розблокована. zone.config.complete = Ви досягли {0}-тої хвилі.\nМожливість вивантаження ресурсів тепер розблокована.
@@ -466,7 +472,7 @@ blocks.boosteffect = Прискорювальний ефект
blocks.maxunits = Максимальна кількість активних одиниць blocks.maxunits = Максимальна кількість активних одиниць
blocks.health = Здоров’я blocks.health = Здоров’я
blocks.buildtime = Час будівництва blocks.buildtime = Час будівництва
blocks.buildcost = Build Cost blocks.buildcost = Вартість будування
blocks.inaccuracy = Розкид blocks.inaccuracy = Розкид
blocks.shots = Постріли blocks.shots = Постріли
blocks.reload = Постріли/секунду blocks.reload = Постріли/секунду
@@ -475,7 +481,7 @@ bar.drilltierreq = Потребується кращий бур
bar.drillspeed = Швидкість буріння: {0}/с bar.drillspeed = Швидкість буріння: {0}/с
bar.efficiency = Ефективність: {0}% bar.efficiency = Ефективність: {0}%
bar.powerbalance = Енергія: {0}/с bar.powerbalance = Енергія: {0}/с
bar.powerstored = Stored: {0}/{1} bar.powerstored = Зберігає: {0}/{1}
bar.poweramount = Енергія: {0} bar.poweramount = Енергія: {0}
bar.poweroutput = Вихідна енергія: {0} bar.poweroutput = Вихідна енергія: {0}
bar.items = Предмети: {0} bar.items = Предмети: {0}
@@ -524,7 +530,7 @@ setting.antialias.name = Згладжування[lightgray] (потребує
setting.indicators.name = Показувати у сторону ворогів та союзників setting.indicators.name = Показувати у сторону ворогів та союзників
setting.autotarget.name = Авто-стрільба setting.autotarget.name = Авто-стрільба
setting.keyboard.name = Миш+Керування з клавіатури setting.keyboard.name = Миш+Керування з клавіатури
setting.touchscreen.name = Touchscreen Controls setting.touchscreen.name = Керування сенсорним екраном
setting.fpscap.name = Максимальний FPS setting.fpscap.name = Максимальний FPS
setting.fpscap.none = Необмежений setting.fpscap.none = Необмежений
setting.fpscap.text = {0} FPS setting.fpscap.text = {0} FPS
@@ -534,7 +540,7 @@ setting.difficulty.training = Навчання
setting.difficulty.easy = Легка setting.difficulty.easy = Легка
setting.difficulty.normal = Нормальна setting.difficulty.normal = Нормальна
setting.difficulty.hard = Важка setting.difficulty.hard = Важка
setting.difficulty.insane = Зачистка setting.difficulty.insane = Неможлива
setting.difficulty.name = Складність: setting.difficulty.name = Складність:
setting.screenshake.name = Тряска екрану setting.screenshake.name = Тряска екрану
setting.effects.name = Ефекти setting.effects.name = Ефекти
@@ -555,9 +561,10 @@ setting.sfxvol.name = Гучність звукових ефектів
setting.mutesound.name = Заглушити звук setting.mutesound.name = Заглушити звук
setting.crashreport.name = Відсилати анонімні звіти про аварійне завершення гри setting.crashreport.name = Відсилати анонімні звіти про аварійне завершення гри
setting.savecreate.name = Автоматичне створення збережень setting.savecreate.name = Автоматичне створення збережень
setting.publichost.name = Public Game Visibility setting.publichost.name = Загальнодоступність гри
setting.chatopacity.name = Непрозорість чату setting.chatopacity.name = Непрозорість чату
setting.playerchat.name = Відображати хмару чата над гравцями setting.playerchat.name = Відображати хмару чата над гравцями
public.confirm = Ви хочете зробити цю гру загальнодоступною?\n[lightgray]Це можна змінити у Налаштування->Гра->Public Game Visibility.
uiscale.reset = Масштаб користувальницького інтерфейсу було змінено.\nНатисніть «ОК» для підтверждення цього масшатабу.\n[scarlet]Повернення налаштувань і вихід через[accent] {0}[] … uiscale.reset = Масштаб користувальницького інтерфейсу було змінено.\nНатисніть «ОК» для підтверждення цього масшатабу.\n[scarlet]Повернення налаштувань і вихід через[accent] {0}[] …
uiscale.cancel = Скасувати & Вийти uiscale.cancel = Скасувати & Вийти
setting.bloom.name = Світіння setting.bloom.name = Світіння
@@ -567,7 +574,7 @@ category.general.name = Основне
category.view.name = Перегляд category.view.name = Перегляд
category.multiplayer.name = Мережева гра category.multiplayer.name = Мережева гра
command.attack = Атакувати command.attack = Атакувати
command.rally = Rally command.rally = Точка збору
command.retreat = Відступити command.retreat = Відступити
keybind.gridMode.name = Вибрати блок keybind.gridMode.name = Вибрати блок
keybind.gridModeShift.name = Вибрати категорію keybind.gridModeShift.name = Вибрати категорію
@@ -604,6 +611,7 @@ mode.survival.name = Хвилі
mode.survival.description = Звичайний режим. В цьому режимі треба самим добувати ресурси та хвилі йдуть автоматично.\n[gray]Потребуються точки появи ворогів для гри. mode.survival.description = Звичайний режим. В цьому режимі треба самим добувати ресурси та хвилі йдуть автоматично.\n[gray]Потребуються точки появи ворогів для гри.
mode.sandbox.name = Пісочниця mode.sandbox.name = Пісочниця
mode.sandbox.description = В режимі «Пісочниця» незкінченні ресурси(але їх все одно можно добувати) та хвилі йдуть за вашим бажанням. mode.sandbox.description = В режимі «Пісочниця» незкінченні ресурси(але їх все одно можно добувати) та хвилі йдуть за вашим бажанням.
mode.editor.name = Редактор
mode.pvp.name = PVP mode.pvp.name = PVP
mode.pvp.description = боріться проти інших гравців.\n[gray]Для гри потрібно принаймні 2 ядра різного кольору на мапі. mode.pvp.description = боріться проти інших гравців.\n[gray]Для гри потрібно принаймні 2 ядра різного кольору на мапі.
mode.attack.name = Атака mode.attack.name = Атака
@@ -784,13 +792,13 @@ block.hail.name = Град
block.lancer.name = Списоносець block.lancer.name = Списоносець
block.conveyor.name = Конвеєр block.conveyor.name = Конвеєр
block.titanium-conveyor.name = Титановий конвеєр block.titanium-conveyor.name = Титановий конвеєр
block.armored-conveyor.name = Armored Conveyor block.armored-conveyor.name = Броньований конвеєр
block.armored-conveyor.description = Moves items at the same speed as titanium conveyors, but possesses more armor. Does not accept inputs from the sides from anything but other conveyors. block.armored-conveyor.description = Переміщує предмети з тією ж швидкістю, як і титанові конвеєри, але має більше міцності. Не приймає введення з боків ні з чого, крім інших конвеєрів.
block.junction.name = Перехрестя block.junction.name = Перехрестя
block.router.name = Маршрутизатор block.router.name = Маршрутизатор
block.distributor.name = Розподілювач block.distributor.name = Розподілювач
block.sorter.name = Сортувальник block.sorter.name = Сортувальник
block.message.name = Message block.message.name = Повідомлення
block.overflow-gate.name = Надмірний затвор block.overflow-gate.name = Надмірний затвор
block.silicon-smelter.name = Кремнієвий плавильний завод block.silicon-smelter.name = Кремнієвий плавильний завод
block.phase-weaver.name = Фазовий ткач block.phase-weaver.name = Фазовий ткач
@@ -879,8 +887,8 @@ block.overdrive-projector.name = Сверхприводний проектор
block.force-projector.name = Силовий проектор block.force-projector.name = Силовий проектор
block.arc.name = Дуга block.arc.name = Дуга
block.rtg-generator.name = Радіоізотопний термоелектричний генератор block.rtg-generator.name = Радіоізотопний термоелектричний генератор
block.spectre.name = Мара block.spectre.name = Спектр
block.meltdown.name = Катастрофа block.meltdown.name = Випалювач
block.container.name = Склад block.container.name = Склад
block.launch-pad.name = Стартовий майданчик block.launch-pad.name = Стартовий майданчик
block.launch-pad-large.name = Великий стартовий майданчик block.launch-pad-large.name = Великий стартовий майданчик
@@ -892,7 +900,7 @@ team.derelict.name = Залишена
team.green.name = Зелена team.green.name = Зелена
team.purple.name = Фіолетова team.purple.name = Фіолетова
unit.spirit.name = Ремонтувальний дрон «Привид» unit.spirit.name = Ремонтувальний дрон «Привид»
unit.draug.name = Draug Miner Drone unit.draug.name = Добувний дрон «Драугр»
unit.phantom.name = Будівельний дрон «Фантом» unit.phantom.name = Будівельний дрон «Фантом»
unit.dagger.name = Кинджал unit.dagger.name = Кинджал
unit.crawler.name = Камікадзе unit.crawler.name = Камікадзе
@@ -907,7 +915,7 @@ unit.eradicator.name = Викорінювач
unit.lich.name = Лич unit.lich.name = Лич
unit.reaper.name = Жнець unit.reaper.name = Жнець
tutorial.next = [lightgray]<Натисніть для продовження> tutorial.next = [lightgray]<Натисніть для продовження>
tutorial.intro = Ви розпочали[scarlet] навчання по Mindustry.[]\nРозпочність з[accent] видобування міді[]. Натисніть на мідну жилу біля вашого ядра, щоб зробити це.\n\n[accent]{0}/{1} міді tutorial.intro = Ви розпочали[scarlet] навчання по Mindustry.[]\nРозпочність з[accent] видобування міді[]. Використовуйте [[WASD] для руху, а потім натисність на мідну жилу біля вашого ядра, щоб зробити це.\n\n[accent]{0}/{1} міді
tutorial.drill = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисніть на вкладку свердла знизу зправа.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням.\n[accent]Натисніть ПКМ[], щоб зупинити будування. tutorial.drill = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисніть на вкладку свердла знизу зправа.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням.\n[accent]Натисніть ПКМ[], щоб зупинити будування.
tutorial.drill.mobile = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисність на вкладку сведла знизу зправа.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням, потім натисність на [accent] галочку[] нижче, щоб підтвердити розміщення to confirm your selection.\nPress the[accent] X button[] to cancel placement. tutorial.drill.mobile = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисність на вкладку сведла знизу зправа.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням, потім натисність на [accent] галочку[] нижче, щоб підтвердити розміщення to confirm your selection.\nPress the[accent] X button[] to cancel placement.
tutorial.blockinfo = Кожен блок має різні характеристики. Кожний бур може видобувати тільки певні руди.\nЩоб переглянути інформацію та характеристики блока,[accent] натисність на кнопку «?», коли Ви вибрали блок у меню будування.[]\n\n[accent]Перегляньте характеристику Механічного бура прямо зараз.[] tutorial.blockinfo = Кожен блок має різні характеристики. Кожний бур може видобувати тільки певні руди.\nЩоб переглянути інформацію та характеристики блока,[accent] натисність на кнопку «?», коли Ви вибрали блок у меню будування.[]\n\n[accent]Перегляньте характеристику Механічного бура прямо зараз.[]
@@ -965,7 +973,7 @@ unit.eruptor.description = Важкий мех, призначеней для з
unit.wraith.description = Швидкий перехоплювач, який використовується для тактики «атакуй і біжи». Пріоритет — енергетичні генератори. unit.wraith.description = Швидкий перехоплювач, який використовується для тактики «атакуй і біжи». Пріоритет — енергетичні генератори.
unit.ghoul.description = Важкий килимовий бомбардувальник. Пробиває ворожі структури, орієнтуючись на віжливу інфраструктуру. unit.ghoul.description = Важкий килимовий бомбардувальник. Пробиває ворожі структури, орієнтуючись на віжливу інфраструктуру.
unit.revenant.description = Важкий ракетний масив. unit.revenant.description = Важкий ракетний масив.
block.message.description = Stores a message. Used for communication between allies. block.message.description = Зберігає повідомлення. Використовується для комунікаціх між союзниками.
block.graphite-press.description = Стискає шматки вугілля в чисті аркуші графіту. block.graphite-press.description = Стискає шматки вугілля в чисті аркуші графіту.
block.multi-press.description = Модернізована версія графітового преса. Використовує воду та енергію для швидкої та ефективної переробки вугілля. block.multi-press.description = Модернізована версія графітового преса. Використовує воду та енергію для швидкої та ефективної переробки вугілля.
block.silicon-smelter.description = Змішує пісок з чистим вугіллям. Виробляє кремній. block.silicon-smelter.description = Змішує пісок з чистим вугіллям. Виробляє кремній.
@@ -1050,7 +1058,7 @@ block.core-foundation.description = Друга версія ядра. Краще
block.core-nucleus.description = Третя і остання ітерація капсули ядра. Надзвичайно добре броньований. Зберігає величезні обсяги ресурсів. block.core-nucleus.description = Третя і остання ітерація капсули ядра. Надзвичайно добре броньований. Зберігає величезні обсяги ресурсів.
block.vault.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища. block.vault.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища.
block.container.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища. block.container.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища.
block.unloader.description = Вивантажує предмети з контейнера, склепіння або серцевини на конвеєр або безпосередньо в сусідній блок. Тип предмета для завантаження можна змінити, натиснувши на блок. block.unloader.description = Вивантажує предмети з блока, який не переміщує предмети, на конвеєр або безпосередньо в сусідній блок. Тип предмета для завантаження можна змінити, натиснувши на блок.
block.launch-pad.description = Запускає партії предметів без необхідності запуску ядра. block.launch-pad.description = Запускає партії предметів без необхідності запуску ядра.
block.launch-pad-large.description = Покращена версія стартового майданчика. Зберігає більше предметів. Запускається частіше. block.launch-pad-large.description = Покращена версія стартового майданчика. Зберігає більше предметів. Запускається частіше.
block.duo.description = Невелика дешева башта. Корисна проти наземних одиниць. block.duo.description = Невелика дешева башта. Корисна проти наземних одиниць.

View File

@@ -13,6 +13,7 @@ import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import static io.anuke.arc.Core.*; import static io.anuke.arc.Core.*;
@@ -43,6 +44,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
assets.load("sprites/error.png", Texture.class); assets.load("sprites/error.png", Texture.class);
atlas = TextureAtlas.blankAtlas(); atlas = TextureAtlas.blankAtlas();
Vars.net = new Net(platform.getNet()); Vars.net = new Net(platform.getNet());
Vars.mods = new Mods();
UI.loadSystemCursors(); UI.loadSystemCursors();
@@ -71,6 +73,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
add(netServer = new NetServer()); add(netServer = new NetServer());
add(netClient = new NetClient()); add(netClient = new NetClient());
assets.load(mods);
assets.loadRun("contentinit", ContentLoader.class, () -> { assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init(); content.init();
content.load(); content.load();
@@ -108,6 +112,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
listener.init(); listener.init();
} }
super.resize(graphics.getWidth(), graphics.getHeight()); super.resize(graphics.getWidth(), graphics.getHeight());
mods.each(Mod::init);
finished = true; finished = true;
Events.fire(new ClientLoadEvent()); Events.fire(new ClientLoadEvent());
} }
@@ -182,7 +187,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
if(assets.getCurrentLoading() != null){ if(assets.getCurrentLoading() != null){
String name = assets.getCurrentLoading().fileName.toLowerCase(); String name = assets.getCurrentLoading().fileName.toLowerCase();
String key = name.contains("content") ? "content" : name.contains("msav") || name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system"; String key = name.contains("content") ? "content" : name.contains("mod") ? "mods" : name.contains("msav") || name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system";
font.draw(bundle.get("load." + key, ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f - height / 2f - Scl.scl(10f), Align.center); font.draw(bundle.get("load." + key, ""), graphics.getWidth() / 2f, graphics.getHeight() / 2f - height / 2f - Scl.scl(10f), Align.center);
} }
} }

View File

@@ -18,14 +18,14 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.input.*; import io.anuke.mindustry.input.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.world.blocks.defense.ForceProjector.*; import io.anuke.mindustry.world.blocks.defense.ForceProjector.*;
import java.nio.charset.*; import java.nio.charset.*;
import java.util.*; import java.util.*;
import static io.anuke.arc.Core.settings; import static io.anuke.arc.Core.*;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Vars implements Loadable{ public class Vars implements Loadable{
@@ -120,8 +120,8 @@ public class Vars implements Loadable{
public static FileHandle tmpDirectory; public static FileHandle tmpDirectory;
/** data subdirectory used for saves */ /** data subdirectory used for saves */
public static FileHandle saveDirectory; public static FileHandle saveDirectory;
/** data subdirectory used for plugins */ /** data subdirectory used for mods */
public static FileHandle pluginDirectory; public static FileHandle modDirectory;
/** map file extension */ /** map file extension */
public static final String mapExtension = "msav"; public static final String mapExtension = "msav";
/** save file extension */ /** save file extension */
@@ -130,6 +130,7 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */ /** list of all locales that can be switched to */
public static Locale[] locales; public static Locale[] locales;
public static FileTree filet;
public static Net net; public static Net net;
public static ContentLoader content; public static ContentLoader content;
public static GameState state; public static GameState state;
@@ -138,7 +139,7 @@ public class Vars implements Loadable{
public static DefaultWaves defaultWaves; public static DefaultWaves defaultWaves;
public static LoopControl loops; public static LoopControl loops;
public static Platform platform = new Platform(){}; public static Platform platform = new Platform(){};
public static Plugins plugins; public static Mods mods;
public static World world; public static World world;
public static Maps maps; public static Maps maps;
@@ -193,6 +194,10 @@ public class Vars implements Loadable{
Version.init(); Version.init();
filet = new FileTree();
if(mods == null){
mods = new Mods();
}
content = new ContentLoader(); content = new ContentLoader();
loops = new LoopControl(); loops = new LoopControl();
defaultWaves = new DefaultWaves(); defaultWaves = new DefaultWaves();
@@ -240,8 +245,11 @@ public class Vars implements Loadable{
mapPreviewDirectory = dataDirectory.child("previews/"); mapPreviewDirectory = dataDirectory.child("previews/");
saveDirectory = dataDirectory.child("saves/"); saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/"); tmpDirectory = dataDirectory.child("tmp/");
pluginDirectory = dataDirectory.child("plugins/"); modDirectory = dataDirectory.child("mods/");
modDirectory.mkdirs();
mods.load();
maps.load(); maps.load();
} }

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.function.*; import io.anuke.arc.function.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.async.*; import io.anuke.arc.util.async.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
@@ -32,7 +33,8 @@ public class Pathfinder implements Runnable{
/** handles task scheduling on the update thread. */ /** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue(); private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */ /** current pathfinding thread */
private @Nullable Thread thread; private @Nullable
Thread thread;
public Pathfinder(){ public Pathfinder(){
Events.on(WorldLoadEvent.class, event -> { Events.on(WorldLoadEvent.class, event -> {
@@ -92,7 +94,11 @@ public class Pathfinder implements Runnable{
int x = tile.x, y = tile.y; int x = tile.x, y = tile.y;
tile.getLinkedTiles(t -> tiles[t.x][t.y] = packTile(t)); tile.getLinkedTiles(t -> {
if(Structs.inBounds(t.x, t.y, tiles)){
tiles[t.x][t.y] = packTile(t);
}
});
//can't iterate through array so use the map, which should not lead to problems //can't iterate through array so use the map, which should not lead to problems
for(PathData[] arr : pathMap){ for(PathData[] arr : pathMap){

View File

@@ -48,7 +48,7 @@ public class WaveSpawner{
for(SpawnGroup group : state.rules.spawns){ for(SpawnGroup group : state.rules.spawns){
int spawned = group.getUnitsSpawned(state.wave - 1); int spawned = group.getUnitsSpawned(state.wave - 1);
if(group.type.isFlying){ if(group.type.flying){
float spread = margin / 1.5f; float spread = margin / 1.5f;
eachFlyerSpawn((spawnX, spawnY) -> { eachFlyerSpawn((spawnX, spawnY) -> {

View File

@@ -18,7 +18,7 @@ public class UnitTypes implements ContentList{
@Override @Override
public void load(){ public void load(){
draug = new UnitType("draug", Draug.class, Draug::new){{ draug = new UnitType("draug", Draug.class, Draug::new){{
isFlying = true; flying = true;
drag = 0.01f; drag = 0.01f;
speed = 0.3f; speed = 0.3f;
maxVelocity = 1.2f; maxVelocity = 1.2f;
@@ -33,7 +33,7 @@ public class UnitTypes implements ContentList{
}}; }};
spirit = new UnitType("spirit", Spirit.class, Spirit::new){{ spirit = new UnitType("spirit", Spirit.class, Spirit::new){{
isFlying = true; flying = true;
drag = 0.01f; drag = 0.01f;
speed = 0.4f; speed = 0.4f;
maxVelocity = 1.6f; maxVelocity = 1.6f;
@@ -54,7 +54,7 @@ public class UnitTypes implements ContentList{
}}; }};
phantom = new UnitType("phantom", Phantom.class, Phantom::new){{ phantom = new UnitType("phantom", Phantom.class, Phantom::new){{
isFlying = true; flying = true;
drag = 0.01f; drag = 0.01f;
mass = 2f; mass = 2f;
speed = 0.45f; speed = 0.45f;
@@ -244,7 +244,7 @@ public class UnitTypes implements ContentList{
maxVelocity = 1.9f; maxVelocity = 1.9f;
drag = 0.01f; drag = 0.01f;
mass = 1.5f; mass = 1.5f;
isFlying = true; flying = true;
health = 75; health = 75;
engineOffset = 5.5f; engineOffset = 5.5f;
range = 140f; range = 140f;
@@ -264,7 +264,7 @@ public class UnitTypes implements ContentList{
maxVelocity = 1.4f; maxVelocity = 1.4f;
mass = 3f; mass = 3f;
drag = 0.01f; drag = 0.01f;
isFlying = true; flying = true;
targetAir = false; targetAir = false;
engineOffset = 7.8f; engineOffset = 7.8f;
range = 140f; range = 140f;
@@ -291,7 +291,7 @@ public class UnitTypes implements ContentList{
drag = 0.01f; drag = 0.01f;
range = 80f; range = 80f;
shootCone = 40f; shootCone = 40f;
isFlying = true; flying = true;
rotateWeapon = true; rotateWeapon = true;
engineOffset = 12f; engineOffset = 12f;
engineSize = 3f; engineSize = 3f;
@@ -322,7 +322,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f; drag = 0.02f;
range = 80f; range = 80f;
shootCone = 20f; shootCone = 20f;
isFlying = true; flying = true;
rotateWeapon = true; rotateWeapon = true;
engineOffset = 21; engineOffset = 21;
engineSize = 5.3f; engineSize = 5.3f;
@@ -355,7 +355,7 @@ public class UnitTypes implements ContentList{
drag = 0.02f; drag = 0.02f;
range = 80f; range = 80f;
shootCone = 30f; shootCone = 30f;
isFlying = true; flying = true;
rotateWeapon = true; rotateWeapon = true;
engineOffset = 40; engineOffset = 40;
engineSize = 7.3f; engineSize = 7.3f;

View File

@@ -11,6 +11,7 @@ import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import static io.anuke.arc.Core.files; import static io.anuke.arc.Core.files;
import static io.anuke.mindustry.Vars.mods;
/** /**
* Loads all game content. * Loads all game content.
@@ -57,6 +58,23 @@ public class ContentLoader{
list.load(); list.load();
} }
setupMapping();
if(mods != null){
mods.loadContent();
}
setupMapping();
loaded = true;
}
private void setupMapping(){
for(ContentType type : ContentType.values()){
contentNameMap[type.ordinal()].clear();
}
for(ContentType type : ContentType.values()){ for(ContentType type : ContentType.values()){
for(Content c : contentMap[type.ordinal()]){ for(Content c : contentMap[type.ordinal()]){
@@ -79,8 +97,6 @@ public class ContentLoader{
} }
} }
} }
loaded = true;
} }
/** Logs content statistics.*/ /** Logs content statistics.*/

View File

@@ -0,0 +1,23 @@
package io.anuke.mindustry.core;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
/** Handles files in a modded context. */
public class FileTree{
private ObjectMap<String, FileHandle> files = new ObjectMap<>();
public void addFile(FileHandle f){
files.put(f.path(), f);
}
/** Gets an asset file.*/
public FileHandle get(String path){
if(files.containsKey(path)){
return files.get(path);
}else{
return Core.files.internal(path);
}
}
}

View File

@@ -200,6 +200,11 @@ public class NetServer implements ApplicationListener{
registerCommands(); registerCommands();
} }
@Override
public void init(){
mods.each(mod -> mod.registerClientCommands(clientCommands));
}
private void registerCommands(){ private void registerCommands(){
clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> { clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> {
if(args.length > 0 && !Strings.canParseInt(args[0])){ if(args.length > 0 && !Strings.canParseInt(args[0])){

View File

@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public DeployDialog deploy; public DeployDialog deploy;
public TechTreeDialog tech; public TechTreeDialog tech;
public MinimapDialog minimap; public MinimapDialog minimap;
public ModsDialog mods;
public Cursor drillCursor, unloadCursor; public Cursor drillCursor, unloadCursor;
@@ -222,6 +223,7 @@ public class UI implements ApplicationListener, Loadable{
deploy = new DeployDialog(); deploy = new DeployDialog();
tech = new TechTreeDialog(); tech = new TechTreeDialog();
minimap = new MinimapDialog(); minimap = new MinimapDialog();
mods = new ModsDialog();
Group group = Core.scene.root; Group group = Core.scene.root;
@@ -410,6 +412,18 @@ public class UI implements ApplicationListener, Loadable{
dialog.show(); dialog.show();
} }
public void showOkText(String title, String text, Runnable confirmed){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.addButton("$ok", () -> {
dialog.hide();
confirmed.run();
});
dialog.show();
}
public String formatAmount(int number){ public String formatAmount(int number){
if(number >= 1000000){ if(number >= 1000000){
return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]"; return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]";

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.core; package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
@@ -82,7 +82,8 @@ public class World{
return height()*tilesize; return height()*tilesize;
} }
public @Nullable Tile tile(int pos){ public @Nullable
Tile tile(int pos){
return tiles == null ? null : tile(Pos.x(pos), Pos.y(pos)); return tiles == null ? null : tile(Pos.x(pos), Pos.y(pos));
} }

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.editor; package io.anuke.mindustry.editor;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.files.*; import io.anuke.arc.files.*;
@@ -16,6 +15,7 @@ import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
@@ -24,7 +24,7 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.ui.Styles; import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;

View File

@@ -47,10 +47,8 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
@Remote(called = Loc.server) @Remote(called = Loc.server)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){ public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null || tile.entity == null || tile.entity.items == null) return; if(tile == null || tile.entity == null || tile.entity.items == null) return;
if(!Units.canInteract(player, tile)) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){ for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Time.run(i * 3, () -> create(item, x, y, tile, () -> { Time.run(i * 3, () -> create(item, x, y, tile, () -> {}));
}));
} }
tile.entity.items.add(item, amount); tile.entity.items.add(item, amount);
} }

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.traits; package io.anuke.mindustry.entities.traits;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.collection.Queue; import io.anuke.arc.collection.Queue;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
@@ -8,6 +7,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
@@ -200,7 +200,8 @@ public interface BuilderTrait extends Entity, TeamTrait{
* Return the build requests currently active, or the one at the top of the queue. * Return the build requests currently active, or the one at the top of the queue.
* May return null. * May return null.
*/ */
default @Nullable BuildRequest buildRequest(){ default @Nullable
BuildRequest buildRequest(){
return buildQueue().size == 0 ? null : buildQueue().first(); return buildQueue().size == 0 ? null : buildQueue().first();
} }

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
@@ -93,7 +94,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
} }
} }
public @Nullable Tile getSpawner(){ public @Nullable
Tile getSpawner(){
return world.tile(spawner); return world.tile(spawner);
} }
@@ -263,7 +265,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
@Override @Override
public boolean isFlying(){ public boolean isFlying(){
return type.isFlying; return type.flying;
} }
@Override @Override

View File

@@ -10,6 +10,7 @@ import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*; import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
@@ -48,7 +49,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public float baseRotation; public float baseRotation;
public float pointerX, pointerY; public float pointerX, pointerY;
public String name = "noname"; public String name = "noname";
public @Nullable String uuid, usid; public @Nullable
String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping; public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping;
public float boostHeat, shootHeat, destructTime; public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight; public boolean achievedFlight;

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.math.geom.Point2; import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.math.geom.Vector2; import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.EntityGroup; import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.HealthTrait; import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.traits.TargetTrait;
@@ -42,7 +43,8 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
private boolean dead = false; private boolean dead = false;
private boolean sleeping; private boolean sleeping;
private float sleepTime; private float sleepTime;
private @Nullable SoundLoop sound; private @Nullable
SoundLoop sound;
@Remote(called = Loc.server, unreliable = true) @Remote(called = Loc.server, unreliable = true)
public static void onTileDamage(Tile tile, float health){ public static void onTileDamage(Tile tile, float health){

View File

@@ -1,6 +1,5 @@
package io.anuke.mindustry.entities.type; package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
@@ -9,6 +8,7 @@ import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*; import io.anuke.mindustry.entities.effect.*;
@@ -234,7 +234,8 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
} }
} }
public @Nullable TileEntity getClosestCore(){ public @Nullable
TileEntity getClosestCore(){
TeamData data = state.teams.get(team); TeamData data = state.teams.get(team);
Tile tile = Geometry.findClosest(x, y, data.cores); Tile tile = Geometry.findClosest(x, y, data.cores);

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.math.Mathf; import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry; import io.anuke.arc.math.geom.Geometry;
import io.anuke.mindustry.entities.type.FlyingUnit;
import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.meta.BlockFlag;

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Crawler extends GroundUnit{ public class Crawler extends GroundUnit{
} }

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Dagger extends GroundUnit{ public class Dagger extends GroundUnit{
} }

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Eruptor extends GroundUnit{ public class Eruptor extends GroundUnit{
} }

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.type; package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
@@ -8,6 +8,7 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
@@ -15,7 +16,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
public abstract class FlyingUnit extends BaseUnit{ public class FlyingUnit extends BaseUnit{
protected float[] weaponAngles = {0,0}; protected float[] weaponAngles = {0,0};
protected final UnitState protected final UnitState

View File

@@ -1,6 +1,4 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Fortress extends GroundUnit{ public class Fortress extends GroundUnit{
} }

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Ghoul extends FlyingUnit{ public class Ghoul extends FlyingUnit{
} }

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.type; package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
@@ -9,6 +9,7 @@ import io.anuke.mindustry.*;
import io.anuke.mindustry.ai.Pathfinder.*; import io.anuke.mindustry.ai.Pathfinder.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.entities.units.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
@@ -18,7 +19,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
public abstract class GroundUnit extends BaseUnit{ public class GroundUnit extends BaseUnit{
protected static Vector2 vec = new Vector2(); protected static Vector2 vec = new Vector2();
protected float walkTime; protected float walkTime;

View File

@@ -4,7 +4,6 @@ import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles; import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf; import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Revenant extends FlyingUnit{ public class Revenant extends FlyingUnit{

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Titan extends GroundUnit{ public class Titan extends GroundUnit{
} }

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.type.base; package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Wraith extends FlyingUnit{ public class Wraith extends FlyingUnit{
} }

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.game; package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.traits.BuilderTrait; import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
@@ -195,7 +195,8 @@ public class EventType{
public static class BlockBuildEndEvent{ public static class BlockBuildEndEvent{
public final Tile tile; public final Tile tile;
public final Team team; public final Team team;
public final @Nullable Player player; public final @Nullable
Player player;
public final boolean breaking; public final boolean breaking;
public BlockBuildEndEvent(Tile tile, @Nullable Player player, Team team, boolean breaking){ public BlockBuildEndEvent(Tile tile, @Nullable Player player, Team team, boolean breaking){

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.game; package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.audio.*; import io.anuke.arc.audio.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
@@ -22,7 +22,8 @@ public class MusicControl{
public Array<Music> darkMusic = Array.with(); public Array<Music> darkMusic = Array.with();
private Music lastRandomPlayed; private Music lastRandomPlayed;
private Interval timer = new Interval(); private Interval timer = new Interval();
private @Nullable Music current; private @Nullable
Music current;
private float fade; private float fade;
private boolean silenced; private boolean silenced;

View File

@@ -1,17 +1,18 @@
package io.anuke.mindustry.graphics; package io.anuke.mindustry.graphics;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.Core; import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.graphics.glutils.Shader; import io.anuke.arc.graphics.glutils.Shader;
import io.anuke.arc.scene.ui.layout.Scl; import io.anuke.arc.scene.ui.layout.Scl;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.Time; import io.anuke.arc.util.Time;
public class Shaders{ public class Shaders{
public static Shadow shadow; public static Shadow shadow;
public static BlockBuild blockbuild; public static BlockBuild blockbuild;
public static @Nullable Shield shield; public static @Nullable
Shield shield;
public static UnitBuild build; public static UnitBuild build;
public static FogShader fog; public static FogShader fog;
public static MenuShader menu; public static MenuShader menu;

View File

@@ -0,0 +1,193 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.reflect.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@SuppressWarnings("unchecked")
public class ContentParser{
private static final boolean ignoreUnknownFields = true;
private ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
private Json parser = new Json(){
public <T> T readValue(Class<T> type, Class elementType, JsonValue jsonData){
try{
if(type == BulletType.class){
BulletType b = (BulletType)Bullets.class.getField(jsonData.asString()).get(null);
if(b == null) throw new IllegalArgumentException("Bullet type not found: " + jsonData.asString());
return (T)b;
}
if(type != null && Content.class.isAssignableFrom(type)){
return (T)Vars.content.getByName(contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName())), jsonData.asString());
}
return super.readValue(type, elementType, jsonData);
}catch(Exception e){
throw new RuntimeException(e);
}
}
};
private ObjectMap<ContentType, TypeParser<?>> parsers = ObjectMap.of(
ContentType.block, (TypeParser<Block>)(mod, name, value) -> {
String clas = value.getString("type");
Class<Block> type = resolve("io.anuke.mindustry.world." + clas, "io.anuke.mindustry.world.blocks." + clas, "io.anuke.mindustry.world.blocks.defense" + clas);
Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name);
value.remove("type");
readFields(block, value);
//make block visible
if(block.buildRequirements != null){
block.buildVisibility = () -> true;
}
return block;
},
ContentType.item, (TypeParser<Item>)(mod, name, value) -> {
Item item = new Item(mod + "-" + name, new Color(Color.black));
readFields(item, value);
return item;
},
ContentType.unit, (TypeParser<UnitType>)(mod, name, value) -> {
String clas = value.getString("type");
Class<BaseUnit> type = resolve("io.anuke.mindustry.entities.type.base." + clas);
java.lang.reflect.Constructor<BaseUnit> cons = type.getDeclaredConstructor();
UnitType unit = new UnitType(mod + "-" + name, type, () -> {
try{
return cons.newInstance();
}catch(Exception e){
throw new RuntimeException(e);
}
});
value.remove("type");
readFields(unit, value);
return unit;
}
);
private void init(){
for(ContentType type : ContentType.all){
Array<Content> arr = Vars.content.getBy(type);
if(!arr.isEmpty()){
Class<?> c = arr.first().getClass();
//get base content class, skipping intermediates
while(!(c.getSuperclass() == Content.class || c.getSuperclass() == UnlockableContent.class || c.getSuperclass() == UnlockableContent.class)){
c = c.getSuperclass();
}
contentTypes.put(c, type);
}
}
}
/**
* Parses content from a json file.
* @param name the name of the file without its extension
* @param json the json to parse
* @param type the type of content this is
* @return the content that was parsed
*/
public Content parse(String mod, String name, String json, ContentType type) throws Exception{
if(contentTypes.isEmpty()){
init();
}
JsonValue value = parser.fromJson(null, json);
if(!parsers.containsKey(type)){
throw new SerializationException("No parsers for content type '" + type + "'");
}
Content c = parsers.get(type).parse(mod, name, value);
checkNulls(c);
return c;
}
/** Checks all @NonNull fields in this object, recursively.
* Throws an exception if any are null.*/
private void checkNulls(Object object){
checkNulls(object, new ObjectSet<>());
}
private void checkNulls(Object object, ObjectSet<Object> checked){
checked.add(object);
parser.getFields(object.getClass()).each((name, field) -> {
try{
if(field.field.getType().isPrimitive()) return;
Object obj = field.field.get(object);
if(field.field.isAnnotationPresent(NonNull.class) && field.field.get(object) == null){
throw new RuntimeException("Field '" + name + "' in " + object.getClass().getSimpleName() + " is missing!");
}
if(obj != null && !checked.contains(obj)){
checkNulls(obj, checked);
checked.add(obj);
}
}catch(Exception e){
throw new RuntimeException(e);
}
});
}
private void readFields(Object object, JsonValue jsonMap){
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = parser.getFields(type);
for(JsonValue child = jsonMap.child; child != null; child = child.next){
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
if(metadata == null){
if(ignoreUnknownFields){
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
continue;
}else{
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
ex.addTrace(child.trace());
throw ex;
}
}
Field field = metadata.field;
try{
field.set(object, parser.readValue(field.getType(), metadata.elementType, child));
}catch(ReflectionException ex){
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex);
}catch(SerializationException ex){
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}catch(RuntimeException runtimeEx){
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(child.trace());
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}
}
}
/** Tries to resolve a class from a list of potential class names. */
private <T> Class<T> resolve(String... potentials) throws Exception{
for(String type : potentials){
try{
return (Class<T>)Class.forName(type);
}catch(Exception ignored){
}
}
throw new IllegalArgumentException("Type not found: " + potentials[0]);
}
public interface TypeParser<T extends Content>{
T parse(String mod, String name, JsonValue value) throws Exception;
}
}

View File

@@ -0,0 +1,30 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.files.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
public class Mod{
/** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/
public FileHandle getConfig(){
return Vars.mods.getConfig(this);
}
/** Called after all plugins have been created and commands have been registered.*/
public void init(){
}
/** Create any content needed here. */
public void loadContent(){}
/** Register any commands to be used on the server side, e.g. from the console. */
public void registerServerCommands(CommandHandler handler){
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
public void registerClientCommands(CommandHandler handler){
}
}

View File

@@ -0,0 +1,284 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Pixmap.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
import java.io.*;
import java.net.*;
import static io.anuke.mindustry.Vars.*;
public class Mods implements Loadable{
private Json json = new Json();
private ContentParser parser = new ContentParser();
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>();
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites");
private int totalSprites;
private PixmapPacker packer;
private Array<LoadedMod> loaded = new Array<>();
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresRestart;
/** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */
public FileHandle getConfig(Mod mod){
ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json");
}
/** @return the loaded mod found by class, or null if not found. */
public @Nullable
LoadedMod getMod(Class<? extends Mod> type){
return loaded.find(l -> l.mod.getClass() == type);
}
/** Imports an external mod file.*/
public void importMod(FileHandle file) throws IOException{
FileHandle dest = modDirectory.child(file.name());
if(dest.exists()){
throw new IOException("A mod with the same filename already exists!");
}
file.copyTo(dest);
try{
loaded.add(loadMod(file));
requiresRestart = true;
}catch(IOException e){
dest.delete();
throw e;
}catch(Throwable t){
dest.delete();
throw new IOException(t);
}
}
/** Repacks all in-game sprites. */
@Override
public void loadAsync(){
if(loaded.isEmpty()) return;
packer = new PixmapPacker(2048, 2048, Format.RGBA8888, 2, true);
for(LoadedMod mod : loaded){
try{
int packed = 0;
for(FileHandle file : mod.root.child("sprites").list()){
if(file.extension().equals("png")){
try(InputStream stream = file.read()){
byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512));
Pixmap pixmap = new Pixmap(bytes, 0, bytes.length);
packer.pack(mod.name + "-" + file.nameWithoutExtension(), pixmap);
pixmap.dispose();
packed ++;
totalSprites ++;
}
}
}
Log.info("Packed {0} images for mod '{1}'.", packed, mod.meta.name);
}catch(IOException e){
Log.err("Error packing images for mod: {0}", mod.meta.name);
e.printStackTrace();
if(!headless) ui.showException(e);
}
}
}
@Override
public void loadSync(){
if(packer == null) return;
if(totalSprites > 0){
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest;
packer.getPages().each(page -> page.updateTexture(filter, filter, false));
packer.getPages().each(page -> page.getRects().each((name, rect) -> Core.atlas.addRegion(name, page.getTexture(), (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height)));
}
packer.dispose();
}
/** Removes a mod file and marks it for requiring a restart. */
public void removeMod(LoadedMod mod){
if(mod.file.isDirectory()){
mod.file.deleteDirectory();
}else{
mod.file.delete();
}
loaded.remove(mod);
requiresRestart = true;
}
public boolean requiresRestart(){
return requiresRestart;
}
/** Loads all mods from the folder, but does call any methods on them.*/
public void load(){
for(FileHandle file : modDirectory.list()){
if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && file.child("mod.json").exists())) continue;
try{
loaded.add(loadMod(file));
}catch(IllegalArgumentException ignored){
}catch(Exception e){
Log.err("Failed to load plugin file {0}. Skipping.", file);
e.printStackTrace();
}
}
buildFiles();
}
private void buildFiles(){
for(LoadedMod mod : loaded){
for(FileHandle file : mod.root.list()){
//ignore special folders like bundles or sprites
if(file.isDirectory() && !specialFolders.contains(file.name())){
//TODO calling child/parent on these files will give you gibberish; create wrapper class.
file.walk(f -> filet.addFile(f));
}
}
//load up bundles.
FileHandle folder = mod.root.child("bundles");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.name().startsWith("bundle") && file.extension().equals("properties")){
String name = file.nameWithoutExtension();
bundles.getOr(name, Array::new).add(file);
}
}
}
}
//add new keys to each bundle
I18NBundle bundle = Core.bundle;
while(bundle != null){
String str = bundle.getLocale().toString();
String locale = "bundle" + (str.isEmpty() ? "" : "_" + str);
for(FileHandle file : bundles.getOr(locale, Array::new)){
try{
PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e);
}
}
bundle = bundle.getParent();
}
}
/** Creates all the content found in mod files. */
public void loadContent(){
for(LoadedMod mod : loaded){
if(mod.root.child("content").exists()){
FileHandle contentRoot = mod.root.child("content");
for(ContentType type : ContentType.all){
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s");
if(folder.exists()){
for(FileHandle file : folder.list()){
if(file.extension().equals("json")){
try{
Content loaded = parser.parse(mod.name, file.nameWithoutExtension(), file.readString(), type);
Log.info("[{0}] Loaded '{1}'.", mod.meta.name, loaded);
}catch(Exception e){
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
}
}
}
}
}
}
}
each(Mod::loadContent);
}
/** @return all loaded mods. */
public Array<LoadedMod> all(){
return loaded;
}
/** Iterates through each mod with a main class.*/
public void each(Consumer<Mod> cons){
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
}
/** Loads a mod file+meta, but does not add it to the list.
* Note that directories can be loaded as mods.*/
private LoadedMod loadMod(FileHandle sourceFile) throws Exception{
FileHandle zip = sourceFile.isDirectory() ? sourceFile : new ZipFileHandle(sourceFile);
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", sourceFile);
throw new IllegalArgumentException("No mod.json found.");
}
ModMeta meta = json.fromJson(ModMeta.class, metaf.readString());
String camelized = meta.name.replace(" ", "");
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
Mod mainMod;
//make sure the main class exists before loading it; if it doesn't just don't put it there
if(zip.child(mainClass.replace('.', '/') + ".class").exists()){
//other platforms don't have standard java class loaders
if(mobile){
throw new IllegalArgumentException("This mod is not compatible with " + (ios ? "iOS" : "Android") + ".");
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{sourceFile.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(mainClass);
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
mainMod = null;
}
return new LoadedMod(sourceFile, zip, mainMod, meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod{
/** The location of this mod's zip file/folder on the disk. */
public final FileHandle file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
public final FileHandle root;
/** The mod's main class; may be null. */
public final @Nullable Mod mod;
/** Internal mod name. Used for textures. */
public final String name;
/** This mod's metadata. */
public final ModMeta meta;
//TODO implement
protected boolean enabled;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
this.root = root;
this.file = file;
this.mod = mod;
this.meta = meta;
this.name = meta.name.toLowerCase().replace(" ", "-");
}
}
/** Plugin metadata information.*/
public static class ModMeta{
public String name, author, description, version, main;
public String[] dependencies = {}; //TODO implement
}
}

View File

@@ -26,7 +26,7 @@ public class CrashSender{
exception.printStackTrace(); exception.printStackTrace();
//don't create crash logs for custom builds, as it's expected //don't create crash logs for custom builds, as it's expected
if(Version.build == -1) return; if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
//attempt to load version regardless //attempt to load version regardless
if(Version.number == 0){ if(Version.number == 0){
@@ -143,8 +143,7 @@ public class CrashSender{
private static void ex(Runnable r){ private static void ex(Runnable r){
try{ try{
r.run(); r.run();
}catch(Throwable t){ }catch(Throwable ignored){
t.printStackTrace();
} }
} }
} }

View File

@@ -1,10 +1,10 @@
package io.anuke.mindustry.net; package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.function.*; import io.anuke.arc.function.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*; import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
@@ -21,7 +21,8 @@ public class Net{
private boolean server; private boolean server;
private boolean active; private boolean active;
private boolean clientLoaded; private boolean clientLoaded;
private @Nullable StreamBuilder currentStream; private @Nullable
StreamBuilder currentStream;
private final Array<Object> packetQueue = new Array<>(); private final Array<Object> packetQueue = new Array<>();
private final ObjectMap<Class<?>, Consumer> clientListeners = new ObjectMap<>(); private final ObjectMap<Class<?>, Consumer> clientListeners = new ObjectMap<>();

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.net; package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.Administration.*; import io.anuke.mindustry.net.Administration.*;
@@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.netServer;
public abstract class NetConnection{ public abstract class NetConnection{
public final String address; public final String address;
public boolean mobile, modclient; public boolean mobile, modclient;
public @Nullable Player player; public @Nullable
Player player;
/** ID of last recieved client snapshot. */ /** ID of last recieved client snapshot. */
public int lastRecievedClientSnapshot = -1; public int lastRecievedClientSnapshot = -1;

View File

@@ -1,28 +1,9 @@
package io.anuke.mindustry.plugin; package io.anuke.mindustry.plugin;
import io.anuke.arc.files.*; import io.anuke.mindustry.mod.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
public abstract class Plugin{ /** Use Mod instead. */
@Deprecated
/** @return the config file for this plugin, as the file 'plugins/[plugin-name]/config.json'.*/ public abstract class Plugin extends Mod{
public FileHandle getConfig(){
return Vars.plugins.getConfig(this);
}
/** Called after all plugins have been created and commands have been registered.*/
public void init(){
} }
/** Register any commands to be used on the server side, e.g. from the console. */
public void registerServerCommands(CommandHandler handler){
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
public void registerClientCommands(CommandHandler handler){
}
}

View File

@@ -1,93 +0,0 @@
package io.anuke.mindustry.plugin;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.io.*;
import java.net.*;
import static io.anuke.mindustry.Vars.pluginDirectory;
public class Plugins{
private Array<LoadedPlugin> loaded = new Array<>();
private ObjectMap<Class<?>, PluginMeta> metas = new ObjectMap<>();
/** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */
public FileHandle getConfig(Plugin plugin){
PluginMeta load = metas.get(plugin.getClass());
if(load == null) throw new IllegalArgumentException("Plugin is not loaded yet (or missing)!");
return pluginDirectory.child(load.name).child("config.json");
}
/** @return the loaded plugin found by class, or null if not found. */
public @Nullable LoadedPlugin getPlugin(Class<? extends Plugin> type){
return loaded.find(l -> l.plugin.getClass() == type);
}
/** Loads all plugins from the folder, but does call any methods on them.*/
public void load(){
for(FileHandle file : pluginDirectory.list()){
if(!file.extension().equals("jar")) continue;
try{
loaded.add(loadPlugin(file));
}catch(IllegalArgumentException ignored){
}catch(Exception e){
Log.err("Failed to load plugin file {0}. Skipping.", file);
e.printStackTrace();
}
}
}
/** @return all loaded plugins. */
public Array<LoadedPlugin> all(){
return loaded;
}
/** Iterates through each plugin.*/
public void each(Consumer<Plugin> cons){
loaded.each(p -> cons.accept(p.plugin));
}
private LoadedPlugin loadPlugin(FileHandle jar) throws Exception{
FileHandle zip = new ZipFileHandle(jar);
FileHandle metaf = zip.child("plugin.json");
if(!metaf.exists()){
Log.warn("Plugin {0} doesn't have a 'plugin.json' file, skipping.", jar);
throw new IllegalArgumentException();
}
PluginMeta meta = JsonIO.read(PluginMeta.class, metaf.readString());
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(meta.main);
metas.put(main, meta);
return new LoadedPlugin(jar, zip, (Plugin)main.getDeclaredConstructor().newInstance(), meta);
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedPlugin{
public final FileHandle jarFile;
public final FileHandle zipRoot;
public final Plugin plugin;
public final PluginMeta meta;
public LoadedPlugin(FileHandle jarFile, FileHandle zipRoot, Plugin plugin, PluginMeta meta){
this.zipRoot = zipRoot;
this.jarFile = jarFile;
this.plugin = plugin;
this.meta = meta;
}
}
/** Plugin metadata information.*/
public static class PluginMeta{
public String name, author, main, description;
public String version;
}
}

View File

@@ -13,5 +13,7 @@ public enum ContentType{
effect, effect,
zone, zone,
loadout, loadout,
typeid typeid;
public static final ContentType[] all = values();
} }

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.function.*; import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
@@ -13,8 +14,9 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
public class UnitType extends UnlockableContent{ public class UnitType extends UnlockableContent{
public final TypeID typeID; public final @NonNull
public final Supplier<? extends BaseUnit> constructor; TypeID typeID;
public final @NonNull Supplier<? extends BaseUnit> constructor;
public float health = 60; public float health = 60;
public float hitsize = 7f; public float hitsize = 7f;
@@ -25,7 +27,7 @@ public class UnitType extends UnlockableContent{
public float baseRotateSpeed = 0.1f; public float baseRotateSpeed = 0.1f;
public float shootCone = 15f; public float shootCone = 15f;
public float mass = 1f; public float mass = 1f;
public boolean isFlying; public boolean flying;
public boolean targetAir = true; public boolean targetAir = true;
public boolean rotateWeapon = false; public boolean rotateWeapon = false;
public float drag = 0.1f; public float drag = 0.1f;
@@ -34,7 +36,7 @@ public class UnitType extends UnlockableContent{
public int itemCapacity = 30; public int itemCapacity = 30;
public ObjectSet<Item> toMine = ObjectSet.with(Items.lead, Items.copper); public ObjectSet<Item> toMine = ObjectSet.with(Items.lead, Items.copper);
public float buildPower = 0.3f, minePower = 0.7f; public float buildPower = 0.3f, minePower = 0.7f;
public Weapon weapon; public @NonNull Weapon weapon;
public float weaponOffsetY, engineOffset = 6f, engineSize = 2f; public float weaponOffsetY, engineOffset = 6f, engineSize = 2f;
public ObjectSet<StatusEffect> immunities = new ObjectSet<>(); public ObjectSet<StatusEffect> immunities = new ObjectSet<>();
public Sound deathSound = Sounds.bang; public Sound deathSound = Sounds.bang;

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.audio.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
@@ -25,7 +26,7 @@ public class Weapon{
protected static float minPlayerDist = 20f; protected static float minPlayerDist = 20f;
protected static int sequenceNum = 0; protected static int sequenceNum = 0;
/** bullet shot */ /** bullet shot */
public BulletType bullet; public @NonNull BulletType bullet;
/** shell ejection effect */ /** shell ejection effect */
public Effect ejectEffect = Fx.none; public Effect ejectEffect = Fx.none;
/** weapon reload in frames */ /** weapon reload in frames */

View File

@@ -1,10 +1,10 @@
package io.anuke.mindustry.ui.dialogs; package io.anuke.mindustry.ui.dialogs;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
@@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.*;
public class MapPlayDialog extends FloatingDialog{ public class MapPlayDialog extends FloatingDialog{
CustomRulesDialog dialog = new CustomRulesDialog(); CustomRulesDialog dialog = new CustomRulesDialog();
Rules rules; Rules rules;
@NonNull Gamemode selectedGamemode = Gamemode.survival; @NonNull
Gamemode selectedGamemode = Gamemode.survival;
Map lastMap; Map lastMap;
public MapPlayDialog(){ public MapPlayDialog(){

View File

@@ -0,0 +1,88 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.ui.*;
import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class ModsDialog extends FloatingDialog{
public ModsDialog(){
super("$mods");
addCloseButton();
shown(this::setup);
hidden(() -> {
if(mods.requiresRestart()){
ui.showOkText("$mods", "$mod.requiresrestart", () -> {
Core.app.exit();
});
}
});
shown(() -> Core.app.post(() -> {
Core.settings.getBoolOnce("modsalpha", () -> {
ui.showText("$mods", "$mods.alphainfo");
});
}));
}
void setup(){
cont.clear();
cont.defaults().width(520f).pad(4);
if(!mods.all().isEmpty()){
cont.pane(table -> {
table.margin(10f).top();
for(LoadedMod mod : mods.all()){
table.table(Styles.black6, t -> {
t.defaults().pad(2).left().top();
t.margin(14f).left();
t.table(title -> {
title.left();
title.add("[accent]" + mod.meta.name + "[lightgray] v" + mod.meta.version);
title.add().growX();
title.addImageButton(Icon.trash16Small, Styles.cleari, () -> ui.showConfirm("$confirm", "$mod.remove.confirm", () -> {
mods.removeMod(mod);
setup();
})).size(50f);
}).growX().left().padTop(-14f).padRight(-14f);
t.row();
if(mod.meta.author != null){
t.add(Core.bundle.format("mod.author", mod.meta.author));
t.row();
}
if(mod.meta.description != null){
t.labelWrap("[lightgray]" + mod.meta.description).growX();
t.row();
}
}).width(500f);
table.row();
}
});
}else{
cont.table(Styles.black6, t -> t.add("$mods.none")).height(80f);
}
cont.row();
cont.addImageTextButton("$mod.import", Icon.add, () -> {
platform.showFileChooser(true, "zip", file -> {
try{
mods.importMod(file);
setup();
}catch(IOException e){
ui.showException(e);
e.printStackTrace();
}
});
}).margin(12f).width(500f);
}
}

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.*;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*; import io.anuke.arc.math.geom.*;
import io.anuke.arc.scene.*; import io.anuke.arc.scene.*;

View File

@@ -163,6 +163,7 @@ public class MenuFragment extends Fragment{
), ),
new Buttoni("$editor", Icon.editorSmall, ui.maps::show), new Buttoni("$editor", Icon.editorSmall, ui.maps::show),
steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null, steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show),
new Buttoni("$settings", Icon.toolsSmall, ui.settings::show), new Buttoni("$settings", Icon.toolsSmall, ui.settings::show),
new Buttoni("$about.button", Icon.infoSmall, ui.about::show), new Buttoni("$about.button", Icon.infoSmall, ui.about::show),
new Buttoni("$quit", Icon.exitSmall, Core.app::exit) new Buttoni("$quit", Icon.exitSmall, Core.app::exit)

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.Graphics.Cursor.SystemCursor;
import io.anuke.arc.graphics.g2d.Draw; import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf; import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Effects; import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.effect.RubbleDecal; import io.anuke.mindustry.entities.effect.RubbleDecal;
@@ -172,7 +173,8 @@ public class BuildBlock extends Block{
* The recipe of the block that is being constructed. * The recipe of the block that is being constructed.
* If there is no recipe for this block, as is the case with rocks, 'previous' is used. * If there is no recipe for this block, as is the case with rocks, 'previous' is used.
*/ */
public @Nullable Block cblock; public @Nullable
Block cblock;
public float progress = 0; public float progress = 0;
public float buildCost; public float buildCost;

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.world.blocks.storage; package io.anuke.mindustry.world.blocks.storage;
import io.anuke.annotations.Annotations.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.type.TileEntity; import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Block;
@@ -75,6 +75,7 @@ public abstract class StorageBlock extends Block{
} }
public class StorageBlockEntity extends TileEntity{ public class StorageBlockEntity extends TileEntity{
protected @Nullable Tile linkedCore; protected @Nullable
Tile linkedCore;
} }
} }

View File

@@ -0,0 +1,109 @@
"lang"
{
"Language" "ukrainian"
"Tokens"
{
"NEW_ACHIEVEMENT_20_0_NAME" "Перевірено"
"NEW_ACHIEVEMENT_20_0_DESC" "Завершіть навчання."
"NEW_ACHIEVEMENT_20_1_NAME" "Забіяка"
"NEW_ACHIEVEMENT_20_1_DESC" "Знищьте 1000 ворожих одиниць."
"NEW_ACHIEVEMENT_20_2_NAME" "Чистка"
"NEW_ACHIEVEMENT_20_2_DESC" "Знишьте 100 000 ворожих одиниць."
"NEW_ACHIEVEMENT_20_3_NAME" "Атмосферне перевезення"
"NEW_ACHIEVEMENT_20_3_DESC" "Запустіть 10 000 предметів загалом."
"NEW_ACHIEVEMENT_20_5_NAME" "Нескінченні поставки"
"NEW_ACHIEVEMENT_20_5_DESC" "Запустіть 1000 000 предметів загалом."
"NEW_ACHIEVEMENT_20_6_NAME" "Завойовник"
"NEW_ACHIEVEMENT_20_6_DESC" "Виграйте 10 матчів у режимі атаки."
"NEW_ACHIEVEMENT_20_7_NAME" "Чемпіон"
"NEW_ACHIEVEMENT_20_7_DESC" "Виграйте 10 матчів у режимі PvP."
"NEW_ACHIEVEMENT_20_8_NAME" "Бліц"
"NEW_ACHIEVEMENT_20_8_DESC" "Знищіть вороже ядро в зоні атаки за 5 хвиль або менше."
"NEW_ACHIEVEMENT_20_9_NAME" "Ядерний дощ"
"NEW_ACHIEVEMENT_20_9_DESC" "Запустіть своє ядро в зону 30 разів."
"NEW_ACHIEVEMENT_20_10_NAME" "Наполегливий"
"NEW_ACHIEVEMENT_20_10_DESC" "Виживіть 100 хвиль."
"NEW_ACHIEVEMENT_20_11_NAME" "Неперевершений"
"NEW_ACHIEVEMENT_20_11_DESC" "Виживіть 500 хвиль."
"NEW_ACHIEVEMENT_20_12_NAME" "Дослідник"
"NEW_ACHIEVEMENT_20_12_DESC" "Дослідіть все."
"NEW_ACHIEVEMENT_20_13_NAME" "Перевертень"
"NEW_ACHIEVEMENT_20_13_DESC" "Розблокуйте та перетворіться на кожного меха у грі."
"NEW_ACHIEVEMENT_20_14_NAME" "Перевантаження"
"NEW_ACHIEVEMENT_20_14_DESC" "Нанесіть шкоду мокрому ворогу електрикою."
"NEW_ACHIEVEMENT_20_15_NAME" "Рикошет"
"NEW_ACHIEVEMENT_20_15_DESC" "Знищіть ворога його же власною відбитою кулею."
"NEW_ACHIEVEMENT_20_17_NAME" "ВЕЛИЧЕЗНА ПОМИЛКА"
"NEW_ACHIEVEMENT_20_17_DESC" "Дослідіть маршрутизатора."
"NEW_ACHIEVEMENT_20_18_NAME" "Створення"
"NEW_ACHIEVEMENT_20_18_DESC" "Побудуйте 10 000 блоків."
"NEW_ACHIEVEMENT_20_19_NAME" "Зрівняти з землею"
"NEW_ACHIEVEMENT_20_19_DESC" "Знищьте 1000 ворожих блоків."
"NEW_ACHIEVEMENT_20_20_NAME" "Ефектна катастрофа"
"NEW_ACHIEVEMENT_20_20_DESC" "Торієвий реактор повинен перегрітися й вибихнути."
"NEW_ACHIEVEMENT_20_21_NAME" "Картограф"
"NEW_ACHIEVEMENT_20_21_DESC" "Зробіть 10 мап."
"NEW_ACHIEVEMENT_20_22_NAME" "Веб-переглядач"
"NEW_ACHIEVEMENT_20_22_DESC" "Завантажте карту з Майстерні."
"NEW_ACHIEVEMENT_20_23_NAME" "Творець"
"NEW_ACHIEVEMENT_20_23_DESC" "Опублікуйте карту в Майстерні."
"NEW_ACHIEVEMENT_20_24_NAME" "Убивця"
"NEW_ACHIEVEMENT_20_24_DESC" "Переможіть боса."
"NEW_ACHIEVEMENT_20_25_NAME" "Дослідник"
"NEW_ACHIEVEMENT_20_25_DESC" "Розблокуйте всі зони в кампанії."
"NEW_ACHIEVEMENT_20_26_NAME" "Завершувач"
"NEW_ACHIEVEMENT_20_26_DESC" "Досягніть необхідної хвилі для розблокування конфігурації у всіх зонах."
"NEW_ACHIEVEMENT_20_29_NAME" "Матеріал II"
"NEW_ACHIEVEMENT_20_29_DESC" "Розблокуйте торій."
"NEW_ACHIEVEMENT_20_31_NAME" "Матеріал I"
"NEW_ACHIEVEMENT_20_31_DESC" "Розблокуйте титан."
"NEW_ACHIEVEMENT_21_0_NAME" "Камікадзе"
"NEW_ACHIEVEMENT_21_0_DESC" "Заповніть свого меха вибуховими матеріалами і помріть, створивши вибух."
"NEW_ACHIEVEMENT_21_1_NAME" "Це починається"
"NEW_ACHIEVEMENT_21_1_DESC" "Побудуйте завод мехів «Кинджал»."
"NEW_ACHIEVEMENT_21_2_NAME" "Прямий наступ"
"NEW_ACHIEVEMENT_21_2_DESC" "Видайте команду «Атакувати» за допомогою командного центру."
"NEW_ACHIEVEMENT_21_3_NAME" "Рій"
"NEW_ACHIEVEMENT_21_3_DESC" "100 бойових одиниць повинні бути активними одночасно."
"NEW_ACHIEVEMENT_21_4_NAME" "Зграя"
"NEW_ACHIEVEMENT_21_4_DESC" "10 фантомних дронів повинні бути активними одночасно."
"NEW_ACHIEVEMENT_21_5_NAME" "Летуча армія"
"NEW_ACHIEVEMENT_21_5_DESC" "50 Камікадзе повинні бути активними одночасно."
"NEW_ACHIEVEMENT_21_6_NAME" "Легіони"
"NEW_ACHIEVEMENT_21_6_DESC" "Побудуйте 1000 одиниць загалом."
"NEW_ACHIEVEMENT_21_7_NAME" "Супер"
"NEW_ACHIEVEMENT_21_7_DESC" "Досягніть рангу S на будь-якій зоні."
"NEW_ACHIEVEMENT_21_8_NAME" "А ти молодець"
"NEW_ACHIEVEMENT_21_8_DESC" "Досягніть рангу SS на будь-якій зоні."
"NEW_ACHIEVEMENT_21_9_NAME" "Ти не послухався"
"NEW_ACHIEVEMENT_21_9_DESC" "Помріть у зоні анігіляції."
"NEW_ACHIEVEMENT_21_10_NAME" "Просто натисни Shift"
"NEW_ACHIEVEMENT_21_10_DESC" "Потоніть, якось."
"NEW_ACHIEVEMENT_21_11_NAME" "Збирач"
"NEW_ACHIEVEMENT_21_11_DESC" "Наповніть ядро повністю кожним типом матеріалу."
"NEW_ACHIEVEMENT_21_12_NAME" "Натовп"
"NEW_ACHIEVEMENT_21_12_DESC" "Створіть сервер, на якому буде 10 гравців одночасно."
"NEW_ACHIEVEMENT_21_13_NAME" "Невразливий"
"NEW_ACHIEVEMENT_21_13_DESC" "Побудуйте Катастрофу і Випалювач."
"NEW_ACHIEVEMENT_21_14_NAME" "Зліт"
"NEW_ACHIEVEMENT_21_14_DESC" "Запустіть."
"NEW_ACHIEVEMENT_21_15_NAME" "Компенсація"
"NEW_ACHIEVEMENT_21_15_DESC" "Двічі пропустіть запуск і допустіть знищення ядра ворогом."
"NEW_ACHIEVEMENT_21_16_NAME" "Єресь"
"NEW_ACHIEVEMENT_21_16_DESC" "Побудуйте два маршрутизатори поруч."
"NEW_ACHIEVEMENT_21_17_NAME" "Одинокий захисник"
"NEW_ACHIEVEMENT_21_17_DESC" "Виживіть 10 хвиль в першій-ліпшій зоні без будування будування будь-яких блоків."
"NEW_ACHIEVEMENT_21_18_NAME" "Спалити"
"NEW_ACHIEVEMENT_21_18_DESC" "Використайте піротит в будь-якій башті."
"NEW_ACHIEVEMENT_21_19_NAME" "Ефективність"
"NEW_ACHIEVEMENT_21_19_DESC" "Охолодіть башту водою чи кріогенною рідиною."
"NEW_ACHIEVEMENT_21_20_NAME" "Класичний режим"
"NEW_ACHIEVEMENT_21_20_DESC" "Увімкніть пікселізацію."
"NEW_ACHIEVEMENT_21_21_NAME" "Науковець"
"NEW_ACHIEVEMENT_21_21_DESC" "Відкрийти Wiki з гри."
"NEW_ACHIEVEMENT_21_22_NAME" "Впевнений початок"
"NEW_ACHIEVEMENT_21_22_DESC" "Вивантажіть в зону 10 000 або більше предметів."
"NEW_ACHIEVEMENT_21_23_NAME" "Запалювання"
"NEW_ACHIEVEMENT_21_23_DESC" "Підвищте потужність імпульсного реактора."
}
}

View File

@@ -0,0 +1,60 @@
Створюйте складні логістичні мережі для перенесення боєприпасів у башти, видобувайте ресурси для будівництва, і захищайте своє ядро від різних хвиль ворогів. Грайте з друзями на різних платформах у кооперативні ігри, або киньте їм виклик у командних PvP-матчах.
[img]{STEAM_APP_IMAGE}/extras/ezgif-4-0e70c282f775.gif[/img]
[h2]Ігровий процес[/h2]
[list]
[*] Створіть бури і конвеєри для переміщення ресурсів в ядро.
[*] Використовуйте виробничі блоки для створення передових матеріалів.
[*] Створюйте дронів для автоматичного видобутку ресурсів, допомоги в будівництві.
[*] Доставляйте рідини й боріться з осередками вогню.
[*] Збільшуйте виробництво, постачаючи необов’язкові теплоносії та мастила до ваших оборонно-виробничих блоків.
[/list]
[h2]Кампанія[/h2]
[list]
[*] Просуньтесь через 12 вбудованих зон з випадковим розташуванням точок появи ворогів.
[*] Збирайте і запускайте ресурси.
[*] Дослідження нових блоків для сприяння прогресу.
[*] Налаштовуйте початкові ресурси для кожної зони
[*] Різноманітність цілей та завдань місій.
[*] Запросіть своїх друзів для спільного виконання місій.
[*] Понад 120 технологічних блоків для освоєння.
[*] 19 різних типів дронів, мехів і кораблів.
[*] Понад 50 досягнень для завершення.
[/list]
[h2][h2]Ігрові режими[/h2][/h2]
[*] [b]Виживання[/b]: створіть башти для захисту від ворогів в ігровому процесі, заснованому на захисті башт. Виживайте якомога довше, за бажанням можна запустит своє ядро, щоб використовувати зібрані ресурси для досліджень. Підготуйте свою базу для періодичних атак повітряних босів.
[*] [b]Атака[/b]: будуйте заводи бойових одиниць для знищення ворожих ядер, одночасно захищаючи свою базу від хвиль ворожих одиниць.
[*] [b]PvP[/b]: змагайтеся з іншими гравцями, щоб знищити ядра один одного. Створюйте бойові одиниці або нападайте на інші бази безпосередньо зі своїми мехами. Створіть різноманітні типи підтримувальиних та наступальних підрозділів, щоб допоможуть вам у досягненні цілей.
[*] [b]Пісочниця[/b]: грайте з нескінченними ресурсами без загрози з боку ворога. Використовуйте специфічні для пісочниці елементи та блоки рідких джерел для тестування конструкцій та появи ворогів за запитом.
[/list]
[h2]Користувацькі ігри & багатоплатформовий мультиплеєр[/h2]
[list]
[*] 12 вбудованих мап на додаток кампанії.
[*] Грайте в кооперативі, PvP чи пісочниці.
[*] Приєднуйтесь до загальнодоступного сервера, або запросіть друзів на свій приватний сеанс.
[*] Користувацькі налаштування гри — ціна блоків, сила ворогів, кількість початкових ресурсів, інтервал між хвилями, тощо.
[*] Змішуйте і поєднуйте ігрові режими — комбінуйте PvP і PvE режими разом.
[/list]
[h2]Користувацький редактор мап[/h2]
[list]
[*] Створюйте ландшафт за допомогою візуального редактора.
[*] Змінюйте і переглядайте споруди так само як в грі.
[*] Регульовані режими інструментів — змініть функціонування кожного інструменту.
[*] Потужна система генерації мап, що має безліч різних типів фільтрів для процедурного маніпулювання місцевістю.
[*] Застосуйте на своїх мапах шум, спотворення, згладжування, ерозію, симетрію, генерацію руди та випадкову генерацію місцевості.
[*] Відкрита гра в жанрі «захист башт» з акцентом на керуванні ресурсами.
[*] Розкладки хвиль для мап можна налаштувати.
[*] Поширюйте експортовані мапи в Майстерні Steam.
[*] Правила для мап можна налаштувати.
[*] Використовуйте понад 75 видів блоків навколишнього середовища.
[/list]

View File

@@ -0,0 +1 @@
Нескінченна гра у жанрі «захист вежі» з акцентом на керуванні ресурсами.

View File

@@ -1,3 +1,3 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m org.gradle.jvmargs=-Xms256m -Xmx1024m
archash=d4519a9721927165850bfe3135ef6b095c990474 archash=678237e0fd2a2e0c26de1ea562960d1effe5ab97

View File

@@ -1,9 +1,9 @@
package io.anuke.mindustry.server; package io.anuke.mindustry.server;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.mod.*;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
@@ -28,5 +28,9 @@ public class MindustryServer implements ApplicationListener{
Core.app.addListener(logic = new Logic()); Core.app.addListener(logic = new Logic());
Core.app.addListener(netServer = new NetServer()); Core.app.addListener(netServer = new NetServer());
Core.app.addListener(new ServerControl(args)); Core.app.addListener(new ServerControl(args));
mods.each(Mod::init);
} }
} }

View File

@@ -18,10 +18,9 @@ import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.net.Administration.*; import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.plugin.Plugins.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import java.io.*; import java.io.*;
@@ -51,8 +50,6 @@ public class ServerControl implements ApplicationListener{
private PrintWriter socketOutput; private PrintWriter socketOutput;
public ServerControl(String[] args){ public ServerControl(String[] args){
plugins = new Plugins();
Core.settings.defaults( Core.settings.defaults(
"shufflemode", "normal", "shufflemode", "normal",
"bans", "", "bans", "",
@@ -110,9 +107,6 @@ public class ServerControl implements ApplicationListener{
Effects.setScreenShakeProvider((a, b) -> {}); Effects.setScreenShakeProvider((a, b) -> {});
Effects.setEffectProvider((a, b, c, d, e, f) -> {}); Effects.setEffectProvider((a, b, c, d, e, f) -> {});
//load plugins
plugins.load();
registerCommands(); registerCommands();
Core.app.post(() -> { Core.app.post(() -> {
@@ -134,7 +128,6 @@ public class ServerControl implements ApplicationListener{
}); });
customMapDirectory.mkdirs(); customMapDirectory.mkdirs();
pluginDirectory.mkdirs();
Thread thread = new Thread(this::readCommands, "Server Controls"); Thread thread = new Thread(this::readCommands, "Server Controls");
thread.setDaemon(true); thread.setDaemon(true);
@@ -179,11 +172,8 @@ public class ServerControl implements ApplicationListener{
} }
}); });
//initialize plugins if(!mods.all().isEmpty()){
plugins.each(io.anuke.mindustry.plugin.Plugin::init); info("&lc{0} mods loaded.", mods.all().size);
if(!plugins.all().isEmpty()){
info("&lc{0} plugins loaded.", plugins.all().size);
} }
info("&lcServer loaded. Type &ly'help'&lc for help."); info("&lcServer loaded. Type &ly'help'&lc for help.");
@@ -326,28 +316,28 @@ public class ServerControl implements ApplicationListener{
} }
}); });
handler.register("plugins", "Display all loaded plugins.", arg -> { handler.register("mods", "Display all loaded mods.", arg -> {
if(!plugins.all().isEmpty()){ if(!mods.all().isEmpty()){
info("Plugins:"); info("Mods:");
for(LoadedPlugin plugin : plugins.all()){ for(LoadedMod mod : mods.all()){
info(" &ly{0} &lcv{1}", plugin.meta.name, plugin.meta.version); info(" &ly{0} &lcv{1}", mod.meta.name, mod.meta.version);
} }
}else{ }else{
info("No plugins found."); info("No mods found.");
} }
info("&lyPlugin directory: &lb&fi{0}", pluginDirectory.file().getAbsoluteFile().toString()); info("&lyMod directory: &lb&fi{0}", modDirectory.file().getAbsoluteFile().toString());
}); });
handler.register("plugin", "<name...>", "Display information about a loaded plugin.", arg -> { handler.register("mod", "<name...>", "Display information about a loaded plugin.", arg -> {
LoadedPlugin plugin = plugins.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0])); LoadedMod mod = mods.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0]));
if(plugin != null){ if(mod != null){
info("Name: &ly{0}", plugin.meta.name); info("Name: &ly{0}", mod.meta.name);
info("Version: &ly{0}", plugin.meta.version); info("Version: &ly{0}", mod.meta.version);
info("Author: &ly{0}", plugin.meta.author); info("Author: &ly{0}", mod.meta.author);
info("Path: &ly{0}", plugin.jarFile.path()); info("Path: &ly{0}", mod.file.path());
info("Description: &ly{0}", plugin.meta.description); info("Description: &ly{0}", mod.meta.description);
}else{ }else{
info("No plugin with name &ly'{0}'&lg found."); info("No mod with name &ly'{0}'&lg found.");
} }
}); });
@@ -759,8 +749,9 @@ public class ServerControl implements ApplicationListener{
info("&ly{0}&lg MB collected. Memory usage now at &ly{1}&lg MB.", pre - post, post); info("&ly{0}&lg MB collected. Memory usage now at &ly{1}&lg MB.", pre - post, post);
}); });
plugins.each(p -> p.registerServerCommands(handler)); mods.each(p -> p.registerServerCommands(handler));
plugins.each(p -> p.registerClientCommands(netServer.clientCommands)); //TODO
//plugins.each(p -> p.registerClientCommands(netServer.clientCommands));
} }
private void readCommands(){ private void readCommands(){

View File

@@ -218,7 +218,7 @@ public class Generators{
}); });
ImagePacker.generate("unit-icons", () -> { ImagePacker.generate("unit-icons", () -> {
content.<UnitType>getBy(ContentType.unit).each(type -> !type.isFlying, type -> { content.<UnitType>getBy(ContentType.unit).each(type -> !type.flying, type -> {
type.load(); type.load();
type.weapon.load(); type.weapon.load();