diff --git a/README.md b/README.md index 30e864545c..25cc008cd0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See [CONTRIBUTING](CONTRIBUTING.md). Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases). If you'd rather compile on your own, follow these instructions. -First, make sure you have [JDK 17](https://adoptium.net/archive.html?variant=openjdk17&jvmVariant=hotspot) installed. **Other JDK versions will not work.** Open a terminal in the Mindustry directory and run the following commands: +First, make sure you have [JDK 17](https://adoptium.net/temurin/releases/?os=any&arch=any&version=17) installed. **Other JDK versions will not work.** Open a terminal in the Mindustry directory and run the following commands: ### Windows diff --git a/android/src/mindustry/android/AndroidLauncher.java b/android/src/mindustry/android/AndroidLauncher.java index 548f1b6078..e6809bec45 100644 --- a/android/src/mindustry/android/AndroidLauncher.java +++ b/android/src/mindustry/android/AndroidLauncher.java @@ -73,28 +73,57 @@ public class AndroidLauncher extends AndroidApplication{ @Override public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{ //Required to load jar files in Android 14: https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading - jar.file().setReadOnly(); - return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent){ - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ - //check for loaded state - Class loadedClass = findLoadedClass(name); - if(loadedClass == null){ - try{ - //try to load own class first - loadedClass = findClass(name); - }catch(ClassNotFoundException | NoClassDefFoundError e){ - //use parent if not found - return parent.loadClass(name); + try{ + jar.file().setReadOnly(); + return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent){ + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ + //check for loaded state + Class loadedClass = findLoadedClass(name); + if(loadedClass == null){ + try{ + //try to load own class first + loadedClass = findClass(name); + }catch(ClassNotFoundException | NoClassDefFoundError e){ + //use parent if not found + return parent.loadClass(name); + } } - } - if(resolve){ - resolveClass(loadedClass); + if(resolve){ + resolveClass(loadedClass); + } + return loadedClass; } - return loadedClass; + }; + }catch(SecurityException e){ + //`setReadOnly` to jar file in `/sdcard/Android/data/...` does not work on some Android 14 device + //But in `/data/...` it works + + if(Build.VERSION.SDK_INT < VERSION_CODES.O_MR1){ + throw e; } - }; + + Fi cacheDir = new Fi(getCacheDir()).child("mods"); + cacheDir.mkdirs(); + + //long file name support + Fi modCacheDir = cacheDir.child(jar.nameWithoutExtension()); + Fi modCache = modCacheDir.child(Long.toHexString(jar.lastModified()) + ".zip"); + + if(modCacheDir.equals(jar.parent())){ + //should not reach here, just in case + throw e; + } + + //Cache will be deleted when mod is removed + if(!modCache.exists() || jar.length() != modCache.length()){ + modCacheDir.mkdirs(); + jar.copyTo(modCache); + } + modCache.file().setReadOnly(); + return loadJar(modCache, parent); + } } @Override diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index a2098265a6..fb255a8813 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2448,7 +2449,7 @@ unit.collaris.description = Fires long-range fragmenting artillery at enemy targ unit.elude.description = Fires pairs of homing bullets at enemy targets. Can float over bodies of liquid. unit.avert.description = Fires twisting pairs of bullets at enemy targets. unit.obviate.description = Fires twisting pairs of lightning orbs at enemy targets. -unit.quell.description = Fires long-range homing missiles at enemy targets. Suppresses enemy structure repair blocks. Only attacks ground targets. +unit.quell.description = Fires long-range homing missiles with unstable plasma shielding at enemy targets. Suppresses enemy structure repair blocks. Only attacks ground targets. unit.disrupt.description = Fires long-range homing suppression missiles at enemy targets. Suppresses enemy structure repair blocks. Only attacks ground targets. unit.evoke.description = Builds structures to defend the Bastion core. Repairs structures with a beam. Capable of carrying 2x2 structures. unit.incite.description = Builds structures to defend the Citadel core. Repairs structures with a beam. Capable of carrying 2x2 structures. @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_be.properties b/core/assets/bundles/bundle_be.properties index 7c540f7f1d..633ca8b6b0 100644 --- a/core/assets/bundles/bundle_be.properties +++ b/core/assets/bundles/bundle_be.properties @@ -793,6 +793,7 @@ sectors.wave = Хваля: sectors.stored = Захавана: sectors.resume = Працягнуць sectors.launch = Запусціць +sectors.viewsubmission = \ue80d View Submissions sectors.select = Выбраць sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]нічога (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_bg.properties b/core/assets/bundles/bundle_bg.properties index 95c1b5cc76..f594a8149f 100644 --- a/core/assets/bundles/bundle_bg.properties +++ b/core/assets/bundles/bundle_bg.properties @@ -793,6 +793,7 @@ sectors.wave = Вълна: sectors.stored = Съхранени: sectors.resume = Продължи sectors.launch = Изстреляй +sectors.viewsubmission = \ue80d View Submissions sectors.select = Избери sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]няма (Слънцето) @@ -2566,6 +2567,7 @@ laccess.id = ID на единица/блок/предмет/течност.\nТ laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Неизвестно lcategory.unknown.description = Некатегоризирани указания. @@ -2599,11 +2601,13 @@ lenum.always = Винаги вярно lenum.idiv = Деление с цели числа. lenum.div = Деление.\nВръща [accent]null[] при делене на 0. lenum.mod = Модул. +lenum.emod = True modulo, result is always positive. lenum.equal = Равенство. Конвертира променливите в еднакъв тип.\nНе-null обекти стават 1, null обекти стават 0. lenum.notequal = Неравенство. Конвертира променливите в еднакъв тип. lenum.strictequal = Стриктно равенство. Отрицателно при различни типове променливи.\nМоже да се използва за проверка на [accent]null[]. lenum.shl = Побитово изместване наляво. lenum.shr = Побитово изместване надясно. +lenum.ushr = Unsigned bit-shift right. lenum.or = Побитово ИЛИ. lenum.land = Логическо И. lenum.and = Побитово И. diff --git a/core/assets/bundles/bundle_ca.properties b/core/assets/bundles/bundle_ca.properties index 2cc2771af6..e5b04cfe2f 100644 --- a/core/assets/bundles/bundle_ca.properties +++ b/core/assets/bundles/bundle_ca.properties @@ -793,6 +793,7 @@ sectors.wave = Onada: sectors.stored = Emmagatzemat: sectors.resume = Continua sectors.launch = Llança +sectors.viewsubmission = \ue80d View Submissions sectors.select = Selecciona sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]cap (sol) @@ -2566,6 +2567,7 @@ laccess.id = Identificador d’unitat/bloc/element/líquid.\nÉs l’invers de l laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Desconegut lcategory.unknown.description = Instruccions sense categoria. @@ -2599,11 +2601,13 @@ lenum.always = Sempre cert. lenum.idiv = Divisió entera. lenum.div = Divisió.\nRetorna [accent]null[] si es divideix per zero. lenum.mod = Mòdul (residu de la divisió entera). +lenum.emod = True modulo, result is always positive. lenum.equal = Igual. Força els tipus.\nCompara objectes no nuls amb nombres. Si són iguals, retorna 1. Si no, retorna 0. lenum.notequal = No igual. Força els tipus. lenum.strictequal = Igualtat estricta sense forçar el tipus.\nEs pot fer servir amb objectes nuls. lenum.shl = Desplaça els bits a l’esquerra. lenum.shr = Desplaça els bits a la dreta. +lenum.ushr = Unsigned bit-shift right. lenum.or = Operació lògica OR bit a bit. lenum.land = Operació lògica AND bit a bit. lenum.and = Operació lògica AND bit a bit. diff --git a/core/assets/bundles/bundle_cs.properties b/core/assets/bundles/bundle_cs.properties index 0bc5bd515f..776045a123 100644 --- a/core/assets/bundles/bundle_cs.properties +++ b/core/assets/bundles/bundle_cs.properties @@ -793,6 +793,7 @@ sectors.wave = Vlna: sectors.stored = Uskladněno: sectors.resume = Pokračovat sectors.launch = Vyslat +sectors.viewsubmission = \ue80d View Submissions sectors.select = Vybrat sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]bez (slunce)[] @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Šířka displeje v pixelech. laccess.displayheight = Výška displeje v pixelech. laccess.bufferusage = Počet nezpracovaných příkazů ve vyrovnávací paměti displeje. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Neznámé lcategory.unknown.description = Nezařazené instrukce. @@ -2599,11 +2601,13 @@ lenum.always = Vždy pravda. lenum.idiv = Číselné dělení. lenum.div = Dělení.\nVrací [accent]null[], pokud je děleno nulou. lenum.mod = Modulo (Vydělí 2 hodnoty a vrací zbytek). +lenum.emod = True modulo, result is always positive. lenum.equal = Stejné. Vynucuje typy.\nNon-null objekty porovnané s čísly se stanou 1, jinak 0. lenum.notequal = Není stejné. Vynucuje typy. lenum.strictequal = Přísná rovnost. Nevynucuje typy.\nMůže být použít, jestli je [accent]null[]. lenum.shl = Bitový posun vlevo. lenum.shr = Bitový posun vpravo. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitový OR. lenum.land = Logický AND. lenum.and = Bitový AND. diff --git a/core/assets/bundles/bundle_da.properties b/core/assets/bundles/bundle_da.properties index 2d7af2122f..f63f404e72 100644 --- a/core/assets/bundles/bundle_da.properties +++ b/core/assets/bundles/bundle_da.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Genoptag sectors.launch = Affyr +sectors.viewsubmission = \ue80d View Submissions sectors.select = Vælg sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]ingen (solen) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index fd56c0fb0c..e768e33aba 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -793,6 +793,7 @@ sectors.wave = Welle: sectors.stored = Gelagert: sectors.resume = Weiterspielen sectors.launch = Start +sectors.viewsubmission = \ue80d View Submissions sectors.select = Auswählen sectors.launchselect = Ziel auswählen sectors.nonelaunch = [lightgray]keiner (Sonne) @@ -2566,6 +2567,7 @@ laccess.id = ID einer Einheit/eines Blocks/eines Materials/einer Flüssigkeit\nT laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unbekannt lcategory.unknown.description = Unbekannte Anweisungen @@ -2599,11 +2601,13 @@ lenum.always = Immer. lenum.idiv = Division mit ganzen Zahlen. lenum.div = Division.\nGibt bei Teilung durch null [accent]null[] zurück. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Prüft Gleichheit.\nNicht-null Objekte, die mit Zahlen verglichen werden, werden 1. lenum.notequal = Prüft Ungleichheit. lenum.strictequal = Prüft strenge Gleichheit.\nKann verwendet werden, um [accent]null[] zu finden. lenum.shl = Bit-Shift nacht links. lenum.shr = Bit-Shift nach rechts. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise ODER. lenum.land = Logisches AND. lenum.and = Bitweises UND. diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index c2167d1371..5c4ffedad9 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -793,6 +793,7 @@ sectors.wave = Oleada: sectors.stored = Almacenado: sectors.resume = Reanudar sectors.launch = Lanzar +sectors.viewsubmission = \ue80d View Submissions sectors.select = Seleccionar sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]Ninguno (sol) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Desconocido lcategory.unknown.description = Instrucciones no clasificadas. @@ -2599,11 +2601,13 @@ lenum.always = Siempre "true". lenum.idiv = División de un número entero. lenum.div = División.\nDevuelve [accent]null[] al dividir entre cero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Igual. Coacciona tipos.\nObjetos no-nulos coaccionados con números pasan a 1, si no coinciden pasan a 0. lenum.notequal = No igual. Coacciona tipos. lenum.strictequal = Igualdad estricta. No coacciona tipos.\nSe puede usar para comprobar si un resultado es [accent]null[]. lenum.shl = Cambia bits a izquierda. lenum.shr = Cambia bits a derecha. +lenum.ushr = Unsigned bit-shift right. lenum.or = Comprobación bit a bit OR. lenum.land = Comprobación lógica AND. lenum.and = Comprobación bit a bit AND. diff --git a/core/assets/bundles/bundle_et.properties b/core/assets/bundles/bundle_et.properties index ccdeb2a02f..d8d2dbbefa 100644 --- a/core/assets/bundles/bundle_et.properties +++ b/core/assets/bundles/bundle_et.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_eu.properties b/core/assets/bundles/bundle_eu.properties index a5e2d30886..6f848b4fc9 100644 --- a/core/assets/bundles/bundle_eu.properties +++ b/core/assets/bundles/bundle_eu.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_fi.properties b/core/assets/bundles/bundle_fi.properties index f457b166ea..e27b253357 100644 --- a/core/assets/bundles/bundle_fi.properties +++ b/core/assets/bundles/bundle_fi.properties @@ -793,6 +793,7 @@ sectors.wave = Taso: sectors.stored = Säilötty: sectors.resume = Jatka sectors.launch = Laukaise +sectors.viewsubmission = \ue80d View Submissions sectors.select = Valitse sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]ei mitään (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Tuntematon lcategory.unknown.description = Luokittelemattomat ohjeet. @@ -2599,11 +2601,13 @@ lenum.always = Aina tosi. lenum.idiv = Kokonaislukujen osamäärä. lenum.div = Osamäärä.\nPalauttaa arvon [accent]null[] jaettaessa nollalla. lenum.mod = Lukuun ottamatta. +lenum.emod = True modulo, result is always positive. lenum.equal = Yhtä suuri. Pakottaa tyypit.\nMuut kohteet kuin null palauttavat arvon 1 verrattaessa numeroihin, muussa tapauksessa palautus on 0. lenum.notequal = Erisuuri. Pakottaa tyypit. lenum.strictequal = Tarkka yhtäsuuruus. Ei pakota tyyppejä.\nVoidaan käyttää tarkistamaan arvon [accent]null[] varalta. lenum.shl = Siirrä bittejä vasemmalle. lenum.shr = Siirrä bittejä oikealle. +lenum.ushr = Unsigned bit-shift right. lenum.or = Binäärinen OR. lenum.land = Looginen AND. lenum.and = Binäärinen AND. diff --git a/core/assets/bundles/bundle_fil.properties b/core/assets/bundles/bundle_fil.properties index a4242fc278..7691c6bb59 100644 --- a/core/assets/bundles/bundle_fil.properties +++ b/core/assets/bundles/bundle_fil.properties @@ -793,6 +793,7 @@ sectors.wave = Mga Waves: sectors.stored = Stored: sectors.resume = Resume sectors.launch = I-Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = I-Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index cb158cfe19..aa2481790d 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -793,6 +793,7 @@ sectors.wave = Vague : sectors.stored = Stockage : sectors.resume = Reprendre sectors.launch = Décoller +sectors.viewsubmission = \ue80d View Submissions sectors.select = Sélectionner sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]Vide (soleil) @@ -2566,6 +2567,7 @@ laccess.id = L'ID d'une unité/bloc/ressource/liquide.\nCeci est l'inverse de l' laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Inconnu lcategory.unknown.description = Instructions sans catégorie. @@ -2599,11 +2601,13 @@ lenum.always = Toujours [accent]true[]. lenum.idiv = Division entière. lenum.div = Division.\nRetourne [accent]null[] lors d'une division par zéro. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Égalité. Conversion des types.\nLes objets non-nuls comparés avec des nombres deviennent 1, sinon 0. lenum.notequal = Inégalité. Conversion des types. lenum.strictequal = Égalité stricte. Ne convertit pas les types.\nPeut être utilisé pour vérifier les valeurs [accent]null[]. lenum.shl = Décalage de bits vers la gauche. lenum.shr = Décalage de bits vers la droite. +lenum.ushr = Unsigned bit-shift right. lenum.or = Opération binaire OR. lenum.land = Opération logique AND. lenum.and = Opération binaire AND. diff --git a/core/assets/bundles/bundle_hu.properties b/core/assets/bundles/bundle_hu.properties index a6008a2733..e4d10987b2 100644 --- a/core/assets/bundles/bundle_hu.properties +++ b/core/assets/bundles/bundle_hu.properties @@ -793,6 +793,7 @@ sectors.wave = Hullám: sectors.stored = Tárolt nyersanyagok: sectors.resume = Folytatás sectors.launch = Kilövés +sectors.viewsubmission = \ue80d View Submissions sectors.select = Kiválasztás sectors.launchselect = Célállomás kiválasztása sectors.nonelaunch = [lightgray]semmi (nap) @@ -2566,6 +2567,7 @@ laccess.id = Egy egység/blokk/nyersanyag/folyadék azonosítója.\nEz a keresé laccess.displaywidth = Egy kijelzőblokk szélessége pixelben. laccess.displayheight = Egy kijelzőblokk magassága pixelben. laccess.bufferusage = A kijelző grafikus pufferében lévő feldolgozatlan parancsok száma. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Ismeretlen lcategory.unknown.description = Nem kategorizált utasítások. @@ -2599,11 +2601,13 @@ lenum.always = Mindig igaz. lenum.idiv = Egész osztás. lenum.div = Osztás.\nNullával való osztáskor a visszatérési érték [accent]null[]. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Egyenlő. Kényszeríti a típusokat.\nA nem null értékű objektumok értéke 1 lesz, egyébként 0. lenum.notequal = Nem egyenlő. Kényszeríti a típusokat. lenum.strictequal = Szigorúan egyenlőség. Nem kényszeríti a típusokat.\nA [accent]null[] ellenőrzésére is használható. lenum.shl = Biteltolás balra. lenum.shr = Biteltolás jobbra. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitenkénti VAGY. lenum.land = Logikai ÉS. lenum.and = Bitenkénti ÉS. diff --git a/core/assets/bundles/bundle_id_ID.properties b/core/assets/bundles/bundle_id_ID.properties index 9eec13d42f..62a88a3a9f 100644 --- a/core/assets/bundles/bundle_id_ID.properties +++ b/core/assets/bundles/bundle_id_ID.properties @@ -793,6 +793,7 @@ sectors.wave = Gelombang: sectors.stored = Terisi: sectors.resume = Lanjutkan sectors.launch = Luncurkan +sectors.viewsubmission = \ue80d View Submissions sectors.select = Pilih sectors.launchselect = Pilih Destinasi Peluncuran sectors.nonelaunch = [lightgray]tidak ada (matahari) @@ -2566,6 +2567,7 @@ laccess.id = ID suatu unit/blok/bahan/cairan.\nIni adalah kebalikan dari operasi laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Tak Diketahui lcategory.unknown.description = Instruksi tanpa kategori. @@ -2599,11 +2601,13 @@ lenum.always = Selalu benar. lenum.idiv = Pembagian integer. lenum.div = Pembagian.\nMengembalikan [accent]null[] pada pembagian dengan nol. lenum.mod = Modulus. +lenum.emod = True modulo, result is always positive. lenum.equal = Kesetaraan. Mengonversikan tipe.\nObjek bukan nol dibandingkan dengan angka menjadi 1, jika tidak 0. lenum.notequal = Kesetaraan tanpa jenis pemaksaan. Mengonversikan tipe. lenum.strictequal = Kesetaraan dengan jenis pemaksaan. Tidak mengonversikan tipe.\nDapat digunakan untuk memeriksa [accent]null[]. lenum.shl = Bit-shift kiri. lenum.shr = Bit-shift kanan. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logika AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_it.properties b/core/assets/bundles/bundle_it.properties index 9a1ba71c60..27750736c1 100644 --- a/core/assets/bundles/bundle_it.properties +++ b/core/assets/bundles/bundle_it.properties @@ -793,6 +793,7 @@ sectors.wave = Ondata: sectors.stored = Immagazzinato: sectors.resume = Riprendi sectors.launch = Lancia +sectors.viewsubmission = \ue80d View Submissions sectors.select = Seleziona sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]nessuno (sole) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_ja.properties b/core/assets/bundles/bundle_ja.properties index c09dc24d25..b4766dcfff 100644 --- a/core/assets/bundles/bundle_ja.properties +++ b/core/assets/bundles/bundle_ja.properties @@ -793,6 +793,7 @@ sectors.wave = ウェーブ: sectors.stored = コアの資源: sectors.resume = 再開 sectors.launch = 打ち上げ +sectors.viewsubmission = \ue80d View Submissions sectors.select = 選択 sectors.launchselect = 発射先 sectors.nonelaunch = [lightgray]無し (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = 不明 lcategory.unknown.description = 未分類の指示です。 @@ -2599,11 +2601,13 @@ lenum.always = 常にtrueを返します。 lenum.idiv = 整数の割り算をします。 lenum.div = 割り算をします。\nゼロ除算で [accent]null[] を返します。 lenum.mod = 割ったあまりを返します。 +lenum.emod = True modulo, result is always positive. lenum.equal = 等しいかどうかを比較します。型を強制します。\n数値と比較される非NULLオブジェクトは1になり、そうでない場合は0になる。 lenum.notequal = 等しくないかどうかを比較します。型を強制します。 lenum.strictequal = より厳密な比較をします。型の強制はしません。\n [accent]null[] のチェックに使用することができます。 lenum.shl = ビットを左にシフトします。 lenum.shr = ビットを右にシフトします。 +lenum.ushr = Unsigned bit-shift right. lenum.or = ビット単位でのOR演算をします。 lenum.land = 論理的なAND演算をします。 lenum.and = ビット単位でのAND演算をします。 diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index 66eca86845..53952fcabd 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -793,6 +793,7 @@ sectors.wave = 단계: sectors.stored = 저장량: sectors.resume = 재개 sectors.launch = 출격 +sectors.viewsubmission = \ue80d View Submissions sectors.select = 선택 sectors.launchselect = 발사 대상 선택 sectors.nonelaunch = [lightgray]없음 (태양)[] @@ -2566,6 +2567,7 @@ laccess.id = 유닛/블록/아이템/액체의 ID.\n이것은 조회 작업의 laccess.displaywidth = 디스플레이 블록의 픽셀 단위 너비. laccess.displayheight = 디스플레이 블록의 픽셀 단위 높이. laccess.bufferusage = 디스플레이의 그래픽 버퍼에 있는 처리되지 않은 명령의 수. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = 알 수 없음 lcategory.unknown.description = 분류되지 않은 설명 @@ -2599,11 +2601,13 @@ lenum.always = 항상 참 lenum.idiv = 정수 나누기 lenum.div = 나누기\n0으로 나누면 [accent]null[]을 반환합니다. lenum.mod = 나머지 +lenum.emod = True modulo, result is always positive. lenum.equal = 동치 비교. 형변환 가능\nNull이 아닌 객체가 숫자와 비교하려면 1이 되고, 아니면 0이 됩니다. lenum.notequal = 동치 부정. 형변환 가능 lenum.strictequal = 엄격한 동치 비교. 형변환 불가능\n[accent]null[]을 확인할 때 쓸 수 있습니다. lenum.shl = 왼쪽으로 비트 이동 lenum.shr = 오른쪽으로 비트 이동 +lenum.ushr = Unsigned bit-shift right. lenum.or = 비트연산자 OR lenum.land = 논리연산자 AND lenum.and = 비트연산자 AND diff --git a/core/assets/bundles/bundle_lt.properties b/core/assets/bundles/bundle_lt.properties index a5777616ee..2ee4d91a3f 100644 --- a/core/assets/bundles/bundle_lt.properties +++ b/core/assets/bundles/bundle_lt.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_nl.properties b/core/assets/bundles/bundle_nl.properties index 388d43d9ba..8823da211c 100644 --- a/core/assets/bundles/bundle_nl.properties +++ b/core/assets/bundles/bundle_nl.properties @@ -793,6 +793,7 @@ sectors.wave = Golf: sectors.stored = Opgeslagen: sectors.resume = Doorgaan sectors.launch = Lanceer +sectors.viewsubmission = \ue80d View Submissions sectors.select = Selecteer sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]geen (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_nl_BE.properties b/core/assets/bundles/bundle_nl_BE.properties index 4ce0a9690e..dc1807484d 100644 --- a/core/assets/bundles/bundle_nl_BE.properties +++ b/core/assets/bundles/bundle_nl_BE.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_pl.properties b/core/assets/bundles/bundle_pl.properties index 260a82bd89..d74fa7d3d8 100644 --- a/core/assets/bundles/bundle_pl.properties +++ b/core/assets/bundles/bundle_pl.properties @@ -793,6 +793,7 @@ sectors.wave = Fala: sectors.stored = Zmagazynowane: sectors.resume = Kontynuuj sectors.launch = Wystrzel +sectors.viewsubmission = \ue80d View Submissions sectors.select = Wybierz sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]Żaden (Słońce) @@ -2566,6 +2567,7 @@ laccess.id = ID jednostki/bloku/przedmiotu/płynu.\nOdwrotnośc operacji wyszuki laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Inne lcategory.unknown.description = Niezkategoryzowane instrukcje. @@ -2599,11 +2601,13 @@ lenum.always = Zawsze prawda. lenum.idiv = Dzielenie liczb całkowitych. lenum.div = Dzielenie.\nZwraca [accent]null[] w trakcie dzielenia przez zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Równość. Wymusza typ.\nNiezerowe objekty połączone z liczbami stają się 1, w innym wypadku 0. lenum.notequal = Nierówność. Wymusza typ. lenum.strictequal = Ścisła równość. Nie wymusza typów.\nMoże być użyte do wykrycia [accent]null[]. lenum.shl = Przesunięcie bitowe w lewo. lenum.shr = Przesunięcie bitowe w prawo. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitowe OR (lub). lenum.land = Logiczne AND (i). lenum.and = Bitowe AND (i). diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index f407bafac1..0b381cb0d2 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -793,6 +793,7 @@ sectors.wave = Horda: sectors.stored = Armazenado: sectors.resume = Continuar sectors.launch = Lançar +sectors.viewsubmission = \ue80d View Submissions sectors.select = Selecionar sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]nenhum (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Desconhecido lcategory.unknown.description = Instruções não categorizadas. @@ -2599,11 +2601,13 @@ lenum.always = Sempre verdade. lenum.idiv = Divisão inteira. lenum.div = Divisão.\nRetorna [accent]null[] na divisão por zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Igual. Coage tipos.\nObjetos não nulos comparados com números tornam-se 1, caso contrário, 0. lenum.notequal = Não igual. Tipos de coerção. lenum.strictequal = Igualdade estrita. Não coage tipos.Pode ser usado para verificar [accent]null[]. lenum.shl = Deslocamento de bit para a esquerda. lenum.shr = Deslocamento de bits para a direita. +lenum.ushr = Unsigned bit-shift right. lenum.or = OU bit a bit. lenum.land = Lógico E. lenum.and = E bit a bit. diff --git a/core/assets/bundles/bundle_pt_PT.properties b/core/assets/bundles/bundle_pt_PT.properties index ae76623896..30d99e2d18 100644 --- a/core/assets/bundles/bundle_pt_PT.properties +++ b/core/assets/bundles/bundle_pt_PT.properties @@ -793,6 +793,7 @@ sectors.wave = Horda: sectors.stored = Armazenado: sectors.resume = Continuar sectors.launch = Lançar +sectors.viewsubmission = \ue80d View Submissions sectors.select = Selecionar sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]nenhum (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Desconhecido lcategory.unknown.description = Instruções não categorizadas. @@ -2599,11 +2601,13 @@ lenum.always = Sempre verdade. lenum.idiv = Divisão inteira. lenum.div = Divisão.\nRetorna [accent]null[] na divisão por zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Igual. Coage tipos.\nObjetos não nulos comparados com números tornam-se 1, caso contrário, 0. lenum.notequal = Não igual. Tipos de coerção. lenum.strictequal = Igualdade estrita. Não coage tipos.Pode ser usado para verificar [accent]null[]. lenum.shl = Deslocamento de bit para a esquerda. lenum.shr = Deslocamento de bits para a direita. +lenum.ushr = Unsigned bit-shift right. lenum.or = OU bit a bit. lenum.land = Lógico E. lenum.and = E bit a bit. diff --git a/core/assets/bundles/bundle_ro.properties b/core/assets/bundles/bundle_ro.properties index 621169d964..f705cf3c20 100644 --- a/core/assets/bundles/bundle_ro.properties +++ b/core/assets/bundles/bundle_ro.properties @@ -793,6 +793,7 @@ sectors.wave = Valul: sectors.stored = Stocat: sectors.resume = Revino sectors.launch = Lansare +sectors.viewsubmission = \ue80d View Submissions sectors.select = Selectează sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]nimic (soarele) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Mereu adevărat. lenum.idiv = Împărțirea naturală a numerelor (int). lenum.div = Împărțirea.\nReturnează [accent]null[] dacă împarți la 0. lenum.mod = Modulo (restul împărțirii). +lenum.emod = True modulo, result is always positive. lenum.equal = Egal. Convertește tipurile variabilelor.\nObiectele nenule comparate cu numere devin 1, cele nule devin 0. lenum.notequal = Nu e egal. Convertește tipurile variabilelor. lenum.strictequal = Egalitate strictă. Nu convertește tipurile variabilelor.\nPoate fi folosit pt a verifica dacă ceva este [accent]null[]. lenum.shl = Shift left pe biți. lenum.shr = Shift right pe biți. +lenum.ushr = Unsigned bit-shift right. lenum.or = OR/SAU. Ține cont de biți. lenum.land = Logical AND/ȘI logic. Nu ține cont de biți. lenum.and = AND/ȘI. Ține cont de biți. diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index bf71eebfa9..391a8ca224 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -793,6 +793,7 @@ sectors.wave = Волна: sectors.stored = Накоплено: sectors.resume = Продолжить sectors.launch = Высадка +sectors.viewsubmission = \ue80d View Submissions sectors.select = Выбор sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]нет (солнце) @@ -2566,6 +2567,7 @@ laccess.id = Идентификатор единицы/блока/предмет laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Неизвестно lcategory.unknown.description = Нет категории. @@ -2599,11 +2601,13 @@ lenum.always = Всегда истина. lenum.idiv = Целочисленное деление. lenum.div = Деление.\nВозвращает [accent]null[] при делении на ноль. lenum.mod = Остаток от деления. +lenum.emod = True modulo, result is always positive. lenum.equal = Равно. Приводит типы.\nНе-null объекты, по сравнению с числами, становятся 1, иначе — 0. lenum.notequal = Не равно. Приводит типы. lenum.strictequal = Строгое равенство. Не приводит типы.\nМожет быть использовано для проверки на [accent]null[]. lenum.shl = Побитовый сдвиг влево. lenum.shr = Побитовый сдвиг вправо. +lenum.ushr = Unsigned bit-shift right. lenum.or = Побитовое ИЛИ. lenum.land = Булевое И. lenum.and = Побитовое И. diff --git a/core/assets/bundles/bundle_sr.properties b/core/assets/bundles/bundle_sr.properties index 46b6a00aa3..cfcf7df9dd 100644 --- a/core/assets/bundles/bundle_sr.properties +++ b/core/assets/bundles/bundle_sr.properties @@ -793,6 +793,7 @@ sectors.wave = Talas: sectors.stored = Skladišćeno: sectors.resume = Nastavi sectors.launch = Lansiraj +sectors.viewsubmission = \ue80d View Submissions sectors.select = Izaberi sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]nema (sunce) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Nepoznato lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Uvek Tačno. lenum.idiv = Integer division. lenum.div = Deljenje.Šalje [accent]null[] kada se deli sa nulom. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Jednakost. Primorava vrste.\nObjekti koji nisu [accent]null[] poređeni sa brojevima postaju 1, u suprotnom 0. lenum.notequal = Nejednakost. Primorava vrste. lenum.strictequal = Zacrtana jednakost. Ne primorava vrste.\nMože se koristiti radi provere [accent]null[]-a. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_sv.properties b/core/assets/bundles/bundle_sv.properties index ffe6dbeaae..e8759c3d0c 100644 --- a/core/assets/bundles/bundle_sv.properties +++ b/core/assets/bundles/bundle_sv.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Lagrade: sectors.resume = Återuppta sectors.launch = Skjuta upp +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_th.properties b/core/assets/bundles/bundle_th.properties index f7b8ebb662..2531956d18 100644 --- a/core/assets/bundles/bundle_th.properties +++ b/core/assets/bundles/bundle_th.properties @@ -793,6 +793,7 @@ sectors.wave = คลื่น: sectors.stored = คลังไอเท็ม: sectors.resume = ไปต่อ sectors.launch = ลงจอด +sectors.viewsubmission = \ue80d View Submissions sectors.select = เลือก sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]ไม่มี (ดวงอาทิตย์) @@ -2566,6 +2567,7 @@ laccess.id = ID ของยูนิต/บล็อก/ไอเท็ม/ข laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = ไม่ทราบ lcategory.unknown.description = คำสั่งที่ไม่อยู่ในหมวดหมู่ใดๆเลย @@ -2599,11 +2601,13 @@ lenum.always = เป็นจริงเสมอ lenum.idiv = หารจำนวนเต็ม lenum.div = หาร\nจะส่งกลับ[accent]ค่าว่าง[] หากหารศูนย์ lenum.mod = โมดูโล่ (หารหาเศษ) +lenum.emod = True modulo, result is always positive. lenum.equal = เท่ากับ แบบบังคับประเภท\nสิ่งที่ไม่ใช่ค่าว่างเมื่อเทียบกับตัวเลขจะส่งกลับค่า 1 นอกนั้นจะส่งกลับค่า 0 lenum.notequal = ไม่เท่ากับ บังคับประเภท lenum.strictequal = เท่ากับที่เข้มงวด ไม่บังคับประเภท\nสามารถใช้ตรวจสอบหา[accent]ค่าว่าง[]ได้ lenum.shl = เลื่อนบิตไปทางซ้าย lenum.shr = เลื่อนบิตไปทางขวา +lenum.ushr = Unsigned bit-shift right. lenum.or = หรือ แบบบิต lenum.land = และ เชิงตรรกะ lenum.and = และ แบบบิต diff --git a/core/assets/bundles/bundle_tk.properties b/core/assets/bundles/bundle_tk.properties index a498bf910f..4612337ea4 100644 --- a/core/assets/bundles/bundle_tk.properties +++ b/core/assets/bundles/bundle_tk.properties @@ -793,6 +793,7 @@ sectors.wave = Wave: sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch +sectors.viewsubmission = \ue80d View Submissions sectors.select = Select sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Unknown lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = Always true. lenum.idiv = Integer division. lenum.div = Division.\nReturns [accent]null[] on divide-by-zero. lenum.mod = Modulo. +lenum.emod = True modulo, result is always positive. lenum.equal = Equal. Coerces types.\nNon-null objects compared with numbers become 1, otherwise 0. lenum.notequal = Not equal. Coerces types. lenum.strictequal = Strict equality. Does not coerce types.\nCan be used to check for [accent]null[]. lenum.shl = Bit-shift left. lenum.shr = Bit-shift right. +lenum.ushr = Unsigned bit-shift right. lenum.or = Bitwise OR. lenum.land = Logical AND. lenum.and = Bitwise AND. diff --git a/core/assets/bundles/bundle_tr.properties b/core/assets/bundles/bundle_tr.properties index 3c6b3fbce8..be8fc804ad 100644 --- a/core/assets/bundles/bundle_tr.properties +++ b/core/assets/bundles/bundle_tr.properties @@ -793,6 +793,7 @@ sectors.wave = Dalga: sectors.stored = Depolanan: sectors.resume = Devam Et sectors.launch = Fırlat +sectors.viewsubmission = \ue80d View Submissions sectors.select = Seç sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]yok (güneş) @@ -2566,6 +2567,7 @@ laccess.id = Bir birim/blok/eşya/sıvı kimliği. \nBu arama operasyonun zıtt laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = ??? lcategory.unknown.description = Kategorize edilmemiş talimatlar @@ -2599,11 +2601,13 @@ lenum.always = Her Zaman Doğru lenum.idiv = Tamsayı Bölme lenum.div = Bölme lenum.mod = Mod +lenum.emod = True modulo, result is always positive. lenum.equal = Eşit lenum.notequal = Eşit Değil lenum.strictequal = Aynı lenum.shl = Shift Sol lenum.shr = Shift Sağ +lenum.ushr = Unsigned bit-shift right. lenum.or = Veya lenum.land = Çapraz Ve lenum.and = Ve diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 2188523398..4da480112b 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -793,6 +793,7 @@ sectors.wave = Хвиля: sectors.stored = Зберігає: sectors.resume = Продовжити sectors.launch = Запустити +sectors.viewsubmission = \ue80d View Submissions sectors.select = Вибрати sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]нічого (сонце) @@ -2566,6 +2567,7 @@ laccess.id = Ідентифікатор одиниці/блоку/предмет laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Невідома категорія lcategory.unknown.description = Команди без категорії. @@ -2599,11 +2601,13 @@ lenum.always = Завжди істинне. lenum.idiv = Ціле ділення. lenum.div = Ділення.\nПовертає [accent]null[] при діленні на нуль. lenum.mod = Залишок від ділення. +lenum.emod = True modulo, result is always positive. lenum.equal = Рівно. Примусове приведення типів.\nНе-null об’єкти у порівнянні з числами стають 1, інакше — 0. lenum.notequal = Не рівно. Примусове приведення типів. lenum.strictequal = Сувора рівність. Примусового приведення типів немає.\nМожна використати для перевірки на [accent]null[]. lenum.shl = Зсув бітів ліворуч. lenum.shr = Зсув бітів праворуч. +lenum.ushr = Unsigned bit-shift right. lenum.or = Побітове АБО (OR). lenum.land = Побітове логічне І. lenum.and = Побітове І. diff --git a/core/assets/bundles/bundle_vi.properties b/core/assets/bundles/bundle_vi.properties index b6cd5e027d..ac7ab1dd32 100644 --- a/core/assets/bundles/bundle_vi.properties +++ b/core/assets/bundles/bundle_vi.properties @@ -793,6 +793,7 @@ sectors.wave = Đợt: sectors.stored = Lưu trữ: sectors.resume = Tiếp tục sectors.launch = Phóng +sectors.viewsubmission = \ue80d View Submissions sectors.select = Chọn sectors.launchselect = Chọn đích phóng sectors.nonelaunch = [lightgray]không có (mặt trời) @@ -2566,6 +2567,7 @@ laccess.id = Định danh của một đơn vị/khối/vật phẩm/chất lỏ laccess.displaywidth = Độ rộng của một khối hiển thị tính bằng pixel. laccess.displayheight = Độ cao của một khối hiển thị tính bằng pixel. laccess.bufferusage = Số lệnh chưa xử lý trong bộ đệm đồ họa của một hiển thị. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = Không xác định lcategory.unknown.description = Chỉ lệnh không được phân loại. @@ -2599,11 +2601,13 @@ lenum.always = Luôn đúng. lenum.idiv = Chia lấy phần nguyên. lenum.div = Phép chia.\nTrả về [accent]rỗng (null)[] khi chia cho 0. lenum.mod = Chia lấy phần dư. +lenum.emod = True modulo, result is always positive. lenum.equal = Bằng nhau. Ép kiểu.\nĐối tượng không-rỗng (non-null) so sánh với số sẽ thành 1, ngược lại là 0. lenum.notequal = Không bằng nhau. Ép kiểu. lenum.strictequal = Bằng nhau ràng buộc. Không ép kiểu.\nCó thể dùng để kiểm tra [accent]rỗng (null)[]. lenum.shl = Nhảy bit sang trái. lenum.shr = Nhảy bit sang phải. +lenum.ushr = Unsigned bit-shift right. lenum.or = Phép toán bit OR. lenum.land = Phép toán logic AND. lenum.and = Phép toán bit AND. diff --git a/core/assets/bundles/bundle_zh_CN.properties b/core/assets/bundles/bundle_zh_CN.properties index a2b1686e48..da56fe8187 100644 --- a/core/assets/bundles/bundle_zh_CN.properties +++ b/core/assets/bundles/bundle_zh_CN.properties @@ -793,6 +793,7 @@ sectors.wave = 波次: sectors.stored = 贮存: sectors.resume = 继续 sectors.launch = 发射 +sectors.viewsubmission = \ue80d View Submissions sectors.select = 选择 sectors.launchselect = 选择发射目的地 sectors.nonelaunch = [lightgray]无(自动销毁) @@ -2566,6 +2567,7 @@ laccess.id = 单位/块/物品/液体的 ID。\n这是 Lookup 的反向操作。 laccess.displaywidth = 显示屏的宽度(以像素为单位)。 laccess.displayheight = 显示屏的高度(以像素为单位)。 laccess.bufferusage = 显示器图形缓冲区中未处理的命令数。 +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = 未知 lcategory.unknown.description = 未分类的指令 @@ -2599,11 +2601,13 @@ lenum.always = 无条件跳转 lenum.idiv = 整数除法,返回不带小数的商 lenum.div = 除法,除以 0 时返回 [accent]null[] lenum.mod = 求除法的余数 +lenum.emod = True modulo, result is always positive. lenum.equal = 相等。转换参数类型后进行比较\n与数字进行比较时,null 转换为 0 ,非 null 对象转换为 1 lenum.notequal = 不相等。转换参数类型后进行比较 lenum.strictequal = 严格相等。不转换参数类型\n可用于准确检查 [accent]null[] 对象 lenum.shl = 左移位 lenum.shr = 右移位 +lenum.ushr = Unsigned bit-shift right. lenum.or = 按位或 lenum.land = 逻辑与 lenum.and = 按位与 diff --git a/core/assets/bundles/bundle_zh_TW.properties b/core/assets/bundles/bundle_zh_TW.properties index 6ecba66c23..af371091ad 100644 --- a/core/assets/bundles/bundle_zh_TW.properties +++ b/core/assets/bundles/bundle_zh_TW.properties @@ -793,6 +793,7 @@ sectors.wave = 波次: sectors.stored = 儲存: sectors.resume = 繼續 sectors.launch = 發射 +sectors.viewsubmission = \ue80d View Submissions sectors.select = 選取 sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]無(太陽) @@ -2566,6 +2567,7 @@ laccess.id = ID of a unit/block/item/liquid.\nThis is the inverse of the lookup laccess.displaywidth = Width of a display block in pixels. laccess.displayheight = Height of a display block in pixels. laccess.bufferusage = Number of unprocessed commands in the graphics buffer of a display. +laccess.operations = Number of operations performed on the block.\nFor displays, returns the number of drawflush operations. lcategory.unknown = 未知 lcategory.unknown.description = Uncategorized instructions. @@ -2599,11 +2601,13 @@ lenum.always = 永遠 true (直接跳). lenum.idiv = 整數除法,無條件捨去. lenum.div = 除法.\n除以零時回傳 [accent]null[] lenum.mod = Modulo,求餘數 +lenum.emod = True modulo, result is always positive. lenum.equal = 是否相等,不管資料型態。\n非null 物件和數值相比時回傳1 lenum.notequal = 是否不相等,不管資料型態. lenum.strictequal = 嚴格檢查是否相等,會比照資料型態。\n可用來檢查[accent]null[] lenum.shl = 左移n位元 lenum.shr = 右移n位元 +lenum.ushr = Unsigned bit-shift right. lenum.or = 位元 OR lenum.land = 邏輯 AND lenum.and = 位元 AND diff --git a/core/assets/contributors b/core/assets/contributors index 7acc360402..d382955c6c 100644 --- a/core/assets/contributors +++ b/core/assets/contributors @@ -181,4 +181,6 @@ IchMagSchokolade MonoChronos RushieWashie ITY +Iniquit +DSFdsfWxp Someone's Shadow \ No newline at end of file diff --git a/core/assets/maps/geothermalStronghold.msav b/core/assets/maps/geothermalStronghold.msav index e76157110d..68503754bf 100644 Binary files a/core/assets/maps/geothermalStronghold.msav and b/core/assets/maps/geothermalStronghold.msav differ diff --git a/core/assets/maps/planetaryTerminal.msav b/core/assets/maps/planetaryTerminal.msav index b45600882a..bbc295ac12 100644 Binary files a/core/assets/maps/planetaryTerminal.msav and b/core/assets/maps/planetaryTerminal.msav differ diff --git a/core/assets/maps/weatheredChannels.msav b/core/assets/maps/weatheredChannels.msav index f9ef2030e1..aa02ffabf7 100644 Binary files a/core/assets/maps/weatheredChannels.msav and b/core/assets/maps/weatheredChannels.msav differ diff --git a/core/assets/planets/serpulo.json b/core/assets/planets/serpulo.json index 147e637a6f..e2dfd3f818 100644 --- a/core/assets/planets/serpulo.json +++ b/core/assets/planets/serpulo.json @@ -1 +1 @@ -{presets:{windsweptIslands:97,stainedMountains:223,weatheredChannels:166,craters:219,extractionOutpost:213,coastline:164,navalFortress:165,frontier:86,groundZero:170,mycelialBastion:143,facility32m:65,atolls:75,overgrowth:142,testingGrounds:169,frozenForest:64,saltFlats:98,taintedWoods:145,infestedCanyons:85,desolateRift:271,nuclearComplex:228,ruinousShores:41,planetaryTerminal:217,impact0078:266,seaPort:214,geothermalStronghold:264,cruxscape:54,fungalPass:221,tarFields:99,biomassFacility:23},attackSectors:[0,2,5,6,10,11,12,13,16,19,24,25,27,28,30,33,36,47,48,49,51,57,59,60,66,67,68,70,71,76,78,82,90,104,106,110,114,115,121,127,128,129,133,138,148,149,154,158,172,180,182,200,202,204,210,224,225,233,234,235,241,243,248,254,255,257,259,265]} \ No newline at end of file +{presets:{windsweptIslands:97,stainedMountains:223,weatheredChannels:166,craters:219,extractionOutpost:213,coastline:164,navalFortress:165,frontier:86,groundZero:170,mycelialBastion:143,facility32m:65,atolls:75,overgrowth:142,testingGrounds:169,frozenForest:64,saltFlats:98,taintedWoods:145,infestedCanyons:85,desolateRift:271,nuclearComplex:228,ruinousShores:41,planetaryTerminal:217,impact0078:266,seaPort:214,geothermalStronghold:264,cruxscape:54,fungalPass:221,tarFields:99,biomassFacility:23},attackSectors:[0,6,13,16,19,20,24,27,30,47,55,66,67,69,76,92,94,103,111,116,127,133,138,150,157,161,162,176,180,185,191,192,197,200,204,207,225,230,237,242,243,244,245,246,247,248,251,254,259,263,265]} \ No newline at end of file diff --git a/core/assets/shaders/planet.vert b/core/assets/shaders/planet.vert index 7c73fb8aa5..e735eb7031 100755 --- a/core/assets/shaders/planet.vert +++ b/core/assets/shaders/planet.vert @@ -1,6 +1,7 @@ attribute vec4 a_position; attribute vec3 a_normal; attribute vec4 a_color; +attribute vec4 a_emissive; uniform mat4 u_proj; uniform mat4 u_trans; @@ -8,6 +9,7 @@ uniform vec3 u_lightdir; uniform vec3 u_camdir; uniform vec3 u_campos; uniform vec3 u_ambientColor; +uniform float u_emissive; varying vec4 v_col; @@ -19,13 +21,18 @@ void main(){ //TODO this calculation is probably wrong vec3 lightReflect = normalize(reflect(a_normal, u_lightdir)); vec3 vertexEye = normalize(u_campos - (u_trans * a_position).xyz); + + float albedo = 1.0 - a_color.a; + float specularFactor = dot(vertexEye, lightReflect); if(specularFactor > 0.0){ - specular = vec3(1.0 * pow(specularFactor, 40.0)) * (1.0-a_color.a); + specular = vec3(1.0 * pow(specularFactor, 40.0)) * albedo; } vec3 norc = (u_ambientColor + specular) * (diffuse + vec3(clamp((dot(a_normal, u_lightdir) + 1.0) / 2.0, 0.0, 1.0))); - v_col = vec4(a_color.rgb, 1.0) * vec4(norc, 1.0); + float emissive = a_emissive.a * u_emissive * min(pow(max(0.0, (1.0 - norc.r) * 1.2), 3.0), 1.1); + + v_col = vec4(mix(a_color.rgb, a_emissive.rgb, emissive), 1.0) * vec4(mix(norc, vec3(1.0), emissive), 1.0); gl_Position = u_proj * u_trans * a_position; } diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index e2394a3c0d..ecaca32343 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -51,6 +51,8 @@ public class Vars implements Loadable{ public static final int minModGameVersion = 136; /** Min game version for java mods specifically - this is higher, as Java mods have more breaking changes. */ public static final int minJavaModGameVersion = 147; + /** If true, a button to view sector submission threads is shown. */ + public static boolean showSectorSubmissions = true; /** If true, the BE server list is always used. */ public static boolean forceBeServers = false; /** If true, mod code and scripts do not run. For internal testing only. This WILL break mods if enabled. */ diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 1b095760ca..2c950e8636 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -540,7 +540,7 @@ public class Pathfinder implements Runnable{ if(!targets.isEmpty()){ boolean any = false; for(Building other : targets){ - if((other.items != null && other.items.any()) || other.status() != BlockStatus.noInput){ + if(((other.items != null && other.items.any()) || other.status() != BlockStatus.noInput) && other.block.targetable){ out.add(other.tile.array()); any = true; } diff --git a/core/src/mindustry/ai/RtsAI.java b/core/src/mindustry/ai/RtsAI.java index 6c5292421b..21c2b8a843 100644 --- a/core/src/mindustry/ai/RtsAI.java +++ b/core/src/mindustry/ai/RtsAI.java @@ -141,6 +141,8 @@ public class RtsAI{ boolean handleSquad(Seq units, boolean noDefenders){ if(units.isEmpty()) return false; + boolean naval = units.first() instanceof WaterMovec; + float health = 0f, dps = 0f; float ax = 0f, ay = 0f; boolean targetAir = true, targetGround = true; @@ -165,7 +167,7 @@ public class RtsAI{ boolean defendingCore = false; //there is something to defend, see if it's worth the time - if(damaged.size > 0){ + if(damaged.size > 0 && !naval){ //TODO do the weights matter at all? //for(var build : damaged){ //float w = estimateStats(ax, ay, dps, health); @@ -251,7 +253,7 @@ public class RtsAI{ } } - var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0, units.first().isFlying()); + var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0, units.first().isFlying(), naval); if(build != null || anyDefend){ for(var unit : units){ @@ -274,7 +276,7 @@ public class RtsAI{ return anyDefend; } - @Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight, boolean air){ + @Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight, boolean air, boolean naval){ if(total < data.team.rules().rtsMinSquad) return null; //flag priority? @@ -282,8 +284,13 @@ public class RtsAI{ //2. factory //3. core targets.clear(); - for(var flag : flags){ - targets.addAll(Vars.indexer.getEnemy(data.team, flag)); + if(naval){ + //naval units can only target enemy cores, because those are assumed to always be reachable. other blocks may not be! + targets.addAll(Vars.indexer.getEnemy(data.team, BlockFlag.core)); + }else{ + for(var flag : flags){ + targets.addAll(Vars.indexer.getEnemy(data.team, flag)); + } } targets.removeAll(b -> assignedTargets.contains(b.id) || invalidTarget.contains(b.pos())); diff --git a/core/src/mindustry/ai/UnitGroup.java b/core/src/mindustry/ai/UnitGroup.java index 0c0366cf29..b79fde2099 100644 --- a/core/src/mindustry/ai/UnitGroup.java +++ b/core/src/mindustry/ai/UnitGroup.java @@ -158,7 +158,7 @@ public class UnitGroup{ } private void updateRaycast(int index, Vec2 dest, Vec2 v1){ - if(collisionLayer != PhysicsProcess.layerFlying){ + if(collisionLayer != PhysicsProcess.layerFlying && originalPositions != null && positions != null){ //coordinates in world space float diff --git a/core/src/mindustry/ai/types/FlyingAI.java b/core/src/mindustry/ai/types/FlyingAI.java index a412c6f4da..c328b41b41 100644 --- a/core/src/mindustry/ai/types/FlyingAI.java +++ b/core/src/mindustry/ai/types/FlyingAI.java @@ -41,7 +41,7 @@ public class FlyingAI extends AIController{ Building closest = null; float cdist = 0f; for(Building t : list){ - if((t.items != null && t.items.any()) || t.status() != BlockStatus.noInput){ + if(((t.items != null && t.items.any()) || t.status() != BlockStatus.noInput) && t.block.targetable){ float dst = t.dst2(x, y); if(closest == null || dst < cdist){ closest = t; diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index c3efb77c8d..a935307e19 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3437,7 +3437,6 @@ public class Blocks{ hitEffect = Fx.hitLancer; despawnEffect = Fx.none; status = StatusEffects.shocked; - statusDuration = 10f; hittable = false; lightColor = Color.white; collidesAir = false; @@ -3489,7 +3488,6 @@ public class Blocks{ despawnEffect = Fx.blastExplosion; status = StatusEffects.blasted; - statusDuration = 60f; hitColor = backColor = trailColor = Pal.blastAmmoBack; frontColor = Pal.blastAmmoFront; @@ -3915,7 +3913,6 @@ public class Blocks{ collidesGround = true; status = StatusEffects.blasted; - statusDuration = 60f; backColor = hitColor = trailColor = Pal.blastAmmoBack; frontColor = Pal.blastAmmoFront; @@ -4385,6 +4382,7 @@ public class Blocks{ targetInterval = 5f; newTargetInterval = 30f; targetUnderBlocks = false; + shootY = 8f; float r = range = 130f; @@ -4421,7 +4419,6 @@ public class Blocks{ ); scaledHealth = 210; - shootY = 7f; size = 3; researchCost = with(Items.tungsten, 400, Items.silicon, 400, Items.oxide, 80, Items.beryllium, 800); @@ -5455,7 +5452,6 @@ public class Blocks{ hitEffect = Fx.hitLancer; despawnEffect = Fx.none; status = StatusEffects.shocked; - statusDuration = 10f; hittable = false; lightColor = Color.white; buildingDamageMultiplier = 0.25f; diff --git a/core/src/mindustry/content/SectorPresets.java b/core/src/mindustry/content/SectorPresets.java index 066afe3241..c1c387e74e 100644 --- a/core/src/mindustry/content/SectorPresets.java +++ b/core/src/mindustry/content/SectorPresets.java @@ -111,7 +111,7 @@ public class SectorPresets{ }}; fungalPass = new SectorPreset("fungalPass", serpulo, 21){{ - difficulty = 4; + difficulty = 2; }}; infestedCanyons = new SectorPreset("infestedCanyons", serpulo, 210){{ diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 6e34ac4264..113193b15a 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -3829,8 +3829,10 @@ public class UnitTypes{ engineSize = 4.8f; engineOffset = 61 / 4f; + range = 4.3f * 60f * 1.4f; abilities.add(new SuppressionFieldAbility(){{ + reload = 60f * 8f; orbRadius = 5.3f; y = 1f; }}); @@ -3846,36 +3848,59 @@ public class UnitTypes{ recoil = 1f; rotationLimit = 60f; - bullet = new BulletType(){{ + bullet = new BasicBulletType(4.3f, 70f, "missile-large"){{ shootEffect = Fx.shootBig; smokeEffect = Fx.shootBigSmoke2; shake = 1f; - speed = 0f; + lifetime = 60 * 0.496f; + rangeOverride = 361.2f; + followAimSpeed = 5f; + + width = 12f; + height = 22f; + hitSize = 7f; + hitColor = backColor = trailColor = Pal.sapBulletBack; + trailWidth = 3f; + trailLength = 12; + hitEffect = despawnEffect = Fx.hitBulletColor; + keepVelocity = false; + collidesGround = true; collidesAir = false; - spawnUnit = new MissileUnitType("quell-missile"){{ - targetAir = false; - speed = 4.3f; - maxRange = 6f; - lifetime = 60f * 1.4f; - outlineColor = Pal.darkOutline; - engineColor = trailColor = Pal.sapBulletBack; - engineLayer = Layer.effect; - health = 45; - loopSoundVolume = 0.1f; + //workaround to get the missile to behave like in spawnUnit while still spawning on death + fragRandomSpread = 0; + fragBullets = 1; + fragVelocityMin = 1f; + fragOffsetMax = 1f; - weapons.add(new Weapon(){{ - shootSound = Sounds.none; - shootCone = 360f; - mirror = false; - reload = 1f; - shootOnDeath = true; - bullet = new ExplosionBulletType(110f, 25f){{ - shootEffect = Fx.massiveExplosion; - collidesAir = false; - }}; - }}); + fragBullet = new BulletType(){{ + speed = 0f; + keepVelocity = false; + collidesAir = false; + spawnUnit = new MissileUnitType("quell-missile"){{ + targetAir = false; + speed = 4.3f; + maxRange = 6f; + lifetime = 60f * (1.4f - 0.496f); + outlineColor = Pal.darkOutline; + engineColor = trailColor = Pal.sapBulletBack; + engineLayer = Layer.effect; + health = 45; + loopSoundVolume = 0.1f; + + weapons.add(new Weapon() {{ + shootSound = Sounds.none; + shootCone = 360f; + mirror = false; + reload = 1f; + shootOnDeath = true; + bullet = new ExplosionBulletType(110f, 25f) {{ + shootEffect = Fx.massiveExplosion; + collidesAir = false; + }}; + }}); + }}; }}; }}; }}); @@ -3909,6 +3934,8 @@ public class UnitTypes{ int parts = 10; abilities.add(new SuppressionFieldAbility(){{ + reload = 60 * 15f; + range = 320f; orbRadius = orbRad; particleSize = partRad; y = 10f; diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index f9f56cf13b..3185bc131a 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -156,6 +156,16 @@ public class Logic implements ApplicationListener{ if(!net.client() && e.sector == state.getSector() && e.sector.isBeingPlayed()){ state.rules.waveTeam.data().destroyToDerelict(); } + + if(!net.client() && e.sector.planet.generator != null){ + e.sector.planet.generator.onSectorCaptured(e.sector); + } + }); + + Events.on(SectorLoseEvent.class, e -> { + if(!net.client() && e.sector.planet.generator != null){ + e.sector.planet.generator.onSectorLost(e.sector); + } }); Events.on(BlockDestroyEvent.class, e -> { @@ -462,7 +472,7 @@ public class Logic implements ApplicationListener{ if(rules.fillItems && data.cores.size > 0){ var core = data.cores.first(); content.items().each(i -> { - if(i.isOnPlanet(Vars.state.getPlanet())){ + if(i.isOnPlanet(Vars.state.getPlanet()) && !i.isHidden()){ core.items.set(i, core.getMaximumAccepted(i)); } }); diff --git a/core/src/mindustry/editor/DrawOperation.java b/core/src/mindustry/editor/DrawOperation.java index a49b09525e..6ef2d2e3b4 100755 --- a/core/src/mindustry/editor/DrawOperation.java +++ b/core/src/mindustry/editor/DrawOperation.java @@ -64,6 +64,9 @@ public class DrawOperation{ Block block = content.block(to); tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation); + if(tile.build != null){ + tile.build.enabled = true; + } tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y)); }else if(type == OpType.rotation.ordinal()){ diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 767a7ace78..d75098db20 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -222,38 +222,38 @@ public class Damage{ public static float collideLaser(Bullet b, float length, boolean large, boolean laser, int pierceCap){ float resultLength = findPierceLength(b, pierceCap, laser, length); - collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap); + collideLine(b, b.team, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap); b.fdata = resultLength; return resultLength; } - public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){ - collideLine(hitter, team, effect, x, y, angle, length, false); + public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length){ + collideLine(hitter, team, x, y, angle, length, false); } /** * Damages entities in a line. * Only enemies of the specified team are damaged. */ - public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){ - collideLine(hitter, team, effect, x, y, angle, length, large, true); + public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large){ + collideLine(hitter, team, x, y, angle, length, large, true); } /** * Damages entities in a line. * Only enemies of the specified team are damaged. */ - public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser){ - collideLine(hitter, team, effect, x, y, angle, length, large, laser, -1); + public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser){ + collideLine(hitter, team, x, y, angle, length, large, laser, -1); } /** * Damages entities in a line. * Only enemies of the specified team are damaged. */ - public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){ + public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){ length = findLength(hitter, length, laser, pierceCap); hitter.fdata = length; @@ -545,8 +545,10 @@ public class Damage{ tileDamage(team, x, y, baseRadius, damage, null); } - public static void tileDamage(Team team, int x, int y, float baseRadius, float damage, @Nullable Bullet source){ + public static void tileDamage(Team team, int tx, int ty, float baseRadius, float damage, @Nullable Bullet source){ Time.run(0f, () -> { + int x = Mathf.clamp(tx, -100, world.width() + 100), y = Mathf.clamp(ty, -100, world.height() + 100); + var in = world.build(x, y); //spawned inside a multiblock. this means that damage needs to be dealt directly. //why? because otherwise the building would absorb everything in one cell, which means much less damage than a nearby explosion. diff --git a/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java index be69a45797..9785ac6597 100644 --- a/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java +++ b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java @@ -17,6 +17,7 @@ public class SuppressionFieldAbility extends Ability{ protected static Rand rand = new Rand(); public float reload = 60f * 1.5f; + public float maxDelay = 60f * 1.5f; public float range = 200f; public float orbRadius = 4.1f, orbMidScl = 0.33f, orbSinScl = 8f, orbSinMag = 1f; @@ -55,9 +56,9 @@ public class SuppressionFieldAbility extends Ability{ public void update(Unit unit){ if(!active) return; - if((timer += Time.delta) >= reload){ + if((timer += Time.delta) >= maxDelay){ Tmp.v1.set(x, y).rotate(unit.rotation - 90f).add(unit); - Damage.applySuppression(unit.team, Tmp.v1.x, Tmp.v1.y, range, reload, reload, applyParticleChance, unit, effectColor); + Damage.applySuppression(unit.team, Tmp.v1.x, Tmp.v1.y, range, reload, maxDelay, applyParticleChance, unit, effectColor); timer = 0f; } } diff --git a/core/src/mindustry/entities/bullet/ContinuousBulletType.java b/core/src/mindustry/entities/bullet/ContinuousBulletType.java index a9ea18876b..9dfb85ad25 100644 --- a/core/src/mindustry/entities/bullet/ContinuousBulletType.java +++ b/core/src/mindustry/entities/bullet/ContinuousBulletType.java @@ -85,7 +85,7 @@ public class ContinuousBulletType extends BulletType{ if(timescaleDamage && b.owner instanceof Building build){ b.damage *= build.timeScale(); } - Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), currentLength(b), largeHit, laserAbsorb, pierceCap); + Damage.collideLine(b, b.team, b.x, b.y, b.rotation(), currentLength(b), largeHit, laserAbsorb, pierceCap); b.damage = damage; } diff --git a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java index df7cebd304..f19e8f7a37 100644 --- a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java +++ b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java @@ -55,13 +55,13 @@ public class ContinuousLaserBulletType extends ContinuousBulletType{ float ellipseLenScl = Mathf.lerp(1 - i / (float)(colors.length), 1f, pointyScaling); Lines.stroke(stroke); - Lines.lineAngle(b.x, b.y, rot, realLength - frontLength, false); + Lines.lineAngle(b.x, b.y, rot, Math.max(0, realLength - frontLength), false); //back ellipse Drawf.flameFront(b.x, b.y, divisions, rot + 180f, backLength, stroke / 2f); //front ellipse - Tmp.v1.trnsExact(rot, realLength - frontLength); + Tmp.v1.trnsExact(rot, Math.max(0, realLength - frontLength)); Drawf.flameFront(b.x + Tmp.v1.x, b.y + Tmp.v1.y, divisions, rot, frontLength * ellipseLenScl, stroke / 2f); } diff --git a/core/src/mindustry/entities/bullet/MassDriverBolt.java b/core/src/mindustry/entities/bullet/MassDriverBolt.java index b9a0526e40..67b581a34b 100644 --- a/core/src/mindustry/entities/bullet/MassDriverBolt.java +++ b/core/src/mindustry/entities/bullet/MassDriverBolt.java @@ -4,8 +4,10 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import mindustry.content.*; +import mindustry.entities.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.type.*; import mindustry.world.blocks.distribution.MassDriver.*; import static mindustry.Vars.*; @@ -89,5 +91,17 @@ public class MassDriverBolt extends BasicBulletType{ public void hit(Bullet b, float hitx, float hity){ super.hit(b, hitx, hity); despawned(b); + if(b.data() instanceof DriverBulletData data){ + float explosiveness = 0f; + float flammability = 0f; + float power = 0f; + for(int i = 0; i < data.items.length; i++){ + Item item = content.item(i); + explosiveness += item.explosiveness * data.items[i]; + flammability += item.flammability * data.items[i]; + power += item.charge * Mathf.pow(data.items[i], 1.1f) * 25f; + } + Damage.dynamicExplosion(b.x, b.y, flammability / 10f, explosiveness / 10f, power, 1f, state.rules.damageExplosions); + } } } diff --git a/core/src/mindustry/entities/bullet/RailBulletType.java b/core/src/mindustry/entities/bullet/RailBulletType.java index dfa41520f4..ea5f336d22 100644 --- a/core/src/mindustry/entities/bullet/RailBulletType.java +++ b/core/src/mindustry/entities/bullet/RailBulletType.java @@ -59,7 +59,7 @@ public class RailBulletType extends BulletType{ super.init(b); b.fdata = length; - Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), length, false, false, pierceCap); + Damage.collideLine(b, b.team, b.x, b.y, b.rotation(), length, false, false, pierceCap); float resultLen = b.fdata; Vec2 nor = Tmp.v1.trns(b.rotation(), 1f).nor(); diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 950226b0c1..68a22f48fc 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -35,7 +35,7 @@ import static mindustry.logic.GlobalVars.*; @Component(base = true) abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{ private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2(); - static final float warpDst = 20f; + static final float warpDst = 8f; @Import boolean dead, disarmed; @Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier; @@ -643,11 +643,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I //repel unit out of bounds if(x < left) dx += (-(x - left)/warpDst); if(y < bot) dy += (-(y - bot)/warpDst); - if(x > right) dx -= (x - right)/warpDst; - if(y > top) dy -= (y - top)/warpDst; + if(x > right - tilesize) dx -= (x - (right - tilesize))/warpDst; + if(y > top - tilesize) dy -= (y - (top - tilesize))/warpDst; velAddNet(dx * Time.delta, dy * Time.delta); - float margin = tilesize * 2f; + float margin = tilesize * 1f; x = Mathf.clamp(x, left - margin, right - tilesize + margin); y = Mathf.clamp(y, bot - margin, top - tilesize + margin); } diff --git a/core/src/mindustry/entities/units/BuildPlan.java b/core/src/mindustry/entities/units/BuildPlan.java index f9930f707f..8dfb53de7a 100644 --- a/core/src/mindustry/entities/units/BuildPlan.java +++ b/core/src/mindustry/entities/units/BuildPlan.java @@ -21,8 +21,6 @@ public class BuildPlan implements Position, QuadTreeObject{ public boolean breaking; /** Config int. Not used unless hasConfig is true.*/ public Object config; - /** Original position, only used in schematics.*/ - public int originalX, originalY, originalWidth, originalHeight; /** Last progress.*/ public float progress; @@ -65,6 +63,7 @@ public class BuildPlan implements Position, QuadTreeObject{ public BuildPlan(){ } + public boolean placeable(Team team){ return Build.validPlace(block, team, x, y, rotation); } @@ -111,22 +110,12 @@ public class BuildPlan implements Position, QuadTreeObject{ copy.block = block; copy.breaking = breaking; copy.config = config; - copy.originalX = originalX; - copy.originalY = originalY; copy.progress = progress; copy.initialized = initialized; copy.animScale = animScale; return copy; } - public BuildPlan original(int x, int y, int originalWidth, int originalHeight){ - originalX = x; - originalY = y; - this.originalWidth = originalWidth; - this.originalHeight = originalHeight; - return this; - } - public Rect bounds(Rect rect){ if(breaking){ return rect.set(-100f, -100f, 0f, 0f); diff --git a/core/src/mindustry/game/FogControl.java b/core/src/mindustry/game/FogControl.java index 8d67a35d7f..4ef68bf44d 100644 --- a/core/src/mindustry/game/FogControl.java +++ b/core/src/mindustry/game/FogControl.java @@ -119,8 +119,7 @@ public final class FogControl implements CustomChunk{ var data = data(team); if(data == null) return false; - if(x < 0 || y < 0 || x >= ww || y >= wh) return false; - return data.read.get(x + y * ww); + return data.read.get(Mathf.clamp(x, 0, ww - 1) + Mathf.clamp(y, 0, wh - 1) * ww); } public void resetFog(){ diff --git a/core/src/mindustry/game/MapObjectives.java b/core/src/mindustry/game/MapObjectives.java index 7c772e0a21..31fac3c1e0 100644 --- a/core/src/mindustry/game/MapObjectives.java +++ b/core/src/mindustry/game/MapObjectives.java @@ -3,6 +3,7 @@ package mindustry.game; import arc.*; import arc.func.*; import arc.graphics.*; +import arc.graphics.Texture.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; @@ -98,7 +99,7 @@ public class MapObjectives implements Iterable, Eachable prov) { + public static void registerLegacyMarker(String name, Prov prov){ Class type = prov.get().getClass(); markerNameToType.put(name, prov); @@ -663,7 +664,7 @@ public class MapObjectives implements Iterable, Eachable, Eachable, Eachable fontSize = (float)p1; case textHeight -> textHeight = (float)p1; - case labelFlags -> { - if(!Mathf.equal((float)p1, 0f)){ - flags |= WorldLabel.flagBackground; - }else{ - flags &= ~WorldLabel.flagBackground; - } - } + case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f)); + case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f)); case radius -> radius = (float)p1; case rotation -> rotation = (float)p1; case color -> color.fromDouble(p1); @@ -838,13 +834,7 @@ public class MapObjectives implements Iterable, Eachable { - if(!Mathf.equal((float)p2, 0f)){ - flags |= WorldLabel.flagOutline; - }else{ - flags &= ~WorldLabel.flagOutline; - } - } + case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f)); } } } @@ -944,7 +934,7 @@ public class MapObjectives implements Iterable, Eachable, Eachable radius = (float)p1; case stroke -> stroke = (float)p1; + case outline -> outline = !Mathf.equal((float)p1, 0f); case rotation -> rotation = (float)p1; case color -> color.fromDouble(p1); case shape -> sides = (int)p1; @@ -1025,25 +1016,14 @@ public class MapObjectives implements Iterable, Eachable fontSize = (float)p1; - case labelFlags -> { - if(!Mathf.equal((float)p1, 0f)){ - flags |= WorldLabel.flagBackground; - }else{ - flags &= ~WorldLabel.flagBackground; - } - } + case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f)); + case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f)); } } if(!Double.isNaN(p2)){ switch(type){ - case labelFlags -> { - if(!Mathf.equal((float)p2, 0f)){ - flags |= WorldLabel.flagOutline; - }else{ - flags &= ~WorldLabel.flagOutline; - } - } + case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f)); } } } @@ -1101,6 +1081,7 @@ public class MapObjectives implements Iterable, Eachable endPos.x = (float)p1 * tilesize; case stroke -> stroke = (float)p1; case color -> color1.set(color2.fromDouble(p1)); + case outline -> outline = !Mathf.equal((float)p1, 0f); } } @@ -1111,7 +1092,7 @@ public class MapObjectives implements Iterable, Eachable ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).x = (float)p2 * tilesize; case colori -> ((int)p1 == 0 ? color1 : (int)p1 == 1 ? color2 : Tmp.c1).fromDouble(p2); } @@ -1199,7 +1180,7 @@ public class MapObjectives implements Iterable, Eachable, Eachable, Eachable= 0 && i < 4){ if(!Double.isNaN(x)) vertices[i * 6] = (float)x * tilesize; @@ -1290,11 +1272,16 @@ public class MapObjectives implements Iterable, Eachable= 0 && i < 4){ if(fetchedRegion == null) setTexture(textureName); - if(!Double.isNaN(u)) vertices[i * 6 + 3] = Mathf.map(Mathf.clamp((float)u), fetchedRegion.u, fetchedRegion.u2); - if(!Double.isNaN(v)) vertices[i * 6 + 4] = Mathf.map(1 - Mathf.clamp((float)v), fetchedRegion.v, fetchedRegion.v2); + if(!Double.isNaN(u)){ + boolean clampU = fetchedRegion.texture.getUWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getUWrap() != TextureWrap.repeat; + vertices[i * 6 + 3] = Mathf.map(clampU ? Mathf.clamp((float)u) : (float)u, fetchedRegion.u, fetchedRegion.u2); + } + if(!Double.isNaN(v)){ + boolean clampV = fetchedRegion.texture.getVWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getVWrap() != TextureWrap.repeat; + vertices[i * 6 + 4] = Mathf.map(clampV ? 1 - Mathf.clamp((float)v) : 1 - (float)v, fetchedRegion.v, fetchedRegion.v2); + } } } - } private static void lookupRegion(String name, TextureRegion out){ diff --git a/core/src/mindustry/game/Saves.java b/core/src/mindustry/game/Saves.java index f01be9db6f..720fc0fe8a 100644 --- a/core/src/mindustry/game/Saves.java +++ b/core/src/mindustry/game/Saves.java @@ -72,7 +72,32 @@ public class Saves{ lastSectorSave = saves.find(s -> s.isSector() && s.getName().equals(Core.settings.getString("last-sector-save", ""))); - ObjectSet infoToClear = new ObjectSet<>(), remapped = new ObjectSet<>(); + class Remap{ + //file in the temp folder + Fi sourceFile; + //slot of source sector to move file for + SaveSlot slot; + Sector sourceSector; + //sector info from source sector to move into + SectorInfo sourceInfo; + + //file to copy to + Fi destFile; + //destination sector to move to + Sector destSector; + + Remap(SaveSlot slot, Fi sourceFile, Sector sourceSector, SectorInfo sourceInfo, Fi destFile, Sector destSector){ + this.slot = slot; + this.sourceFile = sourceFile; + this.sourceSector = sourceSector; + this.sourceInfo = sourceInfo; + this.destFile = destFile; + this.destSector = destSector; + } + } + + Seq remaps = new Seq<>(); + ObjectSet remapped = new ObjectSet<>(); //automatically assign sector save slots for(SaveSlot slot : saves){ @@ -102,22 +127,13 @@ public class Saves{ if(!slot.file.equals(getSectorFile(remapTarget))){ Log.info("Remapping sector: @ -> @ (@)", sector.id, remapTarget.id, remapTarget.preset); - sector.loadInfo(); - //overwrite the target sector's info with the save's info - Core.settings.putJson(remapTarget.planet.name + "-s-" + remapTarget.id + "-info", sector.info); - remapTarget.loadInfo(); - - //queue a clear of the sector that had its data moved - infoToClear.add(sector); - //add to the remapped list (if it was remapped, don't clear it!) - remapped.add(remapTarget); - - remapTarget.save = slot; try{ - Fi target = getSectorFile(remapTarget); - //move over save file - slot.file.moveTo(target); - slot.file = target; + SectorInfo info = Core.settings.getJson(sector.planet.name + "-s-" + sector.id + "-info", SectorInfo.class, SectorInfo::new); + Fi tmpRemapFile = saveDirectory.child("remap_" + sector.planet.name + "_" + sector.id + "." + saveExtension); + slot.file.moveTo(tmpRemapFile); + + remaps.add(new Remap(slot, tmpRemapFile, sector, info, getSectorFile(remapTarget), remapTarget)); + remapped.add(remapTarget); }catch(Exception e){ Log.err("Failed to move sector files when remapping: " + sector.id + " -> " + remapTarget.id, e); } @@ -125,6 +141,7 @@ public class Saves{ remapTarget.save = slot; slot.meta.rules.sector = remapTarget; + }else{ if(sector.save != null){ Log.warn("Sector @ has two corresponding saves: @ and @", sector, sector.save.file, slot.file); @@ -134,10 +151,27 @@ public class Saves{ } } - for(var sector : infoToClear){ - if(!remapped.contains(sector)){ - sector.clearInfo(); - } + //process remaps later to allow swaps of sectors + for(var remap : remaps){ + var remapTarget = remap.destSector; + + //overwrite the target sector's info with the save's info + Core.settings.putJson(remapTarget.planet.name + "-s-" + remapTarget.id + "-info", remap.sourceInfo); + remapTarget.loadInfo(); + + remapTarget.save = remap.slot; + try{ + //move file from tmp directory back into the correct location + remap.sourceFile.moveTo(remap.destFile); + remap.slot.file = remap.destFile; + }catch(Exception e){ + Log.err("Failed to move back sector files when remapping: " + remap.sourceSector.id + " -> " + remapTarget.id, e); + } + + //clear the info, assuming it wasn't a sector that got mapped to + if(!remapped.contains(remap.sourceSector)){ + remap.sourceSector.clearInfo(); + } } } diff --git a/core/src/mindustry/game/Schematics.java b/core/src/mindustry/game/Schematics.java index acab4144c1..93b85a591a 100644 --- a/core/src/mindustry/game/Schematics.java +++ b/core/src/mindustry/game/Schematics.java @@ -97,7 +97,7 @@ public class Schematics implements Loadable{ all.sort(); - if(shadowBuffer == null){ + if(shadowBuffer == null && !headless){ Core.app.post(() -> shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 8, maxSchematicSize + padding + 8)); } } @@ -275,7 +275,7 @@ public class Schematics implements Loadable{ /** Creates an array of build plans from a schematic's data, centered on the provided x+y coordinates. */ public Seq toPlans(Schematic schem, int x, int y){ - return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config).original(t.x, t.y, schem.width, schem.height)) + return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config)) .removeAll(s -> (!s.block.isVisible() && !(s.block instanceof CoreBlock)) || !s.block.unlockedNow()).sort(Structs.comparingInt(s -> -s.block.schematicPriority)); } diff --git a/core/src/mindustry/game/SectorInfo.java b/core/src/mindustry/game/SectorInfo.java index f9ce1acb9c..4366a46d8a 100644 --- a/core/src/mindustry/game/SectorInfo.java +++ b/core/src/mindustry/game/SectorInfo.java @@ -73,6 +73,8 @@ public class SectorInfo{ public float secondsPassed; /** How many minutes this sector has been captured. */ public float minutesCaptured; + /** Light coverage in terms of radius. */ + public float lightCoverage; /** Display name. */ public @Nullable String name; /** Displayed icon. */ @@ -225,6 +227,15 @@ public class SectorInfo{ damage = 0; hasSpawns = spawner.countSpawns() > 0; + lightCoverage = 0f; + for(var build : state.rules.defaultTeam.data().buildings){ + if(build.block.emitLight){ + lightCoverage += build.block.lightRadius * build.efficiency; + } + } + + lightCoverage += state.rules.defaultTeam.data().units.sumf(u -> u.type.lightRadius/2f); + //cap production at raw production. production.each((item, stat) -> { stat.mean = Math.min(stat.mean, rawProduction.get(item, ExportStat::new).mean); @@ -242,6 +253,10 @@ public class SectorInfo{ if(sector.planet.allowWaveSimulation){ SectorDamage.writeParameters(sector); } + + if(sector.planet.generator != null){ + sector.planet.generator.beforeSaveWrite(sector); + } } /** Update averages of various stats, updates some special sector logic. diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index 375ed528d8..2fc005ca68 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -322,6 +322,13 @@ public class Universe{ return net.client() ? netSeconds : seconds; } + public void setSeconds(float seconds){ + this.seconds = (int)seconds; + this.secondCounter = seconds - this.seconds; + + save(); + } + public float secondsf(){ return seconds() + secondCounter; } diff --git a/core/src/mindustry/graphics/LoadRenderer.java b/core/src/mindustry/graphics/LoadRenderer.java index c0baa1ae7a..da6e58c0ed 100644 --- a/core/src/mindustry/graphics/LoadRenderer.java +++ b/core/src/mindustry/graphics/LoadRenderer.java @@ -32,7 +32,7 @@ public class LoadRenderer implements Disposable{ private float testprogress = 0f; private StringBuilder assetText = new StringBuilder(); private Bar[] bars; - private Mesh mesh = MeshBuilder.buildHex(colorRed, 2, true, 1f); + private Mesh mesh = MeshBuilder.buildPlanetGrid(PlanetGrid.create(2), colorRed, 1f); private Camera3D cam = new Camera3D(); private int lastLength = -1; private FxProcessor fx; diff --git a/core/src/mindustry/graphics/Shaders.java b/core/src/mindustry/graphics/Shaders.java index 6351b64873..de6bf9af1d 100644 --- a/core/src/mindustry/graphics/Shaders.java +++ b/core/src/mindustry/graphics/Shaders.java @@ -109,6 +109,7 @@ public class Shaders{ public Color ambientColor = Color.white.cpy(); public Vec3 camDir = new Vec3(); public Vec3 camPos = new Vec3(); + public boolean emissive; public Planet planet; public PlanetShader(){ @@ -123,6 +124,7 @@ public class Shaders{ setUniformf("u_ambientColor", ambientColor.r, ambientColor.g, ambientColor.b); setUniformf("u_camdir", camDir); setUniformf("u_campos", renderer.planets.cam.position); + setUniformf("u_emissive", emissive ? 1f : 0f); } } diff --git a/core/src/mindustry/graphics/g3d/GenericMesh.java b/core/src/mindustry/graphics/g3d/GenericMesh.java index d4198f5d9a..c6e57f4a10 100644 --- a/core/src/mindustry/graphics/g3d/GenericMesh.java +++ b/core/src/mindustry/graphics/g3d/GenericMesh.java @@ -1,7 +1,8 @@ package mindustry.graphics.g3d; import arc.math.geom.*; +import arc.util.*; -public interface GenericMesh{ +public interface GenericMesh extends Disposable{ void render(PlanetParams params, Mat3D projection, Mat3D transform); } diff --git a/core/src/mindustry/graphics/g3d/HexMesh.java b/core/src/mindustry/graphics/g3d/HexMesh.java index bf5af1366e..02a35973f0 100644 --- a/core/src/mindustry/graphics/g3d/HexMesh.java +++ b/core/src/mindustry/graphics/g3d/HexMesh.java @@ -8,11 +8,11 @@ import mindustry.type.*; public class HexMesh extends PlanetMesh{ public HexMesh(Planet planet, int divisions){ - super(planet, MeshBuilder.buildHex(planet.generator, divisions, false, planet.radius, 0.2f), Shaders.planet); + super(planet, MeshBuilder.buildHex(planet.generator, divisions, planet.radius, 0.2f), Shaders.planet); } public HexMesh(Planet planet, HexMesher mesher, int divisions, Shader shader){ - super(planet, MeshBuilder.buildHex(mesher, divisions, false, planet.radius, 0.2f), shader); + super(planet, MeshBuilder.buildHex(mesher, divisions, planet.radius, 0.2f), shader); } public HexMesh(){ @@ -21,6 +21,7 @@ public class HexMesh extends PlanetMesh{ @Override public void preRender(PlanetParams params){ Shaders.planet.planet = planet; + Shaders.planet.emissive = planet.generator != null && planet.generator.isEmissive(); Shaders.planet.lightDir.set(planet.solarSystem.position).sub(planet.position).rotate(Vec3.Y, planet.getRotation()).nor(); Shaders.planet.ambientColor.set(planet.solarSystem.lightColor); } diff --git a/core/src/mindustry/graphics/g3d/HexMesher.java b/core/src/mindustry/graphics/g3d/HexMesher.java index 034f324b09..22a98184ea 100644 --- a/core/src/mindustry/graphics/g3d/HexMesher.java +++ b/core/src/mindustry/graphics/g3d/HexMesher.java @@ -5,8 +5,23 @@ import arc.math.geom.*; /** Defines color and height for a planet mesh. */ public interface HexMesher{ - float getHeight(Vec3 position); - Color getColor(Vec3 position); + + default float getHeight(Vec3 position){ + return 0f; + } + + default void getColor(Vec3 position, Color out){ + + } + + default void getEmissiveColor(Vec3 position, Color out){ + + } + + default boolean isEmissive(){ + return false; + } + default boolean skip(Vec3 position){ return false; } diff --git a/core/src/mindustry/graphics/g3d/HexSkyMesh.java b/core/src/mindustry/graphics/g3d/HexSkyMesh.java index 434e22818f..b2a3e0ac80 100644 --- a/core/src/mindustry/graphics/g3d/HexSkyMesh.java +++ b/core/src/mindustry/graphics/g3d/HexSkyMesh.java @@ -21,15 +21,15 @@ public class HexSkyMesh extends PlanetMesh{ } @Override - public Color getColor(Vec3 position){ - return color; + public void getColor(Vec3 position, Color out){ + out.set(color); } @Override public boolean skip(Vec3 position){ return Simplex.noise3d(7 + seed, octaves, persistence, scl, position.x, position.y * 3f, position.z) >= thresh; } - }, divisions, false, planet.radius, radius), Shaders.clouds); + }, divisions, planet.radius, radius), Shaders.clouds); this.speed = speed; } diff --git a/core/src/mindustry/graphics/g3d/MatMesh.java b/core/src/mindustry/graphics/g3d/MatMesh.java index 5fd064dfda..2595b15ecb 100644 --- a/core/src/mindustry/graphics/g3d/MatMesh.java +++ b/core/src/mindustry/graphics/g3d/MatMesh.java @@ -19,4 +19,9 @@ public class MatMesh implements GenericMesh{ public void render(PlanetParams params, Mat3D projection, Mat3D transform){ mesh.render(params, projection, tmp.set(transform).mul(mat)); } + + @Override + public void dispose(){ + mesh.dispose(); + } } diff --git a/core/src/mindustry/graphics/g3d/MeshBuilder.java b/core/src/mindustry/graphics/g3d/MeshBuilder.java index 6bc0688552..cb9be7b418 100644 --- a/core/src/mindustry/graphics/g3d/MeshBuilder.java +++ b/core/src/mindustry/graphics/g3d/MeshBuilder.java @@ -1,56 +1,72 @@ package mindustry.graphics.g3d; +import arc.*; import arc.graphics.*; import arc.math.geom.*; +import arc.struct.*; import mindustry.graphics.g3d.PlanetGrid.*; import mindustry.maps.generators.*; public class MeshBuilder{ - private static final Vec3 v1 = new Vec3(), v2 = new Vec3(), v3 = new Vec3(), v4 = new Vec3(); - private static final float[] floats = new float[3 + 3 + 1]; - private static Mesh mesh; - - public static Mesh buildIcosphere(int divisions, float radius, Color color){ - begin(20 * (2 << (2 * divisions - 1)) * 3); + private static final boolean gl30 = Core.gl30 != null; + private static volatile float[] tmpHeights = new float[14580]; //highest amount of corners in vanilla + /** Note that the resulting icosphere does not have normals or a color. */ + public static Mesh buildIcosphere(int divisions, float radius){ MeshResult result = Icosphere.create(divisions); - for(int i = 0; i < result.indices.size; i+= 3){ - v1.set(result.vertices.items, result.indices.items[i] * 3).setLength(radius); - v2.set(result.vertices.items, result.indices.items[i + 1] * 3).setLength(radius); - v3.set(result.vertices.items, result.indices.items[i + 2] * 3).setLength(radius); - verts(v1, v3, v2, normal(v1, v2, v3).scl(-1f), color); + Mesh mesh = begin(result.vertices.size / 3, result.indices.size, false, false); + + if(result.vertices.size >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder."); + + float[] items = result.vertices.items; + for(int i = 0; i < result.vertices.size; i ++){ + items[i] *= radius; } - return end(); - } + mesh.getVerticesBuffer().put(items, 0, result.vertices.size); - public static Mesh buildIcosphere(int divisions, float radius){ - return buildIcosphere(divisions, radius, Color.white); + short[] indices = new short[result.indices.size]; + for(int i = 0; i < result.indices.size; i++){ + indices[i] = (short)result.indices.items[i]; + } + + mesh.getIndicesBuffer().put(indices); + + return end(mesh); } public static Mesh buildPlanetGrid(PlanetGrid grid, Color color, float scale){ - int total = 0; - for(Ptile tile : grid.tiles){ - total += tile.corners.length * 2; - } + Mesh mesh = begin(grid.tiles.length * 12, 0, false, false); + + float col = color.toFloatBits(); + float[] floats = new float[8]; - begin(total); for(Ptile tile : grid.tiles){ Corner[] c = tile.corners; - for(int i = 0; i < c.length; i++){ - Vec3 a = v1.set(c[i].v).scl(scale); - Vec3 b = v2.set(c[(i + 1) % c.length].v).scl(scale); - vert(a, Vec3.Z, color); - vert(b, Vec3.Z, color); + for(int i = 0; i < c.length; i++){ + Vec3 v1 = c[i].v; + Vec3 v2 = c[(i + 1) % c.length].v; + + floats[0] = v1.x * scale; + floats[1] = v1.y * scale; + floats[2] = v1.z * scale; + floats[3] = col; + + floats[4] = v2.x * scale; + floats[5] = v2.y * scale; + floats[6] = v2.z * scale; + floats[7] = col; + + mesh.getVerticesBuffer().put(floats); } } - return end(); + return end(mesh); } - public static Mesh buildHex(Color color, int divisions, boolean lines, float radius){ + public static Mesh buildHex(Color color, int divisions, float radius){ return buildHex(new HexMesher(){ @Override public float getHeight(Vec3 position){ @@ -58,20 +74,46 @@ public class MeshBuilder{ } @Override - public Color getColor(Vec3 position){ - return color; + public void getColor(Vec3 position, Color out){ + out.set(color); } - }, divisions, lines, radius, 0); + }, divisions, radius, 0); } - public static Mesh buildHex(HexMesher mesher, int divisions, boolean lines, float radius, float intensity){ + //TODO: in principle this should not be synchronized, but I would rather not realloc tmpHeights every time, and it is unlikely that two planets will be reloading at the same time + public static synchronized Mesh buildHex(HexMesher mesher, int divisions, float radius, float intensity){ PlanetGrid grid = PlanetGrid.create(divisions); + //TODO: this is NOT thread safe, but in practice, it should never cause a problem if(mesher instanceof PlanetGenerator generator){ generator.seed = generator.baseSeed; } - begin(grid.tiles.length * 12); + boolean emit = mesher.isEmissive(); + + if(grid.tiles.length * 6 >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder."); + + Mesh mesh = begin(grid.tiles.length * 6, grid.tiles.length * 4 * 3, true, emit); + + float[] heights; + + if(tmpHeights == null || tmpHeights.length < grid.corners.length){ + heights = tmpHeights = new float[grid.corners.length]; + }else{ + heights = tmpHeights; + } + + //cache heights in an array to prevent redundant calls to getHeight + for(int i = 0; i < grid.corners.length; i++){ + heights[i] = (1f + mesher.getHeight(grid.corners[i].v) * intensity) * radius; + } + int position = 0; + + short[] shorts = new short[12]; + float[] floats = new float[3 + (gl30 ? 1 : 3) + 1 + (emit ? 1 : 0)]; + Vec3 nor = new Vec3(); + + Color tmpCol = new Color(); for(Ptile tile : grid.tiles){ if(mesher.skip(tile.v)){ @@ -80,81 +122,155 @@ public class MeshBuilder{ Corner[] c = tile.corners; - for(Corner corner : c){ - corner.v.setLength((1f + mesher.getHeight(v2.set(corner.v)) * intensity) * radius); + float + h1 = heights[c[0].id], + h2 = heights[c[2].id], + h3 = heights[c[4].id]; + + Vec3 + v1 = c[0].v, + v2 = c[2].v, + v3 = c[4].v; + + normal( + v1.x * h1, v1.y * h1, v1.z * h1, + v2.x * h2, v2.y * h2, v2.z * h2, + v3.x * h3, v3.y * h3, v3.z * h3, + nor); + + tmpCol.set(1f, 1f, 1f, 1f); + mesher.getColor(tile.v, tmpCol); + float color = tmpCol.toFloatBits(); + + float emissive = 0f; + + if(emit){ + tmpCol.set(0f, 0f, 0f, 0f); + mesher.getEmissiveColor(tile.v, tmpCol); + emissive = tmpCol.toFloatBits(); } - Vec3 nor = normal(c[0].v, c[2].v, c[4].v); - Color color = mesher.getColor(v2.set(tile.v)); + for(var corner : c){ + float height = heights[corner.id]; - if(lines){ - nor.set(1f, 1f, 1f); - - for(int i = 0; i < c.length; i++){ - Vec3 v1 = c[i].v; - Vec3 v2 = c[(i + 1) % c.length].v; - - vert(v1, nor, color); - vert(v2, nor, color); - } - }else{ - verts(c[0].v, c[1].v, c[2].v, nor, color); - verts(c[0].v, c[2].v, c[3].v, nor, color); - verts(c[0].v, c[3].v, c[4].v, nor, color); - - if(c.length > 5){ - verts(c[0].v, c[4].v, c[5].v, nor, color); - } + vert(mesh, floats, corner.v.x * height, corner.v.y * height, corner.v.z * height, nor, color, emissive); } - //restore mutated corners - for(Corner corner : c){ - corner.v.nor(); + shorts[0] = (short)(position); + shorts[1] = (short)(position + 1); + shorts[2] = (short)(position + 2); + + shorts[3] = (short)(position); + shorts[4] = (short)(position + 2); + shorts[5] = (short)(position + 3); + + shorts[6] = (short)(position); + shorts[7] = (short)(position + 3); + shorts[8] = (short)(position + 4); + + if(c.length > 5){ + shorts[9] = (short)(position); + shorts[10] = (short)(position + 4); + shorts[11] = (short)(position + 5); } + mesh.getIndicesBuffer().put(shorts, 0, c.length > 5 ? 12 : 9); + position += c.length; } - return end(); + return end(mesh); } - private static void begin(int count){ - mesh = new Mesh(true, count, 0, - VertexAttribute.position3, - VertexAttribute.normal, - VertexAttribute.color + private static Mesh begin(int vertices, int indices, boolean normal, boolean emissive){ + Seq attributes = Seq.with( + VertexAttribute.position3 ); + if(normal){ + //only GL30 supports GL_INT_2_10_10_10_REV + attributes.add(gl30 ? VertexAttribute.packedNormal : VertexAttribute.normal); + } + + attributes.add(VertexAttribute.color); + + if(emissive){ + attributes.add(new VertexAttribute(4, GL20.GL_UNSIGNED_BYTE, true, "a_emissive")); + } + + Mesh mesh = new Mesh(true, vertices, indices, attributes.toArray(VertexAttribute.class)); + mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().capacity()); mesh.getVerticesBuffer().position(0); + + if(indices > 0){ + mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().capacity()); + mesh.getIndicesBuffer().position(0); + } + + return mesh; } - private static Mesh end(){ - Mesh last = mesh; - last.getVerticesBuffer().limit(last.getVerticesBuffer().position()); - mesh = null; - return last; + private static Mesh end(Mesh mesh){ + mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().position()); + if(mesh.getNumIndices() > 0){ + mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().position()); + } + + return mesh; } - private static Vec3 normal(Vec3 v1, Vec3 v2, Vec3 v3){ - return v4.set(v2).sub(v1).crs(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z).nor(); + private static Vec3 normal(Vec3 v1, Vec3 v2, Vec3 v3, Vec3 out){ + return out.set(v2).sub(v1).crs(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z).nor(); } - private static void verts(Vec3 a, Vec3 b, Vec3 c, Vec3 normal, Color color){ - vert(a, normal, color); - vert(b, normal, color); - vert(c, normal, color); + private static void normal(float v1x, float v1y, float v1z, float v2x, float v2y, float v2z, float v3x, float v3y, float v3z, Vec3 out){ + float + x = v2x - v1x, + y = v2y - v1y, + z = v2z - v1z, + vx = v3x - v1x, + vy = v3y - v1y, + vz = v3z - v1z; + + float + cx = y * vz - z * vy, + cy = z * vx - x * vz, + cz = x * vy - y * vx; + + out.set(cx, cy, cz).nor(); } - private static void vert(Vec3 a, Vec3 normal, Color color){ - floats[0] = a.x; - floats[1] = a.y; - floats[2] = a.z; + private static void vert(Mesh mesh, float[] floats, float x, float y, float z, Vec3 normal, float color, float emissive){ + floats[0] = x; + floats[1] = y; + floats[2] = z; - floats[3] = normal.x; - floats[4] = normal.y; - floats[5] = normal.z; + if(gl30){ + floats[3] = packNormals(normal.x, normal.y, normal.z); + + floats[4] = color; + if(floats.length > 5) floats[5] = emissive; + }else{ + floats[3] = normal.x; + floats[4] = normal.x; + floats[5] = normal.x; + + floats[6] = color; + if(floats.length > 7) floats[7] = emissive; + } - floats[6] = color.toFloatBits(); mesh.getVerticesBuffer().put(floats); } + + private static float packNormals(float x, float y, float z){ + int xs = x < -1f/512f ? 1 : 0; + int ys = y < -1f/512f ? 1 : 0; + int zs = z < -1f/512f ? 1 : 0; + + int vi = + zs << 29 | ((int)(z * 511 + (zs << 9)) & 511) << 20 | + ys << 19 | ((int)(y * 511 + (ys << 9)) & 511) << 10 | + xs << 9 | ((int)(x * 511 + (xs << 9)) & 511); + return Float.intBitsToFloat(vi); + } } diff --git a/core/src/mindustry/graphics/g3d/MultiMesh.java b/core/src/mindustry/graphics/g3d/MultiMesh.java index 2512a5f172..7178d8a223 100644 --- a/core/src/mindustry/graphics/g3d/MultiMesh.java +++ b/core/src/mindustry/graphics/g3d/MultiMesh.java @@ -15,4 +15,11 @@ public class MultiMesh implements GenericMesh{ v.render(params, projection, transform); } } + + @Override + public void dispose(){ + for(var mesh : meshes){ + mesh.dispose(); + } + } } diff --git a/core/src/mindustry/graphics/g3d/NoiseMesh.java b/core/src/mindustry/graphics/g3d/NoiseMesh.java index 88709c4021..b3863ac6bc 100644 --- a/core/src/mindustry/graphics/g3d/NoiseMesh.java +++ b/core/src/mindustry/graphics/g3d/NoiseMesh.java @@ -18,10 +18,10 @@ public class NoiseMesh extends HexMesh{ } @Override - public Color getColor(Vec3 position){ - return color; + public void getColor(Vec3 position, Color out){ + out.set(color); } - }, divisions, false, radius, 0.2f); + }, divisions, radius, 0.2f); } /** Two-color variant. */ @@ -35,9 +35,9 @@ public class NoiseMesh extends HexMesh{ } @Override - public Color getColor(Vec3 position){ - return Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1; + public void getColor(Vec3 position, Color out){ + out.set(Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1); } - }, divisions, false, radius, 0.2f); + }, divisions, radius, 0.2f); } } diff --git a/core/src/mindustry/graphics/g3d/PlanetGrid.java b/core/src/mindustry/graphics/g3d/PlanetGrid.java index 993ca34c25..e7f9cfcfc5 100644 --- a/core/src/mindustry/graphics/g3d/PlanetGrid.java +++ b/core/src/mindustry/graphics/g3d/PlanetGrid.java @@ -47,7 +47,7 @@ public class PlanetGrid{ } } - public static PlanetGrid create(int size){ + public static synchronized PlanetGrid create(int size){ //cache grids between calls, since only ~5 different grids total are needed if(size < cache.length && cache[size] != null){ return cache[size]; @@ -240,6 +240,14 @@ public class PlanetGrid{ corners = new Corner[edgeCount]; edges = new Edge[edgeCount]; } + + @Override + public String toString(){ + return "Ptile{" + + "id=" + id + + " " + v + + '}'; + } } public static class Corner{ diff --git a/core/src/mindustry/graphics/g3d/PlanetMesh.java b/core/src/mindustry/graphics/g3d/PlanetMesh.java index 321cd85e31..9e3f9fb359 100644 --- a/core/src/mindustry/graphics/g3d/PlanetMesh.java +++ b/core/src/mindustry/graphics/g3d/PlanetMesh.java @@ -26,6 +26,8 @@ public abstract class PlanetMesh implements GenericMesh{ @Override public void render(PlanetParams params, Mat3D projection, Mat3D transform){ + if(mesh.isDisposed()) return; + preRender(params); shader.bind(); shader.setUniformMatrix4("u_proj", projection.val); @@ -33,4 +35,9 @@ public abstract class PlanetMesh implements GenericMesh{ shader.apply(); mesh.render(shader, Gl.triangles); } + + @Override + public void dispose(){ + mesh.dispose(); + } } diff --git a/core/src/mindustry/graphics/g3d/PlanetRenderer.java b/core/src/mindustry/graphics/g3d/PlanetRenderer.java index 3593f53957..ba2d66fefd 100644 --- a/core/src/mindustry/graphics/g3d/PlanetRenderer.java +++ b/core/src/mindustry/graphics/g3d/PlanetRenderer.java @@ -31,7 +31,7 @@ public class PlanetRenderer implements Disposable{ setThreshold(0.8f); blurPasses = 6; }}; - public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f); + public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, 1.5f); //seed: 8kmfuix03fw public final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/")); diff --git a/core/src/mindustry/graphics/g3d/SunMesh.java b/core/src/mindustry/graphics/g3d/SunMesh.java index b858303e39..399b6cde8c 100644 --- a/core/src/mindustry/graphics/g3d/SunMesh.java +++ b/core/src/mindustry/graphics/g3d/SunMesh.java @@ -3,7 +3,6 @@ package mindustry.graphics.g3d; import arc.graphics.*; import arc.math.*; import arc.math.geom.*; -import arc.util.*; import arc.util.noise.*; import mindustry.graphics.*; import mindustry.type.*; @@ -19,9 +18,9 @@ public class SunMesh extends HexMesh{ } @Override - public Color getColor(Vec3 position){ + public void getColor(Vec3 position, Color out){ double height = Math.pow(Simplex.noise3d(0, octaves, persistence, scl, position.x, position.y, position.z), pow) * mag; - return Tmp.c1.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale); + out.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale); } }, divisions, Shaders.unlit); } diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index 877b2c4cf3..15719eff81 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -56,6 +56,11 @@ public class DesktopInput extends InputHandler{ /** Time of most recent control group selection */ public long lastCtrlGroupSelectMillis; + /** Time of most recent payload pickup/drop key press*/ + public long lastPayloadKeyTapMillis; + /** Time of most recent payload pickup/drop key hold*/ + public long lastPayloadKeyHoldMillis; + private float buildPlanMouseOffsetX, buildPlanMouseOffsetY; private boolean changedCursor; @@ -425,10 +430,6 @@ public class DesktopInput extends InputHandler{ } } - if(Core.input.keyRelease(Binding.select)){ - player.shooting = false; - } - if(state.isGame() && !scene.hasDialog() && !scene.hasField()){ if(Core.input.keyTap(Binding.minimap)) ui.minimapfrag.toggle(); if(Core.input.keyTap(Binding.planetMap) && state.isCampaign()) ui.planet.toggle(); @@ -555,6 +556,10 @@ public class DesktopInput extends InputHandler{ changedCursor = false; } } + + if(Core.input.keyRelease(Binding.select)){ + player.shooting = false; + } } @Override @@ -728,6 +733,7 @@ public class DesktopInput extends InputHandler{ mode = none; }else if(!selectPlans.isEmpty()){ flushPlans(selectPlans); + movedPlan = true; }else if(isPlacing()){ selectX = cursorX; selectY = cursorY; @@ -970,10 +976,26 @@ public class DesktopInput extends InputHandler{ if(unit instanceof Payloadc){ if(Core.input.keyTap(Binding.pickupCargo)){ tryPickupPayload(); + lastPayloadKeyTapMillis = Time.millis(); + } + + if(Core.input.keyDown(Binding.pickupCargo) + && Time.timeSinceMillis(lastPayloadKeyHoldMillis) > 20 + && Time.timeSinceMillis(lastPayloadKeyTapMillis) > 200){ + tryPickupPayload(); + lastPayloadKeyHoldMillis = Time.millis(); } if(Core.input.keyTap(Binding.dropCargo)){ tryDropPayload(); + lastPayloadKeyTapMillis = Time.millis(); + } + + if(Core.input.keyDown(Binding.dropCargo) + && Time.timeSinceMillis(lastPayloadKeyHoldMillis) > 20 + && Time.timeSinceMillis(lastPayloadKeyTapMillis) > 200){ + tryDropPayload(); + lastPayloadKeyHoldMillis = Time.millis(); } } } diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index 6edf52a2cc..345202ea8e 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -1331,9 +1331,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ plans.each(plan -> { if(plan.breaking) return; + float off = plan.block.size % 2 == 0 ? -0.5f : 0f; + plan.pointConfig(p -> { - int cx = p.x, cy = p.y; - int lx = cx; + float cx = p.x + off, cy = p.y + off; + float lx = cx; if(direction >= 0){ cx = -cy; @@ -1342,7 +1344,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ cx = cy; cy = -lx; } - p.set(cx, cy); + p.set(Mathf.floor(cx - off), Mathf.floor(cy - off)); }); //rotate actual plan, centered on its multiblock position @@ -1376,14 +1378,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } plan.pointConfig(p -> { - int corigin = x ? plan.originalWidth/2 : plan.originalHeight/2; - int nvalue = -(x ? p.x : p.y); if(x){ - plan.originalX = -(plan.originalX - corigin) + corigin; - p.x = nvalue; + if(plan.block.size % 2 == 0) p.x --; + p.x = -p.x; }else{ - plan.originalY = -(plan.originalY - corigin) + corigin; - p.y = nvalue; + if(plan.block.size % 2 == 0) p.y --; + p.y = -p.y; } }); diff --git a/core/src/mindustry/input/MobileInput.java b/core/src/mindustry/input/MobileInput.java index 5b61bcb907..3c291a14a3 100644 --- a/core/src/mindustry/input/MobileInput.java +++ b/core/src/mindustry/input/MobileInput.java @@ -98,7 +98,7 @@ public class MobileInput extends InputHandler implements GestureListener{ }else{ Building tile = world.buildWorld(x, y); - if((tile != null && player.team() != tile.team && (tile.team != Team.derelict || state.rules.coreCapture)) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){ + if((tile != null && (player.team() != tile.team && (tile.team != Team.derelict || state.rules.coreCapture)) && player.unit().type.canAttack) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){ player.unit().mineTile = null; target = tile; } @@ -1078,7 +1078,7 @@ public class MobileInput extends InputHandler implements GestureListener{ //this may be a bad idea, aiming for a point far in front could work better, test it out unit.aim(Core.input.mouseWorldX(), Core.input.mouseWorldY()); }else{ - Vec2 intercept = Predict.intercept(unit, target, bulletSpeed); + Vec2 intercept = player.unit().type.weapons.contains(w -> w.predictTarget) ? Predict.intercept(unit, target, bulletSpeed) : Tmp.v1.set(target); player.mouseX = intercept.x; player.mouseY = intercept.y; diff --git a/core/src/mindustry/io/MapIO.java b/core/src/mindustry/io/MapIO.java index 172118097a..26cc9cd8ac 100644 --- a/core/src/mindustry/io/MapIO.java +++ b/core/src/mindustry/io/MapIO.java @@ -172,7 +172,7 @@ public class MapIO{ for(Tile tile : tiles){ //while synthetic blocks are possible, most of their data is lost, so in order to avoid questions like //"why is there air under my drill" and "why are all my conveyors facing right", they are disabled - int color = tile.block().hasColor && !tile.block().synthetic() ? tile.block().mapColor.rgba() : tile.floor().mapColor.rgba(); + int color = tile.block().hasColor && !tile.block().hasBuilding() ? tile.block().mapColor.rgba() : tile.floor().mapColor.rgba(); pix.set(tile.x, tiles.height - 1 - tile.y, color); } return pix; @@ -183,6 +183,9 @@ public class MapIO{ int color = pixmap.get(tile.x, pixmap.height - 1 - tile.y); Block block = ColorMapper.get(color); + //ignore buildings; reading images is only intended for environment tiles + if(block.hasBuilding()) continue; + if(block.isOverlay()){ tile.setOverlay(block.asFloor()); }else if(block.isFloor()){ @@ -194,7 +197,6 @@ public class MapIO{ } } - //guess at floors by grabbing a random adjacent floor for(Tile tile : tiles){ //default to stone floor if(tile.floor() == Blocks.air){ diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index bf0ce88256..7d26433a16 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -1104,7 +1104,7 @@ public class TypeIO{ } } - /** Represents a unit that has not been resolved yet. TODO unimplemented / unused*/ + /** Represents a unit that has not been resolved yet. */ public static class UnitBox implements Boxed{ public int id; diff --git a/core/src/mindustry/logic/LAccess.java b/core/src/mindustry/logic/LAccess.java index 8c25b36c75..d9e3805d10 100644 --- a/core/src/mindustry/logic/LAccess.java +++ b/core/src/mindustry/logic/LAccess.java @@ -41,6 +41,7 @@ public enum LAccess{ displayWidth, displayHeight, bufferUsage, + operations, size, solid, dead, diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 043dcc9f7c..29172fc63f 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -25,6 +25,7 @@ import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.environment.*; import mindustry.world.blocks.logic.*; +import mindustry.world.blocks.logic.CanvasBlock.*; import mindustry.world.blocks.logic.LogicBlock.*; import mindustry.world.blocks.logic.LogicDisplay.*; import mindustry.world.blocks.logic.MemoryBlock.*; @@ -581,6 +582,8 @@ public class LExecutor{ } }else if(target.isobj && target.objval instanceof CharSequence str){ output.setnum(address < 0 || address >= str.length() ? Double.NaN : (int)str.charAt(address)); + }else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){ + output.setnum(canvas.getPixel(address)); } } } @@ -611,6 +614,8 @@ public class LExecutor{ toVar.numval = value.numval; toVar.isobj = value.isobj; } + }else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){ + canvas.setPixel(address, value.numi()); } } } diff --git a/core/src/mindustry/logic/LMarkerControl.java b/core/src/mindustry/logic/LMarkerControl.java index c24775af02..57780d8187 100644 --- a/core/src/mindustry/logic/LMarkerControl.java +++ b/core/src/mindustry/logic/LMarkerControl.java @@ -11,6 +11,7 @@ public enum LMarkerControl{ color("color"), radius("radius"), stroke("stroke"), + outline("outline"), rotation("rotation"), shape("sides", "fill", "outline"), arc("start", "end"), diff --git a/core/src/mindustry/logic/LogicDialog.java b/core/src/mindustry/logic/LogicDialog.java index f5b09226f3..350f1f3694 100644 --- a/core/src/mindustry/logic/LogicDialog.java +++ b/core/src/mindustry/logic/LogicDialog.java @@ -221,7 +221,7 @@ public class LogicDialog extends BaseDialog{ update(() -> setColor(typeColor(s, color))); }}, new Label(() -> " " + typeName(s) + " "){{ setStyle(Styles.outlineLabel); - }}); + }}).minWidth(120f); t.row(); diff --git a/core/src/mindustry/logic/LogicOp.java b/core/src/mindustry/logic/LogicOp.java index 5cdc476209..e474bf68be 100644 --- a/core/src/mindustry/logic/LogicOp.java +++ b/core/src/mindustry/logic/LogicOp.java @@ -11,6 +11,7 @@ public enum LogicOp{ div("/", (a, b) -> a / b), idiv("//", (a, b) -> Math.floor(a / b)), mod("%", (a, b) -> a % b), + emod("%%", (a, b) -> ((a % b) + b) % b), pow("^", Math::pow), equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> Structs.eq(a, b) ? 1 : 0), @@ -24,6 +25,7 @@ public enum LogicOp{ shl("<<", (a, b) -> (long)a << (long)b), shr(">>", (a, b) -> (long)a >> (long)b), + ushr(">>>", (a, b) -> (long)a >>> (long)b), or("or", (a, b) -> (long)a | (long)b), and("b-and", (a, b) -> (long)a & (long)b), xor("xor", (a, b) -> (long)a ^ (long)b), diff --git a/core/src/mindustry/maps/SectorSubmissions.java b/core/src/mindustry/maps/SectorSubmissions.java new file mode 100644 index 0000000000..8fc5758c6c --- /dev/null +++ b/core/src/mindustry/maps/SectorSubmissions.java @@ -0,0 +1,73 @@ +package mindustry.maps; + +import arc.struct.*; +import arc.util.*; +import mindustry.type.*; + +/** Class for temporarily (?) storing links to map submissions on Discord. */ +public class SectorSubmissions{ + private static IntMap hiddenMap = new IntMap<>(); + + static{ + //autogenerated + hiddenMap.put(0, "https://discord.com/channels/391020510269669376/1379926780860698784"); + hiddenMap.put(6, "https://discord.com/channels/391020510269669376/1379926782966497322"); + hiddenMap.put(13, "https://discord.com/channels/391020510269669376/1379926785164312810"); + hiddenMap.put(16, "https://discord.com/channels/391020510269669376/1379926788280680579"); + hiddenMap.put(19, "https://discord.com/channels/391020510269669376/1379926792479183019"); + hiddenMap.put(20, "https://discord.com/channels/391020510269669376/1379926794114961634"); + hiddenMap.put(24, "https://discord.com/channels/391020510269669376/1379926797042581716"); + hiddenMap.put(27, "https://discord.com/channels/391020510269669376/1379926798833287289"); + hiddenMap.put(30, "https://discord.com/channels/391020510269669376/1379926800854945823"); + hiddenMap.put(47, "https://discord.com/channels/391020510269669376/1379926802591645820"); + hiddenMap.put(55, "https://discord.com/channels/391020510269669376/1379926823277695189"); + hiddenMap.put(66, "https://discord.com/channels/391020510269669376/1379926825941078128"); + hiddenMap.put(67, "https://discord.com/channels/391020510269669376/1379926828696866898"); + hiddenMap.put(69, "https://discord.com/channels/391020510269669376/1379926831326822610"); + hiddenMap.put(76, "https://discord.com/channels/391020510269669376/1379926833411391580"); + hiddenMap.put(92, "https://discord.com/channels/391020510269669376/1379926835621527615"); + hiddenMap.put(94, "https://discord.com/channels/391020510269669376/1379926838079393802"); + hiddenMap.put(103, "https://discord.com/channels/391020510269669376/1379926839559979030"); + hiddenMap.put(111, "https://discord.com/channels/391020510269669376/1379926842659569864"); + hiddenMap.put(116, "https://discord.com/channels/391020510269669376/1379926845058711734"); + hiddenMap.put(127, "https://discord.com/channels/391020510269669376/1379926869465632829"); + hiddenMap.put(133, "https://discord.com/channels/391020510269669376/1379926871227240770"); + hiddenMap.put(138, "https://discord.com/channels/391020510269669376/1379926873152164004"); + hiddenMap.put(150, "https://discord.com/channels/391020510269669376/1379926876457537547"); + hiddenMap.put(157, "https://discord.com/channels/391020510269669376/1379926879502598155"); + hiddenMap.put(161, "https://discord.com/channels/391020510269669376/1379926882203730024"); + hiddenMap.put(162, "https://discord.com/channels/391020510269669376/1379926884606808247"); + hiddenMap.put(176, "https://discord.com/channels/391020510269669376/1379926887203213353"); + hiddenMap.put(180, "https://discord.com/channels/391020510269669376/1379926889648619580"); + hiddenMap.put(185, "https://discord.com/channels/391020510269669376/1379926892181983283"); + hiddenMap.put(191, "https://discord.com/channels/391020510269669376/1379926912004001914"); + hiddenMap.put(192, "https://discord.com/channels/391020510269669376/1379926914122256449"); + hiddenMap.put(197, "https://discord.com/channels/391020510269669376/1379926916911599676"); + hiddenMap.put(200, "https://discord.com/channels/391020510269669376/1379926918429806755"); + hiddenMap.put(204, "https://discord.com/channels/391020510269669376/1379926921130807447"); + hiddenMap.put(207, "https://discord.com/channels/391020510269669376/1379926923370827827"); + hiddenMap.put(225, "https://discord.com/channels/391020510269669376/1379926925719376152"); + hiddenMap.put(230, "https://discord.com/channels/391020510269669376/1379926927585841163"); + hiddenMap.put(237, "https://discord.com/channels/391020510269669376/1379926929636851812"); + hiddenMap.put(242, "https://discord.com/channels/391020510269669376/1379926931923013843"); + hiddenMap.put(243, "https://discord.com/channels/391020510269669376/1379926955423694978"); + hiddenMap.put(244, "https://discord.com/channels/391020510269669376/1379926957738954762"); + hiddenMap.put(245, "https://discord.com/channels/391020510269669376/1379926971286290584"); + hiddenMap.put(246, "https://discord.com/channels/391020510269669376/1379926973454745600"); + hiddenMap.put(247, "https://discord.com/channels/391020510269669376/1379926976361533752"); + hiddenMap.put(248, "https://discord.com/channels/391020510269669376/1379926979129774151"); + hiddenMap.put(251, "https://discord.com/channels/391020510269669376/1379928042637361382"); + hiddenMap.put(254, "https://discord.com/channels/391020510269669376/1379928045577703424"); + hiddenMap.put(259, "https://discord.com/channels/391020510269669376/1379928048245280871"); + hiddenMap.put(263, "https://discord.com/channels/391020510269669376/1379928050010951694"); + hiddenMap.put(265, "https://discord.com/channels/391020510269669376/1379928052921929891"); + } + + /** @return the link to the Discord discussion thread of the specified hidden sector submission. */ + public static @Nullable String getSectorThread(Sector sector){ + if(sector.generateEnemyBase){ + return hiddenMap.get(sector.id); + } + return null; + } +} diff --git a/core/src/mindustry/maps/generators/BlankPlanetGenerator.java b/core/src/mindustry/maps/generators/BlankPlanetGenerator.java index 1fcec6fa0b..7b8aeed5ce 100644 --- a/core/src/mindustry/maps/generators/BlankPlanetGenerator.java +++ b/core/src/mindustry/maps/generators/BlankPlanetGenerator.java @@ -1,7 +1,5 @@ package mindustry.maps.generators; -import arc.graphics.*; -import arc.math.geom.*; import mindustry.game.*; import mindustry.type.*; import mindustry.world.*; @@ -9,16 +7,6 @@ import mindustry.world.*; /** A planet generator that provides no weather, height, color or bases. Override generate().*/ public class BlankPlanetGenerator extends PlanetGenerator{ - @Override - public float getHeight(Vec3 position){ - return 0; - } - - @Override - public Color getColor(Vec3 position){ - return Color.white; - } - @Override public void addWeather(Sector sector, Rules rules){ diff --git a/core/src/mindustry/maps/generators/PlanetGenerator.java b/core/src/mindustry/maps/generators/PlanetGenerator.java index 5fd3b19931..59783db265 100644 --- a/core/src/mindustry/maps/generators/PlanetGenerator.java +++ b/core/src/mindustry/maps/generators/PlanetGenerator.java @@ -25,11 +25,22 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe protected @Nullable Sector sector; - /** Should generate sector bases for a planet. */ public void generateSector(Sector sector){ } + public void onSectorCaptured(Sector sector){ + + } + + public void onSectorLost(Sector sector){ + + } + + public void beforeSaveWrite(Sector sector){ + + } + public void getLockedText(Sector hovered, StringBuilder out){ out.append("[gray]").append(Iconc.lock).append(" ").append(Core.bundle.get("locked")); } diff --git a/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java b/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java index 94b4dd8992..927e3e755d 100644 --- a/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/ErekirPlanetGenerator.java @@ -39,20 +39,17 @@ public class ErekirPlanetGenerator extends PlanetGenerator{ } @Override - public Color getColor(Vec3 position){ + public void getColor(Vec3 position, Color out){ Block block = getBlock(position); //more obvious color if(block == Blocks.crystallineStone) block = Blocks.crystalFloor; - //TODO this might be too green - //if(block == Blocks.beryllicStone) block = Blocks.arkyicStone; - return Tmp.c1.set(block.mapColor).a(1f - block.albedo); + out.set(block.mapColor).a(1f - block.albedo); } @Override public float getSizeScl(){ - //TODO should sectors be 600, or 500 blocks? return 2000 * 1.07f * 6f / 5f; } @@ -65,17 +62,17 @@ public class ErekirPlanetGenerator extends PlanetGenerator{ } Block getBlock(Vec3 position){ - float ice = rawTemp(position); - Tmp.v32.set(position); + float px = position.x, py = position.y, pz = position.z; + float ice = rawTemp(position); float height = rawHeight(position); - Tmp.v31.set(position); + height *= 1.2f; height = Mathf.clamp(height); Block result = terrain[Mathf.clamp((int)(height * terrain.length), 0, terrain.length - 1)]; - if(ice < 0.3 + Math.abs(Ridged.noise3d(seed + crystalSeed, position.x + 4f, position.y + 8f, position.z + 1f, crystalOct, crystalScl)) * crystalMag){ + if(ice < 0.3 + Math.abs(Ridged.noise3d(seed + crystalSeed, px + 4f, py + 8f, pz + 1f, crystalOct, crystalScl)) * crystalMag){ return Blocks.crystallineStone; } @@ -86,11 +83,9 @@ public class ErekirPlanetGenerator extends PlanetGenerator{ } } - position = Tmp.v32; - //TODO tweak this to make it more natural //TODO edge distortion? - if(ice < redThresh - noArkThresh && Ridged.noise3d(seed + arkSeed, position.x + 2f, position.y + 8f, position.z + 1f, arkOct, arkScl) > arkThresh){ + if(ice < redThresh - noArkThresh && Ridged.noise3d(seed + arkSeed, px + 2f, py + 8f, pz + 1f, arkOct, arkScl) > arkThresh){ //TODO arkyic in middle result = Blocks.beryllicStone; } diff --git a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java index ccba966da1..8e0931974e 100644 --- a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java @@ -20,13 +20,16 @@ import mindustry.world.blocks.environment.*; import static mindustry.Vars.*; public class SerpuloPlanetGenerator extends PlanetGenerator{ - //alternate, less direct generation (wip) - public static boolean alt = false; + //alternate, less direct generation + public static boolean indirectPaths = false; + //random water patches + public static boolean genLakes = false; BaseGenerator basegen = new BaseGenerator(); + float heightYOffset = 42.7f; float scl = 5f; - float waterOffset = 0.05f; - boolean genLakes = false; + float waterOffset = 0.04f; + float heightScl = 1.01f; Block[][] arr = { @@ -58,10 +61,30 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ ); float water = 2f / arr[0].length; + Vec3 basePos = new Vec3(0.9341721, 0.0, 0.3568221); float rawHeight(Vec3 position){ - position = Tmp.v33.set(position).scl(scl); - return (Mathf.pow(Simplex.noise3d(seed, 7, 0.5f, 1f/3f, position.x, position.y, position.z), 2.3f) + waterOffset) / (1f + waterOffset); + return (Mathf.pow(Simplex.noise3d(seed, 7, 0.5f, 1f/3f, position.x * scl, position.y * scl + heightYOffset, position.z * scl) * heightScl, 2.3f) + waterOffset) / (1f + waterOffset); + } + + @Override + public void onSectorCaptured(Sector sector){ + sector.planet.reloadMeshAsync(); + } + + @Override + public void onSectorLost(Sector sector){ + sector.planet.reloadMeshAsync(); + } + + @Override + public void beforeSaveWrite(Sector sector){ + sector.planet.reloadMeshAsync(); + } + + @Override + public boolean isEmissive(){ + return true; } @Override @@ -86,16 +109,57 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } @Override - public Color getColor(Vec3 position){ + public void getColor(Vec3 position, Color out){ Block block = getBlock(position); //replace salt with sand color - if(block == Blocks.salt) return Blocks.sand.mapColor; - return Tmp.c1.set(block.mapColor).a(1f - block.albedo); + if(block == Blocks.salt) block = Blocks.sand; + out.set(block.mapColor).a(1f - block.albedo); + } + + @Override + public void getEmissiveColor(Vec3 position, Color out){ + float dst = 999f, captureDst = 999f, lightScl = 0f; + + Object[] sectors = Planets.serpulo.sectors.items; + int size = Planets.serpulo.sectors.size; + + for(int i = 0; i < size; i ++){ + var sector = (Sector)sectors[i]; + + if(sector.hasEnemyBase() && !sector.isCaptured()){ + dst = Math.min(dst, position.dst(sector.tile.v) - (sector.preset != null ? sector.preset.difficulty/10f * 0.03f - 0.03f : 0f)); + }else if(sector.hasBase()){ + float cdst = position.dst(sector.tile.v); + if(cdst < captureDst){ + captureDst = cdst; + lightScl = sector.info.lightCoverage; + } + } + } + + lightScl = Math.min(lightScl / 50000f, 1.3f); + if(lightScl < 1f) lightScl = Interp.pow5Out.apply(lightScl); + + float freq = 0.05f; + if(position.dst(basePos) < 0.55f ? + + dst*metalDstScl + Simplex.noise3d(seed + 1, 3, 0.4, 5.5f, position.x, position.y + 200f, position.z)*0.08f + ((basePos.dst(position) + 0.00f) % freq < freq/2f ? 1f : 0f) * 0.07f < 0.08f/* || dst <= 0.0001f*/ : + dst*metalDstScl + Simplex.noise3d(seed, 3, 0.4, 9f, position.x, position.y + 370f, position.z)*0.06f < 0.045){ + + out.set(Team.crux.color) + .mul(0.8f + Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 99f, position.z) * 0.4f) + .lerp(Team.sharded.color, 0.2f*Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 999f, position.z)).toFloatBits(); + }else if(captureDst*metalDstScl + Simplex.noise3d(seed, 3, 0.4, 9f, position.x, position.y + 600f, position.z)*0.07f < 0.05 * lightScl){ + out.set(Team.sharded.color).mul(0.7f + Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 99f, position.z) * 0.4f) + .lerp(Team.crux.color, 0.3f*Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 999f, position.z)).toFloatBits(); + + } } @Override public void genTile(Vec3 position, TileGen tile){ tile.floor = getBlock(position); + if(tile.floor == Blocks.darkPanel6) tile.floor = Blocks.darkPanel3; tile.block = tile.floor.asFloor().wall; if(Ridged.noise3d(seed + 1, position.x, position.y, position.z, 2, 22) > 0.31){ @@ -103,23 +167,46 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } } + static double metalDstScl = 0.25; + Block getBlock(Vec3 position){ float height = rawHeight(position); - Tmp.v31.set(position); - position = Tmp.v33.set(position).scl(scl); + float px = position.x * scl, py = position.y * scl, pz = position.z * scl; + float rad = scl; - float temp = Mathf.clamp(Math.abs(position.y * 2f) / (rad)); - float tnoise = Simplex.noise3d(seed, 7, 0.56, 1f/3f, position.x, position.y + 999f, position.z); + float temp = Mathf.clamp(Math.abs(py * 2f) / (rad)); + float tnoise = Simplex.noise3d(seed, 7, 0.56, 1f/3f, px, py + 999f - 0.1f, pz); temp = Mathf.lerp(temp, tnoise, 0.5f); height *= 1.2f; height = Mathf.clamp(height); - float tar = Simplex.noise3d(seed, 4, 0.55f, 1f/2f, position.x, position.y + 999f, position.z) * 0.3f + Tmp.v31.dst(0, 0, 1f) * 0.2f; + float tar = Simplex.noise3d(seed, 4, 0.55f, 1f/2f, px, py + 999f, pz) * 0.3f + position.dst(0, 0, 1f) * 0.2f; Block res = arr[Mathf.clamp((int)(temp * arr.length), 0, arr[0].length - 1)][Mathf.clamp((int)(height * arr[0].length), 0, arr[0].length - 1)]; if(tar > 0.5f){ return tars.get(res, res); }else{ + if(position.within(basePos, 0.65f)){ + + float dst = 999f; + + Object[] sectors = Planets.serpulo.sectors.items; + int size = Planets.serpulo.sectors.size; + + for(int i = 0; i < size; i ++){ + var sector = (Sector)sectors[i]; + + if(sector.hasEnemyBase()){ + dst = Math.min(dst, position.dst(sector.tile.v)); + } + } + + float freq = 0.05f, freq2 = 0.07f; + + if(dst*0.85f + Simplex.noise3d(seed, 3, 0.4, 5.5f, position.x, position.y + 200f, position.z)*0.015f + ((basePos.dst(position) + 0.00f) % freq < freq/2f ? 1f : 0f) * 0.07f < 0.15f){ + return ((basePos.dst(position) + 0.01f) % freq2 < freq2*0.65f) ? Blocks.metalFloor : Blocks.darkPanel6; + } + } return res; } } @@ -156,7 +243,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ Vec2 midpoint = Tmp.v1.set(to.x, to.y).add(x, y).scl(0.5f); rand.nextFloat(); - if(alt){ + if(indirectPaths){ midpoint.add(Tmp.v2.set(1, 0f).setAngle(Angles.angle(to.x, to.y, x, y) + 90f * (rand.chance(0.5) ? 1f : -1f)).scl(Tmp.v1.dst(x, y) * 2f)); }else{ //add randomized offset to avoid straight lines diff --git a/core/src/mindustry/maps/planet/TantrosPlanetGenerator.java b/core/src/mindustry/maps/planet/TantrosPlanetGenerator.java index 8925536a83..e1f2f6e564 100644 --- a/core/src/mindustry/maps/planet/TantrosPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/TantrosPlanetGenerator.java @@ -14,7 +14,7 @@ import mindustry.world.*; import static mindustry.Vars.*; public class TantrosPlanetGenerator extends PlanetGenerator{ - Color c1 = Color.valueOf("5057a6"), c2 = Color.valueOf("272766"), out = new Color(); + Color c1 = Color.valueOf("5057a6"), c2 = Color.valueOf("272766"); Block[][] arr = { {Blocks.redmat, Blocks.redmat, Blocks.darksand, Blocks.bluemat, Blocks.bluemat} @@ -30,9 +30,9 @@ public class TantrosPlanetGenerator extends PlanetGenerator{ } @Override - public Color getColor(Vec3 position){ + public void getColor(Vec3 position, Color out){ float depth = Simplex.noise3d(seed, 2, 0.56, 1.7f, position.x, position.y, position.z) / 2f; - return c1.write(out).lerp(c2, Mathf.clamp(Mathf.round(depth, 0.15f))).a(0.2f); + out.set(c1).lerp(c2, Mathf.clamp(Mathf.round(depth, 0.15f))).a(1f - 0.2f).toFloatBits(); } @Override diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index b3044a2a4a..a1c6196f30 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -413,11 +413,22 @@ public class Mods implements Loadable{ /** Removes a mod file and marks it for requiring a restart. */ public void removeMod(LoadedMod mod){ - if(!android && mod.loader != null){ - try{ - ClassLoaderCloser.close(mod.loader); - }catch(Exception e){ - Log.err(e); + boolean deleted = true; + + if(mod.loader != null){ + if(android){ + //Try to remove cache for Android 14 security problem + Fi cacheDir = new Fi(Core.files.getCachePath()).child("mods"); + Fi modCacheDir = cacheDir.child(mod.file.nameWithoutExtension()); + if(modCacheDir.exists()){ + deleted = modCacheDir.deleteDirectory(); + } + }else{ + try{ + ClassLoaderCloser.close(mod.loader); + }catch(Exception e){ + Log.err(e); + } } } @@ -425,7 +436,7 @@ public class Mods implements Loadable{ mod.root.delete(); } - boolean deleted = mod.file.isDirectory() ? mod.file.deleteDirectory() : mod.file.delete(); + deleted &= mod.file.isDirectory() ? mod.file.deleteDirectory() : mod.file.delete(); if(!deleted){ ui.showErrorMessage("@mod.delete.error"); @@ -1112,6 +1123,11 @@ public class Mods implements Loadable{ //close the classloader for jar mods if(!android){ ClassLoaderCloser.close(other.loader); + }else if(other.loader != null){ + //Try to remove cache for Android 14 security problem + Fi cacheDir = new Fi(Core.files.getCachePath()).child("mods"); + Fi modCacheDir = cacheDir.child(other.file.nameWithoutExtension()); + modCacheDir.deleteDirectory(); } //close zip file diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index b978d4bc7b..76d21c6028 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -91,6 +91,10 @@ public class Administration{ dosBlacklist.add(address); } + public synchronized void unBlacklistDos(String address){ + dosBlacklist.remove(address); + } + public synchronized boolean isDosBlacklisted(String address){ return dosBlacklist.contains(address); } diff --git a/core/src/mindustry/net/ArcNetProvider.java b/core/src/mindustry/net/ArcNetProvider.java index 66d2d5278c..dde8aba760 100644 --- a/core/src/mindustry/net/ArcNetProvider.java +++ b/core/src/mindustry/net/ArcNetProvider.java @@ -113,6 +113,8 @@ public class ArcNetProvider implements NetProvider{ //kill connections above the limit to prevent spam if((playerLimitCache > 0 && server.getConnections().length > playerLimitCache) || netServer.admins.isDosBlacklisted(ip)){ + Log.info("Closing connection @ - IP marked as a potential DOS attack.", ip); + connection.close(DcReason.closed); return; } diff --git a/core/src/mindustry/net/CrashHandler.java b/core/src/mindustry/net/CrashHandler.java index 7ed5ade848..1b52810d28 100644 --- a/core/src/mindustry/net/CrashHandler.java +++ b/core/src/mindustry/net/CrashHandler.java @@ -28,6 +28,8 @@ public class CrashHandler{ report += "Report this at " + Vars.reportIssueURL + "\n\n"; } + var enabledMods = mods == null ? null : mods.list().select(m -> m.shouldBeEnabled() && m.isSupported()); + return report + "Version: " + Version.combined() + (Version.buildDate.equals("unknown") ? "" : " (Built " + Version.buildDate + ")") + (Vars.headless ? " (Server)" : "") + "\n" + "Date: " + new SimpleDateFormat("MMMM d, yyyy HH:mm:ss a", Locale.getDefault()).format(new Date()) + "\n" @@ -37,7 +39,7 @@ public class CrashHandler{ + "Runtime Available Memory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "mb\n" + "Cores: " + OS.cores + "\n" + (cause == null ? "" : "Likely Cause: " + cause.meta.displayName + " (" + cause.name + " v" + cause.meta.version + ")\n") - + (mods == null ? "" : "Mods: " + (!mods.list().contains(LoadedMod::shouldBeEnabled) ? "none (vanilla)" : mods.list().select(LoadedMod::shouldBeEnabled).toString(", ", mod -> mod.name + ":" + mod.meta.version))) + + (enabledMods == null ? "" : "Mods: " + (enabledMods.isEmpty() ? "none (vanilla)" : enabledMods.toString(", ", mod -> mod.name + ":" + mod.meta.version))) + "\n\n" + error; } diff --git a/core/src/mindustry/service/Achievement.java b/core/src/mindustry/service/Achievement.java index cd62301fae..a883c0ab66 100644 --- a/core/src/mindustry/service/Achievement.java +++ b/core/src/mindustry/service/Achievement.java @@ -67,6 +67,7 @@ public enum Achievement{ have10mItems(SStat.totalCampaignItems, 10_000_000), killEclipseDuo, + killMassDriver, completeErekir, completeSerpulo, diff --git a/core/src/mindustry/service/GameService.java b/core/src/mindustry/service/GameService.java index 341fad2879..814facaf2c 100644 --- a/core/src/mindustry/service/GameService.java +++ b/core/src/mindustry/service/GameService.java @@ -5,6 +5,7 @@ import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.content.*; +import mindustry.entities.bullet.*; import mindustry.game.EventType.*; import mindustry.game.SectorInfo.*; import mindustry.gen.*; @@ -394,6 +395,10 @@ public class GameService{ if(e.unit.type == UnitTypes.eclipse && e.bullet.owner instanceof TurretBuild turret && turret.block == Blocks.duo){ killEclipseDuo.complete(); } + + if(e.bullet.type instanceof MassDriverBolt){ + killMassDriver.complete(); + } } }); diff --git a/core/src/mindustry/type/Planet.java b/core/src/mindustry/type/Planet.java index 06ee987469..502cd0286d 100644 --- a/core/src/mindustry/type/Planet.java +++ b/core/src/mindustry/type/Planet.java @@ -11,7 +11,6 @@ import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; -import arc.util.noise.*; import mindustry.content.*; import mindustry.content.TechTree.*; import mindustry.ctype.*; @@ -348,11 +347,31 @@ public class Planet extends UnlockableContent{ return mat.setToTranslation(position).rotate(Vec3.Y, getRotation()); } - /** Regenerates the planet mesh. For debugging only. */ + /** Regenerates the planet mesh. */ public void reloadMesh(){ + if(headless) return; + + if(mesh != null){ + mesh.dispose(); + } mesh = meshLoader.get(); } + public void reloadMeshAsync(){ + if(headless) return; + + mainExecutor.submit(() -> { + var newMesh = meshLoader.get(); + + Core.app.post(() -> { + if(mesh != null){ + mesh.dispose(); + } + mesh = newMesh; + }); + }); + } + @Override public void load(){ super.load(); @@ -383,7 +402,6 @@ public class Planet extends UnlockableContent{ } if(generator != null){ - Noise.setSeed(sectorSeed < 0 ? id + 1 : sectorSeed); for(Sector sector : sectors){ generator.generateSector(sector); diff --git a/core/src/mindustry/ui/Fonts.java b/core/src/mindustry/ui/Fonts.java index ef68462bf6..f302c762a5 100644 --- a/core/src/mindustry/ui/Fonts.java +++ b/core/src/mindustry/ui/Fonts.java @@ -27,7 +27,7 @@ import java.io.*; public class Fonts{ private static final String mainFont = "fonts/font.woff"; - private static final ObjectSet unscaled = ObjectSet.with("iconLarge"); + private static final ObjectSet unscaled = ObjectSet.with("iconLarge", "logic"); private static ObjectIntMap unicodeIcons = new ObjectIntMap<>(); private static IntMap unicodeToName = new IntMap<>(); private static ObjectMap stringIcons = new ObjectMap<>(); diff --git a/core/src/mindustry/ui/dialogs/KeybindDialog.java b/core/src/mindustry/ui/dialogs/KeybindDialog.java index 2ef05ddf87..e56d2f43cc 100644 --- a/core/src/mindustry/ui/dialogs/KeybindDialog.java +++ b/core/src/mindustry/ui/dialogs/KeybindDialog.java @@ -152,7 +152,6 @@ public class KeybindDialog extends Dialog{ rebindKey = name; rebindDialog.titleTable.getCells().first().pad(4); - rebindDialog.addListener(new InputListener(){ @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){ diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index f177ac7e32..3e7a2e2448 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -365,7 +365,7 @@ public class ModsDialog extends BaseDialog{ private @Nullable String getStateDetails(LoadedMod item){ if(item.isOutdated()){ - return Core.bundle.format("mod.outdated.details", item.isJava() ? minJavaModGameVersion : minModGameVersion); + return "@mod.incompatiblemod.details"; }else if(item.isBlacklisted()){ return "@mod.blacklisted.details"; }else if(!item.isSupported()){ diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index b2659d7d62..1b18bc31db 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -652,6 +652,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(scene.getDialog() == PlanetDialog.this && (scene.getHoverElement() == null || !scene.getHoverElement().isDescendantOf(e -> e instanceof ScrollPane))){ scene.setScrollFocus(PlanetDialog.this); + if(debugSectorAttackEdit){ + int timeShift = input.keyDown(KeyCode.rightBracket) ? 1 : input.keyDown(KeyCode.leftBracket) ? -1 : 0; + if(timeShift != 0){ + universe.setSeconds(universe.secondsf() + timeShift * Time.delta * 2.5f); + } + + if(input.keyTap(KeyCode.r)){ + state.planet.reloadMeshAsync(); + } + } + if(debugSectorAttackEdit && input.ctrl() && input.keyTap(KeyCode.s)){ try{ PlanetData data = new PlanetData(); @@ -665,10 +676,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ data.presets.put(sector.preset.name, sector.id); } } + Log.info("Saving sectors for @: @ presets, @ procedural attack sectors", state.planet.name, data.presets.size, attack.size); data.attackSectors = attack.toArray(); files.local("planets/" + state.planet.name + ".json").writeString(JsonIO.write(data)); - Vars.ui.showInfoFade("@editor.saved"); + ui.showInfoFade("@editor.saved"); }catch(Exception e){ Log.err(e); } @@ -1137,7 +1149,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } } - void selectSector(Sector sector){ + public void selectSector(Sector sector){ selected = sector; updateSelected(); } @@ -1262,7 +1274,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(sector.isAttacked()){ addSurvivedInfo(sector, stable, false); - }else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(Sector::hasEnemyBase)){ + }else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(s -> s.hasEnemyBase() && (s.preset == null || !s.preset.requireUnlock))){ stable.add("@sectors.vulnerable"); stable.row(); }else if(!sector.hasBase() && sector.hasEnemyBase()){ @@ -1287,6 +1299,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } if((sector.hasBase() && mode == look) || canSelect(sector) || (sector.preset != null && sector.preset.alwaysUnlocked) || debugSelect){ + if(Vars.showSectorSubmissions){ + String link = SectorSubmissions.getSectorThread(sector); + if(link != null){ + stable.button("@sectors.viewsubmission", Icon.link, () -> { + Core.app.openURI(link); + }).growX().height(54f).minWidth(170f).padTop(2f).row(); + } + } + stable.button( mode == select ? "@sectors.select" : sector.isBeingPlayed() ? "@sectors.resume" : diff --git a/core/src/mindustry/ui/dialogs/SchematicsDialog.java b/core/src/mindustry/ui/dialogs/SchematicsDialog.java index f64cf14ca2..508ec2a6c9 100644 --- a/core/src/mindustry/ui/dialogs/SchematicsDialog.java +++ b/core/src/mindustry/ui/dialogs/SchematicsDialog.java @@ -557,7 +557,7 @@ public class SchematicsDialog extends BaseDialog{ next.pack(); float w = next.getWidth() + Scl.scl(9f); - if(w + sum >= Core.graphics.getWidth() * 0.9f){ + if(w*2f + sum >= Core.graphics.getWidth() * 0.9f){ p.add(current).row(); current = new Table(); current.left(); diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index c604cf2b32..cac1defa52 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -838,8 +838,12 @@ public class HudFragment{ t.add(new SideBar(() -> player.dead() ? 0f : player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad); t.image(() -> player.icon()).scaling(Scaling.bounded).grow().maxWidth(54f); - t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : player.unit().healthf(), () -> !player.displayAmmo(), false)).width(bw).growY().padLeft(pad).update(b -> { - b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color() : Pal.health); + + Boolp playerHasPayloads = () -> player.unit() instanceof Payloadc pay && !pay.payloads().isEmpty(); + Floatp playerPayloadCapacityUsed = () -> player.unit() instanceof Payloadc pay ? pay.payloadUsed() / player.unit().type().payloadCapacity : 0f; + + t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : playerHasPayloads.get() ? playerPayloadCapacityUsed.get() : player.unit().healthf(), () -> !(player.displayAmmo() || playerHasPayloads.get()), false)).width(bw).growY().padLeft(pad).update(b -> { + b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color() : playerHasPayloads.get() ? Pal.items : Pal.health); }); t.getChildren().get(1).toFront(); diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java index 6213c923c3..013695c336 100644 --- a/core/src/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/mindustry/ui/fragments/PlacementFragment.java @@ -251,12 +251,16 @@ public class PlacementFragment{ } if(Core.input.keyTap(Binding.blockInfo)){ - var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); - Block hovering = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block; - Block displayBlock = menuHoverBlock != null ? menuHoverBlock : input.block != null ? input.block : hovering; - if(displayBlock != null && displayBlock.unlockedNow()){ - ui.content.show(displayBlock); - Events.fire(new BlockInfoEvent()); + if(hovered() instanceof Unit unit && unit.type.unlockedNow()){ + ui.content.show(unit.type()); + }else{ + var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); + Block hovering = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block; + Block displayBlock = menuHoverBlock != null ? menuHoverBlock : input.block != null ? input.block : hovering; + if(displayBlock != null && displayBlock.unlockedNow()){ + ui.content.show(displayBlock); + Events.fire(new BlockInfoEvent()); + } } } diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java index 4a5078513c..3e53462cf1 100644 --- a/core/src/mindustry/world/blocks/ConstructBlock.java +++ b/core/src/mindustry/world/blocks/ConstructBlock.java @@ -57,6 +57,8 @@ public class ConstructBlock extends Block{ @Remote(called = Loc.server) public static void deconstructFinish(Tile tile, Block block, Unit builder){ + if(tile == null) return; + Team team = tile.team(); if(!headless && fogControl.isVisibleTile(Vars.player.team(), tile.x, tile.y)){ block.breakEffect.at(tile.drawx(), tile.drawy(), block.size, block.mapColor); diff --git a/core/src/mindustry/world/blocks/TileBitmask.java b/core/src/mindustry/world/blocks/TileBitmask.java new file mode 100644 index 0000000000..f911469267 --- /dev/null +++ b/core/src/mindustry/world/blocks/TileBitmask.java @@ -0,0 +1,23 @@ +package mindustry.world.blocks; + +public class TileBitmask{ + /** Autotile bitmasks for 8-directional sprites (see tile-gen)*/ + public static final int[] values = { + 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, + 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, + 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, + 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, + 3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20, + 5, 28, 5, 28, 29, 10, 29, 23, 5, 28, 5, 28, 31, 11, 31, 32, + 3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20, + 2, 30, 2, 30, 9, 46, 9, 22, 2, 30, 2, 30, 14, 44, 14, 6, + 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, + 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, + 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, + 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, + 3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12, + 5, 8, 5, 8, 29, 35, 29, 33, 5, 8, 5, 8, 31, 34, 31, 7, + 3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12, + 2, 1, 2, 1, 9, 45, 9, 19, 2, 1, 2, 1, 14, 18, 14, 13, + }; +} diff --git a/core/src/mindustry/world/blocks/campaign/Accelerator.java b/core/src/mindustry/world/blocks/campaign/Accelerator.java index 164e9cd746..3a0efce2f9 100644 --- a/core/src/mindustry/world/blocks/campaign/Accelerator.java +++ b/core/src/mindustry/world/blocks/campaign/Accelerator.java @@ -223,7 +223,7 @@ public class Accelerator extends Block{ } public boolean canLaunch(){ - return isValid() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching; + return isValid() && !net.client() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching; } @Override diff --git a/core/src/mindustry/world/blocks/campaign/LandingPad.java b/core/src/mindustry/world/blocks/campaign/LandingPad.java index e9e44418a6..2179627b6e 100644 --- a/core/src/mindustry/world/blocks/campaign/LandingPad.java +++ b/core/src/mindustry/world/blocks/campaign/LandingPad.java @@ -383,21 +383,24 @@ public class LandingPad extends Block{ t.background(Styles.black6); t.button(Icon.downOpen, Styles.clearNonei, 40f, () -> { - if(config != null && state.isCampaign()){ - for(Sector sector : state.getPlanet().sectors){ - if(sector.hasBase() && sector != state.getSector() && sector.info.destination != state.getSector() && sector.info.hasExport(config)){ - sector.info.destination = state.getSector(); - sector.saveInfo(); - } - } - state.getSector().info.refreshImportRates(state.getPlanet()); + if(config == null || !state.isCampaign()) return; + + for(Sector sector : state.getPlanet().sectors){ + if(!canRedirectExports(sector)) continue; + sector.info.destination = state.getSector(); + sector.saveInfo(); } - }).disabled(b -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(s -> s.hasBase() && s.info.hasExport(config) && s.info.destination != state.getSector()))) + state.getSector().info.refreshImportRates(state.getPlanet()); + }).disabled(button -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(this::canRedirectExports))) .tooltip("@sectors.redirect").get(); }).fillX().left(); } } + private boolean canRedirectExports(Sector sector){ + return sector.hasBase() && sector != state.getSector() && sector.info.hasExport(config) && sector.info.destination != state.getSector(); + } + @Override public void display(Table table){ super.display(table); @@ -416,14 +419,13 @@ public class LandingPad extends Block{ int sources = 0; float perSecond = 0f; - for(var s : state.getPlanet().sectors){ - if(s != state.getSector() && s.hasBase() && s.info.destination == state.getSector()){ - float amount = s.info.getExport(config); - if(amount > 0){ - sources ++; - perSecond += s.info.getExport(config); - } - } + for(var otherSector : state.getPlanet().sectors){ + if(otherSector == state.getSector() || !otherSector.hasBase() || otherSector.info.destination != state.getSector()) continue; + + float amount = otherSector.info.getExport(config); + if(amount <= 0) continue; + sources ++; + perSecond += amount; } String str = Core.bundle.format("landing.sources", sources == 0 ? Core.bundle.get("none") : sources); diff --git a/core/src/mindustry/world/blocks/distribution/DirectionalUnloader.java b/core/src/mindustry/world/blocks/distribution/DirectionalUnloader.java index 313426c159..76d6fb0ae4 100644 --- a/core/src/mindustry/world/blocks/distribution/DirectionalUnloader.java +++ b/core/src/mindustry/world/blocks/distribution/DirectionalUnloader.java @@ -96,8 +96,7 @@ public class DirectionalUnloader extends Block{ front.handleItem(this, item); back.items.remove(item, 1); back.itemTaken(item); - offset ++; - offset %= itemc; + offset = item.id + 1; break; } } diff --git a/core/src/mindustry/world/blocks/distribution/MassDriver.java b/core/src/mindustry/world/blocks/distribution/MassDriver.java index 4aef759357..f79d55042a 100644 --- a/core/src/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/mindustry/world/blocks/distribution/MassDriver.java @@ -215,7 +215,7 @@ public class MassDriver extends Block{ @Override public double sense(LAccess sensor){ - if(sensor == LAccess.progress) return Mathf.clamp(1f - reloadCounter / reload); + if(sensor == LAccess.progress) return Mathf.clamp(1f - reloadCounter); return super.sense(sensor); } @@ -299,13 +299,13 @@ public class MassDriver extends Block{ bullet.create(this, team, x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), - angle, -1f, bulletSpeed, bulletLifetime, data); + angle, totalUsed/2f, bulletSpeed, bulletLifetime, data); shootEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle); smokeEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle); Effect.shake(shake, shake, this); - + shootSound.at(tile, Mathf.random(0.9f, 1.1f)); } diff --git a/core/src/mindustry/world/blocks/environment/Floor.java b/core/src/mindustry/world/blocks/environment/Floor.java index 68809deafe..7b9f9ebe3f 100644 --- a/core/src/mindustry/world/blocks/environment/Floor.java +++ b/core/src/mindustry/world/blocks/environment/Floor.java @@ -15,6 +15,7 @@ import mindustry.graphics.*; import mindustry.graphics.MultiPacker.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.*; import java.util.*; @@ -77,8 +78,11 @@ public class Floor extends Block{ public int blendId = -1; /** If >0, this floor is drawn as parts of a large texture. */ public int tilingVariants = 0; + /** If true, this floor uses autotiling; variants are not supported. See https://github.com/GglLfr/tile-gen*/ + public boolean autotile = false; protected TextureRegion[][][] tilingRegions; + protected TextureRegion[] autotileRegions; protected int tilingSize; protected TextureRegion[][] edges; protected Seq blenders = new Seq<>(); @@ -104,6 +108,10 @@ public class Floor extends Block{ public void load(){ super.load(); + if(autotile){ + variants = 0; + } + int tsize = (int)(tilesize / Draw.scl); if(tilingVariants > 0 && !headless){ @@ -132,6 +140,13 @@ public class Floor extends Block{ variantRegions[0] = Core.atlas.find(name); } + if(autotile){ + autotileRegions = new TextureRegion[47]; + for(int i = 0; i < 47; i++){ + autotileRegions[i] = Core.atlas.find(name + "-" + i); + } + } + if(Core.atlas.has(name + "-edge")){ edges = Core.atlas.find(name + "-edge").split(tsize, tsize); } @@ -208,6 +223,17 @@ public class Floor extends Block{ int index = Mathf.randomSeed(Point2.pack(tile.x / tilingSize, tile.y / tilingSize), 0, tilingVariants - 1); TextureRegion[][] regions = tilingRegions[index]; Draw.rect(regions[tile.x % tilingSize][tilingSize - 1 - tile.y % tilingSize], tile.worldx(), tile.worldy()); + }else if(autotile){ + int bits = 0; + + for(int i = 0; i < 8; i++){ + Tile other = tile.nearby(Geometry.d8[i]); + if(other != null && other.floor().blendGroup == blendGroup){ + bits |= (1 << i); + } + } + + Draw.rect(autotileRegions[TileBitmask.values[bits]], tile.worldx(), tile.worldy()); }else{ Draw.rect(variantRegions[variant(tile.x, tile.y)], tile.worldx(), tile.worldy()); } diff --git a/core/src/mindustry/world/blocks/environment/SteamVent.java b/core/src/mindustry/world/blocks/environment/SteamVent.java index a41efba7a0..c211964a7a 100644 --- a/core/src/mindustry/world/blocks/environment/SteamVent.java +++ b/core/src/mindustry/world/blocks/environment/SteamVent.java @@ -51,7 +51,6 @@ public class SteamVent extends Floor{ parent.drawBase(tile); if(checkAdjacent(tile)){ - Mathf.rand.setSeed(tile.pos()); Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx() - tilesize, tile.worldy() - tilesize); } } diff --git a/core/src/mindustry/world/blocks/logic/CanvasBlock.java b/core/src/mindustry/world/blocks/logic/CanvasBlock.java index 9bab59a42a..6e4e4ab782 100644 --- a/core/src/mindustry/world/blocks/logic/CanvasBlock.java +++ b/core/src/mindustry/world/blocks/logic/CanvasBlock.java @@ -17,6 +17,7 @@ import mindustry.annotations.Annotations.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.logic.*; import mindustry.ui.*; import mindustry.world.*; @@ -35,7 +36,7 @@ public class CanvasBlock extends Block{ public @Load("@-corner1") TextureRegion corner1; public @Load("@-corner2") TextureRegion corner2; - protected @Nullable Pixmap previewPixmap; + protected @Nullable Pixmap previewPixmap; // please use only for previews protected @Nullable Texture previewTexture; protected int tempBlend = 0; @@ -49,7 +50,7 @@ public class CanvasBlock extends Block{ config(byte[].class, (CanvasBuild build, byte[] bytes) -> { if(build.data.length == bytes.length){ - build.data = bytes; + System.arraycopy(bytes, 0, build.data, 0, bytes.length); build.updateTexture(); } }); @@ -65,13 +66,15 @@ public class CanvasBlock extends Block{ bitsPerPixel = Mathf.log2(Mathf.nextPowerOfTwo(palette.length)); clipSize = Math.max(clipSize, size * 8 - padding); + + previewPixmap = new Pixmap(canvasSize, canvasSize); } @Override public void drawPlanRegion(BuildPlan plan, Eachable list){ //only draw the preview in schematics, as it lags otherwise if(!plan.worldContext && plan.config instanceof byte[] data){ - Pixmap pix = makePixmap(data); + Pixmap pix = makePixmap(data, previewPixmap); if(previewTexture == null){ previewTexture = new Texture(pix); @@ -123,20 +126,15 @@ public class CanvasBlock extends Block{ } } - /** returns the same pixmap instance each time, use with care */ - public Pixmap makePixmap(byte[] data){ - if(previewPixmap == null){ - previewPixmap = new Pixmap(canvasSize, canvasSize); - } - + public Pixmap makePixmap(byte[] data, Pixmap target){ int bpp = bitsPerPixel; int pixels = canvasSize * canvasSize; for(int i = 0; i < pixels; i++){ int bitOffset = i * bpp; int pal = getByte(data, bitOffset); - previewPixmap.set(i % canvasSize, i / canvasSize, palette[pal]); + target.set(i % canvasSize, i / canvasSize, palette[pal]); } - return previewPixmap; + return target; } protected int getByte(byte[] data, int bitOffset){ @@ -152,11 +150,41 @@ public class CanvasBlock extends Block{ public @Nullable Texture texture; public byte[] data = new byte[Mathf.ceil(canvasSize * canvasSize * bitsPerPixel / 8f)]; public int blending; + + protected boolean updated = false; + + public void setPixel(int pos, int index){ + if(pos < canvasSize * canvasSize && pos >= 0 && index >= 0 && index < palette.length){ + setByte(data, pos * bitsPerPixel, index); + updated = true; + } + } + + public void setPixel(int x, int y, int index){ + if(x >= 0 && y >= 0 && x < canvasSize && y < canvasSize && index >= 0 && index < palette.length){ + setByte(data, (y * canvasSize + x) * bitsPerPixel, index); + updated = true; + } + } + + public double getPixel(int pos){ + if(pos >= 0 && pos < canvasSize * canvasSize){ + return getByte(data, pos * bitsPerPixel); + } + return Double.NaN; + } + + public int getPixel(int x, int y){ + if(x >= 0 && y >= 0 && x < canvasSize && y < canvasSize){ + return getByte(data, (y * canvasSize + x) * bitsPerPixel); + } + return 0; + } public void updateTexture(){ if(headless) return; - Pixmap pix = makePixmap(data); + Pixmap pix = makePixmap(data, previewPixmap); if(texture != null){ texture.draw(pix); }else{ @@ -214,7 +242,8 @@ public class CanvasBlock extends Block{ super.draw(); } - if(texture == null){ + if(texture == null || updated){ + updated = false; updateTexture(); } Tmp.tr1.set(texture); @@ -237,6 +266,14 @@ public class CanvasBlock extends Block{ } } } + + @Override + public double sense(LAccess sensor){ + return switch(sensor){ + case displayWidth, displayHeight -> canvasSize; + default -> super.sense(sensor); + }; + } @Override public void remove(){ @@ -252,12 +289,17 @@ public class CanvasBlock extends Block{ table.button(Icon.pencil, Styles.cleari, () -> { Dialog dialog = new Dialog(); - Pixmap pix = makePixmap(data); + Pixmap pix = makePixmap(data, new Pixmap(canvasSize, canvasSize)); Texture texture = new Texture(pix); int[] curColor = {palette[0]}; boolean[] modified = {false}; boolean[] fill = {false}; - + + dialog.hidden(() -> { + texture.dispose(); + pix.dispose(); + }); + dialog.resized(dialog::hide); dialog.cont.table(Tex.pane, body -> { @@ -395,7 +437,6 @@ public class CanvasBlock extends Block{ dialog.buttons.button("@ok", Icon.ok, () -> { if(modified[0]){ configure(packPixmap(pix)); - texture.dispose(); } dialog.hide(); }); diff --git a/core/src/mindustry/world/blocks/logic/LogicDisplay.java b/core/src/mindustry/world/blocks/logic/LogicDisplay.java index 66099d29d5..641366a226 100644 --- a/core/src/mindustry/world/blocks/logic/LogicDisplay.java +++ b/core/src/mindustry/world/blocks/logic/LogicDisplay.java @@ -78,6 +78,7 @@ public class LogicDisplay extends Block{ public float stroke = 1f; public LongQueue commands = new LongQueue(256); public @Nullable Mat transform; + public long operations; @Override public void draw(){ @@ -111,6 +112,7 @@ public class LogicDisplay extends Block{ return switch(sensor){ case displayWidth, displayHeight -> displaySize; case bufferUsage -> commands.size; + case operations -> operations; default -> super.sense(sensor); }; } @@ -121,6 +123,8 @@ public class LogicDisplay extends Block{ for(int i = 0; i < added; i++){ commands.addLast(graphicsBuffer.items[i]); } + + operations++; } public void processCommands(){ diff --git a/core/src/mindustry/world/blocks/logic/TileableLogicDisplay.java b/core/src/mindustry/world/blocks/logic/TileableLogicDisplay.java index 03effdfcc4..a6ff9d3aed 100644 --- a/core/src/mindustry/world/blocks/logic/TileableLogicDisplay.java +++ b/core/src/mindustry/world/blocks/logic/TileableLogicDisplay.java @@ -13,6 +13,7 @@ import mindustry.annotations.Annotations.*; import mindustry.graphics.*; import mindustry.logic.*; import mindustry.world.*; +import mindustry.world.blocks.*; import static mindustry.Vars.*; @@ -27,25 +28,6 @@ public class TileableLogicDisplay extends LogicDisplay{ public @Load(value = "@-#", length = 47) TextureRegion[] tileRegion; public @Load("@-back") TextureRegion backRegion; - static final int[] bitmasks = { - 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, - 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, - 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, - 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, - 3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20, - 5, 28, 5, 28, 29, 10, 29, 23, 5, 28, 5, 28, 31, 11, 31, 32, - 3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20, - 2, 30, 2, 30, 9, 46, 9, 22, 2, 30, 2, 30, 14, 44, 14, 6, - 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, - 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, - 39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24, - 38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25, - 3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12, - 5, 8, 5, 8, 29, 35, 29, 33, 5, 8, 5, 8, 31, 34, 31, 7, - 3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12, - 2, 1, 2, 1, 9, 45, 9, 19, 2, 1, 2, 1, 14, 18, 14, 13, - }; - public TileableLogicDisplay(String name){ super(name); @@ -247,7 +229,7 @@ public class TileableLogicDisplay extends LogicDisplay{ Draw.z(Layer.block + 0.02f); - Draw.rect(tileRegion[bitmasks[bits]], x, y); + Draw.rect(tileRegion[TileBitmask.values[bits]], x, y); } @Override diff --git a/core/src/mindustry/world/blocks/payloads/PayloadConveyor.java b/core/src/mindustry/world/blocks/payloads/PayloadConveyor.java index a2bceb9977..37f489f8d9 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadConveyor.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadConveyor.java @@ -48,12 +48,12 @@ public class PayloadConveyor extends Block{ public void drawPlace(int x, int y, int rotation, boolean valid){ super.drawPlace(x, y, rotation, valid); - int ntrns = 1 + size/2; + int ntrns = size; for(int i = 0; i < 4; i++){ - Building other = world.build(x + Geometry.d4x[i] * ntrns, y + Geometry.d4y[i] * ntrns); - if(other != null && other.block.outputsPayload && other.block.size == size){ - Drawf.selected(other.tileX(), other.tileY(), other.block, other.team.color); + Tile tile = world.tile(x + Geometry.d4x[i] * ntrns, y + Geometry.d4y[i] * ntrns); + if(tile != null && tile.build != null && tile.isCenter() && tile.build.block.outputsPayload && tile.build.block.size == size && (i == rotation || tile.block().rotate && i == (tile.build.rotation + 2) % 4)){ + Drawf.selected(tile.x, tile.y, tile.block(), tile.build.team.color); } } } diff --git a/core/src/mindustry/world/blocks/power/LightBlock.java b/core/src/mindustry/world/blocks/power/LightBlock.java index 529ba33ac0..041399f5b7 100644 --- a/core/src/mindustry/world/blocks/power/LightBlock.java +++ b/core/src/mindustry/world/blocks/power/LightBlock.java @@ -1,6 +1,5 @@ package mindustry.world.blocks.power; -import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; @@ -58,14 +57,30 @@ public class LightBlock extends Block{ Placement.calculateNodes(points, this, rotation, (point, other) -> point.dst2(other) <= placeRadius2); } + @Override + public int minimapColor(Tile tile){ + var build = (LightBuild)tile.build; + //make sure A is 255 + return build == null ? 0 : build.color | 0xff; + } + public class LightBuild extends Building{ public int color = Pal.accent.rgba(); public float smoothTime = 1f; + @Override + public void configured(Unit player, Object value){ + super.configured(player, value); + + if(!headless) renderer.minimap.update(tile); + } + @Override public void control(LAccess type, double p1, double p2, double p3, double p4){ if(type == LAccess.color){ color = Tmp.c1.fromDouble(p1).rgba8888(); + + if(!headless) renderer.minimap.update(tile); } super.control(type, p1, p2, p3, p4); @@ -80,11 +95,9 @@ public class LightBlock extends Block{ @Override public void draw(){ super.draw(); - Draw.blend(Blending.additive); - Draw.color(Tmp.c1.set(color), efficiency * 0.3f); + Draw.color(Tmp.c1.set(color).a(0.4f)); Draw.rect(topRegion, x, y); Draw.color(); - Draw.blend(); } @Override diff --git a/core/src/mindustry/world/blocks/power/PowerNode.java b/core/src/mindustry/world/blocks/power/PowerNode.java index a3eae8fb08..f01d93ef9a 100644 --- a/core/src/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/mindustry/world/blocks/power/PowerNode.java @@ -328,6 +328,11 @@ public class PowerNode extends PowerBlock{ } }); + //uncomment for debugging connection translation issues in schematics + //Draw.color(Color.red); + //Lines.line(plan.drawx(), plan.drawy(), px * tilesize, py * tilesize); + //Draw.color(); + if(otherReq == null || otherReq.block == null) continue; drawLaser(plan.drawx(), plan.drawy(), otherReq.drawx(), otherReq.drawy(), size, otherReq.block.size); diff --git a/core/src/mindustry/world/blocks/production/BeamDrill.java b/core/src/mindustry/world/blocks/production/BeamDrill.java index 9921e4d7cf..27c1aac52c 100644 --- a/core/src/mindustry/world/blocks/production/BeamDrill.java +++ b/core/src/mindustry/world/blocks/production/BeamDrill.java @@ -262,7 +262,7 @@ public class BeamDrill extends Block{ time %= drillTime; } - if(timer(timerDump, dumpTime)){ + if(timer(timerDump, dumpTime / timeScale)){ dump(); } } diff --git a/core/src/mindustry/world/blocks/production/BurstDrill.java b/core/src/mindustry/world/blocks/production/BurstDrill.java index 150800705c..2c8563899c 100644 --- a/core/src/mindustry/world/blocks/production/BurstDrill.java +++ b/core/src/mindustry/world/blocks/production/BurstDrill.java @@ -80,7 +80,7 @@ public class BurstDrill extends Drill{ if(invertTime > 0f) invertTime -= delta() / invertedTime; - if(timer(timerDump, dumpTime)){ + if(timer(timerDump, dumpTime / timeScale)){ dump(items.has(dominantItem) ? dominantItem : null); } diff --git a/core/src/mindustry/world/blocks/production/Drill.java b/core/src/mindustry/world/blocks/production/Drill.java index 12ef7bace5..c327111e03 100644 --- a/core/src/mindustry/world/blocks/production/Drill.java +++ b/core/src/mindustry/world/blocks/production/Drill.java @@ -286,7 +286,7 @@ public class Drill extends Block{ @Override public void updateTile(){ - if(timer(timerDump, dumpTime)){ + if(timer(timerDump, dumpTime / timeScale)){ dump(dominantItem != null && items.has(dominantItem) ? dominantItem : null); } diff --git a/core/src/mindustry/world/blocks/production/Separator.java b/core/src/mindustry/world/blocks/production/Separator.java index 1bd5351b5e..22ad5c94ab 100644 --- a/core/src/mindustry/world/blocks/production/Separator.java +++ b/core/src/mindustry/world/blocks/production/Separator.java @@ -162,7 +162,7 @@ public class Separator extends Block{ } } - if(timer(timerDump, dumpTime)){ + if(timer(timerDump, dumpTime / timeScale)){ dump(); } } diff --git a/core/src/mindustry/world/blocks/production/WallCrafter.java b/core/src/mindustry/world/blocks/production/WallCrafter.java index df2395faa4..9c70967212 100644 --- a/core/src/mindustry/world/blocks/production/WallCrafter.java +++ b/core/src/mindustry/world/blocks/production/WallCrafter.java @@ -216,7 +216,7 @@ public class WallCrafter extends Block{ totalTime += edelta() * warmup * (eff <= 0f ? 0f : 1f); - if(timer(timerDump, dumpTime)){ + if(timer(timerDump, dumpTime / timeScale)){ dump(output); } } diff --git a/core/src/mindustry/world/consumers/ConsumePayloadDynamic.java b/core/src/mindustry/world/consumers/ConsumePayloadDynamic.java index ebb6f32430..c2d52fcefd 100644 --- a/core/src/mindustry/world/consumers/ConsumePayloadDynamic.java +++ b/core/src/mindustry/world/consumers/ConsumePayloadDynamic.java @@ -60,6 +60,7 @@ public class ConsumePayloadDynamic extends Consume{ var inv = build.getPayloads(); var pay = payloads.get(build); + table.clear(); table.table(c -> { int i = 0; for(var stack : pay){ diff --git a/core/src/mindustry/world/meta/BuildVisibility.java b/core/src/mindustry/world/meta/BuildVisibility.java index 06d08323f2..8b7042d6b0 100644 --- a/core/src/mindustry/world/meta/BuildVisibility.java +++ b/core/src/mindustry/world/meta/BuildVisibility.java @@ -16,7 +16,7 @@ public class BuildVisibility{ sandboxOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.infiniteResources), campaignOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.isCampaign()), legacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && Vars.state.getPlanet().campaignRules.legacyLaunchPads) && Blocks.advancedLaunchPad != null && Blocks.advancedLaunchPad.unlocked()), - notLegacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && !Vars.state.getPlanet().campaignRules.legacyLaunchPads)), + notLegacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.rules.infiniteResources || Vars.state.isCampaign() && !Vars.state.getPlanet().campaignRules.legacyLaunchPads)), lightingOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()), ammoOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.unitAmmo), fogOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.fog || Vars.state.rules.editor); diff --git a/core/src/mindustry/world/meta/StatValues.java b/core/src/mindustry/world/meta/StatValues.java index cad7a64a4a..886fa305ab 100644 --- a/core/src/mindustry/world/meta/StatValues.java +++ b/core/src/mindustry/world/meta/StatValues.java @@ -710,7 +710,7 @@ public class StatValues{ if(type.status != StatusEffects.none){ sep(bt, (type.status.hasEmoji() ? type.status.emoji() : "") + "[stat]" + type.status.localizedName + (type.status.reactive ? "" : "[lightgray] ~ [stat]" + - ((int)(type.statusDuration / 60f)) + "[lightgray] " + Core.bundle.get("unit.seconds"))).with(c -> withTooltip(c, type.status)); + Strings.autoFixed(type.statusDuration / 60f, 1) + "[lightgray] " + Core.bundle.get("unit.seconds"))).with(c -> withTooltip(c, type.status)); } if(!type.targetMissiles){ diff --git a/gradle.properties b/gradle.properties index fcb7e71f43..a8e0be2bf0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=67b872ff4d +archash=9b55a5d628 diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index 453c73e9a4..6b0a840ec9 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -666,7 +666,7 @@ public class ServerControl implements ApplicationListener{ if(arg.length == 0){ info("Subnets banned: @", netServer.admins.getSubnetBans().isEmpty() ? "" : ""); for(String subnet : netServer.admins.getSubnetBans()){ - info("&lw " + subnet); + info("&lw\t" + subnet); } }else if(arg.length == 1){ err("You must provide a subnet to add or remove."); @@ -1054,6 +1054,35 @@ public class ServerControl implements ApplicationListener{ } }); + handler.register("dos-ban", "[add/remove] [ip]", "Add or remove a DOS ban.", arg -> { + if(arg.length == 0){ + info("DOS bans: @", netServer.admins.dosBlacklist.isEmpty() ? "" : ""); + + netServer.admins.dosBlacklist.forEach(address -> { + info("&lw\t" + address); + }); + return; + }else if(arg.length == 1){ + err("Expected either zero or two parameters, but only got one parameter."); + return; + } + + String action = arg[0].toLowerCase(); + String ip = arg[1]; + + if(action.equals("add")){ + netServer.admins.blacklistDos(ip); + info("Dos banned: @", ip); + return; + }else if(action.equals("remove")){ + netServer.admins.unBlacklistDos(ip); + info("Removed dos ban: @", ip); + return; + } + + err("Unrecognized action: @", action); + }); + mods.eachClass(p -> p.registerServerCommands(handler)); } diff --git a/servers_v7.json b/servers_v7.json index bdbce7a6db..4b3b49a5de 100644 --- a/servers_v7.json +++ b/servers_v7.json @@ -2,24 +2,13 @@ { "name": "EscoCorp", "address": [ - "121.127.37.17", - "121.127.37.17:6568", - "121.127.37.17:6569", - "121.127.37.17:6570", - "121.121.37.17:6571", - "121.127.37.17:6572", - "121.127.37.17:6573", - "31.58.85.20:6567", - "194.164.245.218:6567", - "194.164.245.218:6568", - "194.164.245.218:6569", - "194.164.245.218:6570", - "194.164.245.218:6571", - "194.164.245.218:6572", - "194.164.245.218:6573", - "194.164.245.218:6574", - "194.164.245.218:6575", - "194.164.245.218:6576" + "95.215.56.128", + "95.215.56.128:6568", + "95.215.56.128:6569", + "95.215.56.128:6570", + "95.215.56.128:6571", + "95.215.56.128:6572", + "95.215.56.128:6573" ] }, { @@ -97,17 +86,6 @@ "147.45.230.117:3011" ] }, - { - "name": "Chaotic Neutral", - "address": [ - "37.187.73.180:7011", - "37.187.73.180:7013", - "37.187.73.180:7014", - "37.187.73.180:7015", - "37.187.73.180:7016", - "37.187.73.180:7017" - ] - }, { "name": "io", "address": [ @@ -138,12 +116,6 @@ "name": "OmniCorp Classic", "address": ["omnidustry.ru:6567", "omnidustry.ru:6568", "omnidustry.ru:6569", "omnidustry.ru:6570", "omnidustry.ru:6571", "omnidustry.ru:6572", "omnidustry.ru:6573", "omnidustry.ru:6574", "omnidustry.ru:6575", "omnidustry.ru:6576"] }, - { - "name": "The Devil", - "address": [ - "new.xem8k5.top" - ] - }, { "name": "Eradication Mindustry", "address": [ @@ -260,9 +232,9 @@ ] }, { - "name": "CroCraft Network", + "name": "Kalpe Games", "address": [ - "193.122.53.98:6567" + "mv7-survival.kalpe.games" ] }, { @@ -289,9 +261,7 @@ "95.84.198.97:5401", "95.84.198.97:5402", "95.84.198.97:5403", - "frost-heaven.ru", - "frost-heaven.ru:6568", - "91.200.150.116" + "95.84.198.97:5404" ] }, { @@ -302,11 +272,11 @@ "194.164.245.218:6569", "194.164.245.218:6570", "194.164.245.218:6571", + "194.164.245.218:6572", + "194.164.245.218:6573", + "194.164.245.218:6574", "194.164.245.218:6597", - "194.164.245.218:6598", - "194.164.245.218:6599", - "194.164.245.218:6600", - "194.164.245.218:6601" + "194.164.245.218:6598" ] }, { @@ -331,7 +301,12 @@ "server.mindustry-tool.com:10002", "server.mindustry-tool.com:10003", "server.mindustry-tool.com:10004", - "server.mindustry-tool.com:10005" + "server.mindustry-tool.com:10005", + "144.76.57.59:14899", + "144.76.57.59:24942", + "144.76.57.59:19672", + "144.76.57.59:12858", + "144.76.57.59:14761" ] }, { diff --git a/tools/src/mindustry/tools/Generators.java b/tools/src/mindustry/tools/Generators.java index 8503c80b26..8fbda7d9f8 100644 --- a/tools/src/mindustry/tools/Generators.java +++ b/tools/src/mindustry/tools/Generators.java @@ -69,6 +69,28 @@ public class Generators{ public static void run(){ ObjectMap gens = new ObjectMap<>(); + generate("autotiles", () -> { + for(Floor floor : content.blocks().select(b -> b.isFloor() && b.asFloor().autotile).as()){ + Fi basePath = new Fi("../../../assets-raw/sprites_out/blocks/environment/" + floor.name + "-autotile.png"); + + if(basePath.exists()){ + //theoretically this might not finish in time, but I doubt that will ever happen + mainExecutor.submit(() -> { + try{ + ImageTileGenerator.generate(basePath, floor.name, new Fi("../../../assets-raw/sprites_out/blocks/environment/" + floor.name)); + }catch(Throwable e){ + Log.err("Failed to autotile: " + floor.name, e); + }finally{ + //the raw autotile source image must never be included, it isn't useful + basePath.delete(); + } + }); + }else{ + Log.warn("Autotile floor '@' not found: @", floor.name, basePath.absolutePath()); + } + } + }); + generate("splashes", () -> { int frames = 12; diff --git a/tools/src/mindustry/tools/ImageTileGenerator.java b/tools/src/mindustry/tools/ImageTileGenerator.java new file mode 100644 index 0000000000..bc5c01138d --- /dev/null +++ b/tools/src/mindustry/tools/ImageTileGenerator.java @@ -0,0 +1,68 @@ +package mindustry.tools; + +import arc.files.*; +import arc.graphics.*; +import arc.math.geom.*; +import arc.struct.*; +import arc.util.serialization.*; + +import java.io.*; + +/** Java port of https://github.com/GglLfr/tile-gen/blob/master/src/main.rs */ +public class ImageTileGenerator{ + private static final int layoutWidth = 384, layoutHeight = 128; + private static final Pixmap layout = new Pixmap(Base64Coder.decode("iVBORw0KGgoAAAANSUhEUgAAAYAAAACACAYAAAACsL4LAAAAAXNSR0IArs4c6QAADsNJREFUeJztnUtu5LgSRcMPNc9JAbmDnngH3j+8g5y8HSRQk1xB9cAOtkqlDz9B3gjqHuABr8uZjKBE3eOPUnwTkd8C5IYsLiJ3cP2f4PqfBmPcPgwGqeBu0rzIE70IQLyeIrf3xkEMzsE7OgRAPN5F7g9c/ee7yP9w5ckMoMJfRORpVPv+tBknEi9Hc368gLVbBdhYF/XNx/O7PgVAqkGGv0IJlOMp/JUrSWBdb7QEnov6FACpwkP4K5RAPh7DX7mCBPbqjJLAc1WfAiDFeAp/hRI4x3P4KzNL4Gz83hJYh78IBUAK8Rj+CiWwT4TwV2aUQO64vSSwFf4iFAApwHP4K5TA30QKf2UmCZSOZy2BvfAXoQBIJhHCX6EE/iNi+CszSKB2HCsJHIW/CAVAMogU/golEDv8lcgSaH1/qwTOwl+EAiAnfAT9kM77Q+SnUe8RJTBD+CsRJWD1E0StBHLCX4QCIAdo+L8DP61Yw7LfK0pgpvBXIknA+m8IpRLIDX8RCoDssP7OP4oEtvq8kgRmDH8lggR63UWUK4GS8BehAMgGe7/28S6Bo/6uIIGZw1/xLIHenyM4k0Bp+ItQAGTF2e/8vUogp6+ZJXCF8Fc8SmDUJ4n3JFAT/iIUAFmQ+wdfbxIo6WdGCVwp/BVPEhj9LKG1BGrDX4QCIN+U3u3jRQI1fcwkgSuGv+JBAqiniaoEWsJfhAIgUn+rJ1oCLfVnkAA6/F8PkRf4NuFfjf9r4fGOvQae9/b6FMDFab3PH3UBWNSNLAEP4Z/+P0gCd7B8dA3CroFne30KgDQz+gJA/+SxxUgJeAr/9G+Dw9hL+O/9d/f6qzVQW58CICaMugA8hr8yQgIewz99bVAoewv/s383r7+zBmrqUwDEjN4XgOfwV3pKwHP4p9d0Dmev4Z/79eb6J2ugtD4FQEzpdQFECH+lhwQihH96baeQ9h7+pa8rrp+5BkrqUwDEHOsLIFL4K5YSiBT+6T3GYR0l/Gtffzpe4RrIrU8BkC5YXQARw1+xkEDE8E/vtbrLKlj4t77vr3Eq10BOfQqAdKP1Aogc/kqLBCKHfxqjMbyjhr/Z+xvXwFl9CoB0Bf3dkwdqJDBD+KexKkM8evi3jtMa/jn1KQDSHfTvTz1QIoGZwj+NWRjms4R/7XhW4X9WnwIgQ0DfQeGBHAnMGP5p7MxQny38S8e1Dv+j+hQAGQb6HmoPHElg5vBPNU7Cfdbwzx2/V/jv1acAyFDQn6L0wJYErhD+qdZOyM8e/md1eof/Vn0KgAwH/RwVDywlcKXwTzVXYX+V8N+rNyr81/XfROT32NJ/Aj7vUrjfsjk/wfWRJwD9OF2R9kcCt/J/cH00txc2/J/gNfi4jw//JW9yowCQwAUABi0BCgDH7Y7fVOf5wu9pgOJ256+ACBD0rkponq+v74CvyE13tAJ+B6a7aaHOwe0ucgOtfT3+FACBgN5XFc1zETpXk8BtvactQALrrRRHn4PlMRgtgWVtCoAMZy/sryKB50bYXEUC6/BXRkpgbx/dUedg6xiMksC6NgVAhnIW8rNLYCv8ldklsBf+yggJnG2i3vscHB2D3hLYqk0BkGHkhvusEjgKf2VWCZyFv9JTAmfhr/Q6BznHoJcE9mpTAGQIpaE+mwRywl+ZTQK54a/0kEBu+CvW56DkGFhL4Kg2BUC6Uxvms0igJPyVWSRQGv6KpQRKw1+xOgc1x8BKAme1KQDSldYQjy6BmvBXokugNvwVCwnUhr/Seg5ajkGrBHJqUwCkG1bhHVUCLeGvRJVAa/grLRJoDX+l9hxYHINaCeTWpgBIF6xDO5oELMJfiSYBq/BXaiRgFf5K6TmwPAalEiipTQEQc3qFdRQJWIa/EkUC1uGvlEjAOvyV3HPQ4xjkSqC0NgVATOkd0t4l0CP8Fe8S6BX+So4EeoW/cnYOeh6DMwnU1KYAiBmjwtmrBHqGv+JVAr3DXzmSQO/wV/bOwYhjsCeB2toUADFhdCh7k8CI8Fe8SWBU+CtbEhgV/sr6HIw8BmsJtNSmAC7Op8HCRYVxa91fVneqAALZiwRGh7+ylMDo8Ff0HCCOgUqgtfaP9lZIdD7vIh+Vz2VHfydeu59A5PBXbgbPsv9o7AG5n8/zLtANNe43gW4ocvte+w/+BEBaqflJAB3+SmkfM4S/gv5JALmbFRLdxQy5oY1+49NyDigAkiiRgJfwV3L7mSn8FUpgLOstLBESsNpTmAIgf5AjAW/hr5z1NWP4K5TAGPb2Lx4pgb1fedacAwqA/MWRBLyGv7LX38zhr1ACfTnbvH6EBM7+3lV6DigAssmWBLyHv7Lu8wrhr1ACfTgL//S6jvPPvdmh5BxQAGSXpQSihL+i/V4p/BVKwJbc8E+v7zD/0jvdcs8BBUAO+bzHC3/liuGvUAI2lIZ/ep/h/GtucxbJOwcUADnl9QvdQTl3o54jhr9CCbRRG/7p/Qbzrw3/9P6THigAkkUkCTD8/4MSqKM1/NM4DfNvDf80zkEPFADJJoIEGP5/QwmUYRX+abyK+VuFfxpvpwcKgBThWQIM/30ogTyswz+NWzB/6/BP4270QAGQYjxKgOF/DiVwTK/wT+NnzL9X+KfxVz1QAKQKTxJg+OdDCWzTO/xTnYP59w7/VGfRAwVAqvEgAYZ/OZTAn4wK/1RvY/6jwj/V++6BAiBNICXA8K+HEvhidPinuov5jw7/VPcp8ib/yG9M+S9Axz8BfJy3iEAfZy4iIg90A4K7AEREHq1B2PpA/V/YRwqXbLbehZ8iL+D5v92xxx95+T1e/AmAOCDqJ42tgIcwmLPNznuDOv7PF3A3ve9vfCgA4gJKAN0BlqtJYPlrx+H7aS9qUwDEDZQAugMsV5HA1t+cRq399a88KQDiCkoA3QGW2SVwdMNB77W/9fcuCoC4gxJAd4BlVgnk3G3Wa+3v3exAARCXUALoDrDMJoGSW42t1/7RnW4UAHELJYDuAMssEqj5nInV2j+7zZkCIK6hBNAdYIkugZYPGbau/ZzPuFAAxD2UALoDLFElYPEJ89q1n/sBRwqAhIASQHeAJZoELB8vUrr2Sz7dTgGQMFAC6A6wRJFAj2dL5a790kebUAAkFJQAugMs3iXQ88GCZ2u/5rlWFAAJByWA7gCLVwmMeKrs3tqvfaghBUBCQgmgO8DiTQIjHym+XvstT7SlAEhYKAF0B1i8SACxn4Su/dbHmf9ob4VcFX2O/1WD+H4TkYfIEzj/1ufJtzwKXzd1eVxURK+niAA31nm8f+2l0LK5D38CIFUsN3GB7WgE3EhkuZPUHbijE4plbVQfyI1kXt9zfgF3tNL5t/RAAZBitoJ3+J6mTsI//RtoT1cEW7VH9+Mh/NN/AySwnn9tDxQAKeIoeEeFsrfwT18bNX9n4Z/zNUs8hX/694ES2Jt/TQ8UAMkmJ3h7h7PX8E+v6T1/p+Ff8poWPIZ/+voACZzNv7QHCoBkURK8vULae/in1/aav/Pwr3ltCZ7DP72uowRy51/SAwVATqkJXuuwjhL+6T3W8w8S/i3vOSJC+KfXd5BA6fxze6AAyCEtwWsV2tHCP73Xav7Bwt/ivUsihX96n6EEauef0wMFQHaxCN7WMaKGfxqjdf5Bw99qjIjhn95vsH5a53/WAwVANrEM3tqxood/Gqt2/sHDv3WsyOGfxmlYR1bzP+qBAiB/0SN4S8ecJfzTmKXznyT8a8ecIfzTeBXryXr+ez1QAOQPegZv7tizhX8aO3f+k4V/6dgzhX8at2Bd9Zr/Vg8UAEmMCN6zGrOGf6pxNv9Jwz+3xozhn8bPWF+957/ugQIgIjI2ePdqzR7+qdbe/CcP/7NaM4d/qnOwzkbNf9kDBUAgwbuueZXwTzXX879I+O/VvEL4p3ob6230/LWHN/lHfo8t/SfAh+mJiAj6Sbatj/NtBhg8Il+PtEWG/y9caRH5eqY8MvzR6CONr8jtJSIf2Pn/wCcQgQIMH3T4i2AF/Hh9PU7+E9jDP8DaTwfnH8njQ+T1KW/IHvgrIAIh7WgE3EwFWvt7Ew/krlY34I+/T6MdrVp4vHD1vWyiRAGQ4fy1pyngYvAQ/gpCAh7CX0GE8LLm6Ppewl+EAiCD2Vv8Iy8KT+GvjJSAp/BXRobwVq1R9T2FvwgFQAZytvhHXBwew18ZIQGP4a+MCOGjGr3rewt/EQqADCJ38fe8SDyHv9JTAp7DX+kZwjlj96rvMfxFKAAygNLF3+NiiRD+Sg8JRAh/pUcIl4xpXd9r+ItQAKQztYvf8qKJFP6KpQQihb9iGcI1Y1nV9xz+IhQA6Ujr4re4eCKGv2IhgYjhr1iEcMsYrfW9h78IBUA6YbX4W8aJHP5KiwQih7+CDPCWMSKEvwgFQDpgvfhrxpsh/JUaCcwQ/gryVzg1Y0UJfxEKgBjTa/GXjDtT+CslEpgp/BXkH3FLxowU/iIUADGk9+LPGX/G8FdyJDBj+CvI2zhzxo4W/iIUADFi1OI/qjNz+CtHEpg5/BXkB7mOakQMfxEKgBgwevFv1btC+CtbErhC+CvIRzls1Yoa/iIUAGkEtfiXda8U/spSAlcKfwX5MLdlzcjhLyLyA90AIbVYXHytz+JHbmh0exf8jjZAkI+S1vr3B06CFvAnANLElTf0EMnb6Htmzja5nxndSjTyMaAASDOUALoDLJEDsJb1PtJRjwEFQEygBNAdYIkagDWswz/9e8BjQAEQMygBdAdYIgZgKXvhn74e7BhQAMQUSgDdAZZoAVjCWfin1wU6BhQAMYcSQHeAJVIA5pIb/un1QY4BBUC6QAmgO8ASJQBzKA3/9L4Ax4ACIN2gBNAdYIkQgGfUhn96v/NjQAGQrlAC6A6weA/AI1rDP43j+BhQAKQ7lAC6AyyeA3APq/BP4zk9BhQAGQIlgO4Ai9cA3MI6/NO4Do8BBUCGQQmgO8DiMQDX9Ar/NL6zY0ABkKFQAugOsHgLwCW9wz/VcXQMKAAyHEoA3QEWTwGojAr/VM/JMaAACARKAN0BFi8BKDI+/FNdB8fgX71yI1vKC3JSAAAAAElFTkSuQmCC")); + + public static void generate(Fi path, String name, Fi outputDir) throws IOException{ + Pixmap image = new Pixmap(path); + + try{ + int width = image.width, height = image.height; + + if(width % 4 != 0 || height % 4 != 0) throw new IOException("Image dimensions are not divisible by 4: " + width + "x" + height); + if(width != height) throw new IOException("Image is not square: " + width + "x" + height); + + int cellSize = width / 4; + + IntIntMap colorToPosition = new IntIntMap(); + + for(int x = 0; x < 4; x++){ + for(int y = 0; y < 4; y++){ + colorToPosition.put(layout.get(x * layoutWidth / 12, y * layoutHeight / 4), Point2.pack(x * width / 4, y * height / 4)); + } + } + + int outWidth = width / 4 * 12, outHeight = height; + Pixmap out = new Pixmap(outWidth, outHeight); + + for(int cx = 0; cx < 12; cx++){ + for(int cy = 0; cy < 4; cy++){ + for(int rx = 0; rx < cellSize; rx++){ + for(int ry = 0; ry < cellSize; ry++){ + int point = colorToPosition.get(layout.get( + (cx * cellSize + rx) * layoutWidth / (width * 3), + (cy * cellSize + ry) * layoutHeight / height + ), -1); + + if(point != -1){ + int sx = Point2.x(point), sy = Point2.y(point); + out.set(cx * cellSize + rx, cy * cellSize + ry, image.get(sx + rx, sy + ry)); + } + } + } + } + } + + for(int i = 0; i < 47; i++){ + int cx = i % 12, cy = i / 12; + Pixmap cropped = out.crop(cx * cellSize, cy * cellSize, cellSize, cellSize); + outputDir.child(name + "-" + i).writePng(cropped); + cropped.dispose(); + } + + out.dispose(); + }finally{ + image.dispose(); + } + } +}