diff --git a/annotations/src/main/java/io/anuke/annotations/Annotations.java b/annotations/src/main/java/io/anuke/annotations/Annotations.java index e7afef4f9a..6ee59964e9 100644 --- a/annotations/src/main/java/io/anuke/annotations/Annotations.java +++ b/annotations/src/main/java/io/anuke/annotations/Annotations.java @@ -22,20 +22,6 @@ public class Annotations{ 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. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 6d5c065375..37a25e9c41 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -22,6 +22,7 @@ load.map = Maps load.image = Images load.content = Content load.system = System +load.mod = Mods stat.wave = Waves Defeated:[accent] {0} stat.enemiesDestroyed = Enemies Destroyed:[accent] {0} @@ -64,6 +65,17 @@ uploadingpreviewfile = Uploading Preview File committingchanges = Comitting Changes 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 name = Name: noname = Pick a[accent] player name[] first. diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 6746216066..4e709d929f 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -48,7 +48,7 @@ minimap = Мінімапа close = Закрити website = Веб-сайт quit = Вихід -save.quit = Save & Quit +save.quit = Зберегти & Вийти maps = Мапи maps.browse = Перегляд мап continue = Продовжити @@ -98,7 +98,7 @@ host = Сервер hosting = [accent]Відкриття сервера… hosts.refresh = Оновити hosts.discovering = Пошук локальних ігор -hosts.discovering.any = Discovering games +hosts.discovering.any = Пошук ігор server.refreshing = Оновлення сервера hosts.none = [lightgray]Локальних ігр не знайдено host.invalid = [scarlet]Не вдалося підключитися до сервера. @@ -122,16 +122,16 @@ server.version = [lightgray]Версія: {0} server.custombuild = [yellow]Користувацька збірка confirmban = Ви дійсно хочете заблокувати цього гравця? confirmkick = Ви дійсно хочете викинути цього гравця? -confirmvotekick = Are you sure you want to vote-kick this player? +confirmvotekick = Ви дійсно хочете вигнати цього гравця за допомогою голосуванняr? confirmunban = Ви дійсно хочете розблокувати цього гравця? confirmadmin = Ви дійсно хочете зробити цього гравця адміністратором? confirmunadmin = Ви дійсно хочете видалити статус адміністратора з цього гравця? joingame.title = Приєднатися до гри joingame.ip = IP: disconnect = Відключено. -disconnect.error = Connection error. -disconnect.closed = Connection closed. -disconnect.timeout = Timed out. +disconnect.error = Помилка з’єднання. +disconnect.closed = З'єднання закрито. +disconnect.timeout = Час вийшов. disconnect.data = Не вдалося завантажити дані світу! cantconnect = Не вдалося під’єднатися до гри ([accent]{0}[]). connecting = [accent]Підключення… @@ -159,7 +159,7 @@ save.rename = Перейменувати save.rename.text = Нова назва: selectslot = Виберіть збереження. slot = [accent]Слот {0} -editmessage = Edit Message +editmessage = Редагувати повідомлення save.corrupted = [accent]Збережений файл пошкоджено або недійсний! \nЯкщо ви щойно оновили свою гру, це, мабуть, є зміною формату збереження та [scarlet] не є[] помилкою. empty = <Порожньо> on = Увімкнено @@ -167,13 +167,14 @@ off = Вимкнено save.autosave = Автозбереження: {0} save.map = Мапа: {0} save.wave = Хвиля {0} -save.mode = Gamemode: {0} +save.mode = Режим гри: {0} save.date = Останнє збереження save.playtime = Час гри: {0} warning = Попередження confirm = Підтвердження delete = Видалити view.workshop = Переглянути в Майстерні +workshop.listing = Редагувати список Майстерні ok = ОК open = Відкрити customize = Налаштувати правила @@ -211,10 +212,15 @@ map.nospawn.pvp = У цієї мапи немає ворожих ядер, в я map.nospawn.attack = У цієї мапи немає ворожих ядер, в яких гравець може з’явитися! Додайте [SCARLET]червоне[] ядро до цієї мапи в редакторі. map.invalid = Помилка завантаження мапи: пошкоджений або невірний файл мапи. 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, або ваші мапи не з’являться! eula = Ліцензійна угода -map.publish = Map published. -map.publishing = [accent]Publishing map... +map.publish = Мапа опублікована. +map.publishing = [accent]Публікації мапи... editor.brush = Пензлик editor.openin = Відкрити в редакторі editor.oregen = Генерація руд @@ -222,7 +228,7 @@ editor.oregen.info = Генерація руд: editor.mapinfo = Інформація про мапу editor.author = Автор: editor.description = Опис: -editor.nodescription = A map must have a description of at least 4 characters before being published. +editor.nodescription = Мапа повинна мати щонаймеше 4 символи для публікації. editor.waves = Хвилі: editor.rules = Правила: editor.generation = Генерація: @@ -246,7 +252,7 @@ waves.invalid = Недійсні хвилі у буфері обміну. waves.copied = Хвилі скопійовані. waves.none = Вороги не були встановлені.\nЗазначимо, що пусті хвилі будуть автоматично замінені звичайною хвилею. editor.default = [lightgray]<За замовчуванням> -details = Details... +details = Деталі... edit = Редагувати… editor.name = Назва: editor.spawn = Створити бойову одиницю @@ -256,7 +262,7 @@ editor.errorload = Помилка завантаження зображення: editor.errorsave = Помилка збереження зображення:\n[accent]{0} editor.errorimage = Це зображення, а не мапа. Не змінюйте розширення, очікуючи, що це запрацює.\n\nЯкщо Ви хочете імпортувати застарілку мапу, то використовуйте кнопку «Імпортувати застаріле зображення» у редакторі. editor.errorlegacy = Ця мапа занадто стара і використовує попередній формат мапи, який більше не підтримується. -editor.errornot = This is not a map file. +editor.errornot = Це не мапа. editor.errorheader = Цей файл мапи недійсний або пошкоджений. editor.errorname = Мапа не має імені. Може Ви намагаєтеся завантажити збереження? editor.update = Оновити @@ -289,7 +295,7 @@ editor.resizemap = Змінити розмір мапи editor.mapname = Назва мапи: editor.overwrite = [accent]Попередження!\nЦе перезаписує існуючу мапу. editor.overwrite.confirm = [scarlet]Попередження![] Мапа з такою назвою вже існує. Ви впевнені, що хочете переписати її? -editor.exists = A map with this name already exists. +editor.exists = Мапа за такою назвою вже існує. editor.selectmap = Виберіть мапу для завантаження: toolmode.replace = Замінити toolmode.replace.description = Малює тільки\nна суцільних блоках. @@ -369,7 +375,7 @@ launch.skip.confirm = Якщо Ви пропустите зараз, Ви не uncover = Розкрити configure = Вивантажити конфігурацію configure.locked = [lightgray]Можливість розблокувати вивантаження ресурсів буде доступна на {0}-тій хвилі. -configure.invalid = Amount must be a number between 0 and {0}. +configure.invalid = Кількість повинна бути числом між 0 та {0}. zone.unlocked = Зона «[lightgray]{0}» тепер розблокована. zone.requirement.complete = Ви досягли {0}-тої хвилі,\nВимоги до зони «{1}» виконані. zone.config.complete = Ви досягли {0}-тої хвилі.\nМожливість вивантаження ресурсів тепер розблокована. @@ -466,7 +472,7 @@ blocks.boosteffect = Прискорювальний ефект blocks.maxunits = Максимальна кількість активних одиниць blocks.health = Здоров’я blocks.buildtime = Час будівництва -blocks.buildcost = Build Cost +blocks.buildcost = Вартість будування blocks.inaccuracy = Розкид blocks.shots = Постріли blocks.reload = Постріли/секунду @@ -475,7 +481,7 @@ bar.drilltierreq = Потребується кращий бур bar.drillspeed = Швидкість буріння: {0}/с bar.efficiency = Ефективність: {0}% bar.powerbalance = Енергія: {0}/с -bar.powerstored = Stored: {0}/{1} +bar.powerstored = Зберігає: {0}/{1} bar.poweramount = Енергія: {0} bar.poweroutput = Вихідна енергія: {0} bar.items = Предмети: {0} @@ -524,7 +530,7 @@ setting.antialias.name = Згладжування[lightgray] (потребує setting.indicators.name = Показувати у сторону ворогів та союзників setting.autotarget.name = Авто-стрільба setting.keyboard.name = Миш+Керування з клавіатури -setting.touchscreen.name = Touchscreen Controls +setting.touchscreen.name = Керування сенсорним екраном setting.fpscap.name = Максимальний FPS setting.fpscap.none = Необмежений setting.fpscap.text = {0} FPS @@ -534,7 +540,7 @@ setting.difficulty.training = Навчання setting.difficulty.easy = Легка setting.difficulty.normal = Нормальна setting.difficulty.hard = Важка -setting.difficulty.insane = Зачистка +setting.difficulty.insane = Неможлива setting.difficulty.name = Складність: setting.screenshake.name = Тряска екрану setting.effects.name = Ефекти @@ -555,9 +561,10 @@ setting.sfxvol.name = Гучність звукових ефектів setting.mutesound.name = Заглушити звук setting.crashreport.name = Відсилати анонімні звіти про аварійне завершення гри setting.savecreate.name = Автоматичне створення збережень -setting.publichost.name = Public Game Visibility +setting.publichost.name = Загальнодоступність гри setting.chatopacity.name = Непрозорість чату setting.playerchat.name = Відображати хмару чата над гравцями +public.confirm = Ви хочете зробити цю гру загальнодоступною?\n[lightgray]Це можна змінити у Налаштування->Гра->Public Game Visibility. uiscale.reset = Масштаб користувальницького інтерфейсу було змінено.\nНатисніть «ОК» для підтверждення цього масшатабу.\n[scarlet]Повернення налаштувань і вихід через[accent] {0}[] … uiscale.cancel = Скасувати & Вийти setting.bloom.name = Світіння @@ -567,7 +574,7 @@ category.general.name = Основне category.view.name = Перегляд category.multiplayer.name = Мережева гра command.attack = Атакувати -command.rally = Rally +command.rally = Точка збору command.retreat = Відступити keybind.gridMode.name = Вибрати блок keybind.gridModeShift.name = Вибрати категорію @@ -604,6 +611,7 @@ mode.survival.name = Хвилі mode.survival.description = Звичайний режим. В цьому режимі треба самим добувати ресурси та хвилі йдуть автоматично.\n[gray]Потребуються точки появи ворогів для гри. mode.sandbox.name = Пісочниця mode.sandbox.description = В режимі «Пісочниця» незкінченні ресурси(але їх все одно можно добувати) та хвилі йдуть за вашим бажанням. +mode.editor.name = Редактор mode.pvp.name = PVP mode.pvp.description = боріться проти інших гравців.\n[gray]Для гри потрібно принаймні 2 ядра різного кольору на мапі. mode.attack.name = Атака @@ -784,13 +792,13 @@ block.hail.name = Град block.lancer.name = Списоносець block.conveyor.name = Конвеєр block.titanium-conveyor.name = Титановий конвеєр -block.armored-conveyor.name = Armored Conveyor -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.name = Броньований конвеєр +block.armored-conveyor.description = Переміщує предмети з тією ж швидкістю, як і титанові конвеєри, але має більше міцності. Не приймає введення з боків ні з чого, крім інших конвеєрів. block.junction.name = Перехрестя block.router.name = Маршрутизатор block.distributor.name = Розподілювач block.sorter.name = Сортувальник -block.message.name = Message +block.message.name = Повідомлення block.overflow-gate.name = Надмірний затвор block.silicon-smelter.name = Кремнієвий плавильний завод block.phase-weaver.name = Фазовий ткач @@ -879,8 +887,8 @@ block.overdrive-projector.name = Сверхприводний проектор block.force-projector.name = Силовий проектор block.arc.name = Дуга block.rtg-generator.name = Радіоізотопний термоелектричний генератор -block.spectre.name = Мара -block.meltdown.name = Катастрофа +block.spectre.name = Спектр +block.meltdown.name = Випалювач block.container.name = Склад block.launch-pad.name = Стартовий майданчик block.launch-pad-large.name = Великий стартовий майданчик @@ -892,7 +900,7 @@ team.derelict.name = Залишена team.green.name = Зелена team.purple.name = Фіолетова unit.spirit.name = Ремонтувальний дрон «Привид» -unit.draug.name = Draug Miner Drone +unit.draug.name = Добувний дрон «Драугр» unit.phantom.name = Будівельний дрон «Фантом» unit.dagger.name = Кинджал unit.crawler.name = Камікадзе @@ -907,7 +915,7 @@ unit.eradicator.name = Викорінювач unit.lich.name = Лич unit.reaper.name = Жнець 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.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]Перегляньте характеристику Механічного бура прямо зараз.[] @@ -965,7 +973,7 @@ unit.eruptor.description = Важкий мех, призначеней для з unit.wraith.description = Швидкий перехоплювач, який використовується для тактики «атакуй і біжи». Пріоритет — енергетичні генератори. unit.ghoul.description = Важкий килимовий бомбардувальник. Пробиває ворожі структури, орієнтуючись на віжливу інфраструктуру. unit.revenant.description = Важкий ракетний масив. -block.message.description = Stores a message. Used for communication between allies. +block.message.description = Зберігає повідомлення. Використовується для комунікаціх між союзниками. block.graphite-press.description = Стискає шматки вугілля в чисті аркуші графіту. block.multi-press.description = Модернізована версія графітового преса. Використовує воду та енергію для швидкої та ефективної переробки вугілля. block.silicon-smelter.description = Змішує пісок з чистим вугіллям. Виробляє кремній. @@ -1050,7 +1058,7 @@ block.core-foundation.description = Друга версія ядра. Краще block.core-nucleus.description = Третя і остання ітерація капсули ядра. Надзвичайно добре броньований. Зберігає величезні обсяги ресурсів. block.vault.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища. block.container.description = Зберігає велику кількість предметів кожного типу. Блок розвантажувача може використовуватися для отримання предметів із сховища. -block.unloader.description = Вивантажує предмети з контейнера, склепіння або серцевини на конвеєр або безпосередньо в сусідній блок. Тип предмета для завантаження можна змінити, натиснувши на блок. +block.unloader.description = Вивантажує предмети з блока, який не переміщує предмети, на конвеєр або безпосередньо в сусідній блок. Тип предмета для завантаження можна змінити, натиснувши на блок. block.launch-pad.description = Запускає партії предметів без необхідності запуску ядра. block.launch-pad-large.description = Покращена версія стартового майданчика. Зберігає більше предметів. Запускається частіше. block.duo.description = Невелика дешева башта. Корисна проти наземних одиниць. diff --git a/core/src/io/anuke/mindustry/ClientLauncher.java b/core/src/io/anuke/mindustry/ClientLauncher.java index fe15d49d88..2012d59c26 100644 --- a/core/src/io/anuke/mindustry/ClientLauncher.java +++ b/core/src/io/anuke/mindustry/ClientLauncher.java @@ -13,6 +13,7 @@ import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.*; import io.anuke.mindustry.net.Net; 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); atlas = TextureAtlas.blankAtlas(); Vars.net = new Net(platform.getNet()); + Vars.mods = new Mods(); UI.loadSystemCursors(); @@ -71,6 +73,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform add(netServer = new NetServer()); add(netClient = new NetClient()); + assets.load(mods); + assets.loadRun("contentinit", ContentLoader.class, () -> { content.init(); content.load(); @@ -108,6 +112,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform listener.init(); } super.resize(graphics.getWidth(), graphics.getHeight()); + mods.each(Mod::init); finished = true; Events.fire(new ClientLoadEvent()); } @@ -182,7 +187,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform if(assets.getCurrentLoading() != null){ 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); } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 24807883d1..9315d55bf2 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -18,14 +18,14 @@ import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.input.*; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.*; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.plugin.*; import io.anuke.mindustry.world.blocks.defense.ForceProjector.*; import java.nio.charset.*; import java.util.*; -import static io.anuke.arc.Core.settings; +import static io.anuke.arc.Core.*; @SuppressWarnings("unchecked") public class Vars implements Loadable{ @@ -120,8 +120,8 @@ public class Vars implements Loadable{ public static FileHandle tmpDirectory; /** data subdirectory used for saves */ public static FileHandle saveDirectory; - /** data subdirectory used for plugins */ - public static FileHandle pluginDirectory; + /** data subdirectory used for mods */ + public static FileHandle modDirectory; /** map file extension */ public static final String mapExtension = "msav"; /** save file extension */ @@ -130,6 +130,7 @@ public class Vars implements Loadable{ /** list of all locales that can be switched to */ public static Locale[] locales; + public static FileTree filet; public static Net net; public static ContentLoader content; public static GameState state; @@ -138,7 +139,7 @@ public class Vars implements Loadable{ public static DefaultWaves defaultWaves; public static LoopControl loops; public static Platform platform = new Platform(){}; - public static Plugins plugins; + public static Mods mods; public static World world; public static Maps maps; @@ -193,6 +194,10 @@ public class Vars implements Loadable{ Version.init(); + filet = new FileTree(); + if(mods == null){ + mods = new Mods(); + } content = new ContentLoader(); loops = new LoopControl(); defaultWaves = new DefaultWaves(); @@ -240,8 +245,11 @@ public class Vars implements Loadable{ mapPreviewDirectory = dataDirectory.child("previews/"); saveDirectory = dataDirectory.child("saves/"); tmpDirectory = dataDirectory.child("tmp/"); - pluginDirectory = dataDirectory.child("plugins/"); + modDirectory = dataDirectory.child("mods/"); + modDirectory.mkdirs(); + + mods.load(); maps.load(); } diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index 104669fd58..03ca073a08 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -6,6 +6,7 @@ import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.async.*; import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.*; @@ -32,7 +33,8 @@ public class Pathfinder implements Runnable{ /** handles task scheduling on the update thread. */ private TaskQueue queue = new TaskQueue(); /** current pathfinding thread */ - private @Nullable Thread thread; + private @Nullable + Thread thread; public Pathfinder(){ Events.on(WorldLoadEvent.class, event -> { @@ -92,7 +94,11 @@ public class Pathfinder implements Runnable{ 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 for(PathData[] arr : pathMap){ diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index daf4e7b00a..b4dadcd59a 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -48,7 +48,7 @@ public class WaveSpawner{ for(SpawnGroup group : state.rules.spawns){ int spawned = group.getUnitsSpawned(state.wave - 1); - if(group.type.isFlying){ + if(group.type.flying){ float spread = margin / 1.5f; eachFlyerSpawn((spawnX, spawnY) -> { diff --git a/core/src/io/anuke/mindustry/content/UnitTypes.java b/core/src/io/anuke/mindustry/content/UnitTypes.java index 6c4e54398f..8d005ca4c3 100644 --- a/core/src/io/anuke/mindustry/content/UnitTypes.java +++ b/core/src/io/anuke/mindustry/content/UnitTypes.java @@ -18,7 +18,7 @@ public class UnitTypes implements ContentList{ @Override public void load(){ draug = new UnitType("draug", Draug.class, Draug::new){{ - isFlying = true; + flying = true; drag = 0.01f; speed = 0.3f; maxVelocity = 1.2f; @@ -33,7 +33,7 @@ public class UnitTypes implements ContentList{ }}; spirit = new UnitType("spirit", Spirit.class, Spirit::new){{ - isFlying = true; + flying = true; drag = 0.01f; speed = 0.4f; maxVelocity = 1.6f; @@ -54,7 +54,7 @@ public class UnitTypes implements ContentList{ }}; phantom = new UnitType("phantom", Phantom.class, Phantom::new){{ - isFlying = true; + flying = true; drag = 0.01f; mass = 2f; speed = 0.45f; @@ -244,7 +244,7 @@ public class UnitTypes implements ContentList{ maxVelocity = 1.9f; drag = 0.01f; mass = 1.5f; - isFlying = true; + flying = true; health = 75; engineOffset = 5.5f; range = 140f; @@ -264,7 +264,7 @@ public class UnitTypes implements ContentList{ maxVelocity = 1.4f; mass = 3f; drag = 0.01f; - isFlying = true; + flying = true; targetAir = false; engineOffset = 7.8f; range = 140f; @@ -291,7 +291,7 @@ public class UnitTypes implements ContentList{ drag = 0.01f; range = 80f; shootCone = 40f; - isFlying = true; + flying = true; rotateWeapon = true; engineOffset = 12f; engineSize = 3f; @@ -322,7 +322,7 @@ public class UnitTypes implements ContentList{ drag = 0.02f; range = 80f; shootCone = 20f; - isFlying = true; + flying = true; rotateWeapon = true; engineOffset = 21; engineSize = 5.3f; @@ -355,7 +355,7 @@ public class UnitTypes implements ContentList{ drag = 0.02f; range = 80f; shootCone = 30f; - isFlying = true; + flying = true; rotateWeapon = true; engineOffset = 40; engineSize = 7.3f; diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index a919233c2e..cda4c8bda3 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -11,6 +11,7 @@ import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; import static io.anuke.arc.Core.files; +import static io.anuke.mindustry.Vars.mods; /** * Loads all game content. @@ -57,6 +58,23 @@ public class ContentLoader{ 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(Content c : contentMap[type.ordinal()]){ @@ -79,8 +97,6 @@ public class ContentLoader{ } } } - - loaded = true; } /** Logs content statistics.*/ diff --git a/core/src/io/anuke/mindustry/core/FileTree.java b/core/src/io/anuke/mindustry/core/FileTree.java new file mode 100644 index 0000000000..b55b063635 --- /dev/null +++ b/core/src/io/anuke/mindustry/core/FileTree.java @@ -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 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); + } + } +} diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 1f4a0c90ff..8300309828 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -200,6 +200,11 @@ public class NetServer implements ApplicationListener{ registerCommands(); } + @Override + public void init(){ + mods.each(mod -> mod.registerClientCommands(clientCommands)); + } + private void registerCommands(){ clientCommands.register("help", "[page]", "Lists all commands.", (args, player) -> { if(args.length > 0 && !Strings.canParseInt(args[0])){ diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index ca5dafb8ed..0d2591398f 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{ public DeployDialog deploy; public TechTreeDialog tech; public MinimapDialog minimap; + public ModsDialog mods; public Cursor drillCursor, unloadCursor; @@ -222,6 +223,7 @@ public class UI implements ApplicationListener, Loadable{ deploy = new DeployDialog(); tech = new TechTreeDialog(); minimap = new MinimapDialog(); + mods = new ModsDialog(); Group group = Core.scene.root; @@ -410,6 +412,18 @@ public class UI implements ApplicationListener, Loadable{ 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){ if(number >= 1000000){ return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]"; diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 26e0441e8a..4e2806e62d 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -1,11 +1,11 @@ package io.anuke.mindustry.core; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.game.EventType.*; @@ -82,7 +82,8 @@ public class World{ 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)); } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 717006653e..8554a5abdf 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.editor; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.collection.*; 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.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.core.GameState.*; @@ -24,7 +24,7 @@ import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.io.*; 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.world.*; import io.anuke.mindustry.world.blocks.*; diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java index 69bf90833a..0d8ab641ff 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java +++ b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java @@ -47,10 +47,8 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{ @Remote(called = Loc.server) 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(!Units.canInteract(player, tile)) return; 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); } diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index e9536abbf8..6975a3bf2a 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.entities.traits; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.collection.Queue; 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.geom.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; 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. * May return null. */ - default @Nullable BuildRequest buildRequest(){ + default @Nullable + BuildRequest buildRequest(){ return buildQueue().size == 0 ? null : buildQueue().first(); } diff --git a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java index 006a679daa..e0678e391d 100644 --- a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java @@ -6,6 +6,7 @@ import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; 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); } @@ -263,7 +265,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ @Override public boolean isFlying(){ - return type.isFlying; + return type.flying; } @Override diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 9f35158043..06de1941d0 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -10,6 +10,7 @@ import io.anuke.arc.math.geom.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.pooling.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; @@ -48,7 +49,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ public float baseRotation; public float pointerX, pointerY; public String name = "noname"; - public @Nullable String uuid, usid; + public @Nullable + String uuid, usid; public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping; public float boostHeat, shootHeat, destructTime; public boolean achievedFlight; diff --git a/core/src/io/anuke/mindustry/entities/type/TileEntity.java b/core/src/io/anuke/mindustry/entities/type/TileEntity.java index 30aa8b9cb7..87511f2d08 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -7,6 +7,7 @@ import io.anuke.arc.collection.ObjectSet; import io.anuke.arc.math.geom.Point2; import io.anuke.arc.math.geom.Vector2; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.entities.EntityGroup; import io.anuke.mindustry.entities.traits.HealthTrait; 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 sleeping; private float sleepTime; - private @Nullable SoundLoop sound; + private @Nullable + SoundLoop sound; @Remote(called = Loc.server, unreliable = true) public static void onTileDamage(Tile tile, float health){ diff --git a/core/src/io/anuke/mindustry/entities/type/Unit.java b/core/src/io/anuke/mindustry/entities/type/Unit.java index 63289f53e1..28cedcd4a5 100644 --- a/core/src/io/anuke/mindustry/entities/type/Unit.java +++ b/core/src/io/anuke/mindustry/entities/type/Unit.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.entities.type; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.graphics.*; @@ -9,6 +8,7 @@ import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.entities.*; 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); Tile tile = Geometry.findClosest(x, y, data.cores); diff --git a/core/src/io/anuke/mindustry/entities/type/base/BaseDrone.java b/core/src/io/anuke/mindustry/entities/type/base/BaseDrone.java index 2821a00832..a6a4d0dbfa 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/BaseDrone.java +++ b/core/src/io/anuke/mindustry/entities/type/base/BaseDrone.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.entities.type.base; import io.anuke.arc.math.Mathf; import io.anuke.arc.math.geom.Geometry; -import io.anuke.mindustry.entities.type.FlyingUnit; import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockFlag; diff --git a/core/src/io/anuke/mindustry/entities/type/base/Crawler.java b/core/src/io/anuke/mindustry/entities/type/base/Crawler.java index f3c12c9604..44da8bea5f 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Crawler.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Crawler.java @@ -1,6 +1,4 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.GroundUnit; - public class Crawler extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/base/Dagger.java b/core/src/io/anuke/mindustry/entities/type/base/Dagger.java index 13bf6021c9..09a39daaa7 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Dagger.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Dagger.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.GroundUnit; - public class Dagger extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/base/Eruptor.java b/core/src/io/anuke/mindustry/entities/type/base/Eruptor.java index dfbfe9db39..4c86371811 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Eruptor.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Eruptor.java @@ -1,6 +1,4 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.GroundUnit; - public class Eruptor extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/type/base/FlyingUnit.java similarity index 98% rename from core/src/io/anuke/mindustry/entities/type/FlyingUnit.java rename to core/src/io/anuke/mindustry/entities/type/base/FlyingUnit.java index 5c8963cd49..8a7e04dbcf 100644 --- a/core/src/io/anuke/mindustry/entities/type/FlyingUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/base/FlyingUnit.java @@ -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.g2d.*; @@ -8,6 +8,7 @@ import io.anuke.arc.util.*; import io.anuke.mindustry.*; import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.bullet.*; +import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.world.*; @@ -15,7 +16,7 @@ import io.anuke.mindustry.world.meta.*; import static io.anuke.mindustry.Vars.*; -public abstract class FlyingUnit extends BaseUnit{ +public class FlyingUnit extends BaseUnit{ protected float[] weaponAngles = {0,0}; protected final UnitState diff --git a/core/src/io/anuke/mindustry/entities/type/base/Fortress.java b/core/src/io/anuke/mindustry/entities/type/base/Fortress.java index 992948fa79..c4f36dba69 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Fortress.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Fortress.java @@ -1,6 +1,4 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.GroundUnit; - public class Fortress extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/base/Ghoul.java b/core/src/io/anuke/mindustry/entities/type/base/Ghoul.java index bc1f6a5317..0c4294645a 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Ghoul.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Ghoul.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.FlyingUnit; - public class Ghoul extends FlyingUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/GroundUnit.java b/core/src/io/anuke/mindustry/entities/type/base/GroundUnit.java similarity index 98% rename from core/src/io/anuke/mindustry/entities/type/GroundUnit.java rename to core/src/io/anuke/mindustry/entities/type/base/GroundUnit.java index ddb33a2662..c35c6c1528 100644 --- a/core/src/io/anuke/mindustry/entities/type/GroundUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/base/GroundUnit.java @@ -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.g2d.*; @@ -9,6 +9,7 @@ import io.anuke.mindustry.*; import io.anuke.mindustry.ai.Pathfinder.*; import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.bullet.*; +import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.type.*; @@ -18,7 +19,7 @@ import io.anuke.mindustry.world.meta.*; import static io.anuke.mindustry.Vars.*; -public abstract class GroundUnit extends BaseUnit{ +public class GroundUnit extends BaseUnit{ protected static Vector2 vec = new Vector2(); protected float walkTime; diff --git a/core/src/io/anuke/mindustry/entities/type/base/Revenant.java b/core/src/io/anuke/mindustry/entities/type/base/Revenant.java index 03233dbb5d..393c134891 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Revenant.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Revenant.java @@ -4,7 +4,6 @@ import io.anuke.arc.graphics.g2d.Draw; import io.anuke.arc.math.Angles; import io.anuke.arc.math.Mathf; import io.anuke.mindustry.entities.Units; -import io.anuke.mindustry.entities.type.FlyingUnit; public class Revenant extends FlyingUnit{ diff --git a/core/src/io/anuke/mindustry/entities/type/base/Titan.java b/core/src/io/anuke/mindustry/entities/type/base/Titan.java index 1ac30593ac..9324d4d215 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Titan.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Titan.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.GroundUnit; - public class Titan extends GroundUnit{ } diff --git a/core/src/io/anuke/mindustry/entities/type/base/Wraith.java b/core/src/io/anuke/mindustry/entities/type/base/Wraith.java index 9123ffcb8d..c8923e309f 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Wraith.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Wraith.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.entities.type.base; -import io.anuke.mindustry.entities.type.FlyingUnit; - public class Wraith extends FlyingUnit{ } diff --git a/core/src/io/anuke/mindustry/game/EventType.java b/core/src/io/anuke/mindustry/game/EventType.java index 5be7008c1a..30817303a0 100644 --- a/core/src/io/anuke/mindustry/game/EventType.java +++ b/core/src/io/anuke/mindustry/game/EventType.java @@ -1,6 +1,6 @@ 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.entities.traits.BuilderTrait; import io.anuke.mindustry.entities.type.*; @@ -195,7 +195,8 @@ public class EventType{ public static class BlockBuildEndEvent{ public final Tile tile; public final Team team; - public final @Nullable Player player; + public final @Nullable + Player player; public final boolean breaking; public BlockBuildEndEvent(Tile tile, @Nullable Player player, Team team, boolean breaking){ diff --git a/core/src/io/anuke/mindustry/game/MusicControl.java b/core/src/io/anuke/mindustry/game/MusicControl.java index a582babaca..7b5c314a95 100644 --- a/core/src/io/anuke/mindustry/game/MusicControl.java +++ b/core/src/io/anuke/mindustry/game/MusicControl.java @@ -1,11 +1,11 @@ package io.anuke.mindustry.game; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.audio.*; import io.anuke.arc.collection.*; import io.anuke.arc.math.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.gen.*; @@ -22,7 +22,8 @@ public class MusicControl{ public Array darkMusic = Array.with(); private Music lastRandomPlayed; private Interval timer = new Interval(); - private @Nullable Music current; + private @Nullable + Music current; private float fade; private boolean silenced; diff --git a/core/src/io/anuke/mindustry/graphics/Shaders.java b/core/src/io/anuke/mindustry/graphics/Shaders.java index 2c2316c07d..04c887caa9 100644 --- a/core/src/io/anuke/mindustry/graphics/Shaders.java +++ b/core/src/io/anuke/mindustry/graphics/Shaders.java @@ -1,17 +1,18 @@ package io.anuke.mindustry.graphics; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.Core; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.graphics.glutils.Shader; import io.anuke.arc.scene.ui.layout.Scl; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.Time; public class Shaders{ public static Shadow shadow; public static BlockBuild blockbuild; - public static @Nullable Shield shield; + public static @Nullable + Shield shield; public static UnitBuild build; public static FogShader fog; public static MenuShader menu; diff --git a/core/src/io/anuke/mindustry/mod/ContentParser.java b/core/src/io/anuke/mindustry/mod/ContentParser.java new file mode 100644 index 0000000000..dd8adfe111 --- /dev/null +++ b/core/src/io/anuke/mindustry/mod/ContentParser.java @@ -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, ContentType> contentTypes = new ObjectMap<>(); + + private Json parser = new Json(){ + public T readValue(Class 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> parsers = ObjectMap.of( + ContentType.block, (TypeParser)(mod, name, value) -> { + String clas = value.getString("type"); + Class 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)(mod, name, value) -> { + Item item = new Item(mod + "-" + name, new Color(Color.black)); + readFields(item, value); + return item; + }, + ContentType.unit, (TypeParser)(mod, name, value) -> { + String clas = value.getString("type"); + Class type = resolve("io.anuke.mindustry.entities.type.base." + clas); + java.lang.reflect.Constructor 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 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 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 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 Class resolve(String... potentials) throws Exception{ + for(String type : potentials){ + try{ + return (Class)Class.forName(type); + }catch(Exception ignored){ + } + } + throw new IllegalArgumentException("Type not found: " + potentials[0]); + } + + public interface TypeParser{ + T parse(String mod, String name, JsonValue value) throws Exception; + } + +} diff --git a/core/src/io/anuke/mindustry/mod/Mod.java b/core/src/io/anuke/mindustry/mod/Mod.java new file mode 100644 index 0000000000..c25d4df1c2 --- /dev/null +++ b/core/src/io/anuke/mindustry/mod/Mod.java @@ -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){ + + } +} diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java new file mode 100644 index 0000000000..258c9307bf --- /dev/null +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -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> bundles = new ObjectMap<>(); + private ObjectSet specialFolders = ObjectSet.with("bundles", "sprites"); + + private int totalSprites; + private PixmapPacker packer; + + private Array loaded = new Array<>(); + private ObjectMap, 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 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 all(){ + return loaded; + } + + /** Iterates through each mod with a main class.*/ + public void each(Consumer 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 + } +} diff --git a/core/src/io/anuke/mindustry/net/CrashSender.java b/core/src/io/anuke/mindustry/net/CrashSender.java index 0e7d20c9f9..3546a6de5d 100644 --- a/core/src/io/anuke/mindustry/net/CrashSender.java +++ b/core/src/io/anuke/mindustry/net/CrashSender.java @@ -26,7 +26,7 @@ public class CrashSender{ exception.printStackTrace(); //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 if(Version.number == 0){ @@ -143,8 +143,7 @@ public class CrashSender{ private static void ex(Runnable r){ try{ r.run(); - }catch(Throwable t){ - t.printStackTrace(); + }catch(Throwable ignored){ } } } diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index eddd19c1e6..6cb22a7794 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -1,10 +1,10 @@ package io.anuke.mindustry.net; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.pooling.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.net.Packets.*; @@ -21,7 +21,8 @@ public class Net{ private boolean server; private boolean active; private boolean clientLoaded; - private @Nullable StreamBuilder currentStream; + private @Nullable + StreamBuilder currentStream; private final Array packetQueue = new Array<>(); private final ObjectMap, Consumer> clientListeners = new ObjectMap<>(); diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index a6bea925ba..bdfca50886 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -1,7 +1,7 @@ package io.anuke.mindustry.net; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.net.Administration.*; @@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.netServer; public abstract class NetConnection{ public final String address; public boolean mobile, modclient; - public @Nullable Player player; + public @Nullable + Player player; /** ID of last recieved client snapshot. */ public int lastRecievedClientSnapshot = -1; diff --git a/core/src/io/anuke/mindustry/plugin/Plugin.java b/core/src/io/anuke/mindustry/plugin/Plugin.java index e94ad6a7ae..36714dbf14 100644 --- a/core/src/io/anuke/mindustry/plugin/Plugin.java +++ b/core/src/io/anuke/mindustry/plugin/Plugin.java @@ -1,28 +1,9 @@ package io.anuke.mindustry.plugin; -import io.anuke.arc.files.*; -import io.anuke.arc.util.*; -import io.anuke.mindustry.*; +import io.anuke.mindustry.mod.*; -public abstract class Plugin{ +/** Use Mod instead. */ +@Deprecated +public abstract class Plugin extends Mod{ - /** @return the config file for this plugin, as the file 'plugins/[plugin-name]/config.json'.*/ - 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){ - - } } diff --git a/core/src/io/anuke/mindustry/plugin/Plugins.java b/core/src/io/anuke/mindustry/plugin/Plugins.java deleted file mode 100644 index 946ec00f4f..0000000000 --- a/core/src/io/anuke/mindustry/plugin/Plugins.java +++ /dev/null @@ -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 loaded = new Array<>(); - private ObjectMap, 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 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 all(){ - return loaded; - } - - /** Iterates through each plugin.*/ - public void each(Consumer 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; - } -} diff --git a/core/src/io/anuke/mindustry/type/ContentType.java b/core/src/io/anuke/mindustry/type/ContentType.java index 31ddcf35d2..f6b980a5d5 100644 --- a/core/src/io/anuke/mindustry/type/ContentType.java +++ b/core/src/io/anuke/mindustry/type/ContentType.java @@ -13,5 +13,7 @@ public enum ContentType{ effect, zone, loadout, - typeid + typeid; + + public static final ContentType[] all = values(); } diff --git a/core/src/io/anuke/mindustry/type/UnitType.java b/core/src/io/anuke/mindustry/type/UnitType.java index a32590c1fd..74c51c5547 100644 --- a/core/src/io/anuke/mindustry/type/UnitType.java +++ b/core/src/io/anuke/mindustry/type/UnitType.java @@ -6,6 +6,7 @@ import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.ui.layout.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.*; @@ -13,8 +14,9 @@ import io.anuke.mindustry.gen.*; import io.anuke.mindustry.ui.*; public class UnitType extends UnlockableContent{ - public final TypeID typeID; - public final Supplier constructor; + public final @NonNull + TypeID typeID; + public final @NonNull Supplier constructor; public float health = 60; public float hitsize = 7f; @@ -25,7 +27,7 @@ public class UnitType extends UnlockableContent{ public float baseRotateSpeed = 0.1f; public float shootCone = 15f; public float mass = 1f; - public boolean isFlying; + public boolean flying; public boolean targetAir = true; public boolean rotateWeapon = false; public float drag = 0.1f; @@ -34,7 +36,7 @@ public class UnitType extends UnlockableContent{ public int itemCapacity = 30; public ObjectSet toMine = ObjectSet.with(Items.lead, Items.copper); public float buildPower = 0.3f, minePower = 0.7f; - public Weapon weapon; + public @NonNull Weapon weapon; public float weaponOffsetY, engineOffset = 6f, engineSize = 2f; public ObjectSet immunities = new ObjectSet<>(); public Sound deathSound = Sounds.bang; diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index 980859c01d..de70c5f3d3 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -6,6 +6,7 @@ import io.anuke.arc.audio.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.entities.*; @@ -25,7 +26,7 @@ public class Weapon{ protected static float minPlayerDist = 20f; protected static int sequenceNum = 0; /** bullet shot */ - public BulletType bullet; + public @NonNull BulletType bullet; /** shell ejection effect */ public Effect ejectEffect = Fx.none; /** weapon reload in frames */ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java index b3da2b2f3f..de6dccaefe 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java @@ -1,10 +1,10 @@ package io.anuke.mindustry.ui.dialogs; -import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.maps.*; @@ -15,7 +15,8 @@ import static io.anuke.mindustry.Vars.*; public class MapPlayDialog extends FloatingDialog{ CustomRulesDialog dialog = new CustomRulesDialog(); Rules rules; - @NonNull Gamemode selectedGamemode = Gamemode.survival; + @NonNull + Gamemode selectedGamemode = Gamemode.survival; Map lastMap; public MapPlayDialog(){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java new file mode 100644 index 0000000000..d89d0f70b2 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java @@ -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); + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java index 21e5f76ce2..3e5a55d2f3 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java @@ -4,6 +4,7 @@ import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.g2d.*; +import io.anuke.arc.input.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.scene.*; diff --git a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java index 946723983a..be5d611eae 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java @@ -163,6 +163,7 @@ public class MenuFragment extends Fragment{ ), new Buttoni("$editor", Icon.editorSmall, ui.maps::show), 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("$about.button", Icon.infoSmall, ui.about::show), new Buttoni("$quit", Icon.exitSmall, Core.app::exit) diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index a78445b81e..f0891b72c3 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -8,6 +8,7 @@ import io.anuke.arc.Graphics.Cursor.SystemCursor; import io.anuke.arc.graphics.g2d.Draw; import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.math.Mathf; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.entities.Effects; 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. * 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 buildCost; diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java index 28566f1ca7..aa177b1caf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/StorageBlock.java @@ -1,6 +1,6 @@ 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.type.Item; import io.anuke.mindustry.world.Block; @@ -75,6 +75,7 @@ public abstract class StorageBlock extends Block{ } public class StorageBlockEntity extends TileEntity{ - protected @Nullable Tile linkedCore; + protected @Nullable + Tile linkedCore; } } diff --git a/fastlane/metadata/steam/ukrainian/achievements.vdf b/fastlane/metadata/steam/ukrainian/achievements.vdf new file mode 100644 index 0000000000..65b55d7f73 --- /dev/null +++ b/fastlane/metadata/steam/ukrainian/achievements.vdf @@ -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" "Підвищте потужність імпульсного реактора." + } +} diff --git a/fastlane/metadata/steam/ukrainian/description.txt b/fastlane/metadata/steam/ukrainian/description.txt new file mode 100644 index 0000000000..e53e92394b --- /dev/null +++ b/fastlane/metadata/steam/ukrainian/description.txt @@ -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] diff --git a/fastlane/metadata/steam/ukrainian/short-description.txt b/fastlane/metadata/steam/ukrainian/short-description.txt new file mode 100644 index 0000000000..95bce25229 --- /dev/null +++ b/fastlane/metadata/steam/ukrainian/short-description.txt @@ -0,0 +1 @@ +Нескінченна гра у жанрі «захист вежі» з акцентом на керуванні ресурсами. diff --git a/gradle.properties b/gradle.properties index 678dbb3d37..227bb4e6fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=d4519a9721927165850bfe3135ef6b095c990474 +archash=678237e0fd2a2e0c26de1ea562960d1effe5ab97 diff --git a/server/src/io/anuke/mindustry/server/MindustryServer.java b/server/src/io/anuke/mindustry/server/MindustryServer.java index 92c3303c52..c42e695c1d 100644 --- a/server/src/io/anuke/mindustry/server/MindustryServer.java +++ b/server/src/io/anuke/mindustry/server/MindustryServer.java @@ -1,9 +1,9 @@ package io.anuke.mindustry.server; import io.anuke.arc.*; -import io.anuke.arc.util.*; import io.anuke.mindustry.*; import io.anuke.mindustry.core.*; +import io.anuke.mindustry.mod.*; import static io.anuke.mindustry.Vars.*; @@ -28,5 +28,9 @@ public class MindustryServer implements ApplicationListener{ Core.app.addListener(logic = new Logic()); Core.app.addListener(netServer = new NetServer()); Core.app.addListener(new ServerControl(args)); + + mods.each(Mod::init); } + + } diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 4ccb45f2b3..a78792306a 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -18,10 +18,9 @@ import io.anuke.mindustry.gen.*; import io.anuke.mindustry.io.*; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.net.Administration.*; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.plugin.*; -import io.anuke.mindustry.plugin.Plugins.*; import io.anuke.mindustry.type.*; import java.io.*; @@ -51,8 +50,6 @@ public class ServerControl implements ApplicationListener{ private PrintWriter socketOutput; public ServerControl(String[] args){ - plugins = new Plugins(); - Core.settings.defaults( "shufflemode", "normal", "bans", "", @@ -110,9 +107,6 @@ public class ServerControl implements ApplicationListener{ Effects.setScreenShakeProvider((a, b) -> {}); Effects.setEffectProvider((a, b, c, d, e, f) -> {}); - //load plugins - plugins.load(); - registerCommands(); Core.app.post(() -> { @@ -134,7 +128,6 @@ public class ServerControl implements ApplicationListener{ }); customMapDirectory.mkdirs(); - pluginDirectory.mkdirs(); Thread thread = new Thread(this::readCommands, "Server Controls"); thread.setDaemon(true); @@ -179,11 +172,8 @@ public class ServerControl implements ApplicationListener{ } }); - //initialize plugins - plugins.each(io.anuke.mindustry.plugin.Plugin::init); - - if(!plugins.all().isEmpty()){ - info("&lc{0} plugins loaded.", plugins.all().size); + if(!mods.all().isEmpty()){ + info("&lc{0} mods loaded.", mods.all().size); } 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 -> { - if(!plugins.all().isEmpty()){ - info("Plugins:"); - for(LoadedPlugin plugin : plugins.all()){ - info(" &ly{0} &lcv{1}", plugin.meta.name, plugin.meta.version); + handler.register("mods", "Display all loaded mods.", arg -> { + if(!mods.all().isEmpty()){ + info("Mods:"); + for(LoadedMod mod : mods.all()){ + info(" &ly{0} &lcv{1}", mod.meta.name, mod.meta.version); } }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", "", "Display information about a loaded plugin.", arg -> { - LoadedPlugin plugin = plugins.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0])); - if(plugin != null){ - info("Name: &ly{0}", plugin.meta.name); - info("Version: &ly{0}", plugin.meta.version); - info("Author: &ly{0}", plugin.meta.author); - info("Path: &ly{0}", plugin.jarFile.path()); - info("Description: &ly{0}", plugin.meta.description); + handler.register("mod", "", "Display information about a loaded plugin.", arg -> { + LoadedMod mod = mods.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0])); + if(mod != null){ + info("Name: &ly{0}", mod.meta.name); + info("Version: &ly{0}", mod.meta.version); + info("Author: &ly{0}", mod.meta.author); + info("Path: &ly{0}", mod.file.path()); + info("Description: &ly{0}", mod.meta.description); }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); }); - plugins.each(p -> p.registerServerCommands(handler)); - plugins.each(p -> p.registerClientCommands(netServer.clientCommands)); + mods.each(p -> p.registerServerCommands(handler)); + //TODO + //plugins.each(p -> p.registerClientCommands(netServer.clientCommands)); } private void readCommands(){ diff --git a/tools/src/io/anuke/mindustry/Generators.java b/tools/src/io/anuke/mindustry/Generators.java index e604f4ca3a..f4d595e94d 100644 --- a/tools/src/io/anuke/mindustry/Generators.java +++ b/tools/src/io/anuke/mindustry/Generators.java @@ -218,7 +218,7 @@ public class Generators{ }); ImagePacker.generate("unit-icons", () -> { - content.getBy(ContentType.unit).each(type -> !type.isFlying, type -> { + content.getBy(ContentType.unit).each(type -> !type.flying, type -> { type.load(); type.weapon.load();