diff --git a/.gitignore b/.gitignore index e9f029335a..17c1aab8e5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ steam_appid.txt /android/assets/mindustry-maps/ /android/assets/mindustry-saves/ /core/assets/gifexport/ +/annotations/src/main/resources/META-INF/services /core/assets/version.properties /core/assets/locales /ios/src/mindustry/gen/ diff --git a/SERVERLIST.md b/SERVERLIST.md new file mode 100644 index 0000000000..5d3c0370c5 --- /dev/null +++ b/SERVERLIST.md @@ -0,0 +1,25 @@ +### Adding a server to the list + +Mindustry now has a public list of servers that everyone can see and connect to. +This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers.json) in this repository. + +You may want to add your server to this list. The steps for getting this done are as follows: + +1. **Ensure your server is properly moderated.** For the most part, this applies to survival servers, but PvP servers can be affected as well. +You'll need to either hire some moderators, or make use of (currently non-existent) anti-grief and anti-curse plugins. +*Consider enabling a rate limit:* `config messageRateLimit 2` will make it so that players can only send messages every 2 seconds, for example. +2. **Set an aproppriate MOTD, name and description.** This is set with `config `. "Aproppriate" means that: + - Your name or description must reflect the type of server you're hosting. + Since new players may be exposed to the server list early on, put in a phrase like "Co-op survival" or "PvP" so players know what they're getting into. Yes, this is also displayed in the server mode info text, but having extra info in the name doesn't hurt. + - Make sure players know where to refer to for server support. It should be fairly clear that the server owner is not me, but you. + - Try to be professional in your text; use common sense. +3. **Get some good maps** *(optional, but highly recommended)*. Add some maps to your server and set the map rotation to custom-only. You can get maps from the Steam workshop by subscribing and exporting them; using the `#maps` channel on Discord is also an option. +4. Finally, **submit a pull request** to add your server's IP to the list. +This should be fairly straightforward: Press the edit button on the [server file](https://github.com/Anuken/Mindustry/blob/master/servers.json), then add a JSON object with a single key, indicating your server address. +For example, if your server address is `google.com`, you would add a comma after the last entry and insert: +```json + { + "address": "google.com" + } +``` +Then, press the *'submit pull request'* button and I'll take a look at your server. If I have any issues with it, I'll let you know in the PR comments. diff --git a/android/src/mindustry/android/AndroidLauncher.java b/android/src/mindustry/android/AndroidLauncher.java index 6c0a695000..561ce0b998 100644 --- a/android/src/mindustry/android/AndroidLauncher.java +++ b/android/src/mindustry/android/AndroidLauncher.java @@ -150,10 +150,22 @@ public class AndroidLauncher extends AndroidApplication{ }}); checkFiles(getIntent()); + //new external folder Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath()); + Core.settings.setDataDirectory(data); - //moved to internal storage if there's no file indicating that it moved + //delete old external files due to screwup + if(Core.files.local("files_moved").exists() && !Core.files.local("files_moved_103").exists()){ + for(Fi fi : data.list()){ + fi.deleteDirectory(); + } + + Core.files.local("files_moved").delete(); + Core.files.local("files_moved_103").writeString("files moved again"); + } + + //move to internal storage if there's no file indicating that it moved if(!Core.files.local("files_moved").exists()){ Log.info("Moving files to external storage..."); @@ -161,7 +173,7 @@ public class AndroidLauncher extends AndroidApplication{ //current local storage folder Fi src = Core.files.absolute(Core.files.getLocalStoragePath()); for(Fi fi : src.list()){ - fi.copyTo(data.child(fi.name())); + fi.copyTo(data); } //create marker Core.files.local("files_moved").writeString("files moved to " + data); @@ -170,8 +182,6 @@ public class AndroidLauncher extends AndroidApplication{ Log.err("Failed to move files!"); t.printStackTrace(); } - }else{ - Core.settings.setDataDirectory(data); } } diff --git a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java index f9a83c80fd..effbb72400 100644 --- a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java @@ -3,6 +3,7 @@ package mindustry.annotations; import javax.annotation.processing.*; import javax.lang.model.*; import javax.lang.model.element.*; +import javax.lang.model.util.*; import java.util.*; @SupportedSourceVersion(SourceVersion.RELEASE_8) @@ -10,20 +11,34 @@ public abstract class BaseProcessor extends AbstractProcessor{ /** Name of the base package to put all the generated classes. */ public static final String packageName = "mindustry.gen"; - private int round; + public static Types typeu; + public static Elements elementu; + public static Filer filer; + public static Messager messager; + + protected int round; + + public static String getMethodName(Element element){ + return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); + } + + public static boolean isPrimitive(String type){ + return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int") + || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char"); + } @Override public synchronized void init(ProcessingEnvironment processingEnv){ super.init(processingEnv); - //put all relevant utils into utils class - Utils.typeUtils = processingEnv.getTypeUtils(); - Utils.elementUtils = processingEnv.getElementUtils(); - Utils.filer = processingEnv.getFiler(); - Utils.messager = processingEnv.getMessager(); + + typeu = processingEnv.getTypeUtils(); + elementu = processingEnv.getElementUtils(); + filer = processingEnv.getFiler(); + messager = processingEnv.getMessager(); } @Override - public final boolean process(Set annotations, RoundEnvironment roundEnv){ + public boolean process(Set annotations, RoundEnvironment roundEnv){ if(round++ != 0) return false; //only process 1 round try{ process(roundEnv); @@ -39,5 +54,7 @@ public abstract class BaseProcessor extends AbstractProcessor{ return SourceVersion.RELEASE_8; } - public abstract void process(RoundEnvironment env) throws Exception; + public void process(RoundEnvironment env) throws Exception{ + + } } diff --git a/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java deleted file mode 100644 index 01a3fdb0e1..0000000000 --- a/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java +++ /dev/null @@ -1,62 +0,0 @@ -package mindustry.annotations; - -import com.sun.source.util.*; -import com.sun.tools.javac.tree.*; -import com.sun.tools.javac.tree.JCTree.*; -import mindustry.annotations.Annotations.*; - -import javax.annotation.processing.*; -import javax.lang.model.*; -import javax.lang.model.element.*; -import javax.tools.Diagnostic.*; -import java.util.*; - -@SupportedAnnotationTypes({"java.lang.Override"}) -public class CallSuperAnnotationProcessor extends AbstractProcessor{ - private Trees trees; - - @Override - public void init(ProcessingEnvironment pe){ - super.init(pe); - trees = Trees.instance(pe); - } - - public boolean process(Set annotations, RoundEnvironment roundEnv){ - for(Element e : roundEnv.getElementsAnnotatedWith(Override.class)){ - if(e.getAnnotation(OverrideCallSuper.class) != null) return false; - - CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner(); - codeScanner.setMethodName(e.getSimpleName().toString()); - - TreePath tp = trees.getPath(e.getEnclosingElement()); - codeScanner.scan(tp, trees); - - if(codeScanner.isCallSuperUsed()){ - List list = codeScanner.getMethod().getBody().getStatements(); - - if(!doesCallSuper(list, codeScanner.getMethodName())){ - processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.getMethodName() + "' must explicitly call super method from its parent class.", e); - } - } - } - - return false; - } - - private boolean doesCallSuper(List list, String methodName){ - for(Object object : list){ - if(object instanceof JCTree.JCExpressionStatement){ - JCTree.JCExpressionStatement expr = (JCExpressionStatement)object; - String exprString = expr.toString(); - if(exprString.startsWith("super." + methodName) && exprString.endsWith(");")) return true; - } - } - - return false; - } - - @Override - public SourceVersion getSupportedSourceVersion(){ - return SourceVersion.RELEASE_8; - } -} diff --git a/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java b/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java deleted file mode 100644 index 13059b38b8..0000000000 --- a/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java +++ /dev/null @@ -1,110 +0,0 @@ -package mindustry.annotations; - -import com.sun.source.tree.*; -import com.sun.source.util.TreePathScanner; -import com.sun.source.util.Trees; -import com.sun.tools.javac.code.Scope; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.code.Symbol.MethodSymbol; -import com.sun.tools.javac.code.Type.ClassType; -import com.sun.tools.javac.tree.JCTree.JCIdent; -import com.sun.tools.javac.tree.JCTree.JCTypeApply; -import mindustry.annotations.Annotations.CallSuper; - -import java.lang.annotation.Annotation; - -class CodeAnalyzerTreeScanner extends TreePathScanner { - private String methodName; - private MethodTree method; - private boolean callSuperUsed; - - @Override - public Object visitClass (ClassTree classTree, Trees trees) { - Tree extendTree = classTree.getExtendsClause(); - - if (extendTree instanceof JCTypeApply) { //generic classes case - JCTypeApply generic = (JCTypeApply) extendTree; - extendTree = generic.clazz; - } - - if (extendTree instanceof JCIdent) { - JCIdent tree = (JCIdent) extendTree; - Scope members = tree.sym.members(); - - if (checkScope(members)) - return super.visitClass(classTree, trees); - - if (checkSuperTypes((ClassType) tree.type)) - return super.visitClass(classTree, trees); - - } - callSuperUsed = false; - - return super.visitClass(classTree, trees); - } - - public boolean checkSuperTypes (ClassType type) { - if (type.supertype_field != null && type.supertype_field.tsym != null) { - if (checkScope(type.supertype_field.tsym.members())) - return true; - else - return checkSuperTypes((ClassType) type.supertype_field); - } - - return false; - } - - @SuppressWarnings("unchecked") - public boolean checkScope (Scope members) { - Iterable it; - try{ - it = (Iterable)members.getClass().getMethod("getElements").invoke(members); - }catch(Throwable t){ - try{ - it = (Iterable)members.getClass().getMethod("getSymbols").invoke(members); - }catch(Exception e){ - throw new RuntimeException(e); - } - } - - for (Symbol s : it) { - if (s instanceof MethodSymbol) { - MethodSymbol ms = (MethodSymbol) s; - - if (ms.getSimpleName().toString().equals(methodName)) { - Annotation annotation = ms.getAnnotation(CallSuper.class); - if (annotation != null) { - callSuperUsed = true; - return true; - } - } - } - } - - return false; - } - - @Override - public Object visitMethod (MethodTree methodTree, Trees trees) { - if (methodTree.getName().toString().equals(methodName)) - method = methodTree; - - return super.visitMethod(methodTree, trees); - } - - public void setMethodName (String methodName) { - this.methodName = methodName; - } - - public String getMethodName () { - return methodName; - } - - public MethodTree getMethod () { - return method; - } - - public boolean isCallSuperUsed () { - return callSuperUsed; - } -} \ No newline at end of file diff --git a/annotations/src/main/java/mindustry/annotations/Utils.java b/annotations/src/main/java/mindustry/annotations/Utils.java deleted file mode 100644 index 773d6561d2..0000000000 --- a/annotations/src/main/java/mindustry/annotations/Utils.java +++ /dev/null @@ -1,24 +0,0 @@ -package mindustry.annotations; - -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -public class Utils{ - public static Types typeUtils; - public static Elements elementUtils; - public static Filer filer; - public static Messager messager; - - public static String getMethodName(Element element){ - return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); - } - - public static boolean isPrimitive(String type){ - return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int") - || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char"); - } -} diff --git a/annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/impl/AssetsAnnotationProcessor.java similarity index 94% rename from annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java rename to annotations/src/main/java/mindustry/annotations/impl/AssetsAnnotationProcessor.java index 0ef33f671d..2ac95b7b40 100644 --- a/annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/impl/AssetsAnnotationProcessor.java @@ -1,10 +1,11 @@ -package mindustry.annotations; +package mindustry.annotations.impl; import arc.files.*; import arc.scene.style.*; import arc.struct.*; import arc.util.serialization.*; import com.squareup.javapoet.*; +import mindustry.annotations.*; import mindustry.annotations.Annotations.*; import javax.annotation.processing.*; @@ -20,7 +21,7 @@ public class AssetsAnnotationProcessor extends BaseProcessor{ @Override public void process(RoundEnvironment env) throws Exception{ - path = Fi.get(Utils.filer.createResource(StandardLocation.CLASS_OUTPUT, "no", "no") + path = Fi.get(BaseProcessor.filer.createResource(StandardLocation.CLASS_OUTPUT, "no", "no") .toUri().toURL().toString().substring(System.getProperty("os.name").contains("Windows") ? 6 : "file:".length())) .parent().parent().parent().parent().parent().parent().toString(); path = path.replace("%20", " "); @@ -85,12 +86,12 @@ public class AssetsAnnotationProcessor extends BaseProcessor{ } ictype.addMethod(icload.build()); - JavaFile.builder(packageName, ichtype.build()).build().writeTo(Utils.filer); - JavaFile.builder(packageName, ictype.build()).build().writeTo(Utils.filer); + JavaFile.builder(packageName, ichtype.build()).build().writeTo(BaseProcessor.filer); + JavaFile.builder(packageName, ictype.build()).build().writeTo(BaseProcessor.filer); type.addMethod(load.build()); type.addMethod(loadStyles.build()); - JavaFile.builder(packageName, type.build()).build().writeTo(Utils.filer); + JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer); } void processSounds(String classname, String path, String rtype) throws Exception{ @@ -104,7 +105,7 @@ public class AssetsAnnotationProcessor extends BaseProcessor{ String name = p.nameWithoutExtension(); if(names.contains(name)){ - Utils.messager.printMessage(Kind.ERROR, "Duplicate file name: " + p.toString() + "!"); + BaseProcessor.messager.printMessage(Kind.ERROR, "Duplicate file name: " + p.toString() + "!"); }else{ names.add(name); } @@ -130,7 +131,7 @@ public class AssetsAnnotationProcessor extends BaseProcessor{ type.addMethod(loadBegin.build()); type.addMethod(dispose.build()); - JavaFile.builder(packageName, type.build()).build().writeTo(Utils.filer); + JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer); } static String capitalize(String s){ diff --git a/annotations/src/main/java/mindustry/annotations/impl/CallSuperAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/impl/CallSuperAnnotationProcessor.java new file mode 100644 index 0000000000..4815073331 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/impl/CallSuperAnnotationProcessor.java @@ -0,0 +1,150 @@ +package mindustry.annotations.impl; + +import com.sun.source.tree.*; +import com.sun.source.util.*; +import com.sun.tools.javac.code.Scope; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import mindustry.annotations.Annotations.*; + +import javax.annotation.processing.*; +import javax.lang.model.*; +import javax.lang.model.element.*; +import javax.tools.Diagnostic.*; +import java.lang.annotation.*; +import java.util.*; + +@SupportedAnnotationTypes({"java.lang.Override"}) +public class CallSuperAnnotationProcessor extends AbstractProcessor{ + private Trees trees; + + @Override + public void init(ProcessingEnvironment pe){ + super.init(pe); + trees = Trees.instance(pe); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv){ + for(Element e : roundEnv.getElementsAnnotatedWith(Override.class)){ + if(e.getAnnotation(OverrideCallSuper.class) != null) return false; + + CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner(); + codeScanner.methodName = e.getSimpleName().toString(); + + TreePath tp = trees.getPath(e.getEnclosingElement()); + codeScanner.scan(tp, trees); + + if(codeScanner.callSuperUsed){ + List list = codeScanner.method.getBody().getStatements(); + + if(!doesCallSuper(list, codeScanner.methodName)){ + processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.methodName + "' must explicitly call super method from its parent class.", e); + } + } + } + + return false; + } + + private boolean doesCallSuper(List list, String methodName){ + for(Object object : list){ + if(object instanceof JCTree.JCExpressionStatement){ + JCTree.JCExpressionStatement expr = (JCExpressionStatement)object; + String exprString = expr.toString(); + if(exprString.startsWith("super." + methodName) && exprString.endsWith(");")) return true; + } + } + + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion(){ + return SourceVersion.RELEASE_8; + } + + static class CodeAnalyzerTreeScanner extends TreePathScanner{ + private String methodName; + private MethodTree method; + private boolean callSuperUsed; + + @Override + public Object visitClass(ClassTree classTree, Trees trees){ + Tree extendTree = classTree.getExtendsClause(); + + if(extendTree instanceof JCTypeApply){ //generic classes case + JCTypeApply generic = (JCTypeApply)extendTree; + extendTree = generic.clazz; + } + + if(extendTree instanceof JCIdent){ + JCIdent tree = (JCIdent)extendTree; + com.sun.tools.javac.code.Scope members = tree.sym.members(); + + if(checkScope(members)) + return super.visitClass(classTree, trees); + + if(checkSuperTypes((ClassType)tree.type)) + return super.visitClass(classTree, trees); + + } + callSuperUsed = false; + + return super.visitClass(classTree, trees); + } + + public boolean checkSuperTypes(ClassType type){ + if(type.supertype_field != null && type.supertype_field.tsym != null){ + if(checkScope(type.supertype_field.tsym.members())) + return true; + else + return checkSuperTypes((ClassType)type.supertype_field); + } + + return false; + } + + @SuppressWarnings("unchecked") + public boolean checkScope(Scope members){ + Iterable it; + try{ + it = (Iterable)members.getClass().getMethod("getElements").invoke(members); + }catch(Throwable t){ + try{ + it = (Iterable)members.getClass().getMethod("getSymbols").invoke(members); + }catch(Exception e){ + throw new RuntimeException(e); + } + } + + for(Symbol s : it){ + if(s instanceof MethodSymbol){ + MethodSymbol ms = (MethodSymbol)s; + + if(ms.getSimpleName().toString().equals(methodName)){ + Annotation annotation = ms.getAnnotation(CallSuper.class); + if(annotation != null){ + callSuperUsed = true; + return true; + } + } + } + } + + return false; + } + + @Override + public Object visitMethod(MethodTree methodTree, Trees trees){ + if(methodTree.getName().toString().equals(methodName)) + method = methodTree; + + return super.visitMethod(methodTree, trees); + } + + } +} diff --git a/annotations/src/main/java/mindustry/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/impl/SerializeAnnotationProcessor.java similarity index 93% rename from annotations/src/main/java/mindustry/annotations/SerializeAnnotationProcessor.java rename to annotations/src/main/java/mindustry/annotations/impl/SerializeAnnotationProcessor.java index 16ebcc9ead..3d07b2abce 100644 --- a/annotations/src/main/java/mindustry/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/impl/SerializeAnnotationProcessor.java @@ -1,14 +1,14 @@ -package mindustry.annotations; +package mindustry.annotations.impl; import com.squareup.javapoet.*; +import mindustry.annotations.*; import mindustry.annotations.Annotations.*; +import mindustry.annotations.remote.*; import javax.annotation.processing.*; -import javax.lang.model.*; import javax.lang.model.element.Modifier; import javax.lang.model.element.*; import javax.lang.model.util.*; -import javax.tools.Diagnostic.*; import java.io.*; import java.lang.reflect.*; import java.util.*; @@ -55,13 +55,13 @@ public class SerializeAnnotationProcessor extends BaseProcessor{ readMethod.addStatement("$L object = new $L()", type, type); - List fields = ElementFilter.fieldsIn(Utils.elementUtils.getAllMembers(elem)); + List fields = ElementFilter.fieldsIn(BaseProcessor.elementu.getAllMembers(elem)); for(VariableElement field : fields){ if(field.getModifiers().contains(Modifier.STATIC) || field.getModifiers().contains(Modifier.TRANSIENT) || field.getModifiers().contains(Modifier.PRIVATE)) continue; String name = field.getSimpleName().toString(); - String typeName = Utils.typeUtils.erasure(field.asType()).toString().replace('$', '.'); + String typeName = BaseProcessor.typeu.erasure(field.asType()).toString().replace('$', '.'); String capName = Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); if(field.asType().getKind().isPrimitive()){ @@ -78,7 +78,7 @@ public class SerializeAnnotationProcessor extends BaseProcessor{ serializer.addMethod(writeMethod.build()); serializer.addMethod(readMethod.build()); - method.addStatement("arc.Core.settings.setSerializer($N, $L)", Utils.elementUtils.getBinaryName(elem).toString().replace('$', '.') + ".class", serializer.build()); + method.addStatement("arc.Core.settings.setSerializer($N, $L)", BaseProcessor.elementu.getBinaryName(elem).toString().replace('$', '.') + ".class", serializer.build()); name(writeMethod, "write" + simpleTypeName); name(readMethod, "read" + simpleTypeName); @@ -93,7 +93,7 @@ public class SerializeAnnotationProcessor extends BaseProcessor{ classBuilder.addMethod(method.build()); //write result - JavaFile.builder(packageName, classBuilder.build()).build().writeTo(Utils.filer); + JavaFile.builder(packageName, classBuilder.build()).build().writeTo(BaseProcessor.filer); } static void name(MethodSpec.Builder builder, String name){ diff --git a/annotations/src/main/java/mindustry/annotations/StructAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/impl/StructAnnotationProcessor.java similarity index 95% rename from annotations/src/main/java/mindustry/annotations/StructAnnotationProcessor.java rename to annotations/src/main/java/mindustry/annotations/impl/StructAnnotationProcessor.java index 48d5cf33f0..e4387245c5 100644 --- a/annotations/src/main/java/mindustry/annotations/StructAnnotationProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/impl/StructAnnotationProcessor.java @@ -1,11 +1,11 @@ -package mindustry.annotations; +package mindustry.annotations.impl; import com.squareup.javapoet.*; +import mindustry.annotations.*; import mindustry.annotations.Annotations.Struct; import mindustry.annotations.Annotations.StructField; import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.TypeKind; import javax.lang.model.util.ElementFilter; @@ -29,7 +29,7 @@ public class StructAnnotationProcessor extends BaseProcessor{ for(TypeElement elem : elements){ if(!elem.getSimpleName().toString().endsWith("Struct")){ - Utils.messager.printMessage(Kind.ERROR, "All classes annotated with @Struct must have their class names end in 'Struct'.", elem); + BaseProcessor.messager.printMessage(Kind.ERROR, "All classes annotated with @Struct must have their class names end in 'Struct'.", elem); continue; } @@ -45,7 +45,7 @@ public class StructAnnotationProcessor extends BaseProcessor{ int structTotalSize = (structSize <= 8 ? 8 : structSize <= 16 ? 16 : structSize <= 32 ? 32 : 64); if(variables.size() == 0){ - Utils.messager.printMessage(Kind.ERROR, "making a struct with no fields is utterly pointles.", elem); + BaseProcessor.messager.printMessage(Kind.ERROR, "making a struct with no fields is utterly pointles.", elem); continue; } @@ -130,10 +130,10 @@ public class StructAnnotationProcessor extends BaseProcessor{ constructor.addStatement("return ($T)($L)", structType, cons.toString().substring(3)); classBuilder.addMethod(constructor.build()); - JavaFile.builder(packageName, classBuilder.build()).build().writeTo(Utils.filer); + JavaFile.builder(packageName, classBuilder.build()).build().writeTo(BaseProcessor.filer); }catch(IllegalArgumentException e){ e.printStackTrace(); - Utils.messager.printMessage(Kind.ERROR, e.getMessage(), elem); + BaseProcessor.messager.printMessage(Kind.ERROR, e.getMessage(), elem); } } diff --git a/annotations/src/main/java/mindustry/annotations/ClassEntry.java b/annotations/src/main/java/mindustry/annotations/remote/ClassEntry.java similarity index 90% rename from annotations/src/main/java/mindustry/annotations/ClassEntry.java rename to annotations/src/main/java/mindustry/annotations/remote/ClassEntry.java index 002dcac88c..3474eff468 100644 --- a/annotations/src/main/java/mindustry/annotations/ClassEntry.java +++ b/annotations/src/main/java/mindustry/annotations/remote/ClassEntry.java @@ -1,4 +1,4 @@ -package mindustry.annotations; +package mindustry.annotations.remote; import java.util.ArrayList; diff --git a/annotations/src/main/java/mindustry/annotations/IOFinder.java b/annotations/src/main/java/mindustry/annotations/remote/IOFinder.java similarity index 82% rename from annotations/src/main/java/mindustry/annotations/IOFinder.java rename to annotations/src/main/java/mindustry/annotations/remote/IOFinder.java index e586cbd4a8..16384f241a 100644 --- a/annotations/src/main/java/mindustry/annotations/IOFinder.java +++ b/annotations/src/main/java/mindustry/annotations/remote/IOFinder.java @@ -1,5 +1,6 @@ -package mindustry.annotations; +package mindustry.annotations.remote; +import mindustry.annotations.*; import mindustry.annotations.Annotations.ReadClass; import mindustry.annotations.Annotations.WriteClass; @@ -11,8 +12,8 @@ import java.util.HashMap; import java.util.Set; /** - * This class finds reader and writer methods annotated by the {@link Annotations.WriteClass} - * and {@link Annotations.ReadClass} annotations. + * This class finds reader and writer methods annotated by the {@link WriteClass} + * and {@link ReadClass} annotations. */ public class IOFinder{ @@ -34,21 +35,21 @@ public class IOFinder{ //make sure there's only one read method if(readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count() > 1){ - Utils.messager.printMessage(Kind.ERROR, "Multiple writer methods for type '" + typeName + "'", writer); + BaseProcessor.messager.printMessage(Kind.ERROR, "Multiple writer methods for type '" + typeName + "'", writer); } //make sure there's only one write method long count = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count(); if(count == 0){ - Utils.messager.printMessage(Kind.ERROR, "Writer method does not have an accompanying reader: ", writer); + BaseProcessor.messager.printMessage(Kind.ERROR, "Writer method does not have an accompanying reader: ", writer); }else if(count > 1){ - Utils.messager.printMessage(Kind.ERROR, "Writer method has multiple reader for type: ", writer); + BaseProcessor.messager.printMessage(Kind.ERROR, "Writer method has multiple reader for type: ", writer); } Element reader = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).findFirst().get(); //add to result list - result.put(typeName, new ClassSerializer(Utils.getMethodName(reader), Utils.getMethodName(writer), typeName)); + result.put(typeName, new ClassSerializer(BaseProcessor.getMethodName(reader), BaseProcessor.getMethodName(writer), typeName)); } return result; diff --git a/annotations/src/main/java/mindustry/annotations/MethodEntry.java b/annotations/src/main/java/mindustry/annotations/remote/MethodEntry.java similarity index 98% rename from annotations/src/main/java/mindustry/annotations/MethodEntry.java rename to annotations/src/main/java/mindustry/annotations/remote/MethodEntry.java index 0010d4b4d4..68ea81dec0 100644 --- a/annotations/src/main/java/mindustry/annotations/MethodEntry.java +++ b/annotations/src/main/java/mindustry/annotations/remote/MethodEntry.java @@ -1,4 +1,4 @@ -package mindustry.annotations; +package mindustry.annotations.remote; import mindustry.annotations.Annotations.*; diff --git a/annotations/src/main/java/mindustry/annotations/RemoteMethodAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/remote/RemoteMethodAnnotationProcessor.java similarity index 79% rename from annotations/src/main/java/mindustry/annotations/RemoteMethodAnnotationProcessor.java rename to annotations/src/main/java/mindustry/annotations/remote/RemoteMethodAnnotationProcessor.java index 2da9ddb35c..7f3acc0c86 100644 --- a/annotations/src/main/java/mindustry/annotations/RemoteMethodAnnotationProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/remote/RemoteMethodAnnotationProcessor.java @@ -1,32 +1,28 @@ -package mindustry.annotations; +package mindustry.annotations.remote; import com.squareup.javapoet.*; -import mindustry.annotations.Annotations.Loc; -import mindustry.annotations.Annotations.Remote; -import mindustry.annotations.IOFinder.ClassSerializer; +import mindustry.annotations.*; +import mindustry.annotations.Annotations.*; +import mindustry.annotations.remote.IOFinder.*; import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; import javax.lang.model.element.*; -import javax.tools.Diagnostic.Kind; +import javax.tools.Diagnostic.*; import java.util.*; -import java.util.stream.Collectors; +import java.util.stream.*; /** The annotation processor for generating remote method call code. */ -@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes({ "mindustry.annotations.Annotations.Remote", "mindustry.annotations.Annotations.WriteClass", "mindustry.annotations.Annotations.ReadClass", }) -public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ +public class RemoteMethodAnnotationProcessor extends BaseProcessor{ /** Maximum size of each event packet. */ public static final int maxPacketSize = 4096; /** Warning on top of each autogenerated file. */ public static final String autogenWarning = "Autogenerated file. Do not modify!\n"; - /** Name of the base package to put all the generated classes. */ - private static final String packageName = "mindustry.gen"; /** Name of class that handles reading and invoking packets on the server. */ private static final String readServerName = "RemoteReadServer"; @@ -35,9 +31,6 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ /** Simple class name of generated class name. */ private static final String callLocation = "Call"; - /** Processing round number. */ - private int round; - //class serializers private HashMap serializers; //all elements with the Remote annotation @@ -49,16 +42,6 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ //list of all method entries private ArrayList classes; - @Override - public synchronized void init(ProcessingEnvironment processingEnv){ - super.init(processingEnv); - //put all relevant utils into utils class - Utils.typeUtils = processingEnv.getTypeUtils(); - Utils.elementUtils = processingEnv.getElementUtils(); - Utils.filer = processingEnv.getFiler(); - Utils.messager = processingEnv.getMessager(); - } - @Override public boolean process(Set annotations, RoundEnvironment roundEnv){ if(round > 1) return false; //only process 2 rounds @@ -91,12 +74,12 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ //check for static if(!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)){ - Utils.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element); + BaseProcessor.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element); } //can't generate none methods if(annotation.targets() == Loc.none){ - Utils.messager.printMessage(Kind.ERROR, "A @Remote method's targets() cannot be equal to 'none':", element); + BaseProcessor.messager.printMessage(Kind.ERROR, "A @Remote method's targets() cannot be equal to 'none':", element); } //get and create class entry if needed @@ -109,7 +92,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ ClassEntry entry = classMap.get(callLocation); //create and add entry - MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.targets(), annotation.variants(), + MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element), annotation.targets(), annotation.variants(), annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, (ExecutableElement)element, annotation.priority()); entry.methods.add(method); @@ -139,7 +122,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ //build and write resulting hash class TypeSpec spec = hashBuilder.build(); - JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer); return true; } diff --git a/annotations/src/main/java/mindustry/annotations/RemoteReadGenerator.java b/annotations/src/main/java/mindustry/annotations/remote/RemoteReadGenerator.java similarity index 94% rename from annotations/src/main/java/mindustry/annotations/RemoteReadGenerator.java rename to annotations/src/main/java/mindustry/annotations/remote/RemoteReadGenerator.java index 166c7c5ce5..ab7abb2538 100644 --- a/annotations/src/main/java/mindustry/annotations/RemoteReadGenerator.java +++ b/annotations/src/main/java/mindustry/annotations/remote/RemoteReadGenerator.java @@ -1,7 +1,8 @@ -package mindustry.annotations; +package mindustry.annotations.remote; import com.squareup.javapoet.*; -import mindustry.annotations.IOFinder.ClassSerializer; +import mindustry.annotations.*; +import mindustry.annotations.remote.IOFinder.ClassSerializer; import javax.lang.model.element.*; import javax.tools.Diagnostic.Kind; @@ -82,7 +83,7 @@ public class RemoteReadGenerator{ String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); //write primitives automatically - if(Utils.isPrimitive(typeName)){ + if(BaseProcessor.isPrimitive(typeName)){ if(typeName.equals("boolean")){ readBlock.addStatement("boolean " + varName + " = buffer.get() == 1"); }else{ @@ -93,7 +94,7 @@ public class RemoteReadGenerator{ ClassSerializer ser = serializers.get(typeName); if(ser == null){ //make sure a serializer exists! - Utils.messager.printMessage(Kind.ERROR, "No @ReadClass method to read class type: '" + typeName + "'", var); + BaseProcessor.messager.printMessage(Kind.ERROR, "No @ReadClass method to read class type: '" + typeName + "'", var); return; } @@ -139,6 +140,6 @@ public class RemoteReadGenerator{ //build and write resulting class TypeSpec spec = classBuilder.build(); - JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer); } } diff --git a/annotations/src/main/java/mindustry/annotations/RemoteWriteGenerator.java b/annotations/src/main/java/mindustry/annotations/remote/RemoteWriteGenerator.java similarity index 93% rename from annotations/src/main/java/mindustry/annotations/RemoteWriteGenerator.java rename to annotations/src/main/java/mindustry/annotations/remote/RemoteWriteGenerator.java index dfe3f1fa00..19c5d73df3 100644 --- a/annotations/src/main/java/mindustry/annotations/RemoteWriteGenerator.java +++ b/annotations/src/main/java/mindustry/annotations/remote/RemoteWriteGenerator.java @@ -1,8 +1,9 @@ -package mindustry.annotations; +package mindustry.annotations.remote; import com.squareup.javapoet.*; +import mindustry.annotations.*; import mindustry.annotations.Annotations.Loc; -import mindustry.annotations.IOFinder.ClassSerializer; +import mindustry.annotations.remote.IOFinder.ClassSerializer; import javax.lang.model.element.*; import javax.tools.Diagnostic.Kind; @@ -52,7 +53,7 @@ public class RemoteWriteGenerator{ //build and write resulting class TypeSpec spec = classBuilder.build(); - JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer); } } @@ -73,12 +74,12 @@ public class RemoteWriteGenerator{ //validate client methods to make sure if(methodEntry.where.isClient){ if(elem.getParameters().isEmpty()){ - Utils.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem); + BaseProcessor.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem); return; } if(!elem.getParameters().get(0).asType().toString().equals("mindustry.entities.type.Player")){ - Utils.messager.printMessage(Kind.ERROR, "Client invoke methods should have a first parameter of type Player.", elem); + BaseProcessor.messager.printMessage(Kind.ERROR, "Client invoke methods should have a first parameter of type Player.", elem); return; } } @@ -162,7 +163,7 @@ public class RemoteWriteGenerator{ method.beginControlFlow("if(mindustry.Vars.net.server())"); } - if(Utils.isPrimitive(typeName)){ //check if it's a primitive, and if so write it + if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it if(typeName.equals("boolean")){ //booleans are special method.addStatement("TEMP_BUFFER.put(" + varName + " ? (byte)1 : 0)"); }else{ @@ -174,7 +175,7 @@ public class RemoteWriteGenerator{ ClassSerializer ser = serializers.get(typeName); if(ser == null){ //make sure a serializer exists! - Utils.messager.printMessage(Kind.ERROR, "No @WriteClass method to write class type: '" + typeName + "'", var); + BaseProcessor.messager.printMessage(Kind.ERROR, "No @WriteClass method to write class type: '" + typeName + "'", var); return; } diff --git a/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 0888df74ee..0000000000 --- a/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1,5 +0,0 @@ -mindustry.annotations.RemoteMethodAnnotationProcessor -mindustry.annotations.SerializeAnnotationProcessor -mindustry.annotations.StructAnnotationProcessor -mindustry.annotations.CallSuperAnnotationProcessor -mindustry.annotations.AssetsAnnotationProcessor \ No newline at end of file diff --git a/build.gradle b/build.gradle index 957eddbde9..113ef06226 100644 --- a/build.gradle +++ b/build.gradle @@ -132,6 +132,20 @@ allprojects{ props.store(pfile.newWriter(), "Autogenerated file. Do not modify.") } } + + writeProcessors = { + new File(rootDir, "annotations/src/main/resources/META-INF/services/").mkdirs() + def processorFile = new File(rootDir, "annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor") + def text = new StringBuilder() + def files = new File(rootDir, "annotations/src/main/java") + files.eachFileRecurse(groovy.io.FileType.FILES){ file -> + if(file.name.endsWith(".java") && (file.text.contains(" extends BaseProcessor") || (file.text.contains(" extends AbstractProcessor") && !file.text.contains("abstract class")))){ + text.append(file.path.substring(files.path.length() + 1)).append("\n") + } + } + + processorFile.text = text.toString().replace(".java", "").replace("/", ".").replace("\\", ".") + } } repositories{ @@ -205,6 +219,7 @@ project(":core"){ outputs.upToDateWhen{ false } generateLocales() writeVersion() + writeProcessors() } task copyChangelog{ @@ -229,31 +244,6 @@ project(":core"){ } dependencies{ - if(System.properties["user.name"] == "anuke"){ - task cleanGen{ - doFirst{ - delete{ - delete "../core/src/mindustry/gen/" - } - } - } - - task copyGen{ - doLast{ - copy{ - from("../core/build/generated/sources/annotationProcessor/java/main/mindustry/gen"){ - include "**/*.java" - } - - into "../core/src/mindustry/gen" - } - } - } - - compileJava.dependsOn(cleanGen) - compileJava.finalizedBy(copyGen) - } - compileJava.dependsOn(preGen) compile "org.lz4:lz4-java:1.4.1" diff --git a/core/assets/bundles/bundle_cs.properties b/core/assets/bundles/bundle_cs.properties index dd3759e71a..9249b5d1f5 100644 --- a/core/assets/bundles/bundle_cs.properties +++ b/core/assets/bundles/bundle_cs.properties @@ -532,6 +532,8 @@ error.crashtitle = Objevila se chyba blocks.input = Vstup blocks.output = Výstup blocks.booster = Posilovač +blocks.tiles = Vyžadované dlaždice +blocks.affinities = Synergie block.unknown = [lightgray]???[] blocks.powercapacity = Kapacita energie blocks.powershot = Energie na 1 výstřel @@ -666,6 +668,7 @@ setting.mutesound.name = Ztišit zvuk setting.crashreport.name = Poslat anonymní hlášení o spadnutí Mindustry setting.savecreate.name = Automaticky ukládat hru setting.publichost.name = Veřejná viditelnost hry +setting.playerlimit.name = Nejvyšší počet hráčů setting.chatopacity.name = Průsvitnost kanálu zpráv setting.lasersopacity.name = Průsvitnost energetického laseru setting.bridgeopacity.name = Průsvitnost přemostění @@ -1251,3 +1254,4 @@ block.omega-mech-pad.description = Umožňuje přeměnu Tvého vozidla na těžc block.javelin-ship-pad.description = Umožňuje přeměnu Tvého vozidla na rychlou, lehce obrněnou stíhačku.\nAktivuj kliknutím nebo ťupnutím, když se nacházíš nad plošinou. block.trident-ship-pad.description = Umožňuje přeměnu Tvého vozidla na těžkého podpůrného bombardéra.\nAktivuj kliknutím nebo ťupnutím, když se nacházíš nad plošinou. block.glaive-ship-pad.description = Umožňuje přeměnu Tvého vozidla na velkou, dobře obrněnou střeleckou loď.\nAktivuj kliknutím nebo ťupnutím, když se nacházíš nad plošinou. + \ No newline at end of file diff --git a/core/assets/bundles/bundle_it.properties b/core/assets/bundles/bundle_it.properties index fd746bb659..1e9e778507 100644 --- a/core/assets/bundles/bundle_it.properties +++ b/core/assets/bundles/bundle_it.properties @@ -3,7 +3,7 @@ credits = Crediti contributors = Traduttori e Contributori discord = Entra nel server Discord di Mindustry! link.discord.description = La chatroom ufficiale del server Discord di Mindustry -link.reddit.description = The Mindustry subreddit +link.reddit.description = Il subreddit di Mindustry! link.github.description = Codice sorgente del gioco link.changelog.description = Elenco delle modifiche del gioco link.dev-builds.description = Build di sviluppo versioni instabili @@ -45,7 +45,7 @@ schematic.exportfile = Esporta File schematic.importfile = Importa File schematic.browseworkshop = Naviga nel Workshop schematic.copy = Copia negli Appunti -schematic.copy.import = Importa dagli Appunti +schematic.copy.import = Incolla dagli Appunti schematic.shareworkshop = Condividi nel Workshop schematic.flip = [accent][[{0}][]/[accent][[{1}][]: Ruota Schematica schematic.saved = Schematica salvata. @@ -69,7 +69,7 @@ level.select = Selezione del Livello level.mode = Modalità di Gioco: showagain = Non mostrare più coreattack = < Il Nucleo è sotto attacco! > -nearpoint = [[ [scarlet]LASCIA LA ZONA NEMICA IMMEDIATAMENTE[] ]\nautodistruzione imminente +nearpoint = [[ [scarlet]LASCIA LA ZONA NEMICA IMMEDIATAMENTE[] ]\autodistruzione imminente database = Database Nucleo savegame = Salva loadgame = Carica @@ -140,10 +140,10 @@ researched = [lightgray]{0} cercati. players = {0} giocatori online players.single = {0} giocatore online server.closing = [accent]Chiusura server... -server.kicked.kick = Sei stato cacciato dal server! +server.kicked.kick = Sei stato espulso dal server! server.kicked.whitelist = Non sei presente nella whitelist. server.kicked.serverClose = Server chiuso. -server.kicked.vote = Sei stato cacciato su richiesta dei giocatori. Tanti saluti. +server.kicked.vote = Sei stato esplso su richiesta dei giocatori. Tanti saluti. server.kicked.clientOutdated = Versione del client obsoleta! Aggiorna il gioco! server.kicked.serverOutdated = Server obsoleto! Chiedi all'host di aggiornare la versione del server! server.kicked.banned = Sei stato bandito da questo server. @@ -199,7 +199,7 @@ joingame.ip = Indirizzo: disconnect = Disconnesso. disconnect.error = Errore di connessione. disconnect.closed = Connessione chiusa. -disconnect.timeout = Timed out. +disconnect.timeout = Connessione scaduta. disconnect.data = Errore durante il caricamento del mondo! cantconnect = Impossibile unirsi alla partita ([accent]{0}[]). connecting = [accent]Connessione in corso... @@ -280,7 +280,7 @@ custom = Personalizzato builtin = Incluso map.delete.confirm = Sei sicuro di voler eliminare questa mappa? L'operazione è irreversibile! map.random = [accent]Mappa casuale -map.nospawn = Questa mappa non possiede un Nucleo in cui spawnare! Aggiungine uno nell'editor. +map.nospawn = Questa mappa non possiede un Nucleo in cui generare! Aggiungine uno nell'editor. map.nospawn.pvp = Questa mappa non ha un Nucleo Nemico! Aggiungi dei Nuclei nell'editor per poter giocare. map.nospawn.attack = Questa mappa non ha nessun Nucleo Nemico da poter attaccare! Aggiungi dei Nuclei Nemici nell'editor per poter giocare. map.invalid = Errore nel caricamento della mappa: file mappa corrotto o non valido. diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 3bfa58ede8..a5ffab3aed 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -1,23 +1,23 @@ -credits.text = Створив [ROYAL]Anuken[] — [SKY]anukendev@gmail.com[]\n\nЄ питання по грі або проблеми з перекладом?\nЙдіть в офіційний сервер discord Mindustrу\nу канал #український.\nПерекладач: [blue]Prosta4ok_ua[green]#[yellow]6336 +credits.text = Створив [ROYAL]Anuken[] — [SKY]anukendev@gmail.com[]\n\nЄ ігрові питання або помилки в перекладі?\nЗавітайте до офіційного Discord-сервера Mindustrу\nв канал #український.\nПереклав українською: [blue]Prosta4ok_ua[green]#[yellow]6336 credits = Творці contributors = Перекладачі та помічники -discord = Приєднуйтесь до Mindustry Discord! -link.discord.description = Офіційний Discord сервер Mindustry +discord = Офіційний сервер Mindustry в Discord +link.discord.description = Приєднуйтесь до Discord-сервера Mindustrу! link.reddit.description = Спільнота Mindustry на Reddit link.github.description = Вихідний код гри -link.changelog.description = Список змін +link.changelog.description = Змінопис link.dev-builds.description = Нестабільні версії link.trello.description = Офіційна дошка Trello для запланованих функцій -link.itch.io.description = Itch.io сторінка, на якій можна завантажити гру +link.itch.io.description = Завантажити гру з Itch.io (окрім IOS) link.google-play.description = Завантажити для Android з Google Play link.f-droid.description = Завантажити для Android з F-Droid -link.wiki.description = Офіційна Mindustry wiki +link.wiki.description = Офіційна ігрова Wiki link.feathub.description = Запропонувати нові функції linkfail = Не вдалося відкрити посилання!\nURL-адреса скопійована в буфер обміну. -screenshot = Зняток мапи збережено в {0} +screenshot = Зняток мапи збережено до {0} screenshot.invalid = Мапа занадто велика, тому, мабуть, не вистачає пам’яті для знятку мапи. gameover = Гра завершена -gameover.pvp = [accent] {0}[] команда перемогла! +gameover.pvp = [accent]{0}[] команда перемогла! highscore = [YELLOW]Новий рекорд! copied = Скопійовано. @@ -47,9 +47,9 @@ schematic.browseworkshop = Переглянути в Майстерні schematic.copy = Копіювати в буфер обміну schematic.copy.import = Імпортувати з клавіатури schematic.shareworkshop = Поширити в Майстерню -schematic.flip = [accent][[{0}][]/[accent][[{1}][]: Відобразити схему +schematic.flip = [accent][[{0}][]/[accent][[{1}][]: Обернути схему schematic.saved = Схема збережена. -schematic.delete.confirm = Ця схема буде повністю випалена. +schematic.delete.confirm = Ця схема буде повністю випалена Викорінювачем. schematic.rename = Перейменувати схему. schematic.info = {0}x{1}, {2} блоків @@ -57,19 +57,19 @@ stat.wave = Хвиль відбито:[accent] {0} stat.enemiesDestroyed = Ворогів знищено:[accent] {0} stat.built = Будівель збудувано:[accent] {0} stat.destroyed = Будівель знищено:[accent] {0} -stat.deconstructed = Будівель декоструйовано[accent] {0} +stat.deconstructed = Будівель деконструйовано:[accent] {0} stat.delivered = Ресурсів запущено: stat.rank = Фінальний рахунок: [accent]{0} launcheditems = [accent]Запущені предмети -launchinfo = [unlaunched]Натисніть на кнопку «[[ЗАПУСК]», щоб ваше ядро отримало предмети, які виділені синім кольором. +launchinfo = [unlaunched]Натисніть на кнопку [[ЗАПУСК], щоб ваше ядро отримало предмети, які виділені синім кольором. map.delete = Ви впевнені, що хочете видалити мапу «[accent]{0}[]»? level.highscore = Рекорд: [accent]{0} level.select = Вибір мапи level.mode = Режим гри: showagain = Не показувати знову до наступного сеансу coreattack = < Ядро знаходиться під атакою! > -nearpoint = [[ [scarlet]ЗАЛИШТЕ ЗОНУ ВИСАДКИ НЕГАЙНО[] ]\nАннігіляція неминуча. +nearpoint = [[ [scarlet]ЗАЛИШТЕ ЗОНУ ВИСАДКИ НЕГАЙНО[] ]\nаннігіляція неминуча. database = База даних ядра savegame = Зберегти гру loadgame = Завантажити гру @@ -80,7 +80,7 @@ none = <нічого> minimap = Мінімапа position = Місцерозташування close = Закрити -website = Веб-сайт +website = Вебсайт quit = Вихід save.quit = Зберегти & Вийти maps = Мапи @@ -89,48 +89,49 @@ continue = Продовжити maps.none = [lightgray]Мап не знайдено! invalid = Недійсне pickcolor = Вибрати колір -preparingconfig = Підготовка конфігурації +preparingconfig = Підготовка налаштувань preparingcontent = Підготовка вмісту uploadingcontent = Вивантаження вмісту -uploadingpreviewfile = Вивантаження файлу передперегляду +uploadingpreviewfile = Вивантаження файлу попереднього перегляду committingchanges = Здійснення змін done = Зроблено -feature.unsupported = Your device does not support this feature. +feature.unsupported = Ваш пристрій не підтримує цю функцію -mods.alphainfo = Майте на увазі, що модифікації знаходяться в альфі, і [scarlet]може бути дуже глючними[].\nПовідомте про будь-які проблеми, які ви знайдете до Mindustry Github або Discord. +mods.alphainfo = Майте на увазі, що модифікації знаходяться в альфі, і [scarlet]можуть бути дуже глючними[].\nПовідомте про будь-які проблеми, які ви знайдете до Mindustry Github або Discord. mods.alpha = [scarlet](Альфа) mods = Модифікації mods.none = [LIGHT_GRAY]Модифікацій не знайдено! -mods.guide = Посібник з модифицій +mods.guide = Посібник з модифікацій mods.report = Повідомити про ваду -mods.openfolder = Відкрити теку модифікацій +mods.openfolder = Відкрити мод. теку +mod.display = [gray]Модифікація:[orange] {0} mod.enabled = [lightgray]Увімкнено mod.disabled = [scarlet]Вимкнено -mod.disable = Вимкн. +mod.disable = Вимкнути mod.delete.error = Неможливо видалити модифікацію. Файл, можливо, використовується. mod.requiresversion = [scarlet]Необхідна мінімальна версія гри: [accent]{0} mod.missingdependencies = [scarlet]Відсутні залежності: {0} mod.erroredcontent = [scarlet]Помилки при завантаженнні -mod.errors = Сталася помилка при завантаження змісту. +mod.errors = Виникли помилки при завантаження змісту. mod.noerrorplay = [scarlet]Ви маєте модифікації з помилками.[] Або вимкніть проблемні модифікації, або виправте їх. mod.nowdisabled = [scarlet]Модифікації «{0}» не вистачає залежних модифікацій:[accent] {1}\n[lightgray]Ці модифікації потрібно завантажити спочатку.\nЦя модифікація буде автоматично вимкнена. -mod.enable = Увімк. +mod.enable = Увімкнути mod.requiresrestart = А тепер гра закриється, щоб застосувати зміни модифікацій. mod.reloadrequired = [scarlet]Потрібно перезавантаження mod.import = Імпортувати модифікацію -mod.import.github = Завантажити мод з GitHub +mod.import.github = Завантажити мод. з GitHub mod.item.remove = Цей предмет є частиною модифікації [accent] «{0}»[]. Щоб видалити його, видаліть цю модифікацію. mod.remove.confirm = Цю модифікацію буде видалено. mod.author = [LIGHT_GRAY]Автор:[] {0} mod.missing = Це збереження містить модифікації, які ви нещодавно оновили або більше не встановлювали. Збереження може зіпсуватися. Ви впевнені, що хочете завантажити його?\n[lightgray]Модифікації:\n{0} mod.preview.missing = До публікації цієї модифікації в Майстерні, ви повинні додати зображення попереднього перегляду.\nПомістіть зображення з назвою [accent] preview.png[] у теку з модификаціями і спробуйте знову. mod.folder.missing = Тільки модификації у формі теці можуть бути опубліковані в Майстерні.\nЩоб перетворити будь-яку модификацію у теку, просто розархівуйте цей файлу теку та видаліть старий архів, і потім перезапустіть гру або перезавантажте ваші модификації. -mod.scripts.unsupported = Ваш пристрій не підтримує скрипти модифікацій. Деякі модифифікаціх не будуть працювати правильно. +mod.scripts.unsupported = Ваш пристрій не підтримує скрипти модифікацій. Деякі модифікації не будуть працювати правильно. about.button = Про гру name = Ім’я: noname = Спочатку придумайте[accent] собі ім’я[]. -filename = Ім’я файлу: +filename = Назва файлу: unlocked = Доступний новий вміст! completed = [accent]Завершено techtree = Дерево технологій @@ -141,35 +142,35 @@ players = Гравців: {0} players.single = {0} гравець на сервері server.closing = [accent]Закриття сервера… server.kicked.kick = Ви були вигнані з сервера! -server.kicked.whitelist = Ви не в білому спискі сервера! +server.kicked.whitelist = Ви не в білому списку сервера! server.kicked.serverClose = Сервер закрито. server.kicked.vote = Вас було вигнано із сервера за допомогою голосування. Прощавайте. server.kicked.clientOutdated = Застарілий клієнт! Оновіть свою гру! -server.kicked.serverOutdated = Застарілий сервер! Попросіть адміністратора сервера оновити сервер/гру! +server.kicked.serverOutdated = Застарілий сервер! Попрохайте адміністратора сервера оновити сервер/гру! server.kicked.banned = Ви заблоковані на цьому сервері. server.kicked.typeMismatch = Цей сервер не сумісний з вашим типом збірки. -server.kicked.playerLimit = Цей сервер — заповнений. Дочекайтесь вільного слота. -server.kicked.recentKick = Нещодавно вас вигнали. \nПочекайте трохи перед наступним підключенням. -server.kicked.nameInUse = На цьому сервері є хтось з таким ім’ям. +server.kicked.playerLimit = Цей сервер — заповнений. Дочекайтесь вільного місця. +server.kicked.recentKick = Нещодавно вас вигнали. \nПочекайте трохи перед наступним під’єднанням. +server.kicked.nameInUse = На цьому сервері вже є хтось з таким ім’ям. server.kicked.nameEmpty = Ваше ім’я має містити принаймні один символ або цифру. -server.kicked.idInUse = Ви вже на цьому сервері! Підключення двох облікових записів не дозволяється. +server.kicked.idInUse = Ви вже на цьому сервері! Під’єднанням двох облікових записів не дозволяється. server.kicked.customClient = Цей сервер не підтримує користувацькі збірки. Завантажте офіційну версію. server.kicked.gameover = Гра завершена! server.kicked.serverRestarting = Сервер перезавантажується server.versions = Ваша версія:[accent] {0}[]\nВерсія на сервері:[accent] {1}[] -host.info = Кнопка [accent]Сервер[] розміщує сервер на порті [scarlet]6567[]. \nКористувачі, які знаходяться у тій же [lightgray]WiFi або локальній мережі[], повинні бачити ваш сервер у своєму списку серверів.\n\nЯкщо ви хочете, щоб люди могли приєднуватися з будь-якої точки через IP, то[accent] переадресація порту []обов’язкова.\n\n[lightgray]Примітка. Якщо у вас виникли проблеми з підключенням до вашої локальної гри, переконайтеся, що ви дозволили Mindustry доступ до вашої локальної мережі в налаштуваннях брандмауера. Зауважте, що публічні мережі іноді не дозволяють виявити сервер. -join.info = Тут ви можете ввести [accent]IP сервера[] для підключення або знайти сервери у [accent]локальній мережі[] для підключення до них.\nПідтримується локальна мережа(LAN) і широкосмугова мережа(WAN).\n\n[lightgray] Примітка. Тут немає автоматичного глобального списку серверів; якщо ви хочете підключитися до когось через IP, вам доведеться попросити створювача сервера дати свій ip. +host.info = Кнопка [accent]Сервер[] розміщує сервер на порті [scarlet]6567[]. \nКористувачі, які знаходяться у тій же [lightgray]WiFi або локальній мережах[], повинні побачити ваш сервер у своєму списку серверів.\n\nЯкщо ви хочете, щоб люди могли приєднуватися з будь-якої точки планети через IP, то потрібно зробити[accent] переадресація порту[].\n\n[lightgray]Примітка. Якщо у вас виникли проблеми з приєднанням до вашої локальної гри, переконайтеся, що ви дозволили Mindustry доступ до вашої локальної мережі в налаштуваннях брандмауера. Зауважте, що публічні мережі іноді не дозволяють виявити сервер. +join.info = Тут ви можете ввести [accent]IP сервера[] для під’єднання або знайти сервери у [accent]локальній мережі[] для приєднання до них.\nПідтримується локальна мережа(LAN) і широкосмугова мережа(WAN).\n\n[lightgray] Примітка. Це не є автоматичним глобальним списком серверів; якщо ви хочете приєднатися до когось через IP, вам доведеться попросити власникасервера дати свій ip. hostserver = Запустити багатокористувацький сервер invitefriends = Запросити друзів hostserver.mobile = Запустити\nсервер -host = Сервер +host = Запустити hosting = [accent]Відкриття сервера… hosts.refresh = Оновити hosts.discovering = Пошук локальних ігор hosts.discovering.any = Пошук ігор server.refreshing = Оновлення сервера hosts.none = [lightgray]Локальних ігр не знайдено -host.invalid = [scarlet]Не вдалося підключитися до сервера. +host.invalid = [scarlet]Не вдалося під’єднатися до сервера. trace = Стежити за гравцем trace.playername = Ім’я гравця: [accent]{0} trace.ip = IP: [accent]{0} @@ -182,14 +183,14 @@ server.bans.none = Заблокованих гравців немає! server.admins = Адміністратори server.admins.none = Адміністраторів немає! server.add = Додати сервер -server.delete = Ви впевнені, що хочете видалити цей сервер? +server.delete = Ви дійсно хочете видалити цей сервер? server.edit = Редагувати сервер server.outdated = [crimson]Застарілий сервер![] server.outdated.client = [crimson]Застарілий клієнт![] server.version = [lightgray]Версія: {0} server.custombuild = [yellow]Користувацька збірка confirmban = Ви дійсно хочете заблокувати цього гравця? -confirmkick = Ви дійсно хочете викинути цього гравця? +confirmkick = Ви дійсно хочете вигнати цього гравця? confirmvotekick = Ви дійсно хочете вигнати цього гравця за допомогою голосуванняr? confirmunban = Ви дійсно хочете розблокувати цього гравця? confirmadmin = Ви дійсно хочете зробити цього гравця адміністратором? @@ -200,23 +201,23 @@ disconnect = Відключено. disconnect.error = Помилка з’єднання. disconnect.closed = З'єднання закрито. disconnect.timeout = Час вийшов. -disconnect.data = Не вдалося завантажити дані світу! +disconnect.data = Не вдалося завантажити світові дані! cantconnect = Не вдалося під’єднатися до гри ([accent]{0}[]). -connecting = [accent]Підключення… +connecting = [accent]Приєднання… connecting.data = [accent]Завантаження даних світу… server.port = Порт: server.addressinuse = Ця адреса вже використовується! server.invalidport = Недійсний номер порту! server.error = [crimson]Помилка створення сервера: [accent]{0} save.new = Нове збереження -save.overwrite = Ви впевнені, що хочете перезаписати цей слот для збереження? +save.overwrite = Ви дійсно хочете перезаписати це місце збереження? overwrite = Перезаписати save.none = Збережень не знайдено! saveload = [accent]Збереження… savefail = Не вдалося зберегти гру! save.delete.confirm = Ви дійсно хочете видалити це збереження? save.delete = Видалити -save.export = Експортувати збереження +save.export = Вивантажити збереження save.import.invalid = [accent]Це збереження недійсне! save.import.fail = [crimson]Не вдалося завантажити збереження: [accent]{0} save.export.fail = [crimson]Не вдалося вивантажити збереження: [accent]{0} @@ -224,18 +225,18 @@ save.import = Імпортувати збереження save.newslot = Ім’я збереження: save.rename = Перейменувати save.rename.text = Нова назва: -selectslot = Виберіть збереження. -slot = [accent]Слот {0} +selectslot = Виберіть збережений файл. +slot = [accent]Місце збережання{0} editmessage = Редагувати повідомлення -save.corrupted = [accent]Збережений файл пошкоджено або недійсний! \nЯкщо ви щойно оновили свою гру, це, мабуть, є зміною формату збереження та [scarlet] не є[] помилкою. -empty = <Порожньо> +save.corrupted = [accent]Збережений файл пошкоджено або він є недійсним! \nЯкщо ви щойно оновили свою гру, це, мабуть, є зміною формату збереження та [scarlet] не є[] помилкою. +empty = <порожньо> on = Увімкнено off = Вимкнено save.autosave = Автозбереження: {0} save.map = Мапа: {0} save.wave = Хвиля {0} save.mode = Режим гри: {0} -save.date = Останнє збереження +save.date = Останнє збереження: {0} save.playtime = Час гри: {0} warning = Попередження confirm = Підтвердження @@ -257,8 +258,8 @@ data.invalid = Це не дійсні ігрові дані. data.import.confirm = Вивантаження зовнішніх даних перезапише[scarlet] ВСІ[] ваші поточні ігрові дані.\n[accent]Це неможливо скасувати![]\n\nЩойно дані імпортуються, гра негайно закриється. classic.export = Вивантажити класичні дані classic.export.text = Класичне (версія 3.5 збірка 40) збереження або мапа були знайдені. Ви хочете експортувати ці дані в домашню теку телефону, для використання у застосунку Mindustry Classic? -quit.confirm = Ви впевнені, що хочете вийти? -quit.confirm.tutorial = Ви впевнені, що хочете вийти з навчання? +quit.confirm = Ви дійсно хочете вийти? +quit.confirm.tutorial = Ви дійсно хочете вийти з навчання? loading = [accent]Завантаження… reloading = [accent]Перезавантаження модифікацій… saving = [accent]Збереження… @@ -271,14 +272,14 @@ wave.waiting = Хвиля через {0} wave.waveInProgress = [lightgray]Хвиля триває waiting = Очікування… waiting.players = Очікування гравців… -wave.enemies = [lightgray]{0} ворог. залишилося -wave.enemy = [lightgray]{0} ворог залишився +wave.enemies = Залишилося [lightgray]{0} ворог. +wave.enemy = Залишився [lightgray]{0} ворог loadimage = Завантажити зображення saveimage = Зберегти зображення unknown = Невідомо custom = Користувацька builtin = Вбудована -map.delete.confirm = Ви дійсно хочете видалити цю мапу? Це неможливо буде скасувати! +map.delete.confirm = Ви дійсно хочете видалити цю мапу? Цю дію неможливо буде скасувати! map.random = [accent]Випадкова мапа map.nospawn = Ця мапа не має жодного ядра для появи гравця! Додайте [ROYAL]помаранчеве[] ядро до цієї мапи в редакторі. map.nospawn.pvp = У цієї мапи немає ворожих ядер, в яких гравець може з’явитися! Додайте [SCARLET]не помаранчеве[] ядро до цієї мапи в редакторі. @@ -286,15 +287,15 @@ map.nospawn.attack = У цієї мапи немає ворожих ядер, в map.invalid = Помилка завантаження мапи: пошкоджений або невірний файл мапи. workshop.update = Оновити предмет workshop.error = Помилка при отриманні інформації з Майстерні: {0} -map.publish.confirm = Ви дійсно хочете опублікувати цю мапу?\n\n[lightgray]Переконайтеся, що спершу ви згодні з Ліцензійною угодою Steam, або ваші мапи не з’являться! +map.publish.confirm = Ви дійсно хочете опублікувати цю мапу?\n\n[lightgray]Спершу переконайтеся, що ви згодні з Ліцензійною угодою Steam, або ваші мапи не з’являться! workshop.menu = Виберіть, що ви хочете зробити з цим предметом. workshop.info = Інформація про предмет -changelog = Журнал змін (за бажанням): -eula = Ліцензійна угода +changelog = Змінопис (за бажанням): +eula = Ліцензійна угода Steam missing = Цей предмет було видалено або переміщено.\n[lightgray]Список Майстерні тепер автоматично від’єднано. publishing = [accent]Публікація… publish.confirm = Ви дійсно хочете опублікувати це?\n\n[lightgray]Переконайтеся, що ви спочатку погоджуєтеся з EULA Майстерні, або ваші предмети не з’являться! -publish.error = Сталася помилка при публікації предмета: {0} +publish.error = Виникла помилка при публікації предмета: {0} steam.error = Не вдалося ініціалізувати сервіси Steam.\nПомилка: {0} editor.brush = Пензлик @@ -321,8 +322,8 @@ waves.perspawn = за появу waves.to = до waves.boss = Бос waves.preview = Попередній перегляд -waves.edit = Редагувати … -waves.copy = Копіювати у буфер обміну +waves.edit = Редагувати… +waves.copy = Копіювати в буфер обміну waves.load = Завантажити з буфера обміну waves.invalid = Недійсні хвилі у буфері обміну. waves.copied = Хвилі скопійовані. @@ -336,11 +337,11 @@ editor.removeunit = Видалити бойову одиницю editor.teams = Команди editor.errorload = Помилка завантаження зображення:\n[accent] {0} editor.errorsave = Помилка збереження зображення:\n[accent]{0} -editor.errorimage = Це зображення, а не мапа. Не змінюйте розширення, очікуючи, що це запрацює.\n\nЯкщо ви хочете імпортувати застарілку мапу, то використовуйте кнопку «Імпортувати застаріле зображення» у редакторі. +editor.errorimage = Це зображення, а не мапа. Не змінюйте розширення, очікуючи, що це спрацює.\n\nЯкщо ви хочете імпортувати застарілу мапу, то використовуйте кнопку «Імпортувати застаріле зображення» у редакторі. editor.errorlegacy = Ця мапа занадто стара і використовує попередній формат мапи, який більше не підтримується. editor.errornot = Це не мапа. editor.errorheader = Цей файл мапи недійсний або пошкоджений. -editor.errorname = Мапа не має імені. Може Ви намагаєтеся завантажити збереження? +editor.errorname = Мапа не має назви. Може Ви намагаєтеся завантажити збереження? editor.update = Оновити editor.randomize = Випадково editor.apply = Застосувати @@ -349,8 +350,8 @@ editor.resize = Змінити\nрозмір editor.loadmap = Завантажити мапу editor.savemap = Зберегти мапу editor.saved = Збережено! -editor.save.noname = Ваша мапа не має імені! Встановіть його в «Інформація про мапу». -editor.save.overwrite = Ваша мапа перезаписує вбудовану мапу! Виберіть інше ім’я в «Інформація про мапу». +editor.save.noname = Ваша мапа не має назви! Установіть його в «Інформація про мапу». +editor.save.overwrite = Ваша мапа перезаписує вбудовану мапу! Виберіть іншу назву в «Інформація про мапу». editor.import.exists = [scarlet]Неможливо імпортувати:[] вбудована мапа з назвою «{0}» вже існує! editor.import = Імпорт… editor.importmap = Імпортувати мапу @@ -363,7 +364,7 @@ editor.export = Експорт… editor.exportfile = Експорт файлу editor.exportfile.description = Експортувати файл мапи editor.exportimage = Експорт зображення місцевості -editor.exportimage.description = Експорт файла з зображенням мапи +editor.exportimage.description = Експорт файлу з зображенням мапи editor.loadimage = Завантажити\nзображення editor.saveimage = Зберегти\nзображення editor.unsaved = [scarlet]У вас є незбережені зміни![]\nВи впевнені, що хочете вийти? @@ -371,13 +372,13 @@ editor.resizemap = Змінити розмір мапи editor.mapname = Назва мапи: editor.overwrite = [accent]Попередження!\nЦе перезаписує існуючу мапу. editor.overwrite.confirm = [scarlet]Попередження![] Мапа з такою назвою вже існує. Ви впевнені, що хочете переписати її? -editor.exists = Мапа за такою назвою вже існує. +editor.exists = Мапа з такою назвою вже існує. editor.selectmap = Виберіть мапу для завантаження: toolmode.replace = Замінити toolmode.replace.description = Малює тільки\nна суцільних блоках. toolmode.replaceall = Замінити все -toolmode.replaceall.description = Замінює усі блоки на мапі. +toolmode.replaceall.description = Замінює всі блоки на мапі. toolmode.orthogonal = Ортогональна toolmode.orthogonal.description = Малює лише\nортогональні лінії. toolmode.square = Прямокутник @@ -429,16 +430,16 @@ campaign = Кампанія load = Завантажити save = Зберегти fps = FPS: {0} -ping = Пінг: {0} мс +ping = Затримка: {0} мс language.restart = Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. settings = Налаштування tutorial = Навчання -tutorial.retake = Відкрити навчання +tutorial.retake = Пройти навчання ще раз editor = Редактор mapeditor = Редактор мап abandon = Покинути -abandon.text = Ця зона і всі її ресурси будуть втрачені. +abandon.text = Ця зона і всі її ресурси будуть утрачені. locked = Заблоковано complete = [lightgray]Досягнута: requirement.wave = Досягніть хвилі {0} у зоні «{1}» @@ -450,41 +451,41 @@ launch = < ЗАПУСК > launch.title = Запуск вдалий launch.next = [lightgray]наступна можливість на {0}-тій хвилі launch.unable2 = [scarlet]ЗАПУСК неможливий.[] -launch.confirm = Це видалить всі ресурси у Вашому ядрі.\nВи не зможете повернутися до цієї бази. -launch.skip.confirm = Якщо ви пропустите зараз, Ви не зможете не запускати до більш пізніх хвиль. +launch.confirm = Це видалить всі ресурси у вашому ядрі.\nВи не зможете повернутися до цієї бази. +launch.skip.confirm = Якщо ви пропустите зараз, ви не зможете не запускати до більш пізніх хвиль. uncover = Розкрити -configure = Вивантажити конфігурацію +configure = Налаштувати вивантаження bannedblocks = Заборонені блоки addall = Додати все configure.locked = {0}[lightgray]Тільки після цього можливість розблокувати вивантаження ресурсів буде доступна. configure.invalid = Кількість повинна бути числом між 0 та {0}. zone.unlocked = Зона «[lightgray]{0}» тепер розблокована. -zone.requirement.complete = Ви досягли {0}-тої хвилі. \nВимоги до зони «{1}» виконані. +zone.requirement.complete = Ви досягли {0}-тої хвилі.\nВимоги до зони «{1}» виконані. zone.config.unlocked = Вивантаження розблоковано:[lightgray]\n{0} zone.resources = Виявлені ресурси: zone.objective = [lightgray]Мета: [accent]{0} -zone.objective.survival = Вижити -zone.objective.attack = Знищити вороже ядро +zone.objective.survival = вижити. +zone.objective.attack = знищити вороже ядро. add = Додати… boss.health = Здоров’я босу -connectfail = [crimson]Помилка підключення: [accent]{0} -error.unreachable = Сервер не доступний. +connectfail = [crimson]Помилка з’єднання: [accent]{0} +error.unreachable = Сервер не є доступним.\nЧи правильно написана адреса? error.invalidaddress = Некоректна адреса. error.timedout = Час очікування вийшов.\nПереконайтеся, що адреса коректна і що власник сервера налаштував переадресацію порту! -error.mismatch = Помилка пакету:\nможливе невідповідність версії клієнта/сервера.\nПереконайтеся, що у вас та у володара сервера встановлена остання версія Mindustry! -error.alreadyconnected = Ви вже підключилися. -error.mapnotfound = Файл мапи не знайдено -error.io = Мережева помилка введення-виведення +error.mismatch = Помилка пакету:\nможлива невідповідність версії клієнта/сервера.\nПереконайтеся, що у вас та у власника сервера встановлена остання версія Mindustry! +error.alreadyconnected = Ви вже під’єдналися. +error.mapnotfound = Файл мапи не знайдено! +error.io = Мережева помилка введення-виведення. error.any = Невідома мережева помилка -error.bloom = Не вдалося ініціалізувати цвітіння.\nВаш пристрій, мабуть, не підтримує це. +error.bloom = Не вдалося ініціалізувати світіння.\nВаш пристрій, мабуть, не підтримує це. zone.groundZero.name = Відправний пункт zone.desertWastes.name = Пустельні відходи zone.craters.name = Кратери zone.frozenForest.name = Крижаний ліс -zone.ruinousShores.name = Зруйновані берега -zone.stainedMountains.name = Пофарбовані гори +zone.ruinousShores.name = Зруйновані береги +zone.stainedMountains.name = Забруднені гори zone.desolateRift.name = Пустельний розлом zone.nuclearComplex.name = Ядерний виробничий комплекс zone.overgrowth.name = Зарості @@ -494,24 +495,24 @@ zone.impact0078.name = Імпульс 0078 zone.crags.name = Скелі zone.fungalPass.name = Грибний перевал -zone.groundZero.description = Оптимальне місце для повторних ігор. Низька ворожа загроза. Мало ресурсів. \nЗбирайте якомога більше свинцю та міді. \nЙдіть далі. -zone.frozenForest.description = Навіть тут, ближче до гір, спори поширилися. Холодна температура не може їх утримувати тут завжди.\nЗважтесь створити енергію. Побудуйте генератори внутрішнього згорання. Навчіться користуватися регенераторами. -zone.desertWastes.description = Ці відходи є величезними, непередбачуваними і перетинаються з занедбаними секторальними структурами.\nВугілля присутнє в регіоні. Спаліть його для енергії або синтезуйте у графіт.\n\n[lightgray]Це місце посадки не можна гарантувати. -zone.saltFlats.description = На околиці пустелі лежать соляні рівнини. У цьому місці можна знайти небагато ресурсів.\n\nТут вороги спорудили комплекс сховищ ресурсів. Викорініть їх ядро. Не залишайте нічого цінного. -zone.craters.description = У цьому кратері накопичилася вода, пережиток старих воєн. Відновіть місцевість. Зберіть пісок. Виплавіть метакскло. Накачайте воду, щоб охолодити турелі і бури. -zone.ruinousShores.description = Минулі відходи - це берегова лінія. Колись у цьому місці розташувався береговий оборонний масив. Залишилося не так багато чого. Тільки найосновніші оборонні споруди залишилися непошкодженими, все інше зводиться до брухту.\nПродовжуйте експансію назовні. Повторно розкрийте технологію. -zone.stainedMountains.description = Далі у вглиб материка лежать гори, ще не заражені спорами.\nВидобудьте надлишковий титан у цій місцевості. Дізнайтеся, як використовувати його.\n\nТут ворожа присутність більша. Не дайте їм часу відправити свої найсильніші одиниці. -zone.overgrowth.description = Ця територія заросла, ближче до джерела спор.\nВорог тут встановив форпост. Побудуйте бойові одиниці Кинджал. Знищте його. Поверніть те, що було втрачено. -zone.tarFields.description = Окраїна зони видобутку нафти, між горами та пустелею. Один з небагатьох районів із корисними запасами смоли.\nНезважаючи на те, що покинута, ця територія має поблизу небезпечні сили противника. Не варто їх недооцінювати.\n\n[lightgray]Якщо можливо, дослідіть технологію переробки нафти. -zone.desolateRift.description = Надзвичайно небезпечна зона. Багато ресурсів, але мало місця. Евакуюватися потрібно якомога швидше. Не розслабляйтеся між ворожими атаками. -zone.nuclearComplex.description = Колишній об’єкт для виробництва та переробки торію, зведений до руїн.\n[lightgray]Дослідіть торій та його безліч застосувань.\n\nВраг присутній тут у великій кількості, постійно шукаючи нападників. -zone.fungalPass.description = Перехідна зона між високими і низькими горами, земля яких покрита спорами. Тут знаходиться невелика розвідувальна база ворога.\nЗнижте її.\nВикористовуйте одиниці Кинджал і Камікадзе. +zone.groundZero.description = Оптимальне місце для повторних ігор. Низька ворожа загроза. Мало ресурсів. \nЗбирайте якомога більше свинцю та міді. \nНе затримуйтесь і йдіть далі. +zone.frozenForest.description = Спори поширилися навіть тут, ближче до гір. Холодна температура не може стримувати їх завжди.\nЗважтесь створити енергію. Побудуйте генератори внутрішнього згорання. Навчіться користуватися регенераторами. +zone.desertWastes.description = Ці відходи є величезними, непередбачуваними і перетинаються з занедбаними секторальними структурами.\nВугілля присутнє в регіоні. Спаліть його для енергії або синтезуйте у графіт.\n\n[lightgray]Є декілька варіантів для місць посадок. +zone.saltFlats.description = На околицях пустелі лежать Соляні рівнини. У цьому місці можна знайти небагато ресурсів.\n\nСаме тут вороги спорудили комплекс сховищ ресурсів. Викорініть їхнє ядро. Не залишайте нічого цінного. +zone.craters.description = У цьому кратері накопичилася вода, пережиток старих воєн. Відновіть місцевість. Зберіть пісок. Виплавте метаскло. Качайте воду, щоб охолодити турелі і бури. +zone.ruinousShores.description = Саме берегова лінія є минулим цих відходів. Колись у цьому місці розташувався береговий оборонний масив, проте залишилося не так багато чого. Тільки основні оборонні споруди залишилися неушкодженими, а все інше перетворилося на металобрухт.\nПродовжуйте експансію назовні. Повторно розкрийте технології. +zone.stainedMountains.description = Якщо йти далі у вглиб материка, то можна побачити гори, які ще не заражені спорами.\nВидобудьте надлишковий титан у цій місцевості. Дізнайтеся, як використовувати його.\n\nНа жаль, тут більше ворогів ніж в інших місцевостях. Не дайте їм часу надіслати свої найсильніші одиниці. +zone.overgrowth.description = Ближче до джерела спор є територія, що заросла.\nНе дивуйтеся, що ворог встановив тут свій форпост. Побудуйте бойові одиниці під кодовою назвою «Титан». Зруйнуйте вщент супротивника. Поверніть те, що колись належало нам. +zone.tarFields.description = Між горами та пустелею простягається окраїна зони видобутку нафти. Це один з небагатьох районів із корисними для використання запасами смоли.\nНезважаючи на те, що територія покинута, вона має поблизу небезпечні сили противника. Не варто їх недооцінювати.\n\n[lightgray]Якщо можливо, дослідіть технологію переробки нафти. +zone.desolateRift.description = Надзвичайно небезпечна зона. Багато ресурсів, але мало місця. Евакуюватися потрібно якомога швидше. Не розслабляйтеся між ворожими атаками та знайдіть ахіллесову п'яту супротивника. +zone.nuclearComplex.description = Колишній об’єкт для виробництва та переробки торію було зведено до руїн.\n[lightgray]Дослідіть торій та його нескінченну кількість застосувань.\n\nВорог, який постійно шукає нападників, присутній тут у великій кількості, тому не баріться з евакуацією! +zone.fungalPass.description = Перехідна зона між високими і низькими горами, земля яких вкрита спорами. Тут знаходиться невелика розвідувальна база ворога.\nЗнищте її.\nВикористовуйте одиниці з кодовими назвами «Кинджал» і «Камікадзе» для повного знищення двох ворожих ядер. zone.impact0078.description = <вставити опис тут> zone.crags.description = <вставити опис тут> settings.language = Мова settings.data = Ігрові дані -settings.reset = Скинути за замовчуванням +settings.reset =За замовчуванням settings.rebind = Змінити settings.resetKey = Скинути settings.controls = Керування @@ -519,9 +520,9 @@ settings.game = Гра settings.sound = Звук settings.graphics = Графіка settings.cleardata = Очистити дані… -settings.clear.confirm = Ви впевнені, що хочете очистити ці дані?\nЦя дія не може бути скасовано! -settings.clearall.confirm = [scarlet]УВАГА![]\nЦе очистить всі дані, включаючи збереження, мапи, розблоковане та налаштування керування.\nПісля того, як ви натиснете ОК, гра видалить усі дані та автоматично закриється. -paused = Пауза +settings.clear.confirm = Ви дійсно хочете очистити ці дані?\nЦю дію не можна скасовати! +settings.clearall.confirm = [scarlet]УВАГА![]\nЦе очистить усі дані, включаючи збереження, мапи, розблоковане та налаштування керування.\nПісля натискання ОК гра видалить усі дані та автоматично закриється. +paused = [accent]< Пауза> clear = Очистити banned = [scarlet]Заблоковано yes = Так @@ -532,21 +533,23 @@ error.crashtitle = Виникла помилка blocks.input = Вхід blocks.output = Вихід blocks.booster = Прискорювач +blocks.tiles = Необхідні плитки +blocks.affinities = Збільшення ефективності block.unknown = [lightgray]??? blocks.powercapacity = Місткість енергії blocks.powershot = Енергія за постріл blocks.damage = Шкода -blocks.targetsair = Повітряні мішені -blocks.targetsground = Наземні мішені +blocks.targetsair = Повітряні вороги +blocks.targetsground = Наземні вороги blocks.itemsmoved = Швидкість переміщення blocks.launchtime = Час між запусками blocks.shootrange = Діапазон дії blocks.size = Розмір -blocks.liquidcapacity = Місткість рідини +blocks.liquidcapacity = Рідинна місткість blocks.powerrange = Діапазон передачі енергії blocks.powerconnections = Максимальна кількість з’єднань blocks.poweruse = Енергії використовує -blocks.powerdamage = Енергія/шкода +blocks.powerdamage = Енергії за од. шкоди blocks.itemcapacity = Місткість предметів blocks.basepowergeneration = Базова генерація енергії blocks.productiontime = Час виробництва @@ -584,7 +587,7 @@ bar.input = Ввід bar.output = Вивід bullet.damage = [stat]{0}[lightgray] шкода -bullet.splashdamage = [stat]{0}[lightgray] шкода по ділянці ~[stat] {1}[lightgray] блок. +bullet.splashdamage = [stat]{0}[lightgray] шкода по ділянці ~[stat] {1}[lightgray] плиток. bullet.incendiary = [stat]запальний bullet.homing = [stat]самонаведення bullet.shock = [stat]шок @@ -630,9 +633,9 @@ setting.autotarget.name = Авто-стрільба setting.keyboard.name = Миш+Керування з клавіатури setting.touchscreen.name = Керування сенсорним екраном setting.fpscap.name = Максимальний FPS -setting.fpscap.none = Необмежений +setting.fpscap.none = Жодне setting.fpscap.text = {0} FPS -setting.uiscale.name = Масштаб користувальницького інтерфейсу[lightgray] (потребує перезапуск)[] +setting.uiscale.name = Масштаб користувацького інтерфейсу[lightgray] (потребує перезапуск)[] setting.swapdiagonal.name = Завжди діагональне розміщення setting.difficulty.training = Навчання setting.difficulty.easy = Легка @@ -647,7 +650,7 @@ setting.conveyorpathfinding.name = Пошук шляху для встановл setting.coreselect.name = Дозволити схематичні ядра setting.sensitivity.name = Чутливість контролера setting.saveinterval.name = Інтервал збереження -setting.seconds = {0} с +setting.seconds = {0} секунд setting.blockselecttimeout.name = Час вибору блока setting.milliseconds = {0} мілісекунд setting.fullscreen.name = Повноекранний режим @@ -666,13 +669,14 @@ setting.mutesound.name = Заглушити звук setting.crashreport.name = Відсилати анонімні звіти про аварійне завершення гри setting.savecreate.name = Автоматичне створення збережень setting.publichost.name = Загальнодоступність гри +setting.playerlimit.name = Обмеження гравців setting.chatopacity.name = Непрозорість чату setting.lasersopacity.name = Непрозорість лазерів енергопостачання setting.bridgeopacity.name = Місткість мостів setting.playerchat.name = Відображати хмару чата над гравцями public.confirm = Ви хочете зробити цю гру загальнодоступною?\n[lightgray]Це можна змінити у Налаштування→Гра→Загальнодоступність гри public.beta = Зауважте, що в бета-версії гри ви не можете робити публічні ігри. -uiscale.reset = Масштаб користувальницького інтерфейсу було змінено.\nНатисніть «ОК» для підтверждення цього масшатабу.\n[scarlet]Повернення налаштувань і вихід через[accent] {0}[] … +uiscale.reset = Масштаб користувацького інтерфейсу було змінено.\nНатисніть «ОК» для підтвердження цього масштабу.\n[scarlet]Повернення налаштувань і вихід через[accent] {0}[] секунд… uiscale.cancel = Скасувати & Вийти setting.bloom.name = Світіння keybind.title = Налаштування керування @@ -700,7 +704,7 @@ keybind.schematic_flip_y.name = Відобразити по осі Y keybind.category_prev.name = Попередня категорія keybind.category_next.name = Наступна категорія keybind.block_select_left.name = Вибрати блок ліворуч -keybind.block_select_right.name = Вибрати блок ліворуч праворуч +keybind.block_select_right.name = Вибрати блок праворуч keybind.block_select_up.name = Вибрати блок зверху keybind.block_select_down.name = Вибрати блок знизу keybind.block_select_01.name = Категорія/Вибрати перший блок @@ -735,39 +739,39 @@ keybind.chat_history_prev.name = Попередня історія чату keybind.chat_history_next.name = Наступна історія чату keybind.chat_scroll.name = Прокрутка чату keybind.drop_unit.name = Скинути бойову одиницю -keybind.zoom_minimap.name = Збільшити міні-мапу -mode.help.title = Опис режимів -mode.survival.name = Хвилі -mode.survival.description = Звичайний режим. В цьому режимі треба самим добувати ресурси та хвилі йдуть автоматично.\n[gray]Потребуються точки появи ворогів для гри. +keybind.zoom_minimap.name = Збільшити мінімапу +mode.help.title = Опис режимів гри +mode.survival.name = Виживання +mode.survival.description = Звичайний режим. В цьому режимі треба самим видобувати ресурси та хвилі йдуть автоматично.\n[gray]Потребуються точки появи ворогів для гри. mode.sandbox.name = Пісочниця -mode.sandbox.description = В режимі «Пісочниця» незкінченні ресурси(але їх все одно можно добувати) та хвилі йдуть за вашим бажанням. +mode.sandbox.description = Нескінченні ресурси та хвилі йдуть за вашим бажанням. mode.editor.name = Редактор mode.pvp.name = PVP -mode.pvp.description = боріться проти інших гравців.\n[gray]Для гри потрібно принаймні 2 ядра різного кольору на мапі. +mode.pvp.description = Боріться проти інших гравців.\n[gray]Для гри потрібно принаймні 2 ядра різного кольору на мапі. mode.attack.name = Атака mode.attack.description = Зруйнуйте ворожу базу.\n[gray]Потрібно червоне ядро на мапі для гри. mode.custom = Користувацькі правила rules.infiniteresources = Нескінченні ресурси rules.reactorexplosions = Вибухи реактора -rules.wavetimer = Таймер хвиль +rules.wavetimer = Таймер для хвиль rules.waves = Хвилі rules.attack = Режим атаки -rules.enemyCheat = Нескінченні ресурси для ШІ -rules.unitdrops = Ресурс бойових одиниць +rules.enemyCheat = Нескінченні ресурси для червоної команди ШІ +rules.unitdrops = Випадіння ресурсів з бойових одиниць rules.unitbuildspeedmultiplier = Множник швидкості виробництва бойових одиниць rules.unithealthmultiplier = Множник здоров’я бойових одиниць rules.blockhealthmultiplier = Множник здоров’я блоків rules.playerhealthmultiplier = Множник здоров’я гравця rules.playerdamagemultiplier = Множник шкоди гравця rules.unitdamagemultiplier = Множник шкоди бойових одиниць -rules.enemycorebuildradius = Радіус захисту для ворожого ядра:[lightgray] (блоків) -rules.respawntime = Час відродження:[lightgray] (sec) -rules.wavespacing = Інтервал хвиль:[lightgray] (sec) +rules.enemycorebuildradius = Радіус захисту для ворожого ядра:[lightgray] (плитки) +rules.respawntime = Час відродження:[lightgray] (секунди) +rules.wavespacing = Інтервал хвиль:[lightgray] (секунди) rules.buildcostmultiplier = Множник затрат на будування rules.buildspeedmultiplier = Множник швидкості будування -rules.waitForWaveToEnd = Хвилі чекають на ворогів -rules.dropzoneradius = Радіус зони висадки:[lightgray] (блоків) +rules.waitForWaveToEnd = Хвилі чекають на смерть усіх ворогів +rules.dropzoneradius = Радіус зони висадки:[lightgray] (плитки) rules.respawns = Максимальна кількість відроджень за хвилю rules.limitedRespawns = Обмеження відроджень rules.title.waves = Хвилі @@ -775,7 +779,7 @@ rules.title.respawns = Відродження rules.title.resourcesbuilding = Ресурси & будування rules.title.player = Гравці rules.title.enemy = Вороги -rules.title.unit = Бойов. од. +rules.title.unit = Бойові одиниці rules.title.experimental = Есперементальне! rules.lighting = Світлотінь rules.ambientlight = Навколишнє світло @@ -887,7 +891,7 @@ block.snow.name = Сніг block.craters.name = Кратери block.sand-water.name = Пісок з водою block.darksand-water.name = Темний пісок з водою -block.char.name = Випалена Земля +block.char.name = Випалена земля block.holostone.name = Голографічний камінь block.ice-snow.name = Крижаний сніг block.rocks.name = Камені @@ -898,11 +902,11 @@ block.pine.name = Сосна block.white-tree-dead.name = Мертве біле дерево block.white-tree.name = Біле дерево block.spore-cluster.name = Скупчення спор -block.metal-floor.name = Металевий пол 1 -block.metal-floor-2.name = Металевий пол 2 -block.metal-floor-3.name = Металевий пол 3 -block.metal-floor-5.name = Металевий пол 4 -block.metal-floor-damaged.name = Пошкоджений иеталевий пол +block.metal-floor.name = Металева підлога 1 +block.metal-floor-2.name = Металева підлога 2 +block.metal-floor-3.name = Металева підлога 3 +block.metal-floor-5.name = Металева підлога 4 +block.metal-floor-damaged.name = Пошкоджена металевий підлога block.dark-panel-1.name = Темна панель 1 block.dark-panel-2.name = Темна панель 2 block.dark-panel-3.name = Темна панель 3 @@ -930,11 +934,11 @@ block.duo.name = Подвійна турель block.scorch.name = Випалювач block.scatter.name = Розсіювач block.hail.name = Град -block.lancer.name = Списоносець +block.lancer.name = Списник block.conveyor.name = Конвеєр block.titanium-conveyor.name = Титановий конвеєр block.armored-conveyor.name = Броньований конвеєр -block.armored-conveyor.description = Переміщує предмети з тією ж швидкістю, як і титанові конвеєри, але має більше міцності. Не приймає введення з боків ні з чого, крім інших конвеєрних стрічок. +block.armored-conveyor.description = Переміщує предмети з тією ж швидкістю, що і титанові конвеєри, але має більше міцності. Не приймає введення з боків ні з чого, крім інших конвеєрних стрічок. block.junction.name = Перехрестя block.router.name = Маршрутизатор block.distributor.name = Розподілювач @@ -942,15 +946,16 @@ block.sorter.name = Сортувальник block.inverted-sorter.name = Зворотній сортувальник block.message.name = Повідомлення block.illuminator.name = Освітлювач -block.illuminator.description = Невелике, компактне, джерело світла, яку можна налаштувати. Для функціонування потрібна енергія. -block.overflow-gate.name = Надмірний затвор +block.illuminator.description = Невелике, компактне, джерело світла, яку можна налаштувати. Для роботи потребує енергії. +block.overflow-gate.name = Надмірний затвір +block.underflow-gate.name = Інвертований затвір block.silicon-smelter.name = Кремнієвий плавильний завод block.phase-weaver.name = Фазовий ткач block.pulverizer.name = Подрібнювач block.cryofluidmixer.name = Змішувач кріогенної рідини block.melter.name = Плавильня block.incinerator.name = Сміттєспалювальний завод -block.spore-press.name = Споровий Прес +block.spore-press.name = Споровий прес block.separator.name = Відокремлювач block.coal-centrifuge.name = Вугільна центрифуга block.power-node.name = Енергійний вузол @@ -980,7 +985,7 @@ block.mechanical-pump.name = Механічна помпа block.item-source.name = Нескінченне джерело предметів block.item-void.name = Предметний вакуум block.liquid-source.name = Нескінченне джерело рідин -block.liquid-void.name = Liquid Void +block.liquid-void.name = Рідинний вакуум block.power-void.name = Енергетичний вакуум block.power-source.name = Нескінченне джерело енергії block.unloader.name = Розвантажувач @@ -988,11 +993,11 @@ block.vault.name = Сховище block.wave.name = Хвиля block.swarmer.name = Роєвик block.salvo.name = Залп -block.ripple.name = Рябь +block.ripple.name = Ряб block.phase-conveyor.name = Фазовий конвеєр block.bridge-conveyor.name = Мостовий конвеєр -block.plastanium-compressor.name = Пластиновий компресор -block.pyratite-mixer.name = Змішувач піротиту +block.plastanium-compressor.name = Пластинієвий компресор +block.pyratite-mixer.name = Змішувач піротита block.blast-mixer.name = Мішалка вибухонебезпечного з’єднання block.solar-panel.name = Сонячна панель block.solar-panel-large.name = Велика сонячна панель @@ -1009,12 +1014,12 @@ block.titan-factory.name = Завод мехів «Титан» block.fortress-factory.name = Завод мехів «Фортеця» block.revenant-factory.name = Завод бомбардувальників «Потойбічний вбивця» block.repair-point.name = Ремонтний пункт -block.pulse-conduit.name = Імпульсний водопровід -block.plated-conduit.name = Зміцнений водопровід -block.phase-conduit.name = Фазовий водопровід -block.liquid-router.name = Рідкий маршрутизатор -block.liquid-tank.name = Рідкий резервуар -block.liquid-junction.name = Рідке перехрестя +block.pulse-conduit.name = Імпульсний трубопровід +block.plated-conduit.name = Зміцнений трубопровід +block.phase-conduit.name = Фазовий трубопровід +block.liquid-router.name = Рідинний маршрутизатор +block.liquid-tank.name = Рідинний резервуар +block.liquid-junction.name = Рідинне перехрестя block.bridge-conduit.name = Мостовий водопровід block.rotary-pump.name = Роторний насос block.thorium-reactor.name = Торієвий реактор @@ -1024,18 +1029,18 @@ block.thermal-pump.name = Тепловий насос block.thermal-generator.name = Тепловий генератор block.alloy-smelter.name = Сплавовий завод block.mender.name = Регенератор -block.mend-projector.name = Ремонтувальний гранатомет +block.mend-projector.name = Відновлювальна установка block.surge-wall.name = Кінетична стіна block.surge-wall-large.name = Велика кінетична стіна block.cyclone.name = Циклон block.fuse.name = Підривник block.shock-mine.name = Шокуюча міна -block.overdrive-projector.name = Сверхприводний проектор -block.force-projector.name = Силовий проектор +block.overdrive-projector.name = Швидкісна пускова установка +block.force-projector.name = Силова пускова установка block.arc.name = Дуга block.rtg-generator.name = Радіоізотопний термоелектричний генератор block.spectre.name = Спектр -block.meltdown.name = Розплавитель +block.meltdown.name = Розплавлювач block.container.name = Склад block.launch-pad.name = Стартовий майданчик block.launch-pad-large.name = Великий стартовий майданчик @@ -1063,38 +1068,38 @@ unit.lich.name = Лич unit.reaper.name = Жнець tutorial.next = [lightgray]<Натисніть для продовження> tutorial.intro = Ви розпочали[scarlet] навчання по Mindustry.[]\nРозпочніть з [accent]видобутку міді[]. Використовуйте [[WASD] для руху.\n[accent]Прокручуйте миш[] для приближення і віддалення. Наблизьтесь до мідної жили біля вашого ядра, а потім натисніть на неї, щоб розпочати видобуток.\n\n[accent]{0}/{1} міді -tutorial.intro.mobile = Ви розпочали[scarlet] навчання по Mindustry.[]\nПроведіть екраном, щоб рухатися.\n[accent] Зведіть або розведіть 2 пальця [] для приближення і віддалення відповідно.\nз[accent] видобування міді.[] Наблизьтесь, а потім натисність на мідну жилу біля вашого ядра, щоб зробити це.\n\n[accent]{0}/{1} міді -tutorial.drill = Добування вручну не є ефективним.\n[accent]Бури []можуть добувати автоматично.\nНатисніть на вкладку із зображенням свердла знизу праворуч.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням.\nВи також можете вибрати бур, натиснувши [accent][[2][], а потім швидко натиснути [accent][[1][], незалежно від того, яка вкладка відкрита.\n[accent]Натисніть ПКМ[], щоб зупинити будування. -tutorial.drill.mobile = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисність на вкладку із зображенням сведла знизу зправа.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням, потім натисність на [accent]галочку[] нижче, щоб підтвердити розміщення .\nНатисніть [accent]кнопку X[], щоб скасувати розміщення. -tutorial.blockinfo = Кожен блок має різні характеристики. Кожний бур може видобувати тільки певні руди.\nЩоб переглянути інформацію та характеристики блока,[accent] натисність на кнопку «?», коли ви вибрали блок у меню будування.[]\n\n[accent]Перегляньте характеристику Механічного бура прямо зараз.[] -tutorial.conveyor = [accent]Конвеєри[] використовуються для транспортування предметів до ядра.\nЗробіть лінію конвеєрів від бура до ядра.\n[accent]Утримуйте миш, щоб розмістити у лінію.[]\nУтримуйте[accent] CTRL[] під час вибору лінії для розміщення по діагоналі.\\nПрокручуйте, щоб обертати блоки до їх установлення.\n[accent]Розмістіть 2 конвеєри у лінію, а потім доставте предмет в ядро.tutorial.conveyor.mobile = [accent]Конвеєри[] використовується для транспортування предметів до ядра.\nЗробіть лінію конвеєрів від бура до ядра.\n[accent] Розмістить у лінію, утримуючи палець кілька секунд[] і тягніть у напрямку, який Ви вибрали.\nВикористовуйте колесо прокрутки, щоб обертати блоки перед їх розміщенням\n[accent]{0}/{1} конвеєрів, які розміщені в лінію\n[accent]0/1 предмет доставлено -tutorial.conveyor.mobile = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent] Place in a line by holding down your finger for a few seconds[] and dragging in a direction.\n\n[accent]Place 2 conveyors with the line tool, then deliver an item into the core. +tutorial.intro.mobile = Ви розпочали[scarlet] навчання по Mindustry.[]\nПроведіть екраном, щоб рухатися.\n[accent] Зведіть або розведіть 2 пальця [] для приближення і віддалення відповідно.\nз[accent] видобування міді.[] Наблизьтесь, а потім натисніть на мідну жилу біля вашого ядра, щоб зробити це.\n\n[accent]{0}/{1} міді +tutorial.drill = Добування вручну не є ефективним.\n[accent]Бури []можуть добувати автоматично.\nНатисніть на вкладку із зображенням свердла праворуч знизу.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням.\nВи також можете вибрати бур, натиснувши [accent][[2][], а потім швидко натиснувши [accent][[1][], незалежно від відкритої вкладки.\n[accent]Натисніть ПКМ[], щоб зупинити будування. +tutorial.drill.mobile = Добування вручну неефективне.\n[accent]Бури []можуть добувати автоматично.\nНатисніть на вкладку із зображенням свердла праворуч знизу.\nВиберіть[accent] механічний бур[]. Розмістіть його на мідній жилі натисканням, потім натисніть на [accent]галочку[] нижче, щоб підтвердити розміщення .\nНатисніть [accent]кнопку X[], щоб скасувати розміщення. +tutorial.blockinfo = Кожний блок має різні характеристики. Кожний бур може видобувати тільки певні руди.\nЩоб переглянути інформацію та характеристики блока,[accent] натисність на кнопку «?», коли ви вибрали блок у меню будування.[]\n\n[accent]Перегляньте характеристику Механічного бура негайно[] +tutorial.conveyor = [accent]Конвеєри[] використовуються для транспортування предметів до ядра.\nЗробіть лінію конвеєрів від бура до ядра.\n[accent]Утримуйте миш для розміщення у лінію.[]\nУтримуйте[accent] CTRL[] під час вибору лінії для розміщення по діагоналі.\\nПрокручуйте, щоб обертати блоки до їх установлення.\n[accent]Розмістіть 2 конвеєри у лінію, а потім доставте предмет в ядро. +tutorial.conveyor.mobile =[accent]Конвеєри[] використовується для транспортування предметів до ядра.\nЗробіть лінію конвеєрів від бура до ядра.\n[accent] Розмістить у лінію, утримуючи палець кілька секунд[] і тягніть у напрямку, який Ви вибрали.\nВикористовуйте колесо прокрутки, щоб обертати блоки перед їх розміщенням\n[accent]{0}/{1} конвеєрів, які розміщені в лінію\n[accent]0/1 предмет доставлено tutorial.turret = Оборонні споруди повинні бути побудовані для відбиття[lightgray] ворогів[].\nПобудуйте[accent] башту «Подвійна»[] біля вашої бази. -tutorial.drillturret = «Подвійна» потребує [accent]мідні боєприпаси[] для стрільби.\nРозмістіть бур біля башточки\nПроведіть конвеєри до башточки, щоб заповнити її боєприпасами.\n\n[accent]Доставлено боєприпасів: 0/1 -tutorial.pause = Під час бою ви можете[accent] поставити на павзу гру.[]\nВи можете зробити чергу на будування під час паузи.\n\n[accent]Натисність пробіл для павзи. -tutorial.pause.mobile = Під час бою ви можете[accent] поставити на павзу гру.[]\nВи можете зробити чергу на будування під час паузи.\n\n[accent]Натисніть кнопку вгорі ліворуч для павзи. -tutorial.unpause = Тепер натисність пробіл, щоб зняти павзу. +tutorial.drillturret = «Подвійна» потребує [accent]мідні боєприпаси[] для стрільби.\nРозмістіть бур біля башти\nПроведіть конвеєри до башти, щоб заповнити її боєприпасами.\n\n[accent]Доставлено боєприпасів: 0/1 +tutorial.pause = Під час гри ви можете[accent] поставити на павзу.[]\nВи можете зробити чергу на будування під час паузи.\n\n[accent]Натисніть пробіл для павзи. +tutorial.pause.mobile = Під час гри ви можете[accent] поставити на павзу.[]\nВи можете зробити чергу на будування під час паузи.\n\n[accent]Натисніть кнопку вгорі ліворуч для павзи. +tutorial.unpause = Призупиніть гру, натиснувши на пробіл. tutorial.unpause.mobile = Тепер натисність туди ще раз, щоб зняти павзу. -tutorial.breaking = Блоки часто повинні бути знищені.\n[accent]Утримуючи ПКМ[] ви знищите всі виділені блоки.[]\n\n[accent]Необхідно знищити всі стіни з металобрухту ліворуч від вашого ядра використовуючи видалення у зоні. -tutorial.breaking.mobile = Блоки часто повинні бути знищені.\n[accent]Виберіть режим руйнування[], потім натисніть на блок, щоб зламати його.\nЗнищіть область, утримуючи палець протягом декількох секунд [] і потягнувши в потрібному напрямку.\nНатисніть кнопку галочки, щоб підтвердити руйнування.\n\n[accent]Необхідно знищити всі стіни з металобрухту ліворуч від вашого ядра використовуючи видалення у зоні. +tutorial.breaking = Блоки часто треба знищувати.\n[accent]Утримуючи ПКМ[] ви знищите всі виділені блоки.[]\n\n[accent]Необхідно знищити всі стіни з металобрухту ліворуч від вашого ядра використовуючи видалення у зоні. +tutorial.breaking.mobile = Блоки часто треба знищувати.\n[accent]Виберіть режим руйнування[], потім натисніть на блок, щоб зламати його.\nЗнищте область, утримуючи палець протягом декількох секунд [] і потягнувши в потрібному напрямку.\nНатисніть кнопку галочки, щоб підтвердити руйнування.\n\n[accent]Необхідно знищити всі стіни з металобрухту ліворуч від вашого ядра використовуючи видалення у зоні. tutorial.withdraw = У деяких ситуаціях потрібно брати предмети безпосередньо з блоків.\nЩоб зробити це, [accent]натисність на блок[] з предметами, і потім [accent]натисніть на предмет[] в інвентарі.\nМожна вилучити кілька предметів [accent]натискаючи та утримуючи[].\n\n[accent]Вилучіть трохи міді з ядра.[] -tutorial.deposit = Покладіть предмети в блоки, перетягнувши з вашого корабля в потрібний блок.\n\n[accent]Покладіть мідь назад у ядро.[] -tutorial.waves = [lightgray] Ворог[] з’явився.\n\nЗахистіть ядро від двух хвиль.[accent] Натисніть ЛКМ[], щоб стріляти.\nСтворіть більше башт і бурів. Добудьте більше міді. -tutorial.waves.mobile = [lightgray] Ворог[] з’явився.\n\nЗахистіть ядро від двух хвиль. Ваш корабель буде автоматично атакувати ворогів.\nСтворіть більше башт і бурів. Добудьте більше міді. -tutorial.launch = Як тільки ви досягнете певної хвилі, ви зможете[accent] запустити ядро[], залишивши захисні сили позаду та [accent]отримати всі ресурси у вашому ядрі.[]\nЦі отримані ресурси можуть бути використані для дослідження нових технологій.\n\n[accent]Натисніть кнопку запуску. +tutorial.deposit = Покладіть предмети в блок, перетягнувши з вашого корабля в потрібний блок.\n\n[accent]Покладіть мідь назад у ядро.[] +tutorial.waves = [lightgray] Ворог[] з’явився.\n\nЗахистіть ядро від двух хвиль.[accent] Натисніть ЛКМ[], щоб стріляти.\nПобудуйте більше башт і бурів. Добудьте більше міді. +tutorial.waves.mobile = [lightgray] Ворог[] з’явився.\n\nЗахистіть ядро від двух хвиль. Ваш корабель буде автоматично атакувати ворогів.\nПобудуйте більше башт і бурів. Добудьте більше міді. +tutorial.launch = Як тільки ви досягнете певної хвилі, ви зможете[accent] запустити ядро[], залишивши свою базу позаду, та [accent]отримати всі ресурси у вашому ядрі.[]\nЦі отримані ресурси можуть бути використані для дослідження нових технологій.\n\n[accent]Натисніть кнопку запуску. -item.copper.description = Найбільш базовий будівельний матеріал. Широко використовується у всіх типах блоків. -item.lead.description = Основний стартовий матеріал. Широко застосовується в електроніці та транспортуванні рідин. -item.metaglass.description = Супер жорсткий склад скла. Широко застосовується для розподілу та зберігання рідини. -item.graphite.description = Мінералізований вуглець, що використовується для боєприпасів та як компонент. +item.copper.description = Початковий будівельний матеріал. Широко використовується у всіх типах блоків. +item.lead.description = Основний стартовий матеріал. Широко застосовується в електроніці та у транспортуванні рідин. +item.metaglass.description = Дуже жорсткий склад скла. Широко застосовується для розподілу та зберігання рідини. +item.graphite.description = Мінералізований вуглець, що використовується для боєприпасів та як електричний компонент. item.sand.description = Поширений матеріал, який широко використовується при виплавці, як при сплавленні, так і в якості відходів. item.coal.description = Окам’янілі рослинні речовини, що утворюються задовго до посіву. Широко використовується для виробництва пального та ресурсів. item.titanium.description = Рідкісний надлегкий метал, який широко використовується для транспортування рідини, бурів і літаків. -item.thorium.description = Щільний, радіоактивний метал, що використовується в якості конструкційної опори та ядерного палива. -item.scrap.description = Залишилися залишки старих споруд та підрозділів. Містить мікроелементи багатьох різних металів. -item.silicon.description = Надзвичайно корисний напівпровідник. Застосовується в сонячних батареях, складній електроніці та бойових боєприпасах. -item.plastanium.description = Легкий, пластичний матеріал, що використовується в сучасних літальних апаратах та у роздроблених боєприпасах. -item.phase-fabric.description = Майже невагома речовина, що застосовується в передовій електроніці та технології саморемонту. +item.thorium.description = Щільний радіоактивний метал, що використовується в якості конструкційної опори та ядерного палива. +item.scrap.description = Залишки старих споруд та підрозділів. Містить мікроелементи багатьох різних металів. +item.silicon.description = Надзвичайно корисний напівпровідник. Має застосування в сонячних батареях, складній електроніці та баштових боєприпасах. +item.plastanium.description = Легкий пластичний матеріал, що використовується в сучасних літальних апаратах та у фрагментованих боєприпасах. +item.phase-fabric.description = Майже невагома речовина, що застосовується в передовій електроніці та у технології самовідновлення. item.surge-alloy.description = Удосконалений сплав з унікальними електричними властивостями. item.spore-pod.description = Струмок синтетичних спор, синтезований з атмосферних концентрацій для промислових цілей. Використовується для перетворення на нафту, вибухівку та паливо. item.blast-compound.description = Нестабільна сполука, яка використовується в бомбах і вибухівках. Синтезується із спорових стручків та інших летких речовин. Використовувати як паливо не рекомендується. @@ -1145,7 +1150,7 @@ block.item-source.description = Нескінченно виводить пред block.item-void.description = Знищує будь-які предмети. block.liquid-source.description = Нескінченно виводить рідини. block.liquid-void.description = Removes any liquids. Sandbox only. -block.copper-wall.description = Дешевий захисний блок.\nКорисна для захисту ядра та башто у перші кілька хвиль. +block.copper-wall.description = Дешевий захисний блок.\nКорисна для захисту ядра та башти у перші кілька хвиль. block.copper-wall-large.description = Дешевий захисний блок.\nКорисна для захисту ядра та башт у перші кілька хвиль.\nОхоплює кілька плиток. block.titanium-wall.description = Відносно сильний захисний блок.\nЗабезпечує помірний захист від ворогів. block.titanium-wall-large.description = Відносно сильний захисний блок.\nЗабезпечує помірний захист від ворогів.\nОхоплює кілька плиток. @@ -1159,7 +1164,7 @@ block.surge-wall.description = Надзвичайно міцний захисн block.surge-wall-large.description = Надзвичайно міцний захисний блок.\nЗбільшує заряд при контакті з кулями, вивільняючи його випадковим чином.\nОхоплює кілька плиток. block.door.description = Невеликі двері. Можна відкрити або закрити, натиснувши. block.door-large.description = Великі двері. Можна відкрити або закрити, натиснувши.\nОхоплює кілька плиток. -block.mender.description = Періодично ремонтує блоки у його радіусі дії. Захищає башточки та стіни.\nЗа бажанням, можна використати кремній для підвищення дальності та ефективності. +block.mender.description = Періодично ремонтує блоки у його радіусі дії. Захищає башти та стіни.\nЗа бажанням, можна використати кремній для підвищення дальності та ефективності. block.mend-projector.description = Покращена версія «Регенератора». Періодично ремонтує блоки у його радіусі дії.\nЗа бажанням, можна використати фазова тканина для підвищення дальності та ефективності. block.overdrive-projector.description = Збільшує швидкість прилеглих будівель.\nЗа бажанням, можна використати фазова тканина для підвищення дальності та ефективності. block.force-projector.description = Створює навколо себе шестикутне силове поле, захищаючи будівлі та блоки всередині від пошкоджень.\nПерегрівається, якщо завдано занадто великої шкоди. За бажанням, можна використати теплоносій для запобігання перегріву. Для збільшення розміру щита можна використовувати фазову тканину. @@ -1168,12 +1173,13 @@ block.conveyor.description = Базовий транспортний блок. block.titanium-conveyor.description = Покращений блок транспорту елементів. Переміщує предмети швидше, ніж звичайні конвеєри. block.junction.description = Діє як міст для двох перехресних конвеєрних стрічок. Корисно в ситуаціях, коли два різних конвеєри перевозять різні матеріали в різні місця. block.bridge-conveyor.description = Покращений блок транспорту елементів. Дозволяє транспортувати предмети до 3-ох плиток з будь-якої місцевості чи будівлі. -block.phase-conveyor.description = Покращений блок транспорту елементів. Використовує енергію для телепортування елементів на підключений фазовий конвеєр через кілька плиток. +block.phase-conveyor.description = Покращений блок транспорту елементів. Використовує енергію для телепортування елементів на під’єднаний фазовий конвеєр через кілька плиток. block.sorter.description = Сортує предмети. Якщо елемент відповідає вибраному, його можна передати. В іншому випадку елемент виводиться зліва та справа. block.inverted-sorter.description = Обробляє елементи, як звичайний сортувальник, але виводить обрані елементи на сторони. block.router.description = Приймає елементи з одного напрямку та виводить їх до трьох інших напрямків порівну. Корисно для поділу матеріалів від одного джерела до кількох цілей.\n\n[scarlet]Ніколи не використовуйте поруч із входами до механізмів, оскільки вони будуть забиті вихідними предметами.[] block.distributor.description = Розширений маршрутизатор. Розділяє предмети до 7 інших напрямків порівну. block.overflow-gate.description = Виходи лише вліво і вправо, якщо передній шлях заблокований. +block.underflow-gate.description = Повна протилежність надмірному затвору. Виводить предмет прямо, якщо лівий і/чи правий шлях заблоковано. block.mass-driver.description = Кінцевий елемент транспортного блоку. Збирає кілька предметів, а потім вистрілює їх до іншої електромагнитної катапульти на великій відстані. Для роботи потрібна енергія. block.mechanical-pump.description = Недорогий насос з повільним виходом, але без енергоспоживання. block.rotary-pump.description = Удосконалений насос. Насоси більше викачують, але потребують енергію. @@ -1185,8 +1191,8 @@ block.liquid-router.description = Приймає рідини з одного н block.liquid-tank.description = Зберігає велику кількість рідини. Використовуйте для створення буферів у ситуаціях з непостійним попитом на матеріали або як гарантію охолодження життєво важливих блоків. block.liquid-junction.description = Діє як міст для двох каналів перетину. Корисно в ситуаціях, коли два різні трубопроводи перевозять різні рідини в різні місця. block.bridge-conduit.description = Розширений блок транспортування рідини. Дозволяє транспортувати рідину до 3 плиток будь-якої місцевості чи будівлі. -block.phase-conduit.description = Розширений блок транспортування рідини. Використовує енергію для транспортування рідин до підключеного фазового каналу через декілька плиток. -block.power-node.description = Передає живлення на підключені вузли. Вузол буде отримувати живлення від будь-яких сусідніх блоків або подавати живлення до них. +block.phase-conduit.description = Розширений блок транспортування рідини. Використовує енергію для транспортування рідин до приєднаного фазового каналу через декілька плиток. +block.power-node.description = Передає живлення на приєднані вузли. Вузол буде отримувати живлення від будь-яких сусідніх блоків або подавати живлення до них. block.power-node-large.description = Удосконалений вузол живлення з більшим діапазоном. block.surge-tower.description = Надзвичайно дальний вузол живлення з меншою кількістю доступних з’єднань. block.diode.description = Живлення акумулятора може протікати через цей блок лише в одному напрямку, але лише в тому випадку, якщо інша сторона має менше енергії. @@ -1235,17 +1241,17 @@ block.draug-factory.description = Виробляє дронів, які видо block.spirit-factory.description = Виробляє дронів, які ремонтують блоки. block.phantom-factory.description = Виробляє дронів, які допомогають у будівництві. block.wraith-factory.description = Виробляє швидких перехоплювачів, які використовують тактику «стріляй і біжи». -block.ghoul-factory.description = Виробляє важкі килимові бомбардувальники. -block.revenant-factory.description = Випускає важкі ракетні одиниці. -block.dagger-factory.description = Виробляє елементарні наземні одиниці. -block.crawler-factory.description = Виробляє швидкі саморуйнуючі бойові одиниці. -block.titan-factory.description = Виробляє поліпшених наземних бойових одиниць. -block.fortress-factory.description = Виробляє наземних бойових одиници, які схожі на важку артилерію. -block.repair-point.description = Безперервно лікує найближчий пошкоджену бойову одиницю чи мех, що знаходиться поруч. -block.dart-mech-pad.description = Забезпечує перетворення в основий атакуючий мех.\nВикористовуйте, натискаючи, стоячи на ньому. -block.delta-mech-pad.description = Забезпечує перетворення в легкоброньований атакуючий мех.\nВикористовуйте, натискаючи, стоячи на ньому. -block.tau-mech-pad.description = Забезпечує перетворення в поліпшений мех підтримки.\nВикористовуйте, натискаючи, стоячи на ньому. -block.omega-mech-pad.description = Забезпечує перетворення в тяжкоброньований ракетний мех.\nВикористовуйте, натискаючи, стоячи на ньому. -block.javelin-ship-pad.description = Забезпечує перетворення в швидкий, легкоброньований перехоплювач.\nВикористовуйте, натискаючи, стоячи на ньому. +block.ghoul-factory.description = Виробляє важкокилимових бомбардувальників. +block.revenant-factory.description = Виробляє важких ракетних одиниць. +block.dagger-factory.description = Виробляє початкових наземних одиниць. +block.crawler-factory.description = Виробляє швидких і самовибухових одиниць. +block.titan-factory.description = Виробляє поліпшених наземних одиниць. +block.fortress-factory.description = Виробляє важкоартилерійних наземних одиниць. +block.repair-point.description = Безперервно лікує найближчу пошкоджену бойову одиницю, що знаходиться поруч. +block.dart-mech-pad.description = Забезпечує перетворення в основий атакуючий мех.\nВикористовуйте, натиснувши, коли стоїте на ньому. +block.delta-mech-pad.description = Забезпечує перетворення в легкоброньований атакуючий мех.\nВикористовуйте, натиснувши, коли стоїте на ньому. +block.tau-mech-pad.description = Забезпечує перетворення в поліпшений мех підтримки.\nВикористовуйте, натиснувши, коли стоїте на ньому. +block.omega-mech-pad.description = Забезпечує перетворення в тяжкоброньований ракетний мех.\nВикористовуйте, натиснувши, коли стоїте на ньому. +block.javelin-ship-pad.description = Забезпечує перетворення в швидкий, легкоброньований перехоплювач.\nВикористовуйте, натиснувши, коли стоїте на ньому. block.trident-ship-pad.description = Забезпечує перетворення в тяжкий бомбардувальник.\nВикористовуйте, натискаючи, стоячи на ньому. -block.glaive-ship-pad.description = Забезпечує перетворення в великий, добреброньований корабель.\nВикористовуйте, натискаючи, стоячи на ньому. +block.glaive-ship-pad.description = Забезпечує перетворення в великий добреброньований корабель зі зроєю.\nВикористовуйте, натиснувши, коли стоїте на ньому. diff --git a/core/assets/bundles/bundle_zh_TW.properties b/core/assets/bundles/bundle_zh_TW.properties index a519738a82..5a9da9dddf 100644 --- a/core/assets/bundles/bundle_zh_TW.properties +++ b/core/assets/bundles/bundle_zh_TW.properties @@ -104,6 +104,7 @@ mods.none = [lightgray]找不到模組! mods.guide = 模組指南 mods.report = 回報錯誤 mods.openfolder = 開啟模組資料夾 +mod.display = [gray]模組:[orange]{0} mod.enabled = [lightgray]已啟用 mod.disabled = [scarlet]已禁用 mod.disable = 禁用 @@ -531,7 +532,9 @@ error.title = [crimson]發生錯誤 error.crashtitle = 發生錯誤 blocks.input = 輸入 blocks.output = 輸出 -blocks.booster = 加速器 +blocks.booster = 強化 +blocks.tiles = 需求方塊 +blocks.affinities = 親和方塊 block.unknown = [lightgray]??? blocks.powercapacity = 蓄電量 blocks.powershot = 能量/射擊 @@ -666,6 +669,7 @@ setting.mutesound.name = 靜音 setting.crashreport.name = 發送匿名崩潰報告 setting.savecreate.name = 自動建立存檔 setting.publichost.name = 公開遊戲可見度 +setting.playerlimit.name = 玩家數限制 setting.chatopacity.name = 聊天框不透明度 setting.lasersopacity.name = 激光不透明度 setting.bridgeopacity.name = 橋透明度 diff --git a/core/assets/fonts/font.ttf b/core/assets/fonts/font.ttf index d6b1c442ab..9d7390954d 100644 Binary files a/core/assets/fonts/font.ttf and b/core/assets/fonts/font.ttf differ diff --git a/core/assets/fonts/fontello.ttf b/core/assets/fonts/fontello.ttf new file mode 100644 index 0000000000..7eaf076f22 Binary files /dev/null and b/core/assets/fonts/fontello.ttf differ diff --git a/core/build.gradle b/core/build.gradle index abcec6d8f6..ea07c810b3 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,4 +1,4 @@ apply plugin: "java" sourceCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -sourceSets.main.java.srcDirs = ["src/"] \ No newline at end of file +sourceSets.main.java.srcDirs = ["src/", "$buildDir/generated/sources/annotationProcessor/java/main"] \ No newline at end of file diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 7f08f765a0..efcc10bab0 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -678,7 +678,6 @@ public class Blocks implements ContentList{ int topRegion = reg("-top"); drawIcons = () -> new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top")}; - drawer = tile -> { GenericCrafterEntity entity = tile.ent(); diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 975cd87232..5159f8e7c5 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -103,9 +103,10 @@ public class UnitTypes implements ContentList{ shootSound = Sounds.explosion; bullet = new BombBulletType(2f, 3f, "clear"){{ hitEffect = Fx.pulverize; - lifetime = 2f; + lifetime = 30f; speed = 1.1f; splashDamageRadius = 55f; + instantDisappear = true; splashDamage = 30f; killShooter = true; }}; @@ -126,7 +127,6 @@ public class UnitTypes implements ContentList{ shootSound = Sounds.flame; length = 1f; reload = 14f; - range = 30f; alternate = true; recoil = 1f; ejectEffect = Fx.none; diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index 9508ad94e3..6ecce095e9 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -1,29 +1,29 @@ package mindustry.core; import arc.*; -import mindustry.annotations.Annotations.*; -import arc.struct.*; import arc.graphics.*; import arc.math.*; -import arc.util.CommandHandler.*; +import arc.struct.*; import arc.util.*; +import arc.util.CommandHandler.*; import arc.util.io.*; import arc.util.serialization.*; import mindustry.*; +import mindustry.annotations.Annotations.*; import mindustry.core.GameState.*; -import mindustry.ctype.ContentType; +import mindustry.ctype.*; import mindustry.entities.*; import mindustry.entities.traits.BuilderTrait.*; import mindustry.entities.traits.*; import mindustry.entities.type.*; -import mindustry.game.*; import mindustry.game.EventType.*; +import mindustry.game.*; import mindustry.gen.*; import mindustry.net.Administration.*; import mindustry.net.Net.*; import mindustry.net.*; import mindustry.net.Packets.*; -import mindustry.type.TypeID; +import mindustry.type.*; import mindustry.world.*; import mindustry.world.modules.*; @@ -172,8 +172,8 @@ public class NetClient implements ApplicationListener{ } //special case; graphical server needs to see its message - if(!headless && player == Vars.player){ - Vars.ui.chatfrag.addMessage(message, colorizeName(player.id, player.name)); + if(!headless){ + sendMessage(message, colorizeName(player.id, player.name), player); } //server console logging @@ -266,6 +266,29 @@ public class NetClient implements ApplicationListener{ ui.showText("", message); } + //TODO these are commented out to enforce compatibility with 103! uncomment before 104 release + /* + + @Remote(variants = Variant.both) + public static void onInfoPopup(String message, float duration, int align, int top, int left, int bottom, int right){ + ui.showInfoPopup(message, duration, align, top, left, bottom, right); + } + + @Remote(variants = Variant.both) + public static void onLabel(String info, float duration, float worldx, float worldy){ + ui.showLabel(info, duration, worldx, worldy); + } + + @Remote(variants = Variant.both, unreliable = true) + public static void onEffect(Effect effect, float x, float y, float rotation, Color color){ + Effects.effect(effect, color, x, y, rotation); + } + + @Remote(variants = Variant.both) + public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){ + Effects.effect(effect, color, x, y, rotation); + }*/ + @Remote(variants = Variant.both) public static void onInfoToast(String message, float duration){ ui.showInfoToast(message, duration); diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index b66c5c3cf3..3f7e0d02ee 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -472,7 +472,7 @@ public class NetServer implements ApplicationListener{ Call.onPlayerDisconnect(player.id); } - Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name, player.uuid, reason); + if(Config.showConnectMessages.bool()) Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name, player.uuid, reason); } player.remove(); @@ -620,8 +620,10 @@ public class NetServer implements ApplicationListener{ player.add(); player.con.hasConnected = true; - if(Config.showConnectMessages.bool()) Call.sendMessage("[accent]" + player.name + "[accent] has connected."); - Log.info("&lm[{1}] &y{0} has connected. ", player.name, player.uuid); + if(Config.showConnectMessages.bool()){ + Call.sendMessage("[accent]" + player.name + "[accent] has connected."); + Log.info("&lm[{1}] &y{0} has connected. ", player.name, player.uuid); + } if(!Config.motd.string().equalsIgnoreCase("off")){ player.sendMessage(Config.motd.string()); diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index 70606c55d5..c28a5f5fe1 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -9,6 +9,7 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.input.*; import arc.math.*; +import arc.math.geom.*; import arc.scene.*; import arc.scene.actions.*; import arc.scene.event.*; @@ -289,19 +290,49 @@ public class UI implements ApplicationListener, Loadable{ Core.scene.add(table); } + /** Shows a fading label at the top of the screen. */ public void showInfoToast(String info, float duration){ Table table = new Table(); table.setFillParent(true); + table.touchable(Touchable.disabled); table.update(() -> { - if(state.is(State.menu)){ - table.remove(); - } + if(state.is(State.menu)) table.remove(); }); table.actions(Actions.delay(duration * 0.9f), Actions.fadeOut(duration * 0.1f, Interpolation.fade), Actions.remove()); table.top().table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).padTop(10); Core.scene.add(table); } + /** Shows a label at some position on the screen. Does not fade. */ + public void showInfoPopup(String info, float duration, int align, int top, int left, int bottom, int right){ + Table table = new Table(); + table.setFillParent(true); + table.touchable(Touchable.disabled); + table.update(() -> { + if(state.is(State.menu)) table.remove(); + }); + table.actions(Actions.delay(duration), Actions.remove()); + table.align(align).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).pad(top, left, bottom, right); + Core.scene.add(table); + } + + /** Shows a label in the world. This label is behind everything. Does not fade. */ + public void showLabel(String info, float duration, float worldx, float worldy){ + Table table = new Table(); + table.setFillParent(true); + table.touchable(Touchable.disabled); + table.update(() -> { + if(state.is(State.menu)) table.remove(); + }); + table.actions(Actions.delay(duration), Actions.remove()); + table.align(Align.center).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).update(t -> { + Vec2 v = Core.camera.project(worldx, worldy); + t.setPosition(v.x, v.y, Align.center); + }); + //make sure it's at the back + Core.scene.root.addChildAt(0, table); + } + public void showInfo(String info){ new Dialog(""){{ getCell(cont).growX(); @@ -439,7 +470,6 @@ public class UI implements ApplicationListener, Loadable{ dialog.show(); } - public void showCustomConfirm(String title, String text, String yes, String no, Runnable confirmed, Runnable denied){ FloatingDialog dialog = new FloatingDialog(title); dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center); @@ -472,11 +502,11 @@ public class UI implements ApplicationListener, Loadable{ public String formatAmount(int number){ if(number >= 1000000){ - return Strings.fixed(number / 1000000f, 1) + "[gray]" + Core.bundle.getOrNull("unit.millions") + "[]"; + return Strings.fixed(number / 1000000f, 1) + "[gray]" + Core.bundle.get("unit.millions") + "[]"; }else if(number >= 10000){ - return number / 1000 + "[gray]k[]"; + return number / 1000 + "[gray]" + Core.bundle.get("unit.thousands") + "[]"; }else if(number >= 1000){ - return Strings.fixed(number / 1000f, 1) + "[gray]" + Core.bundle.getOrNull("unit.thousands") + "[]"; + return Strings.fixed(number / 1000f, 1) + "[gray]" + Core.bundle.get("unit.thousands") + "[]"; }else{ return number + ""; } diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index 51e74cf383..65a399b121 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -3,8 +3,7 @@ package mindustry.entities.bullet; import arc.audio.*; import arc.math.*; import mindustry.content.*; -import mindustry.ctype.Content; -import mindustry.ctype.ContentType; +import mindustry.ctype.*; import mindustry.entities.*; import mindustry.entities.Effects.*; import mindustry.entities.effect.*; @@ -41,7 +40,9 @@ public abstract class BulletType extends Content{ public float recoil; /** Whether to kill the shooter when this is shot. For suicide bombers. */ public boolean killShooter; - + /** Whether to instantly make the bullet disappear. */ + public boolean instantDisappear; + /** Damage dealt in splash. 0 to disable.*/ public float splashDamage = 0f; /** Knockback in velocity. */ public float knockback; @@ -151,6 +152,10 @@ public abstract class BulletType extends Content{ if(killShooter && b.getOwner() instanceof HealthTrait){ ((HealthTrait)b.getOwner()).kill(); } + + if(instantDisappear){ + b.time(lifetime); + } } public void update(Bullet b){ diff --git a/core/src/mindustry/entities/type/TileEntity.java b/core/src/mindustry/entities/type/TileEntity.java index cf5a2f4bdb..d83c038e70 100644 --- a/core/src/mindustry/entities/type/TileEntity.java +++ b/core/src/mindustry/entities/type/TileEntity.java @@ -24,7 +24,7 @@ import java.io.*; import static mindustry.Vars.*; public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ - public static final float timeToSleep = 60f * 4; //4 seconds to fall asleep + public static final float timeToSleep = 60f * 1; //1 second to fall asleep private static final ObjectSet tmpTiles = new ObjectSet<>(); /** This value is only used for debugging. */ public static int sleepingEntities = 0; diff --git a/core/src/mindustry/entities/type/base/GroundUnit.java b/core/src/mindustry/entities/type/base/GroundUnit.java index 56ba404a30..bf400dd24a 100644 --- a/core/src/mindustry/entities/type/base/GroundUnit.java +++ b/core/src/mindustry/entities/type/base/GroundUnit.java @@ -42,7 +42,6 @@ public class GroundUnit extends BaseUnit{ moveToCore(PathTarget.enemyCores); } }else{ - float dst = dst(core); if(dst < getWeapon().bullet.range() / 1.1f){ @@ -170,6 +169,7 @@ public class GroundUnit extends BaseUnit{ if(!Units.invalidateTarget(target, this)){ if(dst(target) < getWeapon().bullet.range()){ + rotate(angleTo(target)); if(Angles.near(angleTo(target), rotation, 13f)){ diff --git a/core/src/mindustry/game/Teams.java b/core/src/mindustry/game/Teams.java index e4fa1f1ef3..e4c7aa2c05 100644 --- a/core/src/mindustry/game/Teams.java +++ b/core/src/mindustry/game/Teams.java @@ -125,7 +125,7 @@ public class Teams{ } private void updateEnemies(){ - if(!active.contains(get(state.rules.waveTeam))){ + if(state.rules.waves && !active.contains(get(state.rules.waveTeam))){ active.add(get(state.rules.waveTeam)); } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index ca5a30f5c4..b48080484d 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -420,7 +420,7 @@ public class Administration{ crashReport("Whether to send crash reports.", false, "crashreport"), logging("Whether to log everything to files.", true), strict("Whether strict mode is on - corrects positions and prevents duplicate UUIDs.", true), - antiSpam("Whether spammers are automatically kicked and rate-limited.", true), + antiSpam("Whether spammers are automatically kicked and rate-limited.", headless), messageRateLimit("Message rate limit in seconds. 0 to disable.", 0), messageSpamKick("How many times a player must send a message before the cooldown to get kicked. 0 to disable.", 3), socketInput("Allows a local application to control this server through a local TCP socket.", false, "socket", () -> Events.fire(Trigger.socketConfigChanged)), diff --git a/core/src/mindustry/world/BlockStorage.java b/core/src/mindustry/world/BlockStorage.java index 20c1ca8f98..338ec4526f 100644 --- a/core/src/mindustry/world/BlockStorage.java +++ b/core/src/mindustry/world/BlockStorage.java @@ -274,8 +274,7 @@ public abstract class BlockStorage extends UnlockableContent{ /** Try offloading an item to a nearby container in its facing direction. Returns true if success. */ public boolean offloadDir(Tile tile, Item item){ - Tile other = tile.getNearby(tile.rotation()); - if(other != null) other = other.link(); + Tile other = tile.front(); if(other != null && other.getTeam() == tile.getTeam() && other.block().acceptItem(item, other, tile)){ other.block().handleItem(item, other, tile); return true; diff --git a/core/src/mindustry/world/blocks/distribution/ItemConveyor.java b/core/src/mindustry/world/blocks/distribution/ItemConveyor.java index cce4073706..49d16fb355 100644 --- a/core/src/mindustry/world/blocks/distribution/ItemConveyor.java +++ b/core/src/mindustry/world/blocks/distribution/ItemConveyor.java @@ -2,19 +2,20 @@ package mindustry.world.blocks.distribution; import arc.*; import arc.func.*; -import arc.math.*; -import arc.util.*; -import arc.struct.*; -import mindustry.ui.*; -import arc.math.geom.*; -import mindustry.type.*; -import mindustry.world.*; import arc.graphics.g2d.*; +import arc.math.*; +import arc.math.geom.*; +import arc.struct.*; +import arc.util.ArcAnnotate.*; +import arc.util.*; import mindustry.content.*; -import mindustry.world.meta.*; -import mindustry.world.blocks.*; -import mindustry.entities.type.*; import mindustry.entities.traits.BuilderTrait.*; +import mindustry.entities.type.*; +import mindustry.type.*; +import mindustry.ui.*; +import mindustry.world.*; +import mindustry.world.blocks.*; +import mindustry.world.meta.*; import java.io.*; @@ -22,15 +23,11 @@ import static mindustry.Vars.*; public class ItemConveyor extends BaseConveyor implements Autotiler{ private static final float itemSpace = 0.4f; - private static final float minmove = 1f / (Short.MAX_VALUE - 2); - private static ItemPos drawpos = new ItemPos(); - private static ItemPos pos1 = new ItemPos(); - private static ItemPos pos2 = new ItemPos(); + private static final int capacity = 4; + private final Vec2 tr1 = new Vec2(); private final Vec2 tr2 = new Vec2(); - private TextureRegion[][] regions = new TextureRegion[7][4]; - public float speed = 0f; public float displayedSpeed = 0f; protected ItemConveyor(String name){ @@ -38,16 +35,9 @@ public class ItemConveyor extends BaseConveyor implements Autotiler{ entityType = ItemConveyorEntity::new; } - private static int compareItems(long a, long b){ - pos1.set(a, ItemPos.packShorts); - pos2.set(b, ItemPos.packShorts); - return Float.compare(pos1.y, pos2.y); - } - @Override public void setStats(){ super.setStats(); - //have to add a custom calculated speed, since the actual movement speed is apparently not linear stats.add(BlockStat.itemsMoved, displayedSpeed, StatUnit.itemsSecond); stats.add(BlockStat.boostEffect, "$blocks.itemsmoved"); @@ -65,28 +55,31 @@ public class ItemConveyor extends BaseConveyor implements Autotiler{ } @Override - public void drawLayer(Tile tile){ + public void onProximityUpdate(Tile tile){ + super.onProximityUpdate(tile); + ItemConveyorEntity entity = tile.ent(); + if(tile.front() != null && tile.front().entity != null){ + entity.next = tile.front().entity; + entity.nextc = entity.next instanceof ItemConveyorEntity && entity.next.getTeam() == tile.getTeam() ? (ItemConveyorEntity)entity.next : null; + entity.aligned = entity.nextc != null && tile.rotation() == entity.next.tile.rotation(); + } + } + + @Override + public void drawLayer(Tile tile){ + ItemConveyorEntity e = tile.ent(); byte rotation = tile.rotation(); - try{ + for(int i = 0; i < e.len; i++){ + Item item = e.ids[i]; + tr1.trns(rotation * 90, tilesize, 0); + tr2.trns(rotation * 90, -tilesize / 2f, e.xs[i] * tilesize / 2f); - for(int i = 0; i < entity.convey.size; i++){ - ItemPos pos = drawpos.set(entity.convey.get(i), ItemPos.drawShorts); - - if(pos.item == null) continue; - - tr1.trns(rotation * 90, tilesize, 0); - tr2.trns(rotation * 90, -tilesize / 2f, pos.x * tilesize / 2f); - - Draw.rect(pos.item.icon(Cicon.medium), - (tile.x * tilesize + tr1.x * pos.y + tr2.x), - (tile.y * tilesize + tr1.y * pos.y + tr2.y), itemSize, itemSize); - } - - }catch(IndexOutOfBoundsException e){ - Log.err(e); + Draw.rect(item.icon(Cicon.medium), + (tile.x * tilesize + tr1.x * e.ys[i] + tr2.x), + (tile.y * tilesize + tr1.y * e.ys[i] + tr2.y), itemSize, itemSize); } } @@ -115,109 +108,86 @@ public class ItemConveyor extends BaseConveyor implements Autotiler{ if(Math.abs(tile.worldx() - unit.x) < 1f) centerx = 0f; } - if(entity.convey.size * itemSpace < 0.9f){ + if(entity.len * itemSpace < 0.9f){ unit.applyImpulse((tx * speed + centerx) * entity.delta(), (ty * speed + centery) * entity.delta()); } } @Override public void update(Tile tile){ - ItemConveyorEntity entity = tile.ent(); - entity.minitem = 1f; - Tile next = tile.getNearby(tile.rotation()); - if(next != null) next = next.link(); + ItemConveyorEntity e = tile.ent(); + e.minitem = 1f; + e.mid = 0; - float nextMax = next != null && next.block() instanceof ItemConveyor && next.block().acceptItem(null, next, tile) ? 1f - Math.max(itemSpace - next.ent().minitem, 0) : 1f; - int minremove = Integer.MAX_VALUE; + //skip updates if possible + if(e.len == 0){ + e.clogHeat = 0f; + e.sleep(); + return; + } - for(int i = entity.convey.size - 1; i >= 0; i--){ - long value = entity.convey.get(i); - ItemPos pos = pos1.set(value, ItemPos.updateShorts); + float nextMax = e.nextc != null && tile.rotation() == e.nextc.tile.rotation() ? 1f - Math.max(itemSpace - e.nextc.minitem, 0) : 1f; - //..this should never happen, but in case it does, remove it and stop here - if(pos.item == null){ - entity.convey.removeValue(value); - break; - } + for(int i = e.len - 1; i >= 0; i--){ + float nextpos = (i == e.len - 1 ? 100f : e.ys[i + 1]) - itemSpace; + float maxmove = Mathf.clamp(nextpos - e.ys[i], 0, speed * e.delta()); - float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1), ItemPos.updateShorts).y) - itemSpace; - float maxmove = Math.min(nextpos - pos.y, speed * entity.delta()); + e.ys[i] += maxmove; - if(maxmove > minmove){ - pos.y += maxmove; - if(Mathf.equal(pos.x, 0, 0.1f)){ - pos.x = 0f; + if(e.ys[i] > nextMax) e.ys[i] = nextMax; + if(e.ys[i] > 0.5 && i > 0) e.mid = i - 1; + e.xs[i] = Mathf.approachDelta(e.xs[i], 0, 0.1f); + + if(e.ys[i] >= 1f && offloadDir(tile, e.ids[i])){ + //align X position if passing forwards + if(e.aligned){ + e.nextc.xs[e.nextc.lastInserted] = e.xs[i]; } - pos.x = Mathf.lerpDelta(pos.x, 0, 0.1f); - } - - pos.y = Mathf.clamp(pos.y, 0, nextMax); - - if(pos.y >= 0.9999f && offloadDir(tile, pos.item)){ - if(next != null && next.block() instanceof ItemConveyor){ - ItemConveyorEntity othere = next.ent(); - - ItemPos ni = pos2.set(othere.convey.get(othere.lastInserted), ItemPos.updateShorts); - - if(next.rotation() == tile.rotation()){ - ni.x = pos.x; - } - othere.convey.set(othere.lastInserted, ni.pack()); - } - minremove = Math.min(i, minremove); - tile.entity.items.remove(pos.item, 1); - }else{ - value = pos.pack(); - - if(pos.y < entity.minitem) - entity.minitem = pos.y; - entity.convey.set(i, value); + //remove last item + e.items.remove(e.ids[i], e.len - i); + e.len = Math.min(i, e.len); + }else if(e.ys[i] < e.minitem){ + e.minitem = e.ys[i]; } } - if(entity.minitem < itemSpace){ - entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 1f, 0.02f); + if(e.minitem < itemSpace + (e.blendbits == 1 ? 0.5f : 0f)){ + e.clogHeat = Mathf.lerpDelta(e.clogHeat, 1f, 0.02f); }else{ - entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 0f, 1f); + e.clogHeat = 0f; } - if(entity.items.total() == 0){ - entity.sleep(); - }else{ - entity.noSleep(); - } - - if(minremove != Integer.MAX_VALUE) entity.convey.truncate(minremove); + e.noSleep(); } @Override public Block getReplacement(BuildRequest req, Array requests){ Boolf cont = p -> requests.contains(o -> o.x == req.x + p.x && o.y == req.y + p.y && o.rotation == req.rotation && (req.block instanceof ItemConveyor || req.block instanceof Junction)); return cont.get(Geometry.d4(req.rotation)) && - cont.get(Geometry.d4(req.rotation - 2)) && - req.tile() != null && - req.tile().block() instanceof ItemConveyor && - Mathf.mod(req.tile().rotation() - req.rotation, 2) == 1 ? Blocks.junction : this; + cont.get(Geometry.d4(req.rotation - 2)) && + req.tile() != null && + req.tile().block() instanceof ItemConveyor && + Mathf.mod(req.tile().rotation() - req.rotation, 2) == 1 ? Blocks.junction : this; } @Override public int removeStack(Tile tile, Item item, int amount){ - ItemConveyorEntity entity = tile.ent(); - entity.noSleep(); + ItemConveyorEntity e = tile.ent(); + e.noSleep(); int removed = 0; for(int j = 0; j < amount; j++){ - for(int i = 0; i < entity.convey.size; i++){ - long val = entity.convey.get(i); - ItemPos pos = pos1.set(val, ItemPos.drawShorts); - if(pos.item == item){ - entity.convey.removeValue(val); - entity.items.remove(item, 1); - removed++; + for(int i = 0; i < e.len; i++){ + if(e.ids[i] == item){ + e.remove(i); + removed ++; break; } } } + + e.items.remove(item, removed); + return removed; } @@ -234,150 +204,120 @@ public class ItemConveyor extends BaseConveyor implements Autotiler{ @Override public void handleStack(Item item, int amount, Tile tile, Unit source){ - ItemConveyorEntity entity = tile.ent(); + ItemConveyorEntity e = tile.ent(); for(int i = amount - 1; i >= 0; i--){ - long result = ItemPos.packItem(item, 0f, i * itemSpace); - entity.convey.insert(0, result); - entity.items.add(item, 1); + e.add(0); + e.xs[0] = 0; + e.ys[0] = i * itemSpace; + e.ids[0] = item; + e.items.add(item, 1); } - entity.noSleep(); + e.noSleep(); } @Override public boolean acceptItem(Item item, Tile tile, Tile source){ + ItemConveyorEntity e = tile.ent(); + if(e.len >= capacity) return false; int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.rotation()); - float minitem = tile.ent().minitem; - return (((direction == 0) && minitem > itemSpace) || - ((direction % 2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.rotation() + 2) % 4 == tile.rotation())); + return (((direction == 0) && e.minitem >= itemSpace) || ((direction % 2 == 1) && e.minitem > 0.5f + itemSpace)) && (source == null || !(source.block().rotate && (source.rotation() + 2) % 4 == tile.rotation())); } @Override public void handleItem(Item item, Tile tile, Tile source){ - byte rotation = tile.rotation(); + ItemConveyorEntity e = tile.ent(); + if(e.len >= capacity) return; - int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation); - int ang = ((source.relativeTo(tile.x, tile.y) - rotation)); + byte r = tile.rotation(); + int ang = ((source.relativeTo(tile.x, tile.y) - r)); + float x = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0; - float pos = ch == 0 ? 0 : ch % 2 == 1 ? 0.5f : 1f; - float y = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0; + e.noSleep(); + e.items.add(item, 1); - ItemConveyorEntity entity = tile.ent(); - entity.noSleep(); - long result = ItemPos.packItem(item, y * 0.9f, pos); - - tile.entity.items.add(item, 1); - - for(int i = 0; i < entity.convey.size; i++){ - if(compareItems(result, entity.convey.get(i)) < 0){ - entity.convey.insert(i, result); - entity.lastInserted = (byte)i; - return; - } + if(Math.abs(source.relativeTo(tile.x, tile.y) - r) == 0){ //idx = 0 + e.add(0); + e.xs[0] = x; + e.ys[0] = 0; + e.ids[0] = item; + }else{ //idx = mid + e.add(e.mid); + e.xs[e.mid] = x; + e.ys[e.mid] = 0.5f; + e.ids[e.mid] = item; } - - //this item must be greater than anything there... - entity.convey.add(result); - entity.lastInserted = (byte)(entity.convey.size - 1); } public static class ItemConveyorEntity extends BaseConveyorEntity{ + //parallel array data + Item[] ids = new Item[capacity]; + float[] xs = new float[capacity]; + float[] ys = new float[capacity]; + //amount of items, always < capacity + int len = 0; + //next entity + @Nullable TileEntity next; + @Nullable ItemConveyorEntity nextc; + //whether the next conveyor's rotation == tile rotation + boolean aligned; - LongArray convey = new LongArray(); - byte lastInserted; + int lastInserted, mid; float minitem = 1; + int blendbits; + int blendsclx, blendscly; + + float clogHeat = 0f; + + final void add(int o){ + for(int i = Math.max(o + 1, len); i > o; i--){ + ids[i] = ids[i - 1]; + xs[i] = xs[i - 1]; + ys[i] = ys[i - 1]; + } + + len++; + } + + final void remove(int o){ + for(int i = o; i < len - 1; i++){ + ids[i] = ids[i + 1]; + xs[i] = xs[i + 1]; + ys[i] = ys[i + 1]; + } + + len--; + } + @Override public void write(DataOutput stream) throws IOException{ super.write(stream); - stream.writeInt(convey.size); + stream.writeInt(len); - for(int i = 0; i < convey.size; i++){ - stream.writeInt(ItemPos.toInt(convey.get(i))); + for(int i = 0; i < len; i++){ + stream.writeInt(Pack.intBytes((byte)ids[i].id, (byte)(xs[i] * 127), (byte)(ys[i] * 255 - 128), (byte)0)); } } @Override public void read(DataInput stream, byte revision) throws IOException{ super.read(stream, revision); - convey.clear(); int amount = stream.readInt(); - convey.ensureCapacity(Math.min(amount, 10)); + len = Math.min(amount, capacity); for(int i = 0; i < amount; i++){ - convey.add(ItemPos.toLong(stream.readInt())); + int val = stream.readInt(); + byte id = (byte)(val >> 24); + float x = (float)((byte)(val >> 16)) / 127f; + float y = ((float)((byte)(val >> 8)) + 128f) / 255f; + if(i < capacity){ + ids[i] = content.item(id); + xs[i] = x; + ys[i] = y; + } } } } - - //Container class. Do not instantiate. - static class ItemPos{ - private static short[] writeShort = new short[4]; - private static byte[] writeByte = new byte[4]; - - private static short[] packShorts = new short[4]; - private static short[] drawShorts = new short[4]; - private static short[] updateShorts = new short[4]; - - Item item; - float x, y; - - private ItemPos(){ - } - - static long packItem(Item item, float x, float y){ - short[] shorts = packShorts; - shorts[0] = (short)item.id; - shorts[1] = (short)(x * Short.MAX_VALUE); - shorts[2] = (short)((y - 1f) * Short.MAX_VALUE); - return Pack.longShorts(shorts); - } - - static int toInt(long value){ - short[] values = Pack.shorts(value, writeShort); - - short itemid = values[0]; - float x = values[1] / (float)Short.MAX_VALUE; - float y = ((float)values[2]) / Short.MAX_VALUE + 1f; - - byte[] bytes = writeByte; - bytes[0] = (byte)itemid; - bytes[1] = (byte)(x * 127); - bytes[2] = (byte)(y * 255 - 128); - - return Pack.intBytes(bytes); - } - - static long toLong(int value){ - byte[] values = Pack.bytes(value, writeByte); - - short itemid = content.item(values[0]).id; - float x = values[1] / 127f; - float y = ((int)values[2] + 128) / 255f; - - short[] shorts = writeShort; - shorts[0] = itemid; - shorts[1] = (short)(x * Short.MAX_VALUE); - shorts[2] = (short)((y - 1f) * Short.MAX_VALUE); - return Pack.longShorts(shorts); - } - - ItemPos set(long lvalue, short[] values){ - Pack.shorts(lvalue, values); - - if(values[0] >= content.items().size || values[0] < 0) - item = null; - else - item = content.items().get(values[0]); - - x = values[1] / (float)Short.MAX_VALUE; - y = ((float)values[2]) / Short.MAX_VALUE + 1f; - return this; - } - - long pack(){ - return packItem(item, x, y); - } - } } \ No newline at end of file diff --git a/core/src/mindustry/world/blocks/power/PowerNode.java b/core/src/mindustry/world/blocks/power/PowerNode.java index 7a267abeef..65de453319 100644 --- a/core/src/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/mindustry/world/blocks/power/PowerNode.java @@ -182,8 +182,11 @@ public class PowerNode extends PowerBlock{ if(tile == other){ if(other.entity.power.links.size == 0){ + int[] total = {0}; getPotentialLinks(tile, link -> { - if(!insulated(tile, link)) tile.configure(link.pos()); + if(!insulated(tile, link) && total[0]++ < maxNodes){ + tile.configure(link.pos()); + } }); }else{ while(entity.power.links.size > 0){ diff --git a/fastlane/metadata/android/en-US/changelogs/103.1.txt b/fastlane/metadata/android/en-US/changelogs/103.1.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/103.1.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/fastlane/metadata/android/en-US/changelogs/103.2.txt b/fastlane/metadata/android/en-US/changelogs/103.2.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/103.2.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/fastlane/metadata/android/en-US/changelogs/103.3.txt b/fastlane/metadata/android/en-US/changelogs/103.3.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/103.3.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/fastlane/metadata/android/en-US/changelogs/29591.txt b/fastlane/metadata/android/en-US/changelogs/29591.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29591.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/fastlane/metadata/android/en-US/changelogs/29594.txt b/fastlane/metadata/android/en-US/changelogs/29594.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29594.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/fastlane/metadata/android/en-US/changelogs/29597.txt b/fastlane/metadata/android/en-US/changelogs/29597.txt new file mode 100644 index 0000000000..fa41c882ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29597.txt @@ -0,0 +1,9 @@ +- Added new icons w/ smooth scaling +- Added liquid void (Contributed by @GioIacca9) +- Added bridge opacity slider (Contributed by @Quezler) +- Added "underflow" gate (opposite of overflow gate) +- Added "emojis" for most blocks and items into the font +- Added new tech tree layout w/ better mod support +- Added game log file, stored in data folder +- Added new separator sprite/animation +- Added list of affinity tiles to stats of certain blocks diff --git a/gradle.properties b/gradle.properties index 1aea212e80..e3312d11f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=b54ec5513f66ad97f191b46da10f6c61046a301a +archash=649641d8936160221ce24c47f5b0ad10606de289 diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index 566656b4a5..0ee88e19e2 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -829,7 +829,6 @@ public class ServerControl implements ApplicationListener{ }); mods.eachClass(p -> p.registerServerCommands(handler)); - mods.eachClass(p -> p.registerClientCommands(netServer.clientCommands)); } private void readCommands(){ diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 722bf246d0..1ca2a6d03d 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -1,25 +1,23 @@ -import arc.ApplicationCore; -import arc.Core; -import arc.backend.headless.HeadlessApplication; +import arc.*; +import arc.backend.headless.*; +import arc.math.geom.*; import arc.struct.*; -import arc.math.geom.Point2; -import arc.util.Log; -import arc.util.Time; -import mindustry.Vars; +import arc.util.*; +import mindustry.*; import mindustry.content.*; -import mindustry.core.GameState.State; import mindustry.core.*; -import mindustry.entities.traits.BuilderTrait.BuildRequest; -import mindustry.entities.type.BaseUnit; +import mindustry.core.GameState.*; +import mindustry.ctype.*; +import mindustry.entities.traits.BuilderTrait.*; +import mindustry.entities.type.*; import mindustry.entities.type.base.*; -import mindustry.game.Team; -import mindustry.io.SaveIO; -import mindustry.maps.Map; -import mindustry.net.*; -import mindustry.ctype.ContentType; -import mindustry.type.Item; +import mindustry.game.*; +import mindustry.io.*; +import mindustry.maps.*; +import mindustry.net.Net; +import mindustry.type.*; import mindustry.world.*; -import mindustry.world.blocks.BlockPart; +import mindustry.world.blocks.*; import org.junit.jupiter.api.*; import static mindustry.Vars.*; @@ -216,6 +214,49 @@ public class ApplicationTests{ assertTrue(state.teams.playerCores().size > 0); } + @Test + void conveyorBench(){ + int[] items = {0}; + + world.loadMap(testMap); + state.set(State.playing); + int length = 128; + world.tile(0, 0).setBlock(Blocks.itemSource); + world.tile(0, 0).configureAny(Items.copper.id); + + Array entities = Array.with(world.tile(0, 0).entity); + + for(int i = 0; i < length; i++){ + world.tile(i + 1, 0).setBlock(Blocks.conveyor); + world.tile(i + 1, 0).rotation(0); + entities.add(world.tile(i + 1, 0).entity); + } + + world.tile(length + 1, 0).setBlock(new Block("___"){ + @Override + public void handleItem(Item item, Tile tile, Tile source){ + items[0] ++; + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + return true; + } + }); + + //warmup + for(int i = 0; i < 100000; i++){ + entities.each(TileEntity::update); + } + + Time.mark(); + for(int i = 0; i < 200000; i++){ + entities.each(TileEntity::update); + } + Log.info(Time.elapsed() + "ms to process " + items[0] + " items"); + assertTrue(items[0] > 0); + } + @Test void load77Save(){ resetWorld(); diff --git a/tools/src/mindustry/tools/FontGenerator.java b/tools/src/mindustry/tools/FontGenerator.java index 32bbc121cf..2ebb8fd2a0 100644 --- a/tools/src/mindustry/tools/FontGenerator.java +++ b/tools/src/mindustry/tools/FontGenerator.java @@ -46,7 +46,8 @@ public class FontGenerator{ Log.info("Merge..."); - OS.exec("fontforge", "-script", "core/assets-raw/fontgen/merge.pe"); + //don't merge since it breaks the font + //OS.exec("fontforge", "-script", "core/assets-raw/fontgen/merge.pe"); Log.info("Done."); }