diff --git a/.gitignore b/.gitignore index 79cf5235d0..8b1efce910 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,6 @@ logs/ /server/build/ /test_files/ /annotations/build/ -/desktop-sdl/build/ -desktop-sdl/build/ /android/assets/mindustry-maps/ /android/assets/mindustry-saves/ /core/assets/gifexport/ @@ -33,7 +31,9 @@ desktop-sdl/build/ ios/robovm.properties packr-out/ config/ +changelog *.gif +/core/assets/saves/ version.properties diff --git a/.travis.yml b/.travis.yml index 171280c268..f0586c145c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ deploy: api_key: secure: Cv5wFtWt62/A24EvSEQvMow7gKPbZ3oATEFPuSghhB2TQz1dA40Zee3Qvk4LFlpLrhYo4K0ZSczCZRGpR+hCd8+Dpww52bheYEvWuh3ZQfvu/fXtEx2j5PwP1qMpmIgSxETV/gkD7l9FImdh0VzktYiAvQfmi0bEocG9/D4QwjFpNat7iwBdcMiw1MvAygpdIWRsjiw0RKlB2mWarmoHhQ7Gu7qlU3j50uaEvcrtmU0pBUPggNQwQRv32i9NPvNFxrqqlUjDLIS8JFea99zCkp8BwYqbEvBIMzd+Qip1/stLJJA3+cDUClbsDtg8rAVetzpOrdLEEBmqShFe5MDl2yEHcsgpN9CFsyTaUfvB3P3rVjizvycMm42IsUkXQiarm5xTQ/TIA8Rd8AHiSKuweNCg1Fd5SFaRtKy8JVLXuxyfUccmyje6hhz2L4lS2Wfj3mAG7sqZUCXhWP79EKdGkiPOjKv4CwXEKmuH3BMVqPlNUZJr9Eg3sV1FG0h2l+MVOOnR635qdUbb49sYojYxVruMLX0BH1c4ZCu230m8CUoWA1Em1QNI75ya7+9Y5T6AsgWDVpBvdUo9fWNbdp+VQ0GskFQsJD5wtnxbcbHeFiERAgGBm7z6qt9u9LrQpBH+dsW52ADvYsu3L4nQEa+sdMHwTTwmGY+iUvsxu0DqxGg= file: - - desktop/build/libs/desktop-release.jar + - desktop/build/libs/Mindustry.jar - server/build/libs/server-release.jar on: repo: Anuken/Mindustry diff --git a/README.md b/README.md index 791a99452b..b0ec7c49e3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, --- Gradle may take up to several minutes to download files. Be patient.
-After building, the output .JAR file should be in `/desktop/build/libs/desktop-release.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds. +After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds. ### Downloads diff --git a/TRANSLATING.md b/TRANSLATING.md index 945fc76788..69065a91ba 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -1,8 +1,5 @@ ## Translating for Mindustry -**DISCLAIMER:** *Currently, 4.0 is far from done, which means that things such as block names, descriptions, and core text will be changing often. If you begin translating now, you might have to re-do large chunks of the bundle before final release.* - - To begin, log in to your GitHub account, or if you don't have one yet, create it [here](https://github.com/). Consult [this list](https://www.science.co.il/language/Locale-codes.php) to find the locale code for your language. Once you've found it, diff --git a/android/build.gradle b/android/build.gradle index e24bbab771..5dba2016c9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,7 +8,7 @@ buildscript{ } dependencies{ - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.4.1' } } diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index 4792bd6d89..e65c3be427 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -129,8 +129,8 @@ public class AndroidLauncher extends AndroidApplication{ } config.hideStatusBar = true; - Net.setClientProvider(new ArcNetClient()); - Net.setServerProvider(new ArcNetServer()); + Net.setClientProvider(new MClient()); + Net.setServerProvider(new MServer()); initialize(new Mindustry(), config); checkFiles(getIntent()); } diff --git a/annotations/src/main/java/io/anuke/annotations/AssetsAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/AssetsAnnotationProcessor.java index 32bd471288..6fed3137de 100644 --- a/annotations/src/main/java/io/anuke/annotations/AssetsAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/AssetsAnnotationProcessor.java @@ -80,7 +80,7 @@ public class AssetsAnnotationProcessor extends AbstractProcessor{ } load.addStatement(name + " = io.anuke.arc.Core.audio."+loadMethod+"(io.anuke.arc.Core.files.internal(io.anuke.arc.Core.app.getType() != io.anuke.arc.Application.ApplicationType.iOS ? $S : $S))", - path.substring(path.lastIndexOf("/") + 1) + "/" + fname, (path.substring(path.lastIndexOf("/") + 1) + "/" + fname).replace(".ogg", ".caf")); + path.substring(path.lastIndexOf("/") + 1) + "/" + fname, (path.substring(path.lastIndexOf("/") + 1) + "/" + fname).replace(".ogg", ".mp3")); dispose.addStatement(name + ".dispose()"); dispose.addStatement(name + " = null"); type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new io.anuke.arc.audio.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build()); diff --git a/build.gradle b/build.gradle index f6b7aa6cc6..e3830aa61f 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,7 @@ allprojects{ platform = "${platform.substring(0, platform.length() - 2)}-${platform.substring(platform.length() - 2)}bit" } - return "[${platform}]${getModifierString()}[${getNeatVersionString()}]${appName}.zip" + return "[${platform}]${getModifierString()}[${getNeatVersionString()}]${appName}" } getVersionString = { @@ -145,23 +145,6 @@ allprojects{ project(":desktop"){ apply plugin: "java" - dependencies{ - compile project(":core") - compile project(":net") - - if(debugged()) compile project(":debug") - - compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" - compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" - - compile arcModule("backends:backend-lwjgl3") - compile 'com.github.MinnDevelopment:java-discord-rpc:v2.0.2' - } -} - -project(":desktop-sdl"){ - apply plugin: "java" - dependencies{ compile project(":core") compile project(":net") @@ -246,9 +229,11 @@ project(":core"){ compileJava.dependsOn(preGen) + compile "org.lz4:lz4-java:1.4.1" compile arcModule("arc-core") compile arcModule("extensions:freetype") compile arcModule("extensions:arcnet") + compile arcModule("extensions:mnet") if(localArc() && debugged()) compile arcModule("extensions:recorder") compileOnly project(":annotations") @@ -311,7 +296,26 @@ project(":net"){ dependencies{ compile project(":core") - compile "org.lz4:lz4-java:1.4.1" - compile 'com.github.Anuken:WaifUPnP:05eb46bc577fd7674596946ba288c96c0cedd893' } } + +task deployAll{ + task cleanDeployOutput{ + doFirst{ + if("${getBuildVersion()}" == "custom build" || "${getBuildVersion()}" == "") throw new IllegalArgumentException("----\n\nSET A BUILD NUMBER FIRST!\n\n----") + if(!project.hasProperty("release")) throw new IllegalArgumentException("----\n\nSET THE RELEASE PROJECT PROPERTY FIRST!\n\n----") + + delete{ + delete "deploy/" + } + } + } + + dependsOn cleanDeployOutput + dependsOn "desktop:packrLinux64" + dependsOn "desktop:packrWindows64" + dependsOn "desktop:packrWindows32" + dependsOn "desktop:packrMacOS" + dependsOn "server:deploy" + dependsOn "android:deploy" +} \ No newline at end of file diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 5f19cec78d..9fe58d1b1b 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -75,6 +75,7 @@ server.kicked.nameEmpty = Your chosen name is invalid. server.kicked.idInUse = You are already on this server! Connecting with two accounts is not permitted. server.kicked.customClient = This server does not support custom builds. Download an official version. server.kicked.gameover = Game over! +server.versions = Your version:[accent] {0}[]\nServer version:[accent] {1}[] host.info = The [accent]host[] button hosts a server on port [scarlet]6567[]. \nAnybody on the same [lightgray]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[lightgray]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. Note that public networks sometimes do not allow server discovery. join.info = Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[lightgray]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. hostserver = Host Multiplayer Game @@ -124,7 +125,7 @@ save.new = New Save save.overwrite = Are you sure you want to overwrite\nthis save slot? overwrite = Overwrite save.none = No saves found! -saveload = [accent]Saving... +saveload = Saving... savefail = Failed to save game! save.delete.confirm = Are you sure you want to delete this save? save.delete = Delete @@ -371,7 +372,7 @@ zone.crags.name = Crags zone.fungalPass.name = Fungal Pass zone.groundZero.description = The optimal location to begin once more. Low enemy threat. Few resources.\nGather as much lead and copper as possible.\nMove on. -zone.frozenForest.description = Even here, closer to mountains, the spores have spread. The fridgid temperatures cannot contain them forever.\n\nBegin the venture into power. Build combustion generators. Learn to use menders. +zone.frozenForest.description = Even here, closer to mountains, the spores have spread. The frigid temperatures cannot contain them forever.\n\nBegin the venture into power. Build combustion generators. Learn to use menders. zone.desertWastes.description = These wastes are vast, unpredictable, and criss-crossed with derelict sector structures.\nCoal is present in the region. Burn it for power, or synthesize graphite.\n\n[lightgray]This landing location cannot be guaranteed. zone.saltFlats.description = On the outskirts of the desert lie the Salt Flats. Few resources can be found in this location.\n\nThe enemy has erected a resource storage complex here. Eradicate their core. Leave nothing standing. zone.craters.description = Water has accumulated in this crater, relic of the old wars. Reclaim the area. Collect sand. Smelt metaglass. Pump water to cool turrets and drills. diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index 6c937b3e0c..4a742bd915 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -1,10 +1,10 @@ -credits.text = Created by [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\n\n[GRAY](In case you can't tell, this text is currently unfinished.\nTranslators, don't edit it yet!) +credits.text = Entwickelt von [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\n\n[GRAY] credits = Danksagungen contributors = Übersetzer und Mitwirkende discord = Trete dem Mindustry Discord bei! link.discord.description = Der offizielle Mindustry Discord Chatroom link.github.description = Quellcode des Spiels -link.changelog.description = Liste von Änderungen +link.changelog.description = Liste der Änderungen link.dev-builds.description = Entwicklungs-Builds (instabil) link.trello.description = Offizielles Trello Board für geplante Features link.itch.io.description = itch.io Seite mit Downloads und der Web-Version des Spiels @@ -21,11 +21,11 @@ stat.enemiesDestroyed = Gegner Zerstört:[accent] {0} stat.built = Gebäude Gebaut:[accent] {0} stat.destroyed = Gebäude Zerstört:[accent] {0} stat.deconstructed = Gebäude Abgebaut:[accent] {0} -stat.delivered = Resources Launched: +stat.delivered = Übertragene Ressourcen: stat.rank = Finaler Rang: [accent]{0} -placeline = Du hast einen Block ausgewählt.\nDu kannst[accent] davon eine Reihe bauen,[] indem du[accent] wenige Sekunden mit einem Finger drückst[] und ihn in eine Richtung ziehst.\nVersuch es. -removearea = Du hast den Zerstörungs Modus ausgewählt.\nDu kannst[accent] Blöcke im Rechteck zerstören,[] indem du[accent] wenige Sekunden mit einem Finger drückst[] und ihn ziehst.\nTry it. -launcheditems = [accent]Launched Items +placeline = Du hast einen Block ausgewählt.\nDu kannst[accent] davon eine Reihe bauen,[] indem du[accent] wenige Sekunden mit einem Finger drückst[] und ihn in eine Richtung ziehst.\nVersuche es. +removearea = Du hast den Zerstörungsmodus ausgewählt.\nDu kannst[accent] Blöcke im Rechteck zerstören,[] indem du[accent] wenige Sekunden mit einem Finger drückst[] und ihn ziehst.\nVersuche es. +launcheditems = [accent]Übertragene Items map.delete = Bist du sicher, dass du die Karte "[accent]{0}[]" löschen möchtest? level.highscore = Highscore: [accent]{0} level.select = Level Auswahl @@ -51,11 +51,11 @@ about.button = Info name = Name: noname = Wähle zuerst einen[accent] Spielernamen[]. filename = Dateiname: -unlocked = Neuen Block freigeschaltet! +unlocked = Neuer Block freigeschaltet! completed = [accent]Abgeschlossen -techtree = Tech Tree +techtree = Forschung research.list = [LIGHT_GRAY]Forschung: -research = Forschung +research = Erforschen researched = [LIGHT_GRAY]{0} erforscht. players = {0} Spieler online players.single = {0} Spieler online @@ -70,7 +70,7 @@ server.kicked.nameInUse = Es ist bereits ein Spieler \nmit diesem Namen auf dem server.kicked.nameEmpty = Dein Name muss mindestens einen Buchstaben oder eine Zahl enthalten. server.kicked.idInUse = Du bist bereits auf dem Server! Anmeldungen mit zwei Accounts sind nicht gestattet. server.kicked.customClient = Der Server akzeptiert keine Custom Builds von Mindustry. Lade dir die offizielle Version herunter. -server.kicked.gameover = Game over! +server.kicked.gameover = Game Over! host.info = Der [accent]host[] Knopf startet einen Server auf den Ports [scarlet]6567[] und [scarlet]6568.[]\nJeder im gleichen [LIGHT_GRAY]W-Lan oder lokalem Netzwerk[] sollte deinen Server in seiner Server Liste sehen können.\n\nWenn du Leuten die Verbindung über IP ermöglichen willst, benötigst du [accent]Port-Forwarding[].\n\n[LIGHT_GRAY]Hinweis: Falls es Probleme mit der Verbindung im Netzwerk gibt, stell sicher, dass Mindustry in deinen Firewall Einstellungen Zugriff auf das lokale Netzwerk hat. join.info = Hier kannst du eine [accent]Server IP[] eingeben um dich zu verbinden oder Server im [accent]lokalem Netzwerk[] entdecken und dich mit ihnen verbinden.\nSowohl Spielen über das lokale Netzwerk als auch Spielen über das Internet werden unterstützt.\n\n[LIGHT_GRAY]Hinweis: Es gibt keine globale Server Liste; Wenn du dich mit jemand per IP verbinden willst musst du den Host nach seiner IP fragen. hostserver = Server hosten @@ -86,8 +86,8 @@ trace = Spieler verfolgen trace.playername = Spielername: [accent]{0} trace.ip = IP: [accent]{0} trace.id = Eindeutige ID: [accent]{0} -trace.mobile = Mobile Client: [accent]{0} -trace.modclient = Custom Client: [accent]{0} +trace.mobile = Mobiler Client: [accent]{0} +trace.modclient = Gemoddeter Client: [accent]{0} invalidid = Ungültige Client ID! Berichte den Bug. server.bans = Bans server.bans.none = Keine gebannten Spieler gefunden! @@ -99,7 +99,7 @@ server.edit = Server bearbeiten server.outdated = [crimson]Veralteter Server![] server.outdated.client = [crimson]Veralteter Client![] server.version = [lightgray]Version: {0} -server.custombuild = [yellow]Custom Build +server.custombuild = [yellow]Benutzerdefinierter Build confirmban = Bist du sicher, dass du diesen Spieler verbannen möchtest? confirmkick = Bist du sicher, dass du diesen Spieler kicken willst? confirmunban = Bist du sicher, dass du die Verbannung des Spielers rückgängig machen willst? @@ -155,7 +155,7 @@ openlink = Link öffnen copylink = Kopiere Link back = Zurück quit.confirm = Willst du wirklich aufhören? -loading = [accent]Wird geladen ... +loading = [accent]Wird geladen... saving = [accent]Speichere... wave = [accent]Welle {0} wave.waiting = Welle in {0} @@ -184,8 +184,8 @@ editor.author = Autor: editor.description = Beschreibung: editor.waves = Wellen: editor.rules = Regeln: -editor.generation = Generation: -editor.ingame = In-Game Bearbeiten +editor.generation = Generator: +editor.ingame = Im Spiel Bearbeiten editor.newmap = Neue Karte waves.title = Wellen waves.remove = Entfernen @@ -201,24 +201,24 @@ waves.copy = Aus der Zwischenablage kopieren waves.load = Aus der Zwischenablage laden waves.invalid = Ungültige Wellen in der Zwischenablage. waves.copied = Wellen kopiert. -waves.none = Keine Gegner definiert.\nInfo: Leere Wellen Entwürfe werden automatisch mit dem standard Entwurf ersetzt. -editor.default = [LIGHT_GRAY] +waves.none = Keine Gegner definiert.\nInfo: Leere Wellen Entwürfe werden automatisch mit dem Standard-Entwurf ersetzt. +editor.default = [LIGHT_GRAY] edit = Bearbeiten... editor.name = Name: -editor.spawn = Spawn Unit -editor.removeunit = Remove Unit +editor.spawn = Spawn Bereich +editor.removeunit = Entferne Bereich editor.teams = Teams -editor.errorload = Fehler beim laden der Datei:\n[accent]{0} -editor.errorsave = Fehler beim speichern der Datei:\n[accent]{0} +editor.errorload = Fehler beim Laden der Datei:\n[accent]{0} +editor.errorsave = Fehler beim Speichern der Datei:\n[accent]{0} editor.errorimage = Das ist ein Bild, keine Karte. Wechsel nicht den Dateityp und erwarte, dass es funktioniert.\n\nWenn du eine alte Karte importieren möchtest, benutze den 'Importiere Terrain Bild' Knopf in dem Editor. editor.errorlegacy = Diese Karte ist zu alt und benutzt ein veraltetes Karten Format, das nicht mehr unterstützt wird. editor.errorheader = Diese Karte ist entweder nicht gültig oder beschädigt. editor.errorname = Karte hat keinen Namen. -editor.update = Update +editor.update = Aktualisieren editor.randomize = Zufällig Anordnen editor.apply = Anwenden editor.generate = Generieren -editor.resize = Grösse\nanpassen +editor.resize = Größe\nanpassen editor.loadmap = Karte\nladen editor.savemap = Karte\nspeichern editor.saved = Gespeichert! @@ -227,7 +227,7 @@ editor.save.overwrite = Deine Karte überschreibt eine built-in Karte! Wähle ei editor.import.exists = [scarlet]Fehler beim Import:[] Ein built-in Karte namens '{0}' existiert bereits! editor.import = Import... editor.importmap = Importiere Karte -editor.importmap.description = Importiere von einer bestehende Karte +editor.importmap.description = Importiere von einer bestehenden Karte editor.importfile = Importiere Datei editor.importfile.description = Importiere aus einer Karten Datei editor.importimage = Importiere Terrain Bild @@ -240,7 +240,7 @@ editor.exportimage.description = Exportiere in eine Karten Bild Datei editor.loadimage = Bild\nladen editor.saveimage = Bild\nspeichern editor.unsaved = [crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? -editor.resizemap = Grösse der Karte ändern +editor.resizemap = Größe der Karte ändern editor.mapname = Karten Name editor.overwrite = [accent] Warnung! Dies überschreibt eine vorhandene Karte. editor.overwrite.confirm = [scarlet]Warnung![] Eine Karte mit diesem Namen existiert bereits. Bist du sicher, dass du sie überschreiben willst? @@ -255,40 +255,40 @@ toolmode.square = Quadrat toolmode.square.description = Quadrat Pinsel. toolmode.eraseores = Erze löschen toolmode.eraseores.description = Löscht nur Erze. -toolmode.fillteams = Fill Teams -toolmode.fillteams.description = Fill teams instead of blocks. -toolmode.drawteams = Draw Teams -toolmode.drawteams.description = Draw teams instead of blocks. +toolmode.fillteams = Teams Ausfüllen +toolmode.fillteams.description = Füllt Teams aus anstatt Blöcke. +toolmode.drawteams = Teams Zeichnen +toolmode.drawteams.description = Zeichnet Teams anstatt Blöcke. filters.empty = [LIGHT_GRAY]Keine Filter! Füge einen mit dem unteren Knopf hinzu. -filter.distort = Distort -filter.noise = Noise -filter.median = Median -filter.blend = Blend +filter.distort = Verzerren +filter.noise = Rauschen +filter.median = Mittelwert +filter.blend = Mischen filter.defaultores = Standard Erze filter.ore = Erz -filter.rivernoise = River Noise -filter.mirror = Mirror +filter.rivernoise = Fluss Rauschen +filter.mirror = Spiegel filter.clear = Löschen -filter.option.ignore = Ignore -filter.scatter = Scatter +filter.option.ignore = Ignorieren +filter.scatter = Streuen filter.terrain = Terrain -filter.option.scale = Scale +filter.option.scale = Skalierung filter.option.chance = Wahrscheinlichkeit -filter.option.mag = Magnitude -filter.option.threshold = Threshold -filter.option.circle-scale = Circle Scale -filter.option.octaves = Octaves -filter.option.falloff = Falloff -filter.option.angle = Angle +filter.option.mag = Größe +filter.option.threshold = Grenzwert +filter.option.circle-scale = Kreis Skalierung +filter.option.octaves = Oktaven +filter.option.falloff = Rückgang +filter.option.angle = Winkel filter.option.block = Block -filter.option.floor = Floor -filter.option.flooronto = Target Floor +filter.option.floor = Boden +filter.option.flooronto = Ziel Boden filter.option.wall = Wand filter.option.ore = Erz -filter.option.floor2 = Secondary Floor -filter.option.threshold2 = Secondary Threshold +filter.option.floor2 = Sekundärer Boden +filter.option.threshold2 = Sekundärer Grenzwert filter.option.radius = Radius -filter.option.percentile = Percentile +filter.option.percentile = Perzentil width = Breite: height = Höhe: menu = Menü @@ -299,36 +299,36 @@ save = Speichern fps = FPS: {0} tps = TPS: {0} ping = Ping: {0}ms -language.restart = Bitte Starte dein Spiel neu, damit die Sprach-Einstellung aktiv werden. +language.restart = Bitte Starte dein Spiel neu, damit die Sprach-Einstellung aktiv wird. settings = Einstellungen tutorial = Tutorial editor = Editor mapeditor = Karten Editor donate = Spenden -abandon = Abandon +abandon = Aufgeben abandon.text = Diese Zone sowie alle Ressourcen werden dem Gegner überlassen. -locked = Locked -complete = [LIGHT_GRAY]Complete: +locked = Gesperrt +complete = [LIGHT_GRAY]Abschließen: zone.requirement = Welle {0} in Zone {1} resume = Zu Zone zurückkehren:\n[LIGHT_GRAY]{0} -bestwave = [LIGHT_GRAY]Best: {0} -launch = Launch -launch.title = Launch Successful -launch.next = [LIGHT_GRAY]nächste Möglichkeit bei Welle {0} -launch.unable = [scarlet]Unable to LAUNCH.[] Enemies. -launch.confirm = This will launch all resources in your core.\nYou will not be able to return to this base. -uncover = Uncover -configure = Configure Loadout -configure.locked = [LIGHT_GRAY]Reach wave {0}\nto configure loadout. +bestwave = [LIGHT_GRAY]Beste Welle: {0} +launch = Abschluss +launch.title = Abschluss Erfolgreich +launch.next = [LIGHT_GRAY]Nächste Möglichkeit bei Welle {0} +launch.unable = [scarlet]Abschluss nicht möglich.[] Gegner. +launch.confirm = Dies wird alle Ressourcen in deinen Kern übertragen.\nDu kannst nicht wieder zu dieser Karte zurückkehren. +uncover = Freischalten +configure = Startitems festlegen +configure.locked = [LIGHT_GRAY]Erreiche Welle {0}\n, um Startitems festlegen zu können. zone.unlocked = [LIGHT_GRAY]{0} freigeschaltet. zone.requirement.complete = Welle {0} erreicht:\n{1} Anforderungen der Zone erfüllt. -zone.config.complete = Wave {0} reached:\nLoadout config unlocked. -zone.resources = Resources Detected: -zone.objective = [lightgray]Objective: [accent]{0} -zone.objective.survival = Survive -zone.objective.attack = Destroy Enemy Core +zone.config.complete = Welle {0} erreicht:\nFestlegen von Startitems freigeschaltet. +zone.resources = Ressourcen entdeckt: +zone.objective = [lightgray]Ziel: [accent]{0} +zone.objective.survival = Überlebe +zone.objective.attack = Zerstöre den feindlichen Kern add = Hinzufügen... -boss.health = Boss Health +boss.health = Boss Lebenskraft connectfail = [crimson] Verbindung zum Server konnte nicht hergestellt werden: [accent]{0} error.unreachable = Server nicht erreichbar. error.invalidaddress = Ungültige Adresse. @@ -336,35 +336,35 @@ error.timedout = Zeitüberschreitung!\nStelle sicher, dass die Portweiterleitung error.mismatch = Paketfehler:\nClient und Server passen möglicherweise nicht zusammen.\nStelle sicher, dass du und der Host jeweils die neueste Version von Mindustry haben! error.alreadyconnected = Bereits verbunden. error.mapnotfound = Kartendatei nicht gefunden! -error.io = Network I/O error. +error.io = Netzwerk I/O Fehler. error.any = Unbekannter Netzwerkfehler. -error.bloom = Failed to initialize bloom.\nYour device may not support it. +error.bloom = Bloom konnte nicht initialisiert werden.\nEs kann sein, dass dein Gerät es nicht unterstützt. zone.groundZero.name = Ground Zero -zone.desertWastes.name = Desert Wastes -zone.craters.name = The Craters -zone.frozenForest.name = Frozen Forest -zone.ruinousShores.name = Ruinous Shores -zone.stainedMountains.name = Stained Mountains -zone.desolateRift.name = Desolate Rift -zone.nuclearComplex.name = Nuclear Production Complex -zone.overgrowth.name = Overgrowth -zone.tarFields.name = Tar Fields -zone.saltFlats.name = Salt Flats -zone.impact0078.name = Impact 0078 -zone.crags.name = Crags -zone.groundZero.description = The optimal location to begin once more. Low enemy threat. Few resources.\nGather as much lead and copper as possible.\nMove on. -zone.frozenForest.description = Even here, closer to mountains, the spores have spread. The fridgid temperatures cannot contain them forever.\n\nBegin the venture into power. Build combustion generators. Learn to use menders. -zone.desertWastes.description = These wastes are vast, unpredictable, and criss-crossed with derelict sector structures.\nCoal is present in the region. Burn it for power, or synthesize graphite.\n\n[lightgray]This landing location cannot be guaranteed. -zone.saltFlats.description = On the outskirts of the desert lie the Salt Flats. Few resources can be found in this location.\n\nThe enemy has erected a resource storage complex here. Eradicate their core. Leave nothing standing. -zone.craters.description = Water has accumulated in this crater, relic of the old wars. Reclaim the area. Collect sand. Smelt metaglass. Pump water to cool turrets and drills. -zone.ruinousShores.description = Past the wastes, is the shoreline. Once, this location housed a coastal defense array. Not much of it remains. Only the most basic defense structures have remained unscathed, everything else reduced to scrap.\nContinue the expansion outwards. Rediscover the technology. -zone.stainedMountains.description = Further inland lie the mountains, yet untainted by spores.\nExtract the abundant titanium in this area. Learn how to use it.\n\nThe enemy presence is greater here. Do not give them time to send their strongest units. -zone.overgrowth.description = This area is overgrown, closer to the source of the spores.\nThe enemy has established an outpost here. Build dagger units. Destroy it. Reclaim that which was lost. -zone.tarFields.description = The outskirts of an oil production zone, between the mountains and desert. One of the few areas with usable tar reserves.\nAlthough abandoned, this area has some dangerous enemy forces nearby. Do not underestimate them.\n\n[lightgray]Research oil processing technology if possible. -zone.desolateRift.description = An extremely dangerous zone. Plentiful resources, but little space. High risk of destruction. Leave as soon as possible. Do not be fooled by the long spacing between enemy attacks. -zone.nuclearComplex.description = A former facility for the production and processing of thorium, reduced to ruins.\n[lightgray]Research the thorium and its many uses.\n\nThe enemy is present here in great numbers, constantly scouting for attackers. -zone.impact0078.description = -zone.crags.description = +zone.desertWastes.name = Schrottwüste +zone.craters.name = Krater +zone.frozenForest.name = Gefrorener Wald +zone.ruinousShores.name = Verfallene Ufer +zone.stainedMountains.name = Gefleckte Berge +zone.desolateRift.name = Trostloser Riss +zone.nuclearComplex.name = Kernkraftwerk +zone.overgrowth.name = Überwucherung +zone.tarFields.name = Teerfelder +zone.saltFlats.name = Salzebenen +zone.impact0078.name = Auswirkung 0078 +zone.crags.name = Felsen +zone.groundZero.description = Der optimale Ort, um anzufangen. Niedrige Bedrohung durch Gegner. Wenige Ressourcen.\nSammel so viel Kupfer und Blei wie möglich.\nMach weiter! +zone.frozenForest.description = Sogar hier, näher an den Bergen, haben sich die Sporen verbreitet. Die kalten Temperaturen können sie nicht für immer im Schach halten.\n\nStarte das Wagnis in Strom. Baue Verbrennungsgeneratoren. Lerne Heiler zu benutzen. +zone.desertWastes.description = Diese Abfälle sind riesig, unberechenbar, und durchzogen von verfallenen Sektorstrukturen.\nKohle ist in dieser Region vorhanden. Verbrenne es für Strom, oder synthetisiere Graphit.\n\n[lightgray]Dieser Landeort kann nicht garantiert werden. +zone.saltFlats.description = Am Rande der Wüste liegen die Salzebenen. In dieser Gegend können wenige Ressourcen gefunden werden.\n\nDer Feind hat hier einen Ressourcenspeicherkomplex errichtet. Zerstöre ihren Kern. Lass nichts stehen. +zone.craters.description = Wasser hat sich in diesem Krater angesammelt, ein Relikt von den alten Kriegen. Gewinne dieses Gebiet zurück. Sammle Sand. Schmelze Metaglass. Pumpe Wasser, um Geschütztürme und Bohrer zu kühlen. +zone.ruinousShores.description = Vorbei an der Wüste ist die Küste. An diesem Ort befand sich einst eine Küstenverteidigungsanlage. Davon ist aber nicht mehr viel übrig. Lediglich die einfachsten Verteidigungsstrukturen sind unversehrt, alles andere ist nur noch Schrott.\nSetzen Sie die Ausbreitung nach außen fort. Wiederentdecke die Technologie. +zone.stainedMountains.description = Weiter im Landesinneren liegen die Berge, die noch nicht von Sporen befleckt sind.\nExtrahiere das reichlich vorhandene Titan in diesem Bereich. Erlerne es zu benutzen.\n\nDie feindliche Präsenz ist größer hier. Gib ihnen nicht die Zeit ihre stärksten Einheiten zu schicken. +zone.overgrowth.description = Dieser Bereich ist bewachsen, näher an der Quelle der Sporen.\nDer Feind hat hier einen Außenposten errichtet. Baue Dagger-Einheiten. Zerstöre es. Gewinne zurück, was verloren gegangen ist. +zone.tarFields.description = Der Rand einer Ölförderzone, zwischen Bergen und Wüste. Eine der wenigen Plätze mit nutzbare Teer Reserven.\nObwohl es aufgegeben wurde, hat dieses Gebiet einige gefährliche feindliche Kräfte in der Nähe. Unterschätze sie nicht.\n\n[lightgray]Wenn möglich, erforsche Technologien zur Ölverarbeitung. +zone.desolateRift.description = Eine extrem gefährliche Zone. Reichlich Ressourcen, aber wenig Platz. Hohe Zerstörungsgefahr. Verlasse es so schnell wie möglich. Lassen Sie sich nicht von den großen Abständen zwischen feindlichen Angriffen in die Irre führen. +zone.nuclearComplex.description = Eine ehemalige Anlage zur Herstellung und Verarbeitung von Thorium, die in Trümmern liegt.\n[lightgray]Erforsche das Thorium und seine vielen Verwendungsmöglichkeiten.\n\nDer Feind ist hier in großer Zahl präsent und sucht ständig nach Angreifern. +zone.impact0078.description = +zone.crags.description = settings.language = Sprache settings.reset = Auf Standard zurücksetzen settings.rebind = Zuweisen @@ -373,7 +373,7 @@ settings.game = Spiel settings.sound = Audio settings.graphics = Grafiken settings.cleardata = Spieldaten zurücksetzen... -settings.clear.confirm = Bist du sicher, dass du die Spieldaten zurücksetzen willst?\n Diese Aktion kann nicht rückgänig gemacht werden! +settings.clear.confirm = Bist du sicher, dass du die Spieldaten zurücksetzen willst?\n Diese Aktion kann nicht rückgängig gemacht werden! settings.clearall.confirm = [scarlet]Warnung![]\nDas wird jegliche Spieldaten zurücksetzen inklusive Speicherstände, Karten, Freischaltungen und Tastenbelegungen.\n Nachdem du 'OK' drückst wird alles zurückgesetzt und das Spiel schließt sich automatisch. settings.clearunlocks = Freischaltungen zurücksetzen settings.clearall = Alles zurücksetzen @@ -386,7 +386,7 @@ error.crashtitle = Ein Fehler ist aufgetreten! attackpvponly = [scarlet]Nur in Angriff oder PvP Modus verfügbar. blocks.input = Input blocks.output = Output -blocks.booster = Booster +blocks.booster = Verstärkung block.unknown = [LIGHT_GRAY]??? blocks.powercapacity = Kapazität blocks.powershot = Stromverbrauch/Schuss @@ -394,7 +394,7 @@ blocks.damage = Schaden blocks.targetsair = Visiert Luft Einheiten an blocks.targetsground = Visiert Boden Einheiten an blocks.itemsmoved = Bewegungsgeschwindigkeit -blocks.launchtime = Time Between Launches +blocks.launchtime = Zeit zwischen Starts blocks.shootrange = Reichweite blocks.size = Größe blocks.liquidcapacity = Flüssigkeitskapazität @@ -404,32 +404,32 @@ blocks.powerdamage = Stromverbrauch/Schadenspunkt blocks.itemcapacity = Materialkapazität blocks.basepowergeneration = Basis-Stromerzeugung blocks.productiontime = Produktionszeit -blocks.repairtime = Block Full Repair Time +blocks.repairtime = Block volle Reparaturzeit blocks.speedincrease = Geschwindigkeitserhöhung blocks.range = Reichweite blocks.drilltier = Abbaubare Erze blocks.drillspeed = Bohrgeschwindigkeit -blocks.boosteffect = Boost Effect -blocks.maxunits = Max Active Units +blocks.boosteffect = Verstärkungseffekt +blocks.maxunits = Max aktive Einheiten blocks.health = Lebenspunkte -blocks.buildtime = Build Time +blocks.buildtime = Baudauer blocks.inaccuracy = Ungenauigkeit blocks.shots = Schüsse blocks.reload = Schüsse/Sekunde -blocks.ammo = Ammo +blocks.ammo = Munition bar.drillspeed = Bohrgeschwindigkeit: {0}/s bar.efficiency = Effizienz: {0}% -bar.powerbalance = Power: {0} -bar.poweramount = Power: {0} -bar.poweroutput = Power Output: {0} +bar.powerbalance = Strom: {0} +bar.poweramount = Strom: {0} +bar.poweroutput = Strom Output: {0} bar.items = Items: {0} bar.liquid = Flüssigkeit bar.heat = Hitze -bar.power = Power -bar.progress = Build Progress -bar.spawned = Units: {0}/{1} +bar.power = Strom +bar.progress = Baufortschritt +bar.spawned = Einheiten: {0}/{1} bullet.damage = [stat]{0}[lightgray] Schaden -bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles +bullet.splashdamage = [stat]{0}[lightgray] Flächenschaden ~[stat] {1}[lightgray] Kacheln bullet.incendiary = [stat]entzündend bullet.homing = [stat]verfolgend bullet.shock = [stat]schock @@ -437,8 +437,8 @@ bullet.frag = [stat]explosiv bullet.knockback = [stat]{0}[lightgray] zurückstoßend bullet.freezing = [stat]gefrierend bullet.tarred = [stat]geteert -bullet.multiplier = [stat]{0}[lightgray]x ammo multiplier -bullet.reload = [stat]{0}[lightgray]x reload +bullet.multiplier = [stat]{0}[lightgray]x Munition Multiplikator +bullet.reload = [stat]{0}[lightgray]x neu laden unit.blocks = Blöcke unit.powersecond = Stromeinheiten/Sekunde unit.liquidsecond = Flüssigkeitseinheiten/Sekunde @@ -448,30 +448,30 @@ unit.powerunits = Stromeinheiten unit.degrees = Grad unit.seconds = Sekunden unit.persecond = /sec -unit.timesspeed = x speed +unit.timesspeed = x Geschwindigkeit unit.percent = % unit.items = Materialeinheiten -category.general = Generell +category.general = Allgemeines category.power = Strom category.liquids = Flüssigkeiten category.items = Materialien category.crafting = Erzeugung category.shooting = Schießen -category.optional = Optional Enhancements -setting.landscape.name = Lock Landscape +category.optional = Optionale Verbesserungen +setting.landscape.name = Landschaft sperren setting.shadows.name = Schatten -setting.linear.name = Linear Filtering +setting.linear.name = Lineare Filterung setting.animatedwater.name = Animiertes Wasser setting.animatedshields.name = Animierte Schilde -setting.antialias.name = Antialias[LIGHT_GRAY] (benötigt Neustart)[] -setting.indicators.name = Ally Indicators +setting.antialias.name = Antialias[LIGHT_GRAY] (Neustart erforderlich)[] +setting.indicators.name = Verbündeten-Indikatoren setting.autotarget.name = Auto-Zielauswahl -setting.keyboard.name = Mouse+Keyboard Controls +setting.keyboard.name = Maus+Tastatur Steuerung setting.fpscap.name = Max FPS setting.fpscap.none = kein setting.fpscap.text = {0} FPS -setting.uiscale.name = UI Scaling[lightgray] (require restart)[] -setting.swapdiagonal.name = Always Diagonal Placement +setting.uiscale.name = UI-Skalierung[lightgray] (Neustart erforderlich)[] +setting.swapdiagonal.name = Immer diagonale Platzierung setting.difficulty.training = Training setting.difficulty.easy = Leicht setting.difficulty.normal = Normal @@ -484,7 +484,7 @@ setting.sensitivity.name = Controller-Empfindlichkeit setting.saveinterval.name = Autosave Häufigkeit setting.seconds = {0} Sekunden setting.fullscreen.name = Vollbild -setting.borderlesswindow.name = Borderless Window[LIGHT_GRAY] (may require restart) +setting.borderlesswindow.name = Randloses Fenster[LIGHT_GRAY] (Neustart teilweise erforderlich) setting.fps.name = Zeige FPS setting.vsync.name = VSync setting.lasers.name = Zeige Stromlaser @@ -495,29 +495,29 @@ setting.mutemusic.name = Musik stummschalten setting.sfxvol.name = Audioeffekt-Lautstärke setting.mutesound.name = Audioeffekte stummschalten setting.crashreport.name = Anonyme Absturzberichte senden -setting.chatopacity.name = Chat Opacity -setting.playerchat.name = Display In-Game Chat -uiscale.reset = UI scale has been changed.\nPress "OK" to confirm this scale.\n[scarlet]Reverting and exiting in[accent] {0}[] settings... -uiscale.cancel = Cancel & Exit +setting.chatopacity.name = Chat Deckkraft +setting.playerchat.name = Chat im Spiel anzeigen +uiscale.reset = UI-Skalierung wurde geändert.\nDrücke "OK", um diese Skalierung zu bestätigen.\n[scarlet]Zurückkehren und Beenden in[accent] {0}[] Einstellungen... +uiscale.cancel = Abbrechen & Beenden setting.bloom.name = Bloom keybind.title = Tasten zuweisen -keybinds.mobile = [scarlet]Most keybinds here are not functional on mobile. Only basic movement is supported. +keybinds.mobile = [scarlet]Die meisten Tastenzuweisungen hier funktionieren auf z.B. Handys nicht. Nur grundlegende Bewegung wird unterstützt. category.general.name = Allgemein category.view.name = Ansicht category.multiplayer.name = Mehrspieler command.attack = Angreifen command.retreat = Rückzug command.patrol = Patrouillieren -keybind.gridMode.name = Block Select +keybind.gridMode.name = Block Auswahl keybind.gridModeShift.name = Kategorie auswählen keybind.press = Drücke eine Taste... keybind.press.axis = Drücke eine Taste oder bewege eine Achse... -keybind.screenshot.name = Map Screenshot +keybind.screenshot.name = Karten Screenshot keybind.move_x.name = X-Achse keybind.move_y.name = Y-Achse keybind.select.name = Auswählen/Schießen keybind.diagonal_placement.name = Diagonal platzieren -keybind.pick.name = Pick Block +keybind.pick.name = Block Auswählen keybind.break_block.name = Block zerstören keybind.deselect.name = Auswahl aufheben keybind.shoot.name = Schießen @@ -538,41 +538,41 @@ keybind.chat_scroll.name = Chat scrollen keybind.drop_unit.name = Einheit absetzen keybind.zoom_minimap.name = Minimap-Zoom mode.help.title = Beschreibung der Modi -mode.survival.name = Survival +mode.survival.name = Überleben mode.survival.description = Der normale Modus. Ressourcen sind limitiert und Wellen kommen automatisch. mode.sandbox.name = Sandkasten mode.sandbox.description = Unendliche Ressourcen und kein Timer für Wellen. mode.pvp.name = PvP -mode.pvp.description = Kämpfe gegen andere Spieler local. -mode.attack.name = Attack +mode.pvp.description = Kämpfe gegen andere Spieler lokal. +mode.attack.name = Angriff mode.attack.description = Keine Wellen, das Ziel ist es die gegnerische Basis zu zerstören. mode.custom = Angepasste Regeln rules.infiniteresources = Unbegrenzte Ressourcen rules.wavetimer = Wellen Timer rules.waves = Wellen -rules.attack = Attack Mode +rules.attack = Angriff-Modus rules.enemyCheat = Unbegrenzte Ressourcen für KI -rules.unitdrops = Unit Drops -rules.unitbuildspeedmultiplier = Unit Creation Speed Multiplier -rules.unithealthmultiplier = Unit Health Multiplier -rules.playerhealthmultiplier = Player Health Multiplier -rules.playerdamagemultiplier = Player Damage Multiplier -rules.unitdamagemultiplier = Unit Damage Multiplier -rules.enemycorebuildradius = Enemy Core No-Build Radius:[LIGHT_GRAY] (tiles) -rules.respawntime = Respawn Time:[LIGHT_GRAY] (sec) -rules.wavespacing = Wave Spacing:[LIGHT_GRAY] (sec) -rules.buildcostmultiplier = Build Cost Multiplier -rules.buildspeedmultiplier = Build Speed Multiplier -rules.waitForWaveToEnd = Waves wait for enemies -rules.dropzoneradius = Drop Zone Radius:[LIGHT_GRAY] (tiles) -rules.respawns = Max respawns per wave -rules.limitedRespawns = Limit Respawns -rules.title.waves = Waves +rules.unitdrops = Einheiten-Drops +rules.unitbuildspeedmultiplier = Baugeschwindigkeit-Einheit Multiplikator +rules.unithealthmultiplier = Lebenspunkte-Einheit Multiplikator +rules.playerhealthmultiplier = Spieler-Lebenspunkte Multiplikator +rules.playerdamagemultiplier = Spieler-Schaden Multiplikator +rules.unitdamagemultiplier = Schaden-Einheit Multiplikator +rules.enemycorebuildradius = Bauverbot Radius druch feindlichen Kern:[LIGHT_GRAY] (Kacheln) +rules.respawntime = Respawn Zeit:[LIGHT_GRAY] (Sek) +rules.wavespacing = Wellen Abstand:[LIGHT_GRAY] (Sek) +rules.buildcostmultiplier = Bau-Kosten Multiplikator +rules.buildspeedmultiplier = Bau-Schnelligkeit Multiplikator +rules.waitForWaveToEnd = Warten bis Welle endet +rules.dropzoneradius = Drop Zonen Radius:[LIGHT_GRAY] (Kacheln) +rules.respawns = Max Respawns pro Welle +rules.limitedRespawns = Respawn-Limit +rules.title.waves = Wellen rules.title.respawns = Respawns -rules.title.resourcesbuilding = Resources & Building -rules.title.player = Players -rules.title.enemy = Enemies -rules.title.unit = Units +rules.title.resourcesbuilding = Ressourcen & Gebäude +rules.title.player = Spieler +rules.title.enemy = Gegner +rules.title.unit = Einheiten content.item.name = Materialien content.liquid.name = Flüssigkeiten content.unit.name = Einheiten @@ -581,21 +581,21 @@ content.mech.name = Mechs item.copper.name = Kupfer item.lead.name = Blei item.coal.name = Kohle -item.graphite.name = Graphite +item.graphite.name = Graphit item.titanium.name = Titan item.thorium.name = Uran item.silicon.name = Silizium item.plastanium.name = Plastanium item.phase-fabric.name = Phasengewebe item.surge-alloy.name = Spannungsstoß-Legierung -item.spore-pod.name = Spore Pod +item.spore-pod.name = Sporen-Pod item.sand.name = Sand item.blast-compound.name = Explosive Mischung item.pyratite.name = Pyratit item.metaglass.name = Metaglass -item.scrap.name = Scrap +item.scrap.name = Schrott liquid.water.name = Wasser -liquid.slag.name = Slag +liquid.slag.name = Asche liquid.oil.name = Öl liquid.cryofluid.name = Kryoflüssigkeit mech.alpha-mech.name = Alpha @@ -625,86 +625,86 @@ item.radioactivity = [LIGHT_GRAY]Radioaktivität: {0} unit.health = [LIGHT_GRAY]Lebenskraft: {0} unit.speed = [LIGHT_GRAY]Geschwindigkeit: {0} mech.weapon = [LIGHT_GRAY]Waffe: {0} -mech.health = [LIGHT_GRAY]Health: {0} +mech.health = [LIGHT_GRAY]Lebenspunkte: {0} mech.itemcapacity = [LIGHT_GRAY]Materialkapazität: {0} mech.minespeed = [LIGHT_GRAY]Erzabbaugeschwindigkeit: {0} mech.minepower = [LIGHT_GRAY]Erzabbaukraft: {0} mech.ability = [LIGHT_GRAY]Fähigkeit: {0} -mech.buildspeed = [LIGHT_GRAY]Building Speed: {0}% +mech.buildspeed = [LIGHT_GRAY]Baugeschwindigkeit: {0}% liquid.heatcapacity = [LIGHT_GRAY]Wärmekapazität: {0} liquid.viscosity = [LIGHT_GRAY]Viskosität: {0} liquid.temperature = [LIGHT_GRAY]Temperatur: {0} -block.sand-boulder.name = Sand Boulder +block.sand-boulder.name = Sand Brocken block.grass.name = Gras block.salt.name = Salz -block.saltrocks.name = Salt Rocks -block.pebbles.name = Pebbles -block.tendrils.name = Tendrils +block.saltrocks.name = Salz Gestein +block.pebbles.name = Geröll +block.tendrils.name = Ranken block.sandrocks.name = Sandstein -block.spore-pine.name = Spore Pine -block.sporerocks.name = Spore Rocks +block.spore-pine.name = Sporen Kiefer +block.sporerocks.name = Sporen Gestein block.rock.name = Gestein -block.snowrock.name = Snow Rock -block.snow-pine.name = Snow Pine -block.shale.name = Shale -block.shale-boulder.name = Shale Boulder +block.snowrock.name = Schnee Gestein +block.snow-pine.name = Schnee Kiefer +block.shale.name = Schiefer +block.shale-boulder.name = Schiefer Geröll block.moss.name = Moos -block.shrubs.name = Shrubs -block.spore-moss.name = Spore Moss -block.shalerocks.name = Shale Rocks -block.scrap-wall.name = Scrap Wall -block.scrap-wall-large.name = Large Scrap Wall -block.scrap-wall-huge.name = Huge Scrap Wall -block.scrap-wall-gigantic.name = Gigantic Scrap Wall -block.thruster.name = Thruster -block.kiln.name = Kiln -block.graphite-press.name = Graphite Press -block.multi-press.name = Multi-Press -block.constructing = {0}\n[LIGHT_GRAY](Constructing) +block.shrubs.name = Gestrüpp +block.spore-moss.name = Sporen Moos +block.shalerocks.name = Schiefer Gestein +block.scrap-wall.name = Schrott Mauer +block.scrap-wall-large.name = Große Schrott Mauer +block.scrap-wall-huge.name = Riesige Schrott Mauer +block.scrap-wall-gigantic.name = Gigantische Schrott Mauer +block.thruster.name = Schubdüse +block.kiln.name = Brennofen +block.graphite-press.name = Graphit-Presse +block.multi-press.name = Multipresse +block.constructing = {0}\n[LIGHT_GRAY](Bauen) block.spawn.name = Gegnerischer Startpunkt -block.core-shard.name = Core: Shard -block.core-foundation.name = Core: Foundation -block.core-nucleus.name = Core: Nucleus +block.core-shard.name = Kern: Scherbe +block.core-foundation.name = Kern: Fundament +block.core-nucleus.name = Kern: Nukleus block.deepwater.name = Tiefes Wasser block.water.name = Wasser -block.tainted-water.name = Tainted Water -block.darksand-tainted-water.name = Dark Sand Tainted Water +block.tainted-water.name = Unreines Wasser +block.darksand-tainted-water.name = Dunkler Sand in unreinem Wasser block.tar.name = Teer block.stone.name = Stein block.sand.name = Sand block.darksand.name = Dunkler Sand block.ice.name = Eis block.snow.name = Schnee -block.craters.name = Craters +block.craters.name = Krater block.sand-water.name = Sandiges Wasser -block.darksand-water.name = Dark Sand Water -block.char.name = Char -block.holostone.name = Holo stone -block.ice-snow.name = Ice Snow +block.darksand-water.name = Dunkles Sandiges Wasser +block.char.name = Holzkohle +block.holostone.name = Holo Stein +block.ice-snow.name = Eisschnee block.rocks.name = Felsen -block.icerocks.name = Ice rocks -block.snowrocks.name = Snow Rocks -block.dunerocks.name = Dune Rocks -block.pine.name = Pine -block.white-tree-dead.name = White Tree Dead +block.icerocks.name = Eis Felsen +block.snowrocks.name = Schnee Felsen +block.dunerocks.name = Dünen Felsen +block.pine.name = Kiefer +block.white-tree-dead.name = Weißer Toter Baum block.white-tree.name = Weißer Baum -block.spore-cluster.name = Spore Cluster +block.spore-cluster.name = Sporen Cluster block.metal-floor.name = Metallboden block.metal-floor-2.name = Metallboden 2 block.metal-floor-3.name = Metallboden 3 block.metal-floor-5.name = Metallboden 5 block.metal-floor-damaged.name = Metallboden Beschädigt -block.dark-panel-1.name = Dark Panel 1 -block.dark-panel-2.name = Dark Panel 2 -block.dark-panel-3.name = Dark Panel 3 -block.dark-panel-4.name = Dark Panel 4 -block.dark-panel-5.name = Dark Panel 5 -block.dark-panel-6.name = Dark Panel 6 -block.dark-metal.name = Dark Metal -block.ignarock.name = Igna Rock -block.hotrock.name = Hot Rock -block.magmarock.name = Magma Rock -block.cliffs.name = Cliffs +block.dark-panel-1.name = Dunkles Panel 1 +block.dark-panel-2.name = Dunkles Panel 2 +block.dark-panel-3.name = Dunkles Panel 3 +block.dark-panel-4.name = Dunkles Panel 4 +block.dark-panel-5.name = Dunkles Panel 5 +block.dark-panel-6.name = Dunkles Panel 6 +block.dark-metal.name = Dunkles Metall +block.ignarock.name = Igna Felsen +block.hotrock.name = Heißer Felsen +block.magmarock.name = Magma Felsen +block.cliffs.name = Klippen block.copper-wall.name = Kupfermauer block.copper-wall-large.name = Große Kupfermauer block.titanium-wall.name = Titanmauer @@ -716,10 +716,10 @@ block.thorium-wall-large.name = Große Thorium-Mauer block.door.name = Tür block.door-large.name = Große Tür block.duo.name = Duo -block.scorch.name = Scorch +block.scorch.name = Flammenwerfer block.scatter.name = Scatter -block.hail.name = Hail -block.lancer.name = Lancer +block.hail.name = Streuer +block.lancer.name = Lanzer block.conveyor.name = Förderband block.titanium-conveyor.name = Titan-Förderband block.junction.name = Kreuzung @@ -733,18 +733,18 @@ block.pulverizer.name = Pulverisierer block.cryofluidmixer.name = Kryoflüssigkeitsmixer block.melter.name = Schmelzer block.incinerator.name = Verbrennungsanlage -block.spore-press.name = Spore Press -block.separator.name = Separierer -block.coal-centrifuge.name = Coal Centrifuge +block.spore-press.name = Sporen Presse +block.separator.name = Separator +block.coal-centrifuge.name = Kohlen Zentrifuge block.power-node.name = Stromknoten block.power-node-large.name = Großer Stromknoten -block.surge-tower.name = Surge Tower +block.surge-tower.name = Schwall Turm block.battery.name = Batterie block.battery-large.name = Große Batterie block.combustion-generator.name = Verbrennungsgenerator block.turbine-generator.name = Turbinengenerator -block.differential-generator.name = Differential Generator -block.impact-reactor.name = Impact Reactor +block.differential-generator.name = Differentialgenerator +block.impact-reactor.name = Schlaggenerator block.mechanical-drill.name = Mechanischer Bohrer block.pneumatic-drill.name = Pneumatischer Bohrer block.laser-drill.name = Laser-Bohrer @@ -752,9 +752,9 @@ block.water-extractor.name = Wasser-Extraktor block.cultivator.name = Kultivierer block.dart-mech-pad.name = Dart Mech Pad block.delta-mech-pad.name = Delta Mech Pad -block.javelin-ship-pad.name = Javelin Ship Pad -block.trident-ship-pad.name = Trident Ship Pad -block.glaive-ship-pad.name = Glaive Ship Pad +block.javelin-ship-pad.name = Javelin Luftschiff Pad +block.trident-ship-pad.name = Trident Luftschiff Pad +block.glaive-ship-pad.name = Glaive Luftschiff Pad block.omega-mech-pad.name = Omega Mech Pad block.tau-mech-pad.name = Tau Mech Pad block.conduit.name = Leitungsrohr @@ -777,14 +777,14 @@ block.pyratite-mixer.name = Pyratit-Mixer block.blast-mixer.name = Sprengmixer block.solar-panel.name = Solar Panel block.solar-panel-large.name = Großes Solar Panel -block.oil-extractor.name = Oil Extraktor +block.oil-extractor.name = Öl Extraktor block.draug-factory.name = Draug Miner Drone Factory block.spirit-factory.name = Spirit-Drohnenfabrik block.phantom-factory.name = Phantom-Drohnenfabrik block.wraith-factory.name = Wraith Fighter-Fabrik block.ghoul-factory.name = Ghoul Bomber-Fabrik block.dagger-factory.name = Dagger Mech-Fabrik -block.crawler-factory.name = Crawler Mech Factory +block.crawler-factory.name = Crawler Mech-Fabrik block.titan-factory.name = Titan Mech-Fabrik block.fortress-factory.name = Fortress Mech-Fabrik block.revenant-factory.name = Revenant Fighter-Fabrik @@ -801,23 +801,23 @@ block.mass-driver.name = Massenbeschleuniger block.blast-drill.name = Sprengbohrer block.thermal-pump.name = Thermische Pumpe block.thermal-generator.name = Thermischer Generator -block.alloy-smelter.name = Legierungsschmeltzer -block.mender.name = Mender -block.mend-projector.name = Reparaturprojektor +block.alloy-smelter.name = Legierungsschmelze +block.mender.name = Heiler +block.mend-projector.name = Heilprojektor block.surge-wall.name = Spannungsstoß-Mauer block.surge-wall-large.name = Große Spannungsstoß-Mauer -block.cyclone.name = Cyclone -block.fuse.name = Fuse +block.cyclone.name = Zyklon +block.fuse.name = Zünder block.shock-mine.name = Schock-Mine block.overdrive-projector.name = Beschleunigungs-Projektor block.force-projector.name = Kraftfeld-Projektor -block.arc.name = Arc +block.arc.name = Arcus block.rtg-generator.name = RTG Generator -block.spectre.name = Spectre +block.spectre.name = Phantom block.meltdown.name = Meltdown block.container.name = Container block.launch-pad.name = Launch Pad -block.launch-pad-large.name = Large Launch Pad +block.launch-pad-large.name = Großes Launch Pad team.blue.name = Blau team.red.name = Rot team.orange.name = Orange @@ -844,102 +844,102 @@ tutorial.drill = Manuelles Abbauen von Ressourcen ist ineffizient.\n[accent]Bohr tutorial.conveyor = [accent]Transportbänder[] werden dazu benutzt Materialien zum Kern zu transportieren.\n Erstelle eine Reihe von Transportbändern zum Kern. tutorial.morecopper = Du brauchst [accent]mehr Kupfer[]!\n\nEntweder du baust es manuell ab, oder du platzierst weitere Bohrer. tutorial.turret = Verteidigungsgebäude müssen gebaut werden um[LIGHT_GRAY] Gegner[] abzuwehren.\nBaue einen Duo Geschützturm in die Nähe deiner Basis. -tutorial.drillturret = Duo Geschütztürme benötigen[accent] Kupfermunition []um zu schießen.\nPlatziere einen Bohrer neben das Geschütz um es mit Kupfer zu versorgen. +tutorial.drillturret = Duo Geschütztürme benötigen[accent] Kupfermunition, []um schießen zu können.\nPlatziere neben das Geschütz einen Bohrer, um ihn mit Kupfer zu versorgen. tutorial.waves = Der [LIGHT_GRAY] Gegner[] greift an.\n\nVerteidige deinen Kern 2 Wellen lang. Bau mehr Türme. tutorial.lead = Mehr Erz ist verfügbar. Finde Blei und bau es ab.\n\n Klicke auf deine Einheit und ziehe die Maus auf den Kern um Ressourcen zu übertragen. -tutorial.smelter = Kupfer und Blei sind schwache Metalle.\n Super [accent]dichte Legierung [] kann in einem Schmeltzer erzeugt werden.\n\n Bau einen. -tutorial.densealloy = Der Schmeltzer wird nun Legierung produzieren.\n Produziere einige.\n Verbessere die Produktion sofern notwendig. -tutorial.siliconsmelter = Der Kern wird nun [accent]spirit drohnen[] erstellen. Diese Bauen Rohstoffe und reparieren Blöcke.\n\nFabriken für andere Einheiten benötigen [accent]Silizium[].\n Baue ein Silizium Schmeltzer. +tutorial.smelter = Kupfer und Blei sind schwache Metalle.\n Super [accent]dichte Legierung [] kann in einem Schmelzer erzeugt werden.\n\n Bau einen. +tutorial.densealloy = Der Schmelzer wird nun Legierung produzieren.\n Produziere einige.\n Verbessere die Produktion sofern notwendig. +tutorial.siliconsmelter = Der Kern wird nun [accent]Spirit Drohnen[] erstellen. Diese Bauen Rohstoffe und reparieren Blöcke.\n\nFabriken für andere Einheiten benötigen [accent]Silizium[].\n Baue einen Silizium Schmelzer. tutorial.silicondrill = Silizium benötigt [accent]Kohle[] und [accent]Sand[].\n Fange damit an die Bohrer zu platzieren. -tutorial.generator = Diese Technologie benötigt power.\n Erstelle einen Verbrennungs-Generator dafür. -tutorial.generatordrill = Verbrennungs Generatoren benötigen Kraftstoff.\nBenutze Kohle aus einem Bohrer als Kraftstoff. -tutorial.node = Power muss transportiert werden.\nErstelle einen [accent]Stromknoten[] nahe deinem Verbrennungs Generator um seine Power zu transportieren. -tutorial.nodelink = Power kann über verbundene Power Blocks, Generatoren oder Stromknoten transferierd werden.\n\n Verbinde die Power in dem du auf den Knoten klickst und dann den Generator und den Silizium Schmeltzer auswählst. +tutorial.generator = Diese Technologie benötigt Strom.\n Erstelle einen Verbrennungs-Generator dafür. +tutorial.generatordrill = Verbrennungsgeneratoren benötigen Kraftstoff.\nBenutze Kohle aus einem Bohrer als Kraftstoff. +tutorial.node = Strom muss transportiert werden.\nErstelle einen [accent]Stromknoten[] nahe deinem Verbrennungsgenerator, um sein Strom zu transportieren. +tutorial.nodelink = Strom kann über verbundene Strom Blöcke, Generatoren oder Stromknoten transferiert werden.\n\n Verbinde den Strom, indem du auf den Knoten klickst und dann den Generator und den Silizium Schmelzer auswählst. tutorial.silicon = Silizium wird produziert. Produziere einiges.\n\n Verbesserungen am Produktionssystem werden empfohlen. tutorial.daggerfactory = Konstruiere eine Dagger Mech Fabrik.\n\n Diese wird verwendet um Angreifende Mechs zu erstellen. -tutorial.router = Fabriken benötigen Ressourcen um zu funktionieren.\n Platziere ein Router um Materialien auf Transportbändern aufzuteilen. -tutorial.dagger = Verbinde die Fabrik mit einem Stromknoten. Wenn alle Voraussetzungen gegeben sind, beginnt die Fabrik Mechs zu konstruieren.\n\n Platziere mehr Bohrer und Transportbänder um die Versorgung der Fabrik zu sichern. +tutorial.router = Fabriken benötigen Ressourcen um zu funktionieren.\n Platziere ein Verteiler, um Materialien auf Transportbändern aufzuteilen. +tutorial.dagger = Verbinde die Fabrik mit einem Stromknoten. Wenn alle Voraussetzungen gegeben sind, beginnt die Fabrik Mechs zu konstruieren.\n\n Platziere mehr Bohrer und Transportbänder, um die Versorgung der Fabrik zu gewährleisten. tutorial.battle = Der[LIGHT_GRAY] Gegner[] hat seinen Kern offenbart.\nZerstöre ihn mit deiner Einheit und den Dagger Mechs. item.copper.description = Ein nützliches Material. Wird in allen Arten von Blöcken verwendet. -item.lead.description = Ein grundliegendes Material. Häufig in Elektronik und Flüssigkeits-Transport-Blöcken verwendet. +item.lead.description = Ein grundlegendes Material. Häufig in Elektronik und Flüssigkeits-Transport-Blöcken verwendet. item.metaglass.description = Eine super harte Glasmischung. Wird zur Verteilung und Lagerung von Flüssigkeiten benutzt. -item.graphite.description = Mineralized carbon, used for ammunition and electrical insulation. +item.graphite.description = Mineralisierter Kohlenstoff Wird für Munition und elektrische Isolierung verwendet. item.sand.description = Ein gängiges Material, welches häufig in geschmolzener Form, flüssig oder als Legierung verwendet wird. item.coal.description = Ein sehr häufiger vorkommender Kraftstoff. -item.titanium.description = Ein seltenes, sehr leichtes Metall. Häufig in Flüssigkeits-Transport-Blöcken, Abbauanlagen und Flugzeugen verwendet. +item.titanium.description = Ein seltenes, sehr leichtes Metall. Häufig in Flüssigkeits-Transport-Blöcken, Abbauanlagen und Luftschiffen verwendet. item.thorium.description = Ein dichtes radioaktives Metall, welches als strukturelle Unterstützung und nuklearer Kraftstoff verwendet wird. -item.scrap.description = Überreste alter Gebäude und Einheiten. Enthalten Spuren verschiedenster Metalle. +item.scrap.description = Überreste alter Gebäude und Einheiten. Enthalten Spuren verschiedener Metalle. item.silicon.description = Ein sehr nützlicher Halbleiter. Findet Anwendung in Solaranlagen und komplexer Elektronik. -item.plastanium.description = Ein leichtes dehnbares Material welches in Flugzeugen und Splittermunition verwendet wird. +item.plastanium.description = Ein leichtes dehnbares Material, welches in Flugzeugen und Splittermunition verwendet wird. item.phase-fabric.description = Eine nahezu gewichtslose Substanz, die in fortgeschrittener Elektronik und in selbstreparierender Technologie verwendet wird. item.surge-alloy.description = Eine fortgeschrittene Legierung mit einzigartigen elektrischen Eigenschaften. -item.spore-pod.description = Used for conversion into oil, explosives and fuel. -item.blast-compound.description = Eine flüchtige Mischung, die in Bomben und Sprengstoffen Verwendung findet. Es besteht die Möglichkeit, es als Treibstoff zu verwenden, aber dies ist nicht empfehlenswert. -item.pyratite.description = Eine extrem leicht entflammbare Substanz. Findet Verwendeung in Brandwaffen. -liquid.water.description = Wird überlicherweise zum Kühlen von Maschinen und zur Müllverarbeitung verwendet. -liquid.slag.description = Various different types of molten metal mixed together. Can be separated into its constituent minerals, or sprayed at enemy units as a weapon. +item.spore-pod.description = Wird zur Umwandlung in Öl, Sprengstoff und Kraftstoff verwendet. +item.blast-compound.description = Eine flüchtige Mischung, die in Bomben und Sprengstoffen Verwendung findet. Es besteht die Möglichkeit, es als Treibstoff zu verwenden. Dies ist aber nicht empfehlenswert. +item.pyratite.description = Eine extrem leicht entflammbare Substanz. Findet Verwendung in Brandwaffen. +liquid.water.description = Wird üblicherweise zum Kühlen von Maschinen und zur Müllverarbeitung verwendet. +liquid.slag.description = Ein Gemisch aus verschiedenen Arten von Metall, welche miteinander vermischt wurden. Kann in seine Bestandteile getrennt oder als Waffe auf feindliche Einheiten gesprüht werden. liquid.oil.description = Kann verbrannt, zum explodieren gebracht, oder als Kühlung verwendet werden. liquid.cryofluid.description = Die effizienteste Flüssigkeit, um Dinge herunter zu kühlen. -mech.alpha-mech.description = Der Standard-Mech. Ist angemessen schnell und hat ordentlich Schaden. Kann für erweiterte offensive Fähigkeiten bis zu 3 Drohnen erzeugen. -mech.delta-mech.description = Ein schneller, leicht gepanzerter Mech, der für Überfälle gemacht wurde. Verursacht wenig Schaden gegen Gebäude aber tötet Gruppen von Gegnern durch seine Lichtbogen-Waffen. -mech.tau-mech.description = Der Support Mech. Kann Blöcke durch Schüsse heilen. Kann Feuer löschen und verbündete in seinem Aktionsradius heilen. +mech.alpha-mech.description = Der Standard-Mech. Ist angemessen schnell und macht ordentlich Schaden. Kann für erweiterte offensive Fähigkeiten bis zu 3 Drohnen erzeugen. +mech.delta-mech.description = Ein schneller, leicht gepanzerter Mech, der für Überfälle gemacht wurde. Verursacht wenig Schaden gegen Gebäude, aber tötet Gruppen von Gegnern durch seine Lichtbogen-Waffen. +mech.tau-mech.description = Ein Unterstützungs-Mech. Kann Blöcke durch Schüsse heilen. Kann Feuer löschen und verbündete in seinem Aktionsradius heilen. mech.omega-mech.description = Ein klobiger und gut gepanzerter Mech, der für den Angriff an der Front entwickelt wurde. Seine Rüstungsfähigkeit ermöglicht es ihm, 90% des Schadens abzuwehren. mech.dart-ship.description = Das Standard-Schiff. Einigermaßen schnell und leicht. Hat nur wenig Offensivkraft und geringe Erzabbaugeschwindigkeit. -mech.javelin-ship.description = Ein Schiff für Überfälle. Anfänglich träge, kann es auf hohe Geschwindigkeiten beschleunigen um an gegnerischen Aussenposten vorbei zu fliegen und dabei mit seinen Blitzwaffen und Raketen große Mengen an Schaden verursachen. -mech.trident-ship.description = Ein schwerer Bomber, solide gepanzert. +mech.javelin-ship.description = Ein Schiff für Überfälle. Anfänglich träge, kann es auf hohe Geschwindigkeiten beschleunigen, um an gegnerischen Außenposten vorbei zu fliegen und dabei mit seinen Blitzwaffen und Raketen große Mengen an Schaden verursachen. +mech.trident-ship.description = Ein schwerer Bomber, der solide gepanzert ist. mech.glaive-ship.description = Ein großes, gut gepanzertes Gunship. Ausgerüstet mit einer Brandwaffe. Gute Beschleunigung und maximale Geschwindigkeit. -unit.draug.description = A primitive mining drone. Cheap to produce. Expendable. Automatically mines copper and lead in the vicinity. Delivers mined resources to the closest core. -unit.spirit.description = Die anfängliche Drohne. Sie wird gewöhnlich in der Basis Erz ab, sammelt Materialien und repariert Blöcke. -unit.phantom.description = Eine fortgeschrittene Drohne. Baut automatisch Erz ab, sammelt Materialien und repariert Blöcke. Deutlich effizienter als die Drohne. +unit.draug.description = Eine primitive Bergbaudrohne. Günstig herzustellen. Entbehrlich. Baut automatisch Kupfer und Blei in der Nähe ab. Bringt abgebaute Ressourcen zum nächstgelegenen Kern. +unit.spirit.description = Die anfängliche Drohne. Sie baut gewöhnlich in der Basis Erz ab, sammelt Materialien und repariert Blöcke. +unit.phantom.description = Eine fortgeschrittene Drohne. Baut automatisch Erz ab, sammelt Materialien und repariert Blöcke. Deutlich effizienter als die Standard-Drohne. unit.dagger.description = Eine Standard-Bodeneinheit. Nützlich in Schwärmen. -unit.crawler.description = A ground unit consisting of a stripped-down frame with high explosives strapped on top. Not particular durable. Explodes on contact with enemies. +unit.crawler.description = Eine Bodeneinheit, die aus einem abgespeckten Rahmen mit hochexplosiven Sprengstoffen besteht. Nicht besonders haltbar. Explodiert bei Kontakt mit Gegnern. unit.titan.description = Eine fortgeschrittene gepanzerte Bodeneinheit. Greift sowohl Boden- als auch Luftziele an. unit.fortress.description = Eine schwere Artillerie-Bodeneinheit. -unit.eruptor.description = A heavy mech designed to take down structures. Fires a stream of slag at enemy fortifications, melting them and setting volatiles on fire. +unit.eruptor.description = Ein schwerer Mech, der Strukturen abbaut. Feuert einen Schlackenstrom auf feindliche Befestigungen ab, welcher flüchtige Stoffe in Brand steckt. unit.chaos-array.description = unit.eradicator.description = unit.wraith.description = Eine schneller Abfangjäger. unit.ghoul.description = Ein schwerer Flächenbomber. -unit.revenant.description = A heavy, hovering missile array. +unit.revenant.description = Eine schwere, schwebende Raketengruppe. unit.lich.description = unit.reaper.description = -block.graphite-press.description = Compresses chunks of coal into pure sheets of graphite. -block.multi-press.description = An upgraded version of the graphite press. Employs water and power to process coal quickly and efficiently. +block.graphite-press.description = Komprimiert Kohlestücke zu reinen Graphitplatten. +block.multi-press.description = Eine aktualisierte Version der Graphitpresse. Setzt Wasser und Strom ein, um Kohle schnell und effizient zu verarbeiten. block.silicon-smelter.description = Reduziert Sand mit hochreinem Kohlenstoff, um Silizium zu produzieren. -block.kiln.description = Schmelzt Sand und Blei zu metaglass. Erfordert kleine Mengen Energie. +block.kiln.description = Schmelzt Sand und Blei zu Metaglass. Erfordert kleine Mengen Energie. block.plastanium-compressor.description = Produziert Plastanium aus Öl und Titan. block.phase-weaver.description = Produziert Phasengewebe aus radioaktivem Thorium und großen Mengen an Sand. block.alloy-smelter.description = Verarbeitet Titan, Blei, Silizium und Kupfer zu einer Stromstoßlegierung. block.cryofluidmixer.description = Verarbeitet Wasser mit Titan zu einer Kryoflüssigkeit, die viel effizienter kühlt. -block.blast-mixer.description = Verwendet Öl, um Pyratit in eine weniger enzündliche aber explosivee Mischung umzuwandeln. +block.blast-mixer.description = Verwendet Öl, um Pyratit in eine weniger enzündliche aber explosive Mischung umzuwandeln. block.pyratite-mixer.description = Vermischt Kohle, Blei und Sand zu hochentzündlichem Pyratit. block.melter.description = Erhitzt Stein auf extrem hohe Temperaturen, um Lava zu erhalten. block.separator.description = Setzt Stein Wasserdruck aus, um verschiedene Mineralien im Stein freizulegen. -block.spore-press.description = Compresses spore pods into oil. +block.spore-press.description = Komprimiert Sporenhülsen zu Öl. block.pulverizer.description = Zertrümmert Stein zu Sand. Nützlich, wenn kein natürlicher Sand verfügbar ist. -block.coal-centrifuge.description = Solidifes oil into chunks of coal. +block.coal-centrifuge.description = Verfestigt Öl zu Kohlenstücken. block.incinerator.description = Vernichtet beliebige überschüssige Materialien oder Flüssigkeiten. -block.power-void.description = Verschlingt den kompletten übrigen Strom. Nur im Sandkasten verfügbar. -block.power-source.description = Erzeugt unendlich viel Strom. Nur im Sandkasten verfügbar. -block.item-source.description = Produziert unendlich items. Nur im Sandkasten verfügbar. -block.item-void.description = Zerstört Materialien, die hereingegeben werden, ohne Strom zu verbrauchen. Nur im Sandkasten verfügbar. -block.liquid-source.description = Produziert unendlich Flüssigkeiten. Nur im Sandkasten verfügbar. +block.power-void.description = Verschlingt den kompletten übrigen Strom. Nur im Sandkasten-Modus verfügbar. +block.power-source.description = Erzeugt unendlich viel Strom. Nur im Sandkasten-Modus verfügbar. +block.item-source.description = Produziert unendlich items. Nur im Sandkasten-Modus verfügbar. +block.item-void.description = Zerstört Materialien, die hereingegeben werden, ohne Strom zu verbrauchen. Nur im Sandkasten-Modus verfügbar. +block.liquid-source.description = Produziert unendlich Flüssigkeiten. Nur im Sandkasten-Modus verfügbar. block.copper-wall.description = Ein günstiger Verteidigungsblock.\nNützlich, um die Basis und Türme in den ersten Wellen zu beschützen. block.copper-wall-large.description = Ein günstiger Verteidigungsblock.\nNützlich, um die Basis und Türme in den ersten Wellen zu beschützen.\nBenötigt mehrere Kacheln. -block.titanium-wall.description = A moderately strong defensive block.\nProvides moderate protection from enemies. -block.titanium-wall-large.description = A moderately strong defensive block.\nProvides moderate protection from enemies.\nSpans multiple tiles. -block.thorium-wall.description = Ein starker Verteidigungsblock.\nGuter Schutz vor Feinden. -block.thorium-wall-large.description = Ein starker Verteidigungsblock.\nGuter Schutz vor Feinden.\nBenötigt mehrere Kacheln. -block.phase-wall.description = Nicht so stark wie eine Thorium-Mauer, aber reflektiert Schüsse bis zu einer gewissen Stärke. -block.phase-wall-large.description = Nicht so stark wie eine Thorium-Mauer, aber reflektiert Schüsse bis zu einer gewissen Stärke.\nBenötigt mehrere Kacheln. -block.surge-wall.description = Der stärkste Verteidigungsblock.\nHat eine kleine Chance, bei einem Schuss einen Lichtbogen in Richtung angreifer auszulösen. -block.surge-wall-large.description = Der stärkste Verteidigungsblock.\nHat eine kleine Chance, bei einem Schuss einen Lichtbogen in Richtung angreifer auszulösen.\nBenötigt mehrere Kacheln. -block.door.description = Eine kleine Tür, die durch darauf tippen geöffnet und geschlossen werden kann.\nGegner können durch geöffnete Türen schießen und laufen. -block.door-large.description = Eine kleine Tür, die durch darauf tippen geöffnet und geschlossen werden kann.\nGegner können durch geöffnete Türen schießen und laufen.\nBenötigt mehrere Kacheln. -block.mender.description = Periodically repairs blocks in its vicinity. Keeps defenses repaired in-between waves.\nOptionally uses silicon to boost range and efficiency. +block.titanium-wall.description = Ein mittel starker Verteidigungsblock.\nBietet mäßigen Schutz vor Feinden. +block.titanium-wall-large.description = Ein mittel starker Verteidigungsblock.\nBeitet mäßigen Schutz vor Feinden.\nBenötigt mehrere Kacheln. +block.thorium-wall.description = Ein starker Verteidigungsblock.\nBietet guten Schutz vor Feinden. +block.thorium-wall-large.description = Ein starker Verteidigungsblock.\nBietet Guten Schutz vor Feinden.\nBenötigt mehrere Kacheln. +block.phase-wall.description = Nicht so stark, wie eine Thorium-Mauer, aber reflektiert Schüsse bis zu einer gewissen Stärke. +block.phase-wall-large.description = Nicht so stark, wie eine Thorium-Mauer, aber reflektiert Schüsse bis zu einer gewissen Stärke.\nBenötigt mehrere Kacheln. +block.surge-wall.description = Der stärkste Verteidigungsblock.\nHat eine kleine Chance, bei einem Schuss einen Lichtbogen in Richtung Angreifer auszulösen. +block.surge-wall-large.description = Der stärkste Verteidigungsblock.\nHat eine kleine Chance, bei einem Schuss einen Lichtbogen in Richtung Angreifer auszulösen.\nBenötigt mehrere Kacheln. +block.door.description = Eine kleine Tür, die durch Tippen geöffnet und geschlossen werden kann.\nGegner können durch geöffnete Türen schießen und laufen. +block.door-large.description = Eine kleine Tür, die durch Tippen geöffnet und geschlossen werden kann.\nGegner können durch geöffnete Türen schießen und laufen.\nBenötigt mehrere Kacheln. +block.mender.description = Repariert regelmäßig Blöcke in der Nähe. Hält die Abwehrkräfte zwischen den Wellen instand.\nVerwendet optional Silizium, um Reichweite und Effizienz zu steigern. block.mend-projector.description = Heilt zyklisch Blöcke in seiner Umgebung. -block.overdrive-projector.description = Erhöht die Geschwindigkeit von nahegelegenen Blöcken wie Bohrer und Förderbänder. -block.force-projector.description = Erzeugt ein sechseckiges Kraftfeld um sich selbst, durch das Blöcke und Einheiten vor Schaden beschützt werden. +block.overdrive-projector.description = Erhöht die Geschwindigkeit von nahegelegenen Blöcken, wie Bohrer und Förderbänder. +block.force-projector.description = Erzeugt ein sechseckiges Kraftfeld um sich herum, das Blöcke und Einheiten vor Schaden schützt. block.shock-mine.description = Beschädigt Gegner, die auf die Mine laufen. Für Gegener schwer zu sehen. block.conveyor.description = Basis-Transportblock. Bewegt Materialien vorwärts und lädt sie automatisch in Geschütztürme oder Verarbeitungsanlagen. Rotierbar. block.titanium-conveyor.description = Verbesserter Transportblock. Bewegt Materialien schneller als Standard-Förderbänder. @@ -948,10 +948,10 @@ block.bridge-conveyor.description = Verbesserter Transportblock. Erlaubt es, Mat block.phase-conveyor.description = Verbesserter Transportblock. Verwendet Strom, um Materialien zu einem verbundenen Phasen-Förderband über mehrere Kacheln zu teleportieren. block.sorter.description = Sortiert Materialien. Wenn ein Gegenstand der Auswahl entspricht, darf er vorbei. Andernfalls wird er links oder rechts ausgegeben. block.router.description = Akzeptiert Materialien aus einer Richtung und leitet sie gleichmäßig in bis zu drei andere Richtungen weiter. Nützlich, wenn die Materialien aus einer Richtung an mehrere Empfänger verteilt werden sollen. -block.distributor.description = Ein weiterentwickelter Router, der Materialien in bis zu sieben Richtungen gleichmäßig verteilt. +block.distributor.description = Ein weiterentwickelter Verteiler, der Materialien in bis zu sieben Richtungen gleichmäßig verteilt. block.overflow-gate.description = Ein Verteiler, der nur Materialien nach links oder rechts ausgibt, falls der Weg gerade aus blockiert ist. block.mass-driver.description = Ultimativer Transportblock. Sammelt mehrere Materialien und schießt sie zu einem verbundenen Massenbeschleuniger über eine große Reichweite. -block.mechanical-pump.description = Eine günstige, langsame Punkte, die keine Strom benötigt. +block.mechanical-pump.description = Eine günstige, langsame Pumpe, die keine Strom benötigt. block.rotary-pump.description = Eine fortgeschrittene Pumpe, die mithilfe von Strom doppelt so schnell pumpt. block.thermal-pump.description = Die ultimative Pumpe, dreimal so schnell wie eine mechanische Pumpe und die einzige Pumpe, die Lava fördern kann. block.conduit.description = Standard Flüssigkeits-Transportblock. Funktioniert wie ein Förderband, nur für Flüssigkeiten. Wird am Besten mit Extraktoren, Pumpen oder anderen Kanälen benutzt. @@ -961,20 +961,20 @@ block.liquid-tank.description = Speichert eine große Menge an Flüssigkeiten. V block.liquid-junction.description = Fungiert als Brücke über zwei kreuzende Kanäle. Nützlich in Situationen, in denen sich zwei Kanäle mit verschiedenen Flüssigkeiten kreuzen. block.bridge-conduit.description = Verbesserter Flüssigkeits-Transportblock. Erlaubt es, Flüssigkeiten über bis zu 3 Kacheln beliebigen Terrains oder Inhalts zu transportieren. block.phase-conduit.description = Verbesserter Flüssigkeits-Transportblock. Verwendet Strom, um Flüssigkeiten zu einem verbundenen Phasenkanal zu teleportieren. -block.power-node.description = Überträgt Strom zu verbundenen Knoten. Bis zu vier Stromquellen, -verbraucher oder -knoten können verbunden werden. Der Knoten erhält Strom von benachbarten Knoten und gibt Strom benachbarte Blöcke weiter. +block.power-node.description = Überträgt Strom zu verbundenen Knoten. Bis zu vier Stromquellen, -verbraucher oder -knoten können verbunden werden. Der Knoten erhält Strom von benachbarten Knoten und gibt Strom an benachbarte Blöcke weiter. block.power-node-large.description = Hat einen größeren Radius als der normale Stromknoten und verbindet bis zu sechs Stromquellen, -verbraucher oder -knoten. -block.surge-tower.description = An extremely long-range power node with fewer available connections. +block.surge-tower.description = Ein extrem weitreichender Netzknoten mit weniger verfügbaren Verbindungen. block.battery.description = Speichert Strom, solange ein Überschuss besteht, und gibt ihn bei Knappheit ab, solange Kapazität vorhanden ist. block.battery-large.description = Speichert sehr viel mehr Strom als eine normale Batterie. -block.combustion-generator.description = Generiert Stromg, indem Öl oder entzündliche Materialien verbrannt werden. +block.combustion-generator.description = Generiert Strom, indem Öl oder entzündliche Materialien verbrannt werden. block.thermal-generator.description = Erzeugt große Mengen Strom aus Lava. block.turbine-generator.description = Effizienter als ein Verbrennungsgenerator, benötigt jedoch zusätzlich Wasser. -block.differential-generator.description = Generates large amounts of energy. Utilizes the temperature difference between cryofluid and burning pyratite. +block.differential-generator.description = Erzeugt große Mengen an Energie. Nutzt den Temperaturunterschied zwischen Kryofluid und brennendem Pyratit. block.rtg-generator.description = Ein Radioisotopengenerator, der keine Kühlung benötigt, aber weniger Strom als ein Thorium-Reaktor liefert. block.solar-panel.description = Erzeugt kleine Mengen an Strom aus Sonnenenergie. block.solar-panel-large.description = Erzeugt viel mehr Strom als ein normales Solar Panel, ist aber auch sehr viel teurer in der Anschaffung. block.thorium-reactor.description = Erzeugt riesige Mengen Strom aus radioaktivem Thorium. Benötigt konstante Kühlung. Explodiert verheerend, wenn unzureichende Mengen an Kühlung vorhanden sind. -block.impact-reactor.description = An advanced generator, capable of creating massive amounts of power at peak efficiency. Requires a significant power input to kickstart the process. +block.impact-reactor.description = Ein fortschrittlicher Generator, der in der Lage ist, bei höchster Effizienz enorme Mengen an Leistung zu erzeugen. Erfordert eine erhebliche Leistungsaufnahme, um den Prozess zu starten. block.mechanical-drill.description = Ein günstiger Bohrer. Wenn er auf passende Kacheln gesetzt wird, baut er unbegrenzt Erze des entsprechenden Typs mit geringer Geschwindigkeit ab. block.pneumatic-drill.description = Ein verbesserter Bohrer, der schneller ist und in der Lage ist, härtere Erze abzubauen, indem er von Luftdruck gebrauch macht. block.laser-drill.description = Erlaubt es, durch Lasertechnologie noch schneller zu bohren, benötigt aber Strom. Erlaubt zusätzlich das Abbauen von radioaktivem Thorium. @@ -982,17 +982,17 @@ block.blast-drill.description = Der ultimative Bohrer. Benötigt große Mengen a block.water-extractor.description = Extrahiert Wasser aus dem Boden. Verwende ihn, wenn es keinen See in der Nähe gibt. block.cultivator.description = Kultiviert den Boden mit Wasser, um Biomasse zu erzeugen. block.oil-extractor.description = Verwendet große Mengen an Strom, um Öl aus Sand zu extrahieren. Verwende ihn, wenn es keine direkte Ölquelle gibt. -block.core-shard.description = The first iteration of the core capsule. Once destroyed, all contact to the region is lost. Do not let this happen. -block.core-foundation.description = The second version of the core. Better armored. Stores more resources. -block.core-nucleus.description = The third and final iteration of the core capsule. Extremely well armored. Stores massive amounts of resources. +block.core-shard.description = Die erste Version der Kernkapsel. Einmal zerstört, ist jeglicher Kontakt zur Region verloren. Lass das nicht zu. +block.core-foundation.description = Die zweite Version des Kerns. Besser gepanzert. Speichert mehr Ressourcen. +block.core-nucleus.description = Die dritte und letzte Version der Kernkapsel. Sehr gut gepanzert. Speichert enorme Mengen an Ressourcen. block.vault.description = Speichert eine große Menge an Materialien pro Typ. Benachbarte Container, Tresore und Basen werden zu einem Behälter zusammengefasst. Ein[LIGHT_GRAY] Entlader[] kann verwendet werden, um Materialien auszuladen. block.container.description = Speichert eine kleine Menge an Materialien pro Typ. Benachbarte Container, Tresore und Basen werden zu einem Behälter zusammengefasst. Ein[LIGHT_GRAY] Entlader[] kann verwendet werden, um Materialien auszuladen. block.unloader.description = Entlädt Materialien aus einem Container, Tresor oder einer Basis auf ein Förderband oder direkt in einen benachbarten Block. Der Typ des auszuladenden Materials kann durch darauf tippen verändert werden. -block.launch-pad.description = Launches batches of items without any need for a core launch. Unfinished. -block.launch-pad-large.description = An improved version of the launch pad. Stores more items. Launches more frequently. +block.launch-pad.description = Startet Stapel von Items, ohne dass ein Kernstart erforderlich ist. Unvollendet. +block.launch-pad-large.description = Eine verbesserte Version des Launchpads. Speichert weitere Items. Wird häufiger gestartet. block.duo.description = Ein kleiner, günstiger Geschützturm. -block.scatter.description = A medium-sized anti-air turret. Sprays clumps of lead or scrap flak at enemy units. -block.scorch.description = Burns any ground enemies close to it. Highly effective at close range. +block.scatter.description = Ein mittelgroßer Anti-Luft-Turm. Sprüht Blei- oder Schrottklumpen auf feindliche Einheiten. +block.scorch.description = Verbrennt alle Bodenfeinde in der Nähe. Hochwirksam im Nahbereich. block.hail.description = Ein kleiner Artillerie-Geschützturm. block.wave.description = Ein mittelgroßer Geschützturm, der flüssige Kugeln verschießt. block.lancer.description = Ein mittelgroßer Geschützturm, der sich auflädt und Elektrizitätsstrahlen verschießt. @@ -1004,7 +1004,7 @@ block.ripple.description = Ein großer Artillerie-Geschützturm, der mehrere Sch block.cyclone.description = Ein großer Schnellfeuer-Geschützturm. block.spectre.description = Ein großer Geschützturm, der zwei starke Schüsse gleichzeitig abfeuert. block.meltdown.description = Ein großer Geschützturm, der starke Strahlen mit großer Reichweite abfeuert. -block.draug-factory.description = Produces Draug mining drones. +block.draug-factory.description = Produziert Draug-Mining-Drohnen. block.spirit-factory.description = Produziert leichte Drohnen, die Erz abbauen und Blöcke reparieren können. block.phantom-factory.description = Produziert erweiterte Drohnen, die deutlich effizienter sind als Spirit-Drohnen. block.wraith-factory.description = Produziert schnelle Abfangjäger. @@ -1015,7 +1015,7 @@ block.crawler-factory.description = Produces fast self-destructing swarm units. block.titan-factory.description = Produziert fortgeschrittene, gepanzerte Bodeneinheiten. block.fortress-factory.description = Produziert schwere Artillerie-Bodeneinheiten. block.repair-point.description = Heilt durchgehend die nächste befreundete, beschädigte Einheit in der Umgebung. -block.dart-mech-pad.description = Provides transformation into a basic attack mech.\nUse by tapping while standing on it. +block.dart-mech-pad.description = Bietet die Umwandlung in einen einfachen Angriffs-Mech.\nVerwende es, indem du im Stehen darauf tippst. block.delta-mech-pad.description = Wechsle in einen schnellen, leicht gepanzerten Mech, der für Überfälle gemacht ist.\nVerwende das Pad, indem du doppelt darauf tippst, während du darauf bist. block.tau-mech-pad.description = Wechsle in einen Support-Mech, der befreundete Blöcke und Einheiten heilen kann.\nVerwende das Pad, indem du doppelt darauf tippst, während du darauf bist. block.omega-mech-pad.description = Wechsle in einen klobigen und gut gepanzerten Mech, der für Frontangriffe gemacht ist.\nVerwende das Pad, indem du doppelt darauf tippst, während du darauf bist. diff --git a/core/assets/bundles/bundle_ko.properties b/core/assets/bundles/bundle_ko.properties index 6695221a30..4720f31af5 100644 --- a/core/assets/bundles/bundle_ko.properties +++ b/core/assets/bundles/bundle_ko.properties @@ -46,6 +46,7 @@ newgame = 새 게임 none = <없음> minimap = 미니맵 close = 닫기 +website = 웹사이트 quit = 나가기 maps = 맵 continue = 계속하기 @@ -514,6 +515,7 @@ setting.lasers.name = 전력 노드 레이저 표시 setting.pixelate.name = 픽셀화[LIGHT_GRAY] (애니메이션 효과가 꺼집니다) setting.minimap.name = 미니맵 보기 setting.musicvol.name = 음악 크기 +setting.ambientvol.name = 배경음 크기 setting.mutemusic.name = 음소거 setting.sfxvol.name = 효과음 크기 setting.mutesound.name = 소리 끄기 diff --git a/core/assets/bundles/bundle_zh_CN.properties b/core/assets/bundles/bundle_zh_CN.properties index 4974494ce2..1ae02df398 100644 --- a/core/assets/bundles/bundle_zh_CN.properties +++ b/core/assets/bundles/bundle_zh_CN.properties @@ -172,7 +172,7 @@ saveimage = 保存图片 unknown = 未知 custom = 自定义 builtin = 内建的 -map.delete.confirm = 你确定你想要删除这张地图吗?这个操作无法取消! +map.delete.confirm = 你确定你想要删除这张地图吗?这个操作无法撤销! map.random = [accent]随机地图 map.nospawn = 这个地图没有核心!请在编辑器中添加一个[ROYAL]蓝色[]的核心。 map.nospawn.pvp = 这个地图没有敌人的核心!请在编辑器中添加一个[ROYAL]红色[]的核心。 @@ -349,13 +349,13 @@ error.any = 未知网络错误。 error.bloom = 未能初始化特效。\n您的设备可能不支持它。 zone.groundZero.name = 零号地区 -zone.desertWastes.name = 沙漠废物 +zone.desertWastes.name = 荒芜沙漠 zone.craters.name = 陨石带 zone.frozenForest.name = 冰冻森林 -zone.ruinousShores.name = 毁灭海岸 +zone.ruinousShores.name = 遗迹海岸 zone.stainedMountains.name = 绵延群山 zone.desolateRift.name = 荒芜裂谷 -zone.nuclearComplex.name = 核裂变 +zone.nuclearComplex.name = 核裂阵 zone.overgrowth.name = 增生区 zone.tarFields.name = 石油田 zone.saltFlats.name = 盐碱荒滩 @@ -498,9 +498,9 @@ setting.sensitivity.name = 控制器灵敏度 setting.saveinterval.name = 自动保存间隔 setting.seconds = {0} 秒 setting.fullscreen.name = 全屏 -setting.borderlesswindow.name = 全屏化[LIGHT_GRAY] (可能需要重启) +setting.borderlesswindow.name = 无边框窗口[LIGHT_GRAY] (可能需要重启) setting.fps.name = 显示 FPS -setting.vsync.name = 帧同步 +setting.vsync.name = 垂直同步 setting.lasers.name = 显示能量射线 setting.pixelate.name = 像素画面 [LIGHT_GRAY](可能会降低性能) setting.minimap.name = 显示小地图 @@ -736,7 +736,7 @@ 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.junction.name = 连接点 @@ -825,7 +825,7 @@ block.surge-wall.name = 波动墙 block.surge-wall-large.name = 大型波动墙 block.cyclone.name = 气旋炮 block.fuse.name = 融合炮 -block.shock-mine.name = 休克地雷 +block.shock-mine.name = 脉冲地雷 block.overdrive-projector.name = 超速投影器 block.force-projector.name = 力墙投影器 block.arc.name = 电弧 @@ -955,7 +955,7 @@ block.surge-wall-large.description = 强大的防御区块。\n有很小的机 block.door.description = 一扇小门,可以通过点击打开和关闭。\n如果打开,敌人可以射击并穿过。 block.door-large.description = 一扇大门,可以通过点击打开和关闭。\n如果打开,敌人可以射击并穿过。\n跨越多个区块。 block.mender.description = 定期修理附近的方块,使防御系统在波与波之间得到修复。\n通常使用硅来提高范围和效率。 -block.mend-projector.description = 定期修复附近的建筑物。 +block.mend-projector.description = 修理者的升级。定期修复附近的建筑物。 block.overdrive-projector.description = 提高附近建筑物的速度,如钻头和传送带。 block.force-projector.description = 自身周围创建一个六边形力场,使建筑物和内部单位免受子弹的伤害。 block.shock-mine.description = 伤害踩到它的敌人。敌人几乎看不到它。 @@ -1008,20 +1008,20 @@ block.container.description = 存储少量物品。当存在非恒定的材料 block.vault.description = 存储大量物品。当存在非恒定的材料需求时,使用它来创建缓冲区。 [LIGHT_GRAY]卸载器[]可用于从仓库中获取物品。 block.launch-pad.description = 不通过核心发射物体。 block.launch-pad-large.description = 发射台的改进版。存储更多物体。启动频率更高。 -block.duo.description = 小而便宜的炮塔。 -block.scatter.description = 中型防空炮塔,向空中单位发射铅或废料。 -block.scorch.description = 小型炮塔,燃烧任何靠近它的地面敌人。近距离高效。 +block.duo.description = 小而便宜的炮塔。对地高效。 +block.scatter.description = 不可或缺的防空炮塔,向空中单位发射铅或废料。 +block.scorch.description = 小型炮塔,燃烧任何靠近它的地面敌人。近距离非常有效。 block.arc.description = 小型炮塔,发射电弧。 -block.hail.description = 小型炮兵炮台。 -block.lancer.description = 中型炮塔,发射带电的电子束。 -block.wave.description = 中型快速炮塔,射出液体泡泡。 -block.salvo.description = 中型炮塔,齐射射击。 -block.swarmer.description = 中型炮塔,发射爆炸导弹。 -block.ripple.description = 大型炮兵炮塔,可同时向多个目标开火。 -block.cyclone.description = 大型快速炮塔。 -block.fuse.description = 大型炮塔,发射强大的短程光束。 -block.spectre.description = 大型炮塔,一次射出两颗强大的子弹。 -block.meltdown.description = 发射强大的远程光束的大型炮塔。 +block.hail.description = 小型,远程炮台。 +block.lancer.description = 中型对地炮塔。遇敌时会充能并发射强有力的的能量束。 +block.wave.description = 中型快速炮塔,射出液体泡泡。有液体输入时自动灭火。 +block.salvo.description = 双管炮的升级。中型,齐射射击。 +block.swarmer.description = 中型炮塔,对空对地,发射跟踪爆炸导弹。 +block.ripple.description = 大型远程炮台,非常强力,向远处的敌人投射一簇弹药。 +block.cyclone.description = 大型快速炮塔,对空对地,向周围敌人发射爆炸弹。 +block.fuse.description = 大型炮塔,发射三道刺穿敌人的短程光束。 +block.spectre.description = 超大型炮塔,对空对地,一次射出两颗强大的穿甲子弹。 +block.meltdown.description = 超大型激光炮塔,充能之后持续发射光束,需要冷却剂。 block.crawler-factory.description = 生产快速自毁单元。 block.draug-factory.description = 生产德鲁格釆矿机。 block.spirit-factory.description = 生产幽灵修理机。 @@ -1039,4 +1039,4 @@ block.javelin-ship-pad.description = 离开你当前的装置,换成一个强 block.glaive-ship-pad.description = 离开现有的装置,换成装甲良好的大型武装直升机。\n站在上面时双击切换。 block.tau-mech-pad.description = 离开你当前的装置并换成一个可以治愈友方建筑物和单位的支撑机械。\n站在上面时双击切换。 block.delta-mech-pad.description = 离开你当前的装置并换成一个快速,轻装甲的机械装置,用于快速攻击。\n站在上面时双击切换。 -block.omega-mech-pad.description = 离开你当前的装置并换成一个笨重且装甲良好的机甲,用于前线攻击。\n站在上面时双击切换。 \ No newline at end of file +block.omega-mech-pad.description = 离开你当前的装置并换成一个笨重且装甲良好的机甲,用于前线攻击。\n站在上面时双击切换。 diff --git a/core/assets/contributors b/core/assets/contributors index 0b022ae2f0..46c5a0b47f 100644 --- a/core/assets/contributors +++ b/core/assets/contributors @@ -26,7 +26,7 @@ beito BeefEX Lorex laohuaji233 -CrazyBearTR +Spico The Spirit Guy Zachary Fenr1r Jaiun Lee @@ -71,3 +71,4 @@ Paul T Dominik Arkanic Potion +Markus G diff --git a/core/assets/icons/icon.icns b/core/assets/icons/icon.icns index 6ad4d97da2..e29be339e8 100644 Binary files a/core/assets/icons/icon.icns and b/core/assets/icons/icon.icns differ diff --git a/core/assets/maps/fungalPass.msav b/core/assets/maps/fungalPass.msav index 3935a0c04d..f5403ec8c3 100644 Binary files a/core/assets/maps/fungalPass.msav and b/core/assets/maps/fungalPass.msav differ diff --git a/core/assets/maps/maze.msav b/core/assets/maps/maze.msav index 062d3784be..ea34efa876 100644 Binary files a/core/assets/maps/maze.msav and b/core/assets/maps/maze.msav differ diff --git a/core/assets/maps/veins.msav b/core/assets/maps/veins.msav index d63dc2c1e8..133d783abc 100644 Binary files a/core/assets/maps/veins.msav and b/core/assets/maps/veins.msav differ diff --git a/core/assets/music/game6.ogg b/core/assets/music/game6.ogg index d57cdae0c2..45b063ae9c 100644 Binary files a/core/assets/music/game6.ogg and b/core/assets/music/game6.ogg differ diff --git a/core/assets/saves/settings.bin b/core/assets/saves/settings.bin new file mode 100644 index 0000000000..44231e7b88 Binary files /dev/null and b/core/assets/saves/settings.bin differ diff --git a/core/assets/saves/settings_backup.bin b/core/assets/saves/settings_backup.bin new file mode 100644 index 0000000000..44231e7b88 Binary files /dev/null and b/core/assets/saves/settings_backup.bin differ diff --git a/core/assets/sprites/uiskin.json b/core/assets/sprites/uiskin.json index 30e049a889..b929cf8457 100644 --- a/core/assets/sprites/uiskin.json +++ b/core/assets/sprites/uiskin.json @@ -188,7 +188,8 @@ up: button, over: button-over, imageDisabledColor: gray, - imageUpColor: white + imageUpColor: white, + disabled: button-disabled }, node: { up: button-over, diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 9ed18ca79d..571ade02a3 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -67,6 +67,6 @@ public class Mindustry extends ApplicationCore{ super.init(); Log.info("Time to load [total]: {0}", Time.elapsed()); - Events.fire(new GameLoadEvent()); + Events.fire(new ClientLoadEvent()); } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index f995df588b..76c492a3bc 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -1,7 +1,7 @@ package io.anuke.mindustry; +import io.anuke.arc.*; import io.anuke.arc.Application.ApplicationType; -import io.anuke.arc.Core; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Color; import io.anuke.arc.util.Structs; @@ -15,6 +15,7 @@ import io.anuke.mindustry.entities.traits.DrawTrait; import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.gen.Serialization; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.world.blocks.defense.ForceProjector.ShieldEntity; @@ -222,5 +223,7 @@ public class Vars{ customMapDirectory = dataDirectory.child("maps/"); saveDirectory = dataDirectory.child("saves/"); tmpDirectory = dataDirectory.child("tmp/"); + + Events.fire(new AppLoadEvent()); } } diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java index 5307e4d202..19fdfcfb4a 100644 --- a/core/src/io/anuke/mindustry/ai/BlockIndexer.java +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -119,7 +119,7 @@ public class BlockIndexer{ ObjectSet set = damagedTiles[team.ordinal()]; for(Tile tile : set){ - if((tile.entity == null || tile.entity.getTeam() != team || !tile.entity.damaged()) && !(tile.block() instanceof BuildBlock)){ + if((tile.entity == null || tile.entity.getTeam() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){ returnArray.add(tile); } } diff --git a/core/src/io/anuke/mindustry/content/Bullets.java b/core/src/io/anuke/mindustry/content/Bullets.java index dd8deb3881..c0b2ad732c 100644 --- a/core/src/io/anuke/mindustry/content/Bullets.java +++ b/core/src/io/anuke/mindustry/content/Bullets.java @@ -267,7 +267,7 @@ public class Bullets implements ContentList{ keepVelocity = false; splashDamageRadius = 25f; splashDamage = 10f; - lifetime = 50f; + lifetime = 60f; trailColor = Pal.unitBack; backColor = Pal.unitBack; frontColor = Pal.unitFront; diff --git a/core/src/io/anuke/mindustry/content/TechTree.java b/core/src/io/anuke/mindustry/content/TechTree.java index 6ca60ed2c1..10930e51a5 100644 --- a/core/src/io/anuke/mindustry/content/TechTree.java +++ b/core/src/io/anuke/mindustry/content/TechTree.java @@ -227,12 +227,14 @@ public class TechTree implements ContentList{ node(turbineGenerator, () -> { node(thermalGenerator, () -> { - node(rtgGenerator, () -> { - node(differentialGenerator, () -> { - node(thoriumReactor, () -> { - node(impactReactor, () -> { + node(differentialGenerator, () -> { + node(thoriumReactor, () -> { + node(impactReactor, () -> { + + }); + + node(rtgGenerator, () -> { - }); }); }); }); diff --git a/core/src/io/anuke/mindustry/content/UnitTypes.java b/core/src/io/anuke/mindustry/content/UnitTypes.java index 95fcc9e82a..db601d03af 100644 --- a/core/src/io/anuke/mindustry/content/UnitTypes.java +++ b/core/src/io/anuke/mindustry/content/UnitTypes.java @@ -330,9 +330,9 @@ public class UnitTypes implements ContentList{ baseRotateSpeed = 0.04f; weapon = new Weapon("lich-missiles"){{ length = 4f; - reload = 180f; + reload = 160f; width = 22f; - shots = 22; + shots = 16; shootCone = 100f; shotDelay = 2; inaccuracy = 10f; @@ -341,7 +341,7 @@ public class UnitTypes implements ContentList{ velocityRnd = 0.2f; spacing = 1f; bullet = Bullets.missileRevenant; - shootSound = Sounds.missile; + shootSound = Sounds.artillery; }}; }}; @@ -371,7 +371,18 @@ public class UnitTypes implements ContentList{ inaccuracy = 3f; roundrobin = true; ejectEffect = Fx.none; - bullet = Bullets.standardDenseBig; + bullet = new BasicBulletType(7f, 42, "bullet"){ + { + bulletWidth = 15f; + bulletHeight = 21f; + shootEffect = Fx.shootBig; + } + + @Override + public float range(){ + return 165f; + } + }; shootSound = Sounds.shootBig; }}; }}; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index b0e2eed073..2406ca7556 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -84,7 +84,7 @@ public class Control implements ApplicationListener{ }); Events.on(PlayEvent.class, event -> { - player.setTeam(defaultTeam); + player.setTeam(state.rules.pvp ? netServer.assignTeam(player, playerGroup.all()) : defaultTeam); player.setDead(true); player.add(); @@ -237,7 +237,7 @@ public class Control implements ApplicationListener{ public void playMap(Map map, Rules rules){ ui.loadAnd(() -> { logic.reset(); - world.loadMap(map); + world.loadMap(map, rules); state.rules = rules; logic.play(); }); @@ -350,35 +350,37 @@ public class Control implements ApplicationListener{ //display UI scale changed dialog if(Core.settings.getBool("uiscalechanged", false)){ - FloatingDialog dialog = new FloatingDialog("$confirm"); + Core.app.post(() -> Core.app.post(() -> { + FloatingDialog dialog = new FloatingDialog("$confirm"); + dialog.setFillParent(true); - float[] countdown = {60 * 11}; - Runnable exit = () -> { - Core.settings.put("uiscale", 100); - Core.settings.put("uiscalechanged", false); - settings.save(); - dialog.hide(); - Core.app.exit(); - }; + float[] countdown = {60 * 11}; + Runnable exit = () -> { + Core.settings.put("uiscale", 100); + Core.settings.put("uiscalechanged", false); + settings.save(); + dialog.hide(); + Core.app.exit(); + }; - dialog.setFillParent(false); - dialog.cont.label(() -> { - if(countdown[0] <= 0){ - exit.run(); - } - return Core.bundle.format("uiscale.reset", (int)((countdown[0] -= Time.delta()) / 60f)); - }).pad(10f).expand().left(); + dialog.cont.label(() -> { + if(countdown[0] <= 0){ + exit.run(); + } + return Core.bundle.format("uiscale.reset", (int)((countdown[0] -= Time.delta()) / 60f)); + }).pad(10f).expand().center(); - dialog.buttons.defaults().size(200f, 60f); - dialog.buttons.addButton("$uiscale.cancel", exit); + dialog.buttons.defaults().size(200f, 60f); + dialog.buttons.addButton("$uiscale.cancel", exit); - dialog.buttons.addButton("$ok", () -> { - Core.settings.put("uiscalechanged", false); - settings.save(); - dialog.hide(); - }); + dialog.buttons.addButton("$ok", () -> { + Core.settings.put("uiscalechanged", false); + settings.save(); + dialog.hide(); + }); - Core.app.post(dialog::show); + dialog.show(); + })); } } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index b9c8510b02..8d210cb930 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -40,7 +40,7 @@ import static io.anuke.mindustry.Vars.*; public class NetServer implements ApplicationListener{ public final static int maxSnapshotSize = 430; - private final static float serverSyncTime = 15, kickDuration = 30 * 1000; + private final static float serverSyncTime = 12, kickDuration = 30 * 1000; private final static Vector2 vector = new Vector2(); private final static Rectangle viewport = new Rectangle(); /** If a player goes away of their server-side coordinates by this distance, they get teleported back. */ @@ -176,7 +176,7 @@ public class NetServer implements ApplicationListener{ //playing in pvp mode automatically assigns players to teams if(state.rules.pvp){ - player.setTeam(assignTeam(playerGroup.all())); + player.setTeam(assignTeam(player, playerGroup.all())); Log.info("Auto-assigned player {0} to team {1}.", player.name, player.getTeam()); } @@ -194,13 +194,13 @@ public class NetServer implements ApplicationListener{ }); } - public Team assignTeam(Iterable players){ + public Team assignTeam(Player current, Iterable players){ //find team with minimum amount of players and auto-assign player to that. return Structs.findMin(Team.all, team -> { if(state.teams.isActive(team) && !state.teams.get(team).cores.isEmpty()){ int count = 0; for(Player other : players){ - if(other.getTeam() == team){ + if(other.getTeam() == team && other != current){ count++; } } diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 6041017533..c3005cbe79 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -256,7 +256,7 @@ public class Renderer implements ApplicationListener{ draw(playerGroup, p -> true, Player::drawBuildRequests); if(Entities.countInBounds(shieldGroup) > 0){ - if(settings.getBool("animatedshields")){ + if(settings.getBool("animatedshields") && Shaders.shield != null){ Draw.flush(); shieldBuffer.begin(); graphics.clear(Color.CLEAR); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 59cda8703f..1c330ce397 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -220,6 +220,10 @@ public class World implements ApplicationListener{ } public void loadMap(Map map){ + loadMap(map, new Rules()); + } + + public void loadMap(Map map, Rules checkRules){ try{ SaveIO.load(map.file, new FilterContext(map)); }catch(Exception e){ @@ -238,20 +242,21 @@ public class World implements ApplicationListener{ invalidMap = false; if(!headless){ - if(state.teams.get(defaultTeam).cores.size == 0){ + if(state.teams.get(defaultTeam).cores.size == 0 && !checkRules.pvp){ ui.showError("$map.nospawn"); invalidMap = true; - }else if(state.rules.pvp){ //pvp maps need two cores to be valid - invalidMap = true; + }else if(checkRules.pvp){ //pvp maps need two cores to be valid + int teams = 0; for(Team team : Team.all){ - if(state.teams.get(team).cores.size != 0 && team != defaultTeam){ - invalidMap = false; + if(state.teams.get(team).cores.size != 0){ + teams ++; } } - if(invalidMap){ + if(teams < 2){ + invalidMap = true; ui.showError("$map.nospawn.pvp"); } - }else if(state.rules.attackMode){ //pvp maps need two cores to be valid + }else if(checkRules.attackMode){ //attack maps need two cores to be valid invalidMap = state.teams.get(waveTeam).cores.isEmpty(); if(invalidMap){ ui.showError("$map.nospawn.attack"); diff --git a/core/src/io/anuke/mindustry/entities/Predict.java b/core/src/io/anuke/mindustry/entities/Predict.java index c4143a80b8..ef385cbd26 100644 --- a/core/src/io/anuke/mindustry/entities/Predict.java +++ b/core/src/io/anuke/mindustry/entities/Predict.java @@ -55,7 +55,7 @@ public class Predict{ * See {@link #intercept(float, float, float, float, float, float, float)}. */ public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v){ - return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getTargetVelocityX() - src.getTargetVelocityX(), dst.getTargetVelocityY() - src.getTargetVelocityY(), v); + return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getTargetVelocityX() - src.getTargetVelocityX()/2f, dst.getTargetVelocityY() - src.getTargetVelocityY()/2f, v); } private static Vector2 quad(float a, float b, float c){ diff --git a/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java index 56a6b5ae7f..94220d1ece 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java @@ -5,6 +5,7 @@ import io.anuke.arc.math.Mathf; import io.anuke.arc.util.Time; import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.entities.Effects; +import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.Pal; public class MissileBulletType extends BasicBulletType{ @@ -18,6 +19,7 @@ public class MissileBulletType extends BasicBulletType{ backColor = Pal.missileYellowBack; frontColor = Pal.missileYellow; homingPower = 7f; + hitSound = Sounds.explosion; } @Override diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index 7734838d3d..1b601d3d7f 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -1,33 +1,28 @@ package io.anuke.mindustry.entities.traits; import io.anuke.annotations.Annotations.*; -import io.anuke.arc.Core; -import io.anuke.arc.Events; -import io.anuke.arc.collection.Array; +import io.anuke.arc.*; import io.anuke.arc.collection.Queue; +import io.anuke.arc.collection.*; import io.anuke.arc.graphics.g2d.*; -import io.anuke.arc.math.Angles; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.Time; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.entities.type.TileEntity; -import io.anuke.mindustry.entities.type.Unit; -import io.anuke.mindustry.game.EventType.BuildSelectEvent; -import io.anuke.mindustry.gen.Call; -import io.anuke.mindustry.graphics.Pal; -import io.anuke.mindustry.net.Net; +import io.anuke.arc.math.*; +import io.anuke.arc.math.geom.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.entities.type.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.gen.*; +import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.world.*; -import io.anuke.mindustry.world.blocks.BuildBlock; -import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; +import io.anuke.mindustry.world.blocks.*; +import io.anuke.mindustry.world.blocks.BuildBlock.*; import java.io.*; -import java.util.Arrays; +import java.util.*; import static io.anuke.mindustry.Vars.*; -import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.removal; -import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.tmptr; +import static io.anuke.mindustry.entities.traits.BuilderTrait.BuildDataStatic.*; /** Interface for units that build things.*/ public interface BuilderTrait extends Entity, TeamTrait{ @@ -106,19 +101,14 @@ public interface BuilderTrait extends Entity, TeamTrait{ unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f); } - //progress is synced, thus not updated clientside - if(!Net.client()){ - //deconstructing is 2x as fast - if(current.breaking){ - entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - }else{ - entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - } - - current.progress = entity.progress(); + //deconstructing is 2x as fast + if(current.breaking){ + entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); }else{ - entity.progress = current.progress; + entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); } + + current.progress = entity.progress; } /** Returns the queue for storing build requests. */ @@ -135,7 +125,7 @@ public interface BuilderTrait extends Entity, TeamTrait{ default void writeBuilding(DataOutput output) throws IOException{ BuildRequest request = buildRequest(); - if(request != null){ + if(request != null && (request.block != null || request.breaking)){ output.writeByte(request.breaking ? 1 : 0); output.writeInt(Pos.get(request.x, request.y)); output.writeFloat(request.progress); @@ -174,7 +164,11 @@ public interface BuilderTrait extends Entity, TeamTrait{ if(applyChanges){ buildQueue().addLast(request); }else if(isBuilding()){ - buildRequest().progress = progress; + BuildRequest last = buildRequest(); + last.progress = progress; + if(last.tile() != null && last.tile().entity instanceof BuildEntity){ + ((BuildEntity)last.tile().entity).progress = progress; + } } } } @@ -285,6 +279,10 @@ public interface BuilderTrait extends Entity, TeamTrait{ this.breaking = true; } + public Tile tile(){ + return world.tile(x, y); + } + @Override public String toString(){ return "BuildRequest{" + diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 6d124c9c42..6d0ab81cc5 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -70,7 +70,6 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ private Tile mining; private Vector2 movement = new Vector2(); private boolean moved; - private SoundLoop buildSound = new SoundLoop(Sounds.build, 0.75f); //endregion @@ -131,11 +130,6 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ } } - @Override - public void removed(){ - buildSound.stop(); - } - @Override public float drag(){ return mech.drag; @@ -514,11 +508,13 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ } if(!isDead() && isFlying()){ - loops.play(Sounds.thruster, this, Mathf.clamp(velocity.len() * 2f) * 0.4f); + loops.play(Sounds.thruster, this, Mathf.clamp(velocity.len() * 2f) * 0.3f); } BuildRequest request = buildRequest(); - buildSound.update(request == null ? x : request.x * tilesize, request == null ? y : request.y * tilesize, isBuilding() && (Mathf.within(request.x * tilesize, request.y * tilesize, x, y, placeDistance) || state.isEditor())); + if(isBuilding() && request.tile() != null && (request.tile().withinDst(x, y, placeDistance) || state.isEditor())){ + loops.play(Sounds.build, request.tile(), 0.75f); + } if(isDead()){ isBoosting = false; @@ -531,7 +527,9 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ spawner = null; } - avoidOthers(); + if(isLocal || Net.server()){ + avoidOthers(); + } Tile tile = world.tileWorld(x, y); diff --git a/core/src/io/anuke/mindustry/entities/type/base/RepairDrone.java b/core/src/io/anuke/mindustry/entities/type/base/RepairDrone.java index e5be4d3d07..be16cd1724 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/RepairDrone.java +++ b/core/src/io/anuke/mindustry/entities/type/base/RepairDrone.java @@ -5,6 +5,7 @@ import io.anuke.mindustry.entities.type.TileEntity; import io.anuke.mindustry.entities.units.UnitState; import io.anuke.mindustry.world.Pos; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.*; import java.io.*; @@ -23,6 +24,10 @@ public class RepairDrone extends BaseDrone{ target = Units.findDamagedTile(team, x, y); } + if(target instanceof TileEntity && ((TileEntity)target).block instanceof BuildBlock){ + target = null; + } + if(target != null){ if(target.dst(RepairDrone.this) > type.range){ circle(type.range * 0.9f); diff --git a/core/src/io/anuke/mindustry/game/EventType.java b/core/src/io/anuke/mindustry/game/EventType.java index c9da8c9c57..84d4262650 100644 --- a/core/src/io/anuke/mindustry/game/EventType.java +++ b/core/src/io/anuke/mindustry/game/EventType.java @@ -27,8 +27,13 @@ public class EventType{ } } - /** Called when the game is first loaded. */ - public static class GameLoadEvent{ + /** Called when the client game is first loaded. */ + public static class ClientLoadEvent{ + + } + + /** Called when the core app is first loaded. */ + public static class AppLoadEvent{ } diff --git a/core/src/io/anuke/mindustry/game/MusicControl.java b/core/src/io/anuke/mindustry/game/MusicControl.java index c2364ee8d3..643626c318 100644 --- a/core/src/io/anuke/mindustry/game/MusicControl.java +++ b/core/src/io/anuke/mindustry/game/MusicControl.java @@ -14,7 +14,7 @@ import static io.anuke.mindustry.Vars.*; /** Controls playback of multiple music tracks.*/ public class MusicControl{ - private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.3f, musicWaveChance = 0.24f; + private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.45f, musicWaveChance = 0.35f; /** normal, ambient music, plays at any time */ public final Array ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6); diff --git a/core/src/io/anuke/mindustry/game/Stats.java b/core/src/io/anuke/mindustry/game/Stats.java index ebaa62dfc6..afdd5c0706 100644 --- a/core/src/io/anuke/mindustry/game/Stats.java +++ b/core/src/io/anuke/mindustry/game/Stats.java @@ -26,8 +26,10 @@ public class Stats{ public RankResult calculateRank(Zone zone, boolean launched){ float score = 0; - //each new launch period adds onto the rank 'points' - if(wavesLasted >= zone.conditionWave){ + if(launched && zone.getRules().attackMode){ + score += 3f; + }else if(wavesLasted >= zone.conditionWave){ + //each new launch period adds onto the rank 'points' score += (float)((wavesLasted - zone.conditionWave) / zone.launchPeriod + 1) * 1.2f; } diff --git a/core/src/io/anuke/mindustry/graphics/Shaders.java b/core/src/io/anuke/mindustry/graphics/Shaders.java index 4aa6f02754..afa94cd6ec 100644 --- a/core/src/io/anuke/mindustry/graphics/Shaders.java +++ b/core/src/io/anuke/mindustry/graphics/Shaders.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.graphics; +import io.anuke.annotations.Annotations.*; import io.anuke.arc.Core; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.g2d.TextureRegion; @@ -10,7 +11,7 @@ import io.anuke.arc.util.Time; public class Shaders{ public static Shadow shadow; public static BlockBuild blockbuild; - public static Shield shield; + public static @Nullable Shield shield; public static UnitBuild build; public static FogShader fog; public static MenuShader menu; @@ -19,7 +20,13 @@ public class Shaders{ public static void init(){ shadow = new Shadow(); blockbuild = new BlockBuild(); - shield = new Shield(); + try{ + shield = new Shield(); + }catch(Throwable t){ + //don't load shield shader + shield = null; + t.printStackTrace(); + } build = new UnitBuild(); fog = new FogShader(); menu = new MenuShader(); diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index ae574d906f..caf01eeb25 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -131,7 +131,7 @@ public class DesktopInput extends InputHandler{ player.isShooting = false; } - if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && !ui.chatfrag.chatOpen() && !(scene.getKeyboardFocus() instanceof TextField)){ + if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && (scene.getKeyboardFocus() == ui.minimap || !scene.hasDialog()) && !ui.chatfrag.chatOpen() && !(scene.getKeyboardFocus() instanceof TextField)){ if(!ui.minimap.isShown()){ ui.minimap.show(); }else{ diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index e2360e16e5..111142e76f 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -214,7 +214,12 @@ public class Maps implements Disposable{ return filters; }else{ - return JsonIO.read(Array.class, str); + try{ + return JsonIO.read(Array.class, str.replace("mindustrz", "mindustry")); + }catch(Exception e){ + e.printStackTrace(); + return readFilters(""); + } } } diff --git a/core/src/io/anuke/mindustry/maps/filters/MirrorFilter.java b/core/src/io/anuke/mindustry/maps/filters/MirrorFilter.java index 2832ddaed2..f3c9062b5e 100644 --- a/core/src/io/anuke/mindustry/maps/filters/MirrorFilter.java +++ b/core/src/io/anuke/mindustry/maps/filters/MirrorFilter.java @@ -17,7 +17,6 @@ public class MirrorFilter extends GenerateFilter{ { options(new SliderOption("angle", () -> angle, f -> angle = (int)f, 0, 360, 45)); - buffered = true; } @Override diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java index 0201f980ce..dbc9b422ca 100644 --- a/core/src/io/anuke/mindustry/net/Interpolator.java +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -1,14 +1,15 @@ package io.anuke.mindustry.net; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.Time; +import io.anuke.arc.math.*; +import io.anuke.arc.math.geom.*; +import io.anuke.arc.util.*; public class Interpolator{ //used for movement public Vector2 target = new Vector2(); public Vector2 last = new Vector2(); public float[] targets = {}; + public float[] lasts = {}; public long lastUpdated, updateSpacing; //current state @@ -21,6 +22,12 @@ public class Interpolator{ lastUpdated = Time.millis(); targets = target1ds; + if(lasts.length != values.length){ + lasts = new float[values.length]; + } + for(int i = 0; i < values.length; i++){ + lasts[i] = values[i]; + } last.set(cx, cy); target.set(x, y); } @@ -46,8 +53,12 @@ public class Interpolator{ values = new float[targets.length]; } + if(lasts.length != targets.length){ + lasts = new float[targets.length]; + } + for(int i = 0; i < values.length; i++){ - values[i] = Mathf.slerp(values[i], targets[i], alpha); + values[i] = Mathf.slerp(lasts[i], targets[i], alpha); } }else{ pos.set(target); diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index d0965c3cb8..006def1fcf 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -1,19 +1,18 @@ package io.anuke.mindustry.net; -import io.anuke.arc.Core; +import io.anuke.arc.*; import io.anuke.arc.collection.*; -import io.anuke.arc.function.BiConsumer; -import io.anuke.arc.function.Consumer; +import io.anuke.arc.function.*; import io.anuke.arc.util.*; -import io.anuke.arc.util.pooling.Pools; -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.gen.Call; +import io.anuke.arc.util.pooling.*; +import io.anuke.mindustry.core.*; +import io.anuke.mindustry.gen.*; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.net.Streamable.StreamBuilder; +import io.anuke.mindustry.net.Streamable.*; +import net.jpountz.lz4.*; -import java.io.IOException; -import java.nio.BufferOverflowException; -import java.nio.BufferUnderflowException; +import java.io.*; +import java.nio.*; import static io.anuke.mindustry.Vars.*; @@ -28,6 +27,8 @@ public class Net{ private static ClientProvider clientProvider; private static ServerProvider serverProvider; private static IntMap streams = new IntMap<>(); + private static final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + private static final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); /** Display a network error. Call on the graphics thread. */ public static void showError(Throwable e){ @@ -144,11 +145,11 @@ public class Net{ } public static byte[] compressSnapshot(byte[] input){ - return serverProvider.compressSnapshot(input); + return compressor.compress(input); } public static byte[] decompressSnapshot(byte[] input, int size){ - return clientProvider.decompressSnapshot(input, size); + return decompressor.decompress(input, size); } /** @@ -162,8 +163,8 @@ public class Net{ /** * Returns a list of all connections IDs. */ - public static Array getConnections(){ - return (Array)serverProvider.getConnections(); + public static Iterable getConnections(){ + return (Iterable)serverProvider.getConnections(); } /** @@ -326,7 +327,7 @@ public class Net{ public static void dispose(){ if(clientProvider != null) clientProvider.dispose(); - if(serverProvider != null) serverProvider.dispose(); + if(serverProvider != null) serverProvider.close(); clientProvider = null; serverProvider = null; server = false; @@ -354,9 +355,6 @@ public class Net{ /** Disconnect from the server. */ void disconnect(); - /** Decompress an input snapshot byte array. */ - byte[] decompressSnapshot(byte[] input, int size); - /** * Discover servers. This should run the callback regardless of whether any servers are found. Should not block. * Callback should be run on libGDX main thread. @@ -377,30 +375,61 @@ public class Net{ void host(int port) throws IOException; /** Sends a large stream of data to a specific client. */ - void sendStream(int id, Streamable stream); + default void sendStream(int id, Streamable stream){ + NetConnection connection = getByID(id); + if(connection == null) return; + try{ + int cid; + StreamBegin begin = new StreamBegin(); + begin.total = stream.stream.available(); + begin.type = Registrator.getID(stream.getClass()); + connection.send(begin, SendMode.tcp); + cid = begin.id; - /** Send an object to everyone connected. */ - void send(Object object, SendMode mode); + while(stream.stream.available() > 0){ + byte[] bytes = new byte[Math.min(512, stream.stream.available())]; + stream.stream.read(bytes); - /** Send an object to a specific client ID. */ - void sendTo(int id, Object object, SendMode mode); + StreamChunk chunk = new StreamChunk(); + chunk.id = cid; + chunk.data = bytes; + connection.send(chunk, SendMode.tcp); + } + }catch(IOException e){ + throw new RuntimeException(e); + } + } - /** Send an object to everyone except a client ID. */ - void sendExcept(int id, Object object, SendMode mode); + default void send(Object object, SendMode mode){ + for(NetConnection con : getConnections()){ + con.send(object, mode); + } + } + + default void sendTo(int id, Object object, SendMode mode){ + NetConnection conn = getByID(id); + if(conn == null){ + Log.err("Failed to find connection with ID {0}.", id); + return; + } + conn.send(object, mode); + } + + default void sendExcept(int id, Object object, SendMode mode){ + for(NetConnection con : getConnections()){ + if(con.id != id){ + con.send(object, mode); + } + } + } /** Close the server connection. */ void close(); - /** Compress an input snapshot byte array. */ - byte[] compressSnapshot(byte[] input); - /** Return all connected users. */ - Array getConnections(); + Iterable getConnections(); /** Returns a connection by ID. */ NetConnection getByID(int id); - - /** Close all connections. */ - void dispose(); } } diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index 2bb53fb9f7..ba2a004a03 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -3,6 +3,8 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.net.Net.SendMode; public abstract class NetConnection{ + private static int lastID; + public final int id; public final String address; @@ -18,8 +20,9 @@ public abstract class NetConnection{ public boolean hasBegunConnecting = false; public float viewWidth, viewHeight, viewX, viewY; - public NetConnection(int id, String address){ - this.id = id; + /** Assigns this connection a unique ID. No two connections will ever have the same ID.*/ + public NetConnection(String address){ + this.id = lastID++; this.address = address; } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java index d5ddbb1233..e81c312347 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FileChooser.java @@ -31,7 +31,6 @@ public class FileChooser extends FloatingDialog{ private Predicate filter; private Consumer selectListener; private boolean open; - private int lastWidth = Core.graphics.getWidth(), lastHeight = Core.graphics.getHeight(); public static final Predicate pngFiles = str -> str.equals("png"); public static final Predicate anyMapFiles = str -> str.equals(oldMapExtension) || str.equals(mapExtension); @@ -44,12 +43,14 @@ public class FileChooser extends FloatingDialog{ this.filter = filter; this.selectListener = result; - update(() -> { - if(Core.graphics.getWidth() != lastWidth || Core.graphics.getHeight() != lastHeight){ - updateFiles(false); - lastHeight = Core.graphics.getHeight(); - lastWidth = Core.graphics.getWidth(); - } + onResize(() -> { + cont.clear(); + setupWidgets(); + }); + + shown(() -> { + cont.clear(); + setupWidgets(); }); } @@ -121,8 +122,9 @@ public class FileChooser extends FloatingDialog{ forward.resizeImage(isize); forward.clicked(() -> stack.forward()); - back.clicked(() -> stack.back()); + forward.setDisabled(() -> !stack.canForward()); + back.setDisabled(() -> !stack.canBack()); ImageButton home = new ImageButton("icon-home"); home.resizeImage(isize); @@ -206,7 +208,7 @@ public class FileChooser extends FloatingDialog{ //macs are confined to the Downloads/ directory if(!OS.isMac){ - Image upimage = new Image("icon-folder-parent"); + Image upimage = new Image("icon-folder-parent-small"); TextButton upbutton = new TextButton(".." + directory.toString(), "clear-toggle"); upbutton.clicked(() -> { directory = directory.parent(); @@ -214,7 +216,7 @@ public class FileChooser extends FloatingDialog{ updateFiles(true); }); - upbutton.left().add(upimage).padRight(4f).size(iconsize); + upbutton.left().add(upimage).padRight(4f).size(iconsizesmall).padLeft(4); upbutton.getLabel().setAlignment(Align.left); upbutton.getCells().reverse(); @@ -248,9 +250,9 @@ public class FileChooser extends FloatingDialog{ button.setChecked(filename.equals(filefield.getText())); }); - Image image = new Image(file.isDirectory() ? "icon-folder" : "icon-file-text"); + Image image = new Image(file.isDirectory() ? "icon-folder-small" : "icon-file-text-small"); - button.add(image).padRight(4f).size(iconsize); + button.add(image).padRight(4f).padLeft(4).size(iconsizesmall); button.getCells().reverse(); files.top().left().add(button).align(Align.topLeft).fillX().expandX() .height(50).pad(2).padTop(0).padBottom(0).colspan(2); @@ -273,17 +275,6 @@ public class FileChooser extends FloatingDialog{ } } - @Override - public Dialog show(){ - Time.runTask(2f, () -> { - cont.clear(); - setupWidgets(); - super.show(); - Core.scene.setScrollFocus(pane); - }); - return this; - } - public class FileHistory{ private Array history = new Array<>(); private int index; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java index 748d0e93a6..54a502627e 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/FloatingDialog.java @@ -39,18 +39,6 @@ public class FloatingDialog extends Dialog{ state.set(State.paused); } }); - - boolean[] done = {false}; - - shown(() -> Core.app.post(() -> - forEach(child -> { - if(done[0]) return; - - if(child instanceof ScrollPane){ - Core.scene.setScrollFocus(child); - done[0] = true; - } - }))); } public FloatingDialog(String title){ @@ -59,8 +47,9 @@ public class FloatingDialog extends Dialog{ protected void onResize(Runnable run){ Events.on(ResizeEvent.class, event -> { - if(isShown()){ + if(isShown() && Core.scene.getDialog() == this){ run.run(); + updateScrollFocus(); } }); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/GameOverDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/GameOverDialog.java index ca7cdcf5d7..3078c01d56 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/GameOverDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/GameOverDialog.java @@ -1,11 +1,11 @@ package io.anuke.mindustry.ui.dialogs; -import io.anuke.arc.Core; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.game.Stats.RankResult; -import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Item.Icon; +import io.anuke.arc.*; +import io.anuke.mindustry.core.GameState.*; +import io.anuke.mindustry.game.Stats.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.type.Item.*; import static io.anuke.mindustry.Vars.*; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java index 71f72577fc..7b1486c270 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java @@ -1,21 +1,20 @@ package io.anuke.mindustry.ui.dialogs; -import io.anuke.annotations.Annotations.Serialize; -import io.anuke.arc.Core; -import io.anuke.arc.collection.Array; -import io.anuke.arc.graphics.Color; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.scene.style.Drawable; +import io.anuke.annotations.Annotations.*; +import io.anuke.arc.*; +import io.anuke.arc.collection.*; +import io.anuke.arc.graphics.*; +import io.anuke.arc.math.*; +import io.anuke.arc.scene.style.*; import io.anuke.arc.scene.ui.*; -import io.anuke.arc.scene.ui.layout.Cell; -import io.anuke.arc.scene.ui.layout.Table; -import io.anuke.arc.util.Strings; -import io.anuke.arc.util.Time; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.Version; -import io.anuke.mindustry.net.Host; +import io.anuke.arc.scene.ui.layout.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.core.*; +import io.anuke.mindustry.game.*; import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.net.*; +import io.anuke.mindustry.net.Packets.*; import static io.anuke.mindustry.Vars.*; @@ -97,9 +96,14 @@ public class JoinDialog extends FloatingDialog{ TextButton button = buttons[0] = remote.addButton("[accent]" + server.displayIP(), "clear", () -> { if(!buttons[0].childrenPressed()){ - connect(server.ip, server.port); + if(server.lastHost != null && server.lastHost.version != Version.build && Version.build != -1 && server.lastHost.version != -1){ + ui.showInfo("[scarlet]" + (server.lastHost.version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated).toString() + "\n[]" + + Core.bundle.format("server.versions", Version.build, server.lastHost.version)); + }else{ + connect(server.ip, server.port); + } } - }).width(targetWidth()).height(130f).pad(4f).get(); + }).width(targetWidth()).pad(4f).get(); button.getLabel().setWrap(true); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java index 40fb91dc9b..62b4f24a79 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java @@ -106,7 +106,7 @@ public class PausedDialog extends FloatingDialog{ return; } - if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || state.rules.tutorial){ + if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || state.rules.tutorial || wasClient){ state.set(State.menu); logic.reset(); return; diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 19fbf6a4d3..21cd40a5dd 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -241,7 +241,9 @@ public class SettingsMenuDialog extends SettingsDialog{ graphics.checkPref("fps", false); graphics.checkPref("indicators", true); graphics.checkPref("animatedwater", false); - graphics.checkPref("animatedshields", !mobile); + if(Shaders.shield != null){ + graphics.checkPref("animatedshields", !mobile); + } graphics.checkPref("bloom", false, val -> renderer.toggleBloom(val)); graphics.checkPref("lasers", true); graphics.checkPref("pixelate", false); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java index 36baa4125f..55de763cc2 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java @@ -113,9 +113,16 @@ public class ZoneInfoDialog extends FloatingDialog{ t.add("$zone.resources").padRight(6); if(zone.resources.length > 0){ - for(Item item : zone.resources){ - t.addImage(item.icon(Item.Icon.medium)).size(8 * 3); - } + t.table(r -> { + t.left(); + int i = 0; + for(Item item : zone.resources){ + r.addImage(item.icon(Item.Icon.medium)).size(8 * 3); + if(++i % 4 == 0){ + r.row(); + } + } + }); }else{ t.add("$none"); } diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 6c6b0beaaf..72a01f3070 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -242,9 +242,9 @@ public class HudFragment extends Fragment{ IntFormat fps = new IntFormat("fps"); IntFormat ping = new IntFormat("ping"); - info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left(); + info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style("outline"); info.row(); - info.label(() -> ping.get(Net.getPing())).visible(Net::client).left(); + info.label(() -> ping.get(Net.getPing())).visible(Net::client).left().style("outline"); }).top().left(); }); diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 577d40771e..189911c66f 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -294,9 +294,7 @@ public class Block extends BlockStorage{ /** Called after the block is placed by this client. */ @CallSuper public void playerPlaced(Tile tile){ - if(outputsPower && !consumesPower){ - PowerNode.lastPlaced = tile.pos(); - } + } /** Called after the block is placed by anyone. */ diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 86220b6acc..358fb27a43 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.world.blocks.power; import io.anuke.annotations.Annotations.*; import io.anuke.arc.*; +import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.g2d.*; @@ -20,9 +21,7 @@ import io.anuke.mindustry.world.meta.*; import static io.anuke.mindustry.Vars.*; public class PowerNode extends PowerBlock{ - //last distribution block placed - public static int lastPlaced = -1; - + protected ObjectSet graphs = new ObjectSet<>(); protected Vector2 t1 = new Vector2(), t2 = new Vector2(); protected TextureRegion laser, laserEnd; @@ -99,23 +98,11 @@ public class PowerNode extends PowerBlock{ () -> Mathf.clamp(entity.power.graph.getPowerProduced() / entity.power.graph.getPowerNeeded()))); } - @Override - public void playerPlaced(Tile tile){ - Tile before = world.tile(lastPlaced); - - if(linkValid(tile, before) && !before.entity.proximity().contains(tile)){ - Call.linkPowerNodes(null, tile, before); - } - - lastPlaced = tile.pos(); - super.playerPlaced(tile); - } - @Override public void placed(Tile tile){ if(Net.client()) return; - Predicate valid = other -> other != null && other != tile && ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower)) && linkValid(tile, other) + Predicate valid = other -> other != null && other != tile && ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) && linkValid(tile, other) && !other.entity.proximity().contains(tile) && other.entity.power.graph != tile.entity.power.graph; tempTiles.clear(); @@ -132,6 +119,28 @@ public class PowerNode extends PowerBlock{ super.placed(tile); } + private void getPotentialLinks(Tile tile, Consumer others){ + Predicate valid = other -> other != null && other != tile && other.entity != null && other.entity.power != null && + ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) && + overlaps(tile.x * tilesize + offset(), tile.y *tilesize + offset(), other, laserRange * tilesize) + && !other.entity.proximity().contains(tile) && !graphs.contains(other.entity.power.graph); + + tempTiles.clear(); + graphs.clear(); + Geometry.circle(tile.x, tile.y, (int)(laserRange + 1), (x, y) -> { + Tile other = world.ltile(x, y); + if(valid.test(other)){ + tempTiles.add(other); + } + }); + + tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile))); + tempTiles.each(valid, t -> { + graphs.add(t.entity.power.graph); + others.accept(t); + }); + } + @Override public void setStats(){ super.setStats(); @@ -213,6 +222,11 @@ public class PowerNode extends PowerBlock{ Draw.color(Pal.placing); Drawf.circles(x * tilesize + offset(), y * tilesize + offset(), laserRange * tilesize); + getPotentialLinks(tile, other -> { + Drawf.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f + 2f, Pal.place); + }); + + /* for(int cx = (int)(x - laserRange - 1); cx <= x + laserRange + 1; cx++){ for(int cy = (int)(y - laserRange - 1); cy <= y + laserRange + 1; cy++){ Tile link = world.ltile(cx, cy); @@ -221,7 +235,7 @@ public class PowerNode extends PowerBlock{ Drawf.square(link.drawx(), link.drawy(), link.block().size * tilesize / 2f + 2f, link.pos() == lastPlaced ? Pal.place : Pal.accent); } } - } + }*/ Draw.reset(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java index 232d704d09..a4f5216322 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Drill.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Drill.java @@ -67,7 +67,7 @@ public class Drill extends Block{ hasItems = true; idleSound = Sounds.drill; - idleSoundVolume = 0.002f; + idleSoundVolume = 0.003f; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/production/LiquidConverter.java b/core/src/io/anuke/mindustry/world/blocks/production/LiquidConverter.java index d53f6268c0..7c9da8619d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/LiquidConverter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/LiquidConverter.java @@ -12,6 +12,11 @@ public class LiquidConverter extends GenericCrafter{ hasLiquids = true; } + @Override + public boolean outputsItems(){ + return false; + } + @Override public void init(){ ConsumeLiquidBase cl = consumes.get(ConsumeType.liquid); diff --git a/desktop-sdl/build.gradle b/desktop-sdl/build.gradle deleted file mode 100644 index 006fda77da..0000000000 --- a/desktop-sdl/build.gradle +++ /dev/null @@ -1,87 +0,0 @@ -apply plugin: "java" - -sourceCompatibility = 1.8 -sourceSets.main.java.srcDirs = [ "src/" ] - -project.ext.mainClassName = "io.anuke.mindustry.desktopsdl.DesktopLauncher" -project.ext.assetsDir = new File("../core/assets") - -def IKVM_DIR = System.env.IKVM_HOME -def getTarget = { return project.hasProperty("target") ? project.properties["target"] : "windows" } - -task run(dependsOn: classes, type: JavaExec){ - main = project.mainClassName - classpath = sourceSets.main.runtimeClasspath - standardInput = System.in - workingDir = project.assetsDir - ignoreExitValue = true - - if(System.getProperty("os.name").toLowerCase().contains("mac")){ - jvmArgs("-XstartOnFirstThread", "-Djava.awt.headless=true") - } - - if(project.hasProperty("args")){ - args Eval.me(project.getProperties()["args"]) - } - - if(args.contains("debug")){ - main = "io.anuke.mindustry.DebugLauncher" - } -} - -task dist(type: Jar, dependsOn: classes){ - from files(sourceSets.main.output.classesDirs) - from files(sourceSets.main.output.resourcesDir) - from {configurations.compile.collect {zipTree(it)}} - from files(project.assetsDir) - - //use target = all for all platforms - def target = getTarget() - if(target.contains("windows")) exclude('**.so', "**.dylib") - if(target == "mac") exclude('**.so', "**.dll") - if(target == "linux") exclude('**.dll', "**.dylib") - archivesBaseName = appName + "-" + target - - manifest{ - attributes 'Main-Class': project.mainClassName - } -} - -task ikZip(type: Zip){ - def filename = "$appName-${getTarget()}-${version}" - - from "build/libs/$filename" - archiveName = "${generateDeployName(getTarget())}.zip" -} - -task ikdist{ - dependsOn dist - finalizedBy ikZip - - doLast{ - def filename = "$appName-${getTarget()}-${version}" - def folder = "build/libs/$filename" - def baseArgs = System.properties['os.name'].toLowerCase().contains('windows') ? [] : ["mono"] - def args = baseArgs + ["$IKVM_DIR/ikvmc.exe", "-target:winexe", "-out:build/libs/${filename}.exe", "build/libs/${filename}.jar"] - if(file("../core/assets/sprites/icon.ico").exists()){ - args += ["-win32icon:../core/assets/sprites/icon.ico"] - }else if(file("../core/assets/icons/icon.ico").exists()){ - args += ["-win32icon:../core/assets/icons/icon.ico"] - } - - exec{ - commandLine args - } - - copy{ - from file("build/libs/${filename}.exe") - into file(folder) - } - - copy{ - from(getTarget().contains("32") ? "$IKVM_DIR/libraries_32" : "$IKVM_DIR/libraries") - into folder - } - } -} - diff --git a/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopLauncher.java b/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopLauncher.java deleted file mode 100644 index f773f0e188..0000000000 --- a/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopLauncher.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.anuke.mindustry.desktopsdl; -import io.anuke.arc.Files.*; -import io.anuke.arc.backends.sdl.*; -import io.anuke.mindustry.*; -import io.anuke.mindustry.core.*; -import io.anuke.mindustry.net.*; - -public class DesktopLauncher{ - - public static void main(String[] arg){ - try{ - Platform.instance = new DesktopPlatform(arg); - - Net.setClientProvider(new ArcNetClient()); - Net.setServerProvider(new ArcNetServer()); - - new SdlApplication(new Mindustry(), new SdlConfig(){{ - title = "Mindustry"; - maximized = true; - depth = 0; - stencil = 0; - width = 900; - height = 700; - setWindowIcon(FileType.Internal, "icons/icon_64.png"); - }}); - }catch(Throwable e){ - DesktopPlatform.handleCrash(e); - } - } -} diff --git a/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopPlatform.java b/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopPlatform.java deleted file mode 100644 index 420ef0260c..0000000000 --- a/desktop-sdl/src/io/anuke/mindustry/desktopsdl/DesktopPlatform.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.anuke.mindustry.desktopsdl; - -import club.minnced.discord.rpc.*; -import io.anuke.arc.backends.sdl.jni.SDL; -import io.anuke.arc.collection.*; -import io.anuke.arc.files.*; -import io.anuke.arc.function.*; -import io.anuke.arc.util.*; -import io.anuke.arc.util.serialization.*; -import io.anuke.mindustry.core.GameState.*; -import io.anuke.mindustry.core.*; -import io.anuke.mindustry.net.*; -import io.anuke.mindustry.ui.dialogs.*; - -import java.net.*; -import java.util.*; - -import static io.anuke.mindustry.Vars.*; - - -public class DesktopPlatform extends Platform{ - static boolean useDiscord = OS.is64Bit; - final static String applicationId = "610508934456934412"; - String[] args; - - public DesktopPlatform(String[] args){ - this.args = args; - - testMobile = Array.with(args).contains("-testMobile"); - - if(useDiscord){ - try{ - DiscordEventHandlers handlers = new DiscordEventHandlers(); - DiscordRPC.INSTANCE.Discord_Initialize(applicationId, handlers, true, ""); - - Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC.INSTANCE::Discord_Shutdown)); - }catch(Throwable t){ - useDiscord = false; - Log.err("Failed to initialize discord.", t); - } - } - } - - static void handleCrash(Throwable e){ - Consumer dialog = Runnable::run; - boolean badGPU = false; - - if(e.getMessage() != null && (e.getMessage().contains("Couldn't create window") || e.getMessage().contains("OpenGL 2.0 or higher"))){ - - dialog.accept(() -> message( - e.getMessage().contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + e.getMessage() : - "Your graphics card does not support OpenGL 2.0!\n" + - "Try to update your graphics drivers.\n\n" + - "(If that doesn't work, your computer just doesn't support Mindustry.)")); - badGPU = true; - } - - boolean fbgp = badGPU; - - CrashSender.send(e, file -> { - if(!fbgp){ - dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + (e.getMessage() == null ? "" : "\n" + e.getMessage()))); - } - }); - } - - @Override - public void showFileChooser(String text, String content, Consumer cons, boolean open, Predicate filetype){ - new FileChooser(text, file -> filetype.test(file.extension().toLowerCase()), open, cons).show(); - } - - @Override - public void updateRPC(){ - - if(!useDiscord) return; - - DiscordRichPresence presence = new DiscordRichPresence(); - - if(!state.is(State.menu)){ - String map = world.getMap() == null ? "Unknown Map" : world.isZone() ? world.getZone().localizedName : Strings.capitalize(world.getMap().name()); - String mode = state.rules.pvp ? "PvP" : state.rules.attackMode ? "Attack" : "Survival"; - String players = Net.active() && playerGroup.size() > 1 ? " | " + playerGroup.size() + " Players" : ""; - - presence.state = mode + players; - - if(!state.rules.waves){ - presence.details = map; - }else{ - presence.details = map + " | Wave " + state.wave; - presence.largeImageText = "Wave " + state.wave; - } - }else{ - if(ui.editor != null && ui.editor.isShown()){ - presence.state = "In Editor"; - }else if(ui.deploy != null && ui.deploy.isShown()){ - presence.state = "In Launch Selection"; - }else{ - presence.state = "In Menu"; - } - } - - presence.largeImageKey = "logo"; - - DiscordRPC.INSTANCE.Discord_UpdatePresence(presence); - } - - @Override - public String getUUID(){ - try{ - Enumeration e = NetworkInterface.getNetworkInterfaces(); - NetworkInterface out; - for(out = e.nextElement(); (out.getHardwareAddress() == null || !validAddress(out.getHardwareAddress())) && e.hasMoreElements(); out = e.nextElement()); - - byte[] bytes = out.getHardwareAddress(); - byte[] result = new byte[8]; - System.arraycopy(bytes, 0, result, 0, bytes.length); - - String str = new String(Base64Coder.encode(result)); - - if(str.equals("AAAAAAAAAOA=") || str.equals("AAAAAAAAAAA=")) throw new RuntimeException("Bad UUID."); - - return str; - }catch(Exception e){ - return super.getUUID(); - } - } - - private static void message(String message){ - SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MESSAGEBOX_ERROR, "oh no", message); - } - - private boolean validAddress(byte[] bytes){ - if(bytes == null) return false; - byte[] result = new byte[8]; - System.arraycopy(bytes, 0, result, 0, bytes.length); - return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=") && !new String(Base64Coder.encode(result)).equals("AAAAAAAAAAA="); - } -} diff --git a/desktop/build.gradle b/desktop/build.gradle index 65139a81c3..db32ac4599 100644 --- a/desktop/build.gradle +++ b/desktop/build.gradle @@ -1,43 +1,28 @@ +import com.badlogicgames.packr.Packr +import com.badlogicgames.packr.PackrConfig + apply plugin: "java" sourceCompatibility = 1.8 -sourceSets.main.java.srcDirs = ["src/"] +sourceSets.main.java.srcDirs = [ "src/" ] project.ext.mainClassName = "io.anuke.mindustry.desktop.DesktopLauncher" project.ext.assetsDir = new File("../core/assets") -def IKVM_DIR = System.env.IKVM_HOME - -import com.badlogicgames.packr.Packr -import com.badlogicgames.packr.PackrConfig - def JDK_DIR = "$System.env.PACKR_DIR" def ICON_DIR = new File("core/assets/icons/icon.icns") -ext.getPlatform = { - def lc = project.hasProperty("platform") ? platform.toLowerCase() : "" - if(lc == "windows64"){ - return PackrConfig.Platform.Windows64 - }else if(lc == "windows32"){ - return PackrConfig.Platform.Windows32 - }else if(lc == "linux"){ - return PackrConfig.Platform.Linux64 - }else if(lc == "mac"){ - return PackrConfig.Platform.MacOS - }else{ - throw new InvalidUserDataException("Invalid platform. Set platform with -Pplatform=windows64/windows32/linux/mac") - } -} - task run(dependsOn: classes, type: JavaExec){ main = project.mainClassName classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = project.assetsDir - if(System.getProperty("os.name").toLowerCase().contains("mac")){ - jvmArgs "-XstartOnFirstThread" - } ignoreExitValue = true + + if(System.getProperty("os.name").toLowerCase().contains("mac")){ + jvmArgs("-XstartOnFirstThread", "-Djava.awt.headless=true") + } + if(project.hasProperty("args")){ args Eval.me(project.getProperties()["args"]) } @@ -47,132 +32,111 @@ task run(dependsOn: classes, type: JavaExec){ } } -task debug(dependsOn: classes, type: JavaExec){ - main = project.mainClassName - classpath = sourceSets.main.runtimeClasspath - standardInput = System.in - workingDir = project.assetsDir - ignoreExitValue = true - debug = true -} - -task dist(type: Jar){ - dependsOn classes - - writeVersion() +task dist(type: Jar, dependsOn: classes){ from files(sourceSets.main.output.classesDirs) from files(sourceSets.main.output.resourcesDir) - from{ configurations.compile.collect{ zipTree(it) } } + from {configurations.compile.collect {zipTree(it)}} from files(project.assetsDir) + archiveName = "${appName}.jar" + manifest{ attributes 'Main-Class': project.mainClassName } } -//note: call desktop:dist beforehand -task packrCmd(){ - doLast{ - def config = new PackrConfig() - config.with{ - config.executable = appName - verbose = true - platform = getPlatform() - bundleIdentifier = getPackage() + ".mac" - iconResource = ICON_DIR - outDir = file("packr-out/") - mainClass = project.ext.mainClassName - classpath = ["desktop/build/libs/desktop-release.jar"] - removePlatformLibs = ["desktop/build/libs/desktop-release.jar"] +PackrConfig.Platform.values().each{ platform -> + task "packr${platform.toString()}"{ + def platformName = platform.toString().replace('64', '').replace('32', '').replace('MacOS', 'Mac') - vmArgs = ["Djava.net.preferIPv4Stack=true"] - minimizeJre = "desktop/packr_minimize.json" - jdk = JDK_DIR + "jdk-${getPlatform().toString().toLowerCase()}.zip" - - if(getPlatform() == PackrConfig.Platform.MacOS){ - vmArgs += "XstartOnFirstThread" - } - } - - new Packr().pack(config) - } -} - -task copyTemplate(){ - doLast{ - copy{ - into "packr-out/" - from "${JDK_DIR}/templates/${getPlatform().toString().toLowerCase()}" - } - - copy{ - into "packr-out/" - from "build/libs/desktop-release.jar" - } - } -} - -task packrZip(){ - task clearOut(type: Delete){ - doLast{ - delete "packr-out/" - } - } - - task fixMac(type: Copy){ - dependsOn "packrCmd" - - into "packr-out/" + appName + ".app/Contents/" - from "packr-out/Contents/" - - doLast{ - delete{ - delete "packr-out/Contents/" - } - } - } - - task fixWindows32(type: Copy){ - dependsOn "packrCmd" - - into "packr-out/jre/bin/" - from JDK_DIR + "zip.dll" - rename("zip.dll", "ojdkbuild_zlib.dll") + dependsOn dist doLast{ copy{ - into "packr-out/jre/bin/" - from JDK_DIR + "zip.dll" + into "build/packr/" + rename("${appName}.jar", "desktop.jar") + from "build/libs/${appName}.jar" + } + + delete{ + delete "build/packr/output/" + } + + if(platform == PackrConfig.Platform.Windows32 || platform == PackrConfig.Platform.Windows64){ + copy{ + into "build/packr/output" + from "${JDK_DIR}/templates/${platform.toString().toLowerCase()}" + } + + copy{ + into "build/packr/output/jre" + rename("${appName}.jar", "desktop.jar") + from "build/libs/${appName}.jar" + } + }else{ + def config = new PackrConfig() + config.with{ + config.executable = appName + config.platform = platform + verbose = true + bundleIdentifier = getPackage() + ".mac" + iconResource = ICON_DIR + outDir = file("build/packr/output") + mainClass = project.ext.mainClassName + classpath = ["desktop/build/packr/desktop.jar"] + removePlatformLibs = ["desktop/build/packr/desktop.jar"] + + vmArgs = ["Djava.net.preferIPv4Stack=true"] + minimizeJre = "desktop/packr_minimize.json" + jdk = JDK_DIR + "jdk-${platform.toString().toLowerCase()}.zip" + + if(platform == PackrConfig.Platform.MacOS){ + vmArgs += "XstartOnFirstThread" + } + } + + new Packr().pack(config) + + if(platform == PackrConfig.Platform.Linux64){ + copy{ + into "build/packr/output/jre/" + from "build/packr/output/desktop.jar" + } + + delete{ + delete "build/packr/output/desktop.jar" + } + + file("build/packr/output/config.json").text = file("build/packr/output/config.json").text.replace("desktop.jar", "jre/desktop.jar") + } + + if(platform == PackrConfig.Platform.MacOS){ + copy{ + into "build/packr/${appName}.app/Contents/" + from "build/packr/Contents/" + } + + delete{ + delete "build/packr/Contents/" + } + } } } + + task "zip${platform.toString()}"(type: Zip){ + from "build/packr/output" + archiveName "${generateDeployName(platform.toString())}.zip" + destinationDir(file("../deploy")) + + doLast{ + delete{ + delete "build/packr/" + } + } + } + + finalizedBy "zip${platform.toString()}" } - - finalizedBy "clearOut" - - if(project.hasProperty("platform")){ - def plat = getPlatform() - if(plat == PackrConfig.Platform.Windows32 || plat == PackrConfig.Platform.Windows64){ - dependsOn "copyTemplate" - }else{ - dependsOn "packrCmd" - - if(getPlatform() == PackrConfig.Platform.MacOS){ - dependsOn "fixMac" - } - - if(getPlatform() == PackrConfig.Platform.Windows32){ - dependsOn "fixWindows32" - } - } - - task rzip(type: Zip){ - from "packr-out/" - archiveName "${generateDeployName(getPlatform().toString())}.zip" - destinationDir(file("packr-export")) - } - - finalizedBy 'rzip' - } -} \ No newline at end of file +} diff --git a/desktop/packr_minimize.json b/desktop/packr_minimize.json index 8be61942cc..a9e19bc83a 100644 --- a/desktop/packr_minimize.json +++ b/desktop/packr_minimize.json @@ -27,13 +27,25 @@ "com/sun/jndi", "com/sun/xml", "com/sun/script", - "sum/awt", + "sun/awt", "sun/java2d", "sun/font", "sun/rmi", "sun/swing", "java/util/stream", "com/sun/media", + "sun/print", + "sun/awt", + "sun/instrument", + "sun/security/tools", + "sun/security/smartcardio", + "sun/audio", + "javax/xml", + "javax/sound", + "javax/script", + "java/beans", + "java/applet", + "java/rmi", "com/sun/naming", "java/awt", "com/sun/org/apache/xpath", diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index d87a547712..5838584185 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -1,27 +1,29 @@ package io.anuke.mindustry.desktop; -import io.anuke.arc.backends.lwjgl3.Lwjgl3Application; -import io.anuke.arc.backends.lwjgl3.Lwjgl3ApplicationConfiguration; -import io.anuke.mindustry.Mindustry; -import io.anuke.mindustry.core.Platform; +import io.anuke.arc.Files.*; +import io.anuke.arc.backends.sdl.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.core.*; import io.anuke.mindustry.net.*; public class DesktopLauncher{ public static void main(String[] arg){ try{ - Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); - config.setTitle("Mindustry"); - config.setMaximized(true); - config.setBackBufferConfig(8, 8, 8, 8, 0, 0, 0); - config.setWindowedMode(900, 600); - config.setWindowIcon("icons/icon_64.png"); - Platform.instance = new DesktopPlatform(arg); - Net.setClientProvider(new ArcNetClient()); - Net.setServerProvider(new ArcNetServer()); - new Lwjgl3Application(new Mindustry(), config); + Net.setClientProvider(new MClient()); + Net.setServerProvider(new MServer()); + + new SdlApplication(new Mindustry(), new SdlConfig(){{ + title = "Mindustry"; + maximized = true; + depth = 0; + stencil = 0; + width = 900; + height = 700; + setWindowIcon(FileType.Internal, "icons/icon_64.png"); + }}); }catch(Throwable e){ DesktopPlatform.handleCrash(e); } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java index 92da3ecb1c..6ae3809a12 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopPlatform.java @@ -1,24 +1,23 @@ package io.anuke.mindustry.desktop; import club.minnced.discord.rpc.*; -import io.anuke.arc.collection.Array; -import io.anuke.arc.files.FileHandle; -import io.anuke.arc.function.Consumer; -import io.anuke.arc.function.Predicate; +import io.anuke.arc.backends.sdl.jni.SDL; +import io.anuke.arc.collection.*; +import io.anuke.arc.files.*; +import io.anuke.arc.function.*; import io.anuke.arc.util.*; -import io.anuke.arc.util.serialization.Base64Coder; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.net.CrashSender; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.ui.dialogs.FileChooser; -import org.lwjgl.util.tinyfd.TinyFileDialogs; +import io.anuke.arc.util.serialization.*; +import io.anuke.mindustry.core.GameState.*; +import io.anuke.mindustry.core.*; +import io.anuke.mindustry.net.*; +import io.anuke.mindustry.ui.dialogs.*; -import java.net.NetworkInterface; +import java.net.*; import java.util.*; import static io.anuke.mindustry.Vars.*; + public class DesktopPlatform extends Platform{ static boolean useDiscord = OS.is64Bit; final static String applicationId = "610508934456934412"; @@ -32,7 +31,8 @@ public class DesktopPlatform extends Platform{ if(useDiscord){ try{ DiscordEventHandlers handlers = new DiscordEventHandlers(); - DiscordRPC.INSTANCE.Discord_Initialize(applicationId, handlers, true, ""); + DiscordRPC.INSTANCE.Discord_Initialize(applicationId, handlers, true, "1127400"); + Log.info("Initialized Discord rich presence."); Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC.INSTANCE::Discord_Shutdown)); }catch(Throwable t){ @@ -43,16 +43,16 @@ public class DesktopPlatform extends Platform{ } static void handleCrash(Throwable e){ - Consumer dialog = r -> new Thread(r).start(); + Consumer dialog = Runnable::run; boolean badGPU = false; if(e.getMessage() != null && (e.getMessage().contains("Couldn't create window") || e.getMessage().contains("OpenGL 2.0 or higher"))){ - dialog.accept(() -> TinyFileDialogs.tinyfd_messageBox("oh no", + dialog.accept(() -> message( e.getMessage().contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + e.getMessage() : "Your graphics card does not support OpenGL 2.0!\n" + "Try to update your graphics drivers.\n\n" + - "(If that doesn't work, your computer just doesn't support Mindustry.)", "ok", "error", true)); + "(If that doesn't work, your computer just doesn't support Mindustry.)")); badGPU = true; } @@ -60,7 +60,7 @@ public class DesktopPlatform extends Platform{ CrashSender.send(e, file -> { if(!fbgp){ - dialog.accept(() -> TinyFileDialogs.tinyfd_messageBox("oh no", "A crash has occured. It has been saved in:\n" + file.getAbsolutePath(), "ok", "error", true)); + dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + (e.getMessage() == null ? "" : "\n" + e.getMessage()))); } }); } @@ -72,7 +72,6 @@ public class DesktopPlatform extends Platform{ @Override public void updateRPC(){ - if(!useDiscord) return; DiscordRichPresence presence = new DiscordRichPresence(); @@ -118,7 +117,7 @@ public class DesktopPlatform extends Platform{ String str = new String(Base64Coder.encode(result)); - if(str.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); + if(str.equals("AAAAAAAAAOA=") || str.equals("AAAAAAAAAAA=")) throw new RuntimeException("Bad UUID."); return str; }catch(Exception e){ @@ -126,10 +125,14 @@ public class DesktopPlatform extends Platform{ } } + private static void message(String message){ + SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MESSAGEBOX_ERROR, "oh no", message); + } + private boolean validAddress(byte[] bytes){ if(bytes == null) return false; byte[] result = new byte[8]; System.arraycopy(bytes, 0, result, 0, bytes.length); - return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA="); + return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=") && !new String(Base64Coder.encode(result)).equals("AAAAAAAAAAA="); } } diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000000..3aad746bc0 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file(ENV["FASTLANE_KEY_FILE"]) +package_name("io.anuke.mindustry") diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000000..17a304bf4f --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,46 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + desc "Runs all the tests" + lane :test do + gradle(task: "test") + end + + desc "Submit a new Beta Build to Crashlytics Beta" + lane :beta do + gradle(task: "clean assembleRelease") + crashlytics + + # sh "your_script.sh" + # You can also use other beta testing services here + end + + desc "Deploy a new version to the Google Play" + lane :deploy do + gradle(task: "clean assembleRelease") + upload_to_play_store + end + + #lane :playstore do + # gradle( + # task: 'assemble', + # build_type: 'Release' + # ) + # upload_to_play_store # Uploads the APK built in the gradle step above and releases it to all production users + #end +end diff --git a/fastlane/metadata/android/en-US/changelogs/12203.txt b/fastlane/metadata/android/en-US/changelogs/12203.txt new file mode 100644 index 0000000000..942424c5ea --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12203.txt @@ -0,0 +1,3 @@ +- Fixed incorrect attribution in credits; music was made by "A Drop A Day" +- Removed unneeded 3.5 upgrade dialog that sometimes showed up in main menu +- Added information dialog regarding v4 beta \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/12205.txt b/fastlane/metadata/android/en-US/changelogs/12205.txt new file mode 100644 index 0000000000..70bcab4bfc --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12205.txt @@ -0,0 +1,4 @@ +- Fixed incorrect attribution in credits; music was made by "A Drop A Day" +- Removed unneeded 3.5 upgrade dialog that sometimes showed up in main menu +- Added information dialog regarding v4 beta +- Fixed Discord link \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/26593.txt b/fastlane/metadata/android/en-US/changelogs/26593.txt new file mode 100644 index 0000000000..610563a652 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/26593.txt @@ -0,0 +1 @@ +sound. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/27434.txt b/fastlane/metadata/android/en-US/changelogs/27434.txt new file mode 100644 index 0000000000..5240303123 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27434.txt @@ -0,0 +1 @@ +everything \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/27476.txt b/fastlane/metadata/android/en-US/changelogs/27476.txt new file mode 100644 index 0000000000..ec779fbdba --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27476.txt @@ -0,0 +1 @@ +Fixed some minor sound/multiplayer issues \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/27689.txt b/fastlane/metadata/android/en-US/changelogs/27689.txt new file mode 100644 index 0000000000..92e8a8c47e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27689.txt @@ -0,0 +1 @@ +Bugfixes \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/27770.txt b/fastlane/metadata/android/en-US/changelogs/27770.txt new file mode 100644 index 0000000000..04d19b551f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27770.txt @@ -0,0 +1 @@ +Added ambient sounds for machines. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/27997.txt b/fastlane/metadata/android/en-US/changelogs/27997.txt new file mode 100644 index 0000000000..2b2809c72d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/27997.txt @@ -0,0 +1,2 @@ +The first release of version 4.0; an update that has been in the works for over a year. +Extensive changes, including new gamemodes, customizable rules, a new editor, new graphics, new enemies, unit production, new progression, a campaign, and more. See the in-game changelog link for specific details of what has been added and removed over the past year. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/28409.txt b/fastlane/metadata/android/en-US/changelogs/28409.txt new file mode 100644 index 0000000000..670c65cc45 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/28409.txt @@ -0,0 +1,2 @@ +Fixed multiplayer not syncing positions and shots correctly on certain networks or situations. +Various other bugfixes. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/721.txt b/fastlane/metadata/android/en-US/changelogs/721.txt new file mode 100644 index 0000000000..8361896cef --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/721.txt @@ -0,0 +1,5 @@ +- Fixed most formatted numbers being missing from Spanish translation +- Fixed wave timer being extremely slow at high TPS with multithreading enabled +- Fixed scrolling on player menu causing the whole screen to pan +- Updated Polish translation +- Updated Russian translation with new text (Thanks to @Prosta4okua) \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000000..b9958053f1 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,14 @@ +Create elaborate supply chains of conveyor belts to feed ammo into your turrets, produce materials to use for building, and defend your structures from waves of enemies. Play with your friends in cross-platform multiplayer co-op games, or challenge them in team-based PvP matches. + +Feature include: +- 24 built-in maps +- A campaign, complete with a tech tree and unlockable areas +- 4 powerful wave bosses to defeat +- Energy, liquid and item transportation systems +- 19 different types of drones, mechs and ships +- 120+ technology blocks to master +- 75+ different environmental blocks +- Cross-platform multiplayer via local networks or dedicated servers +- Custom game rules: Change block costs, enemy stats, starting items, wave timing and more +- A powerful editor, with tools to randomly generate ores, terrain, decoration and apply symmetry to maps +- Customizable map wave layouts \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png new file mode 100644 index 0000000000..90e27dddca Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000000..705e880a20 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png new file mode 100644 index 0000000000..44949de11d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png new file mode 100644 index 0000000000..7d37a27105 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png new file mode 100644 index 0000000000..53525bbd13 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png new file mode 100644 index 0000000000..091f53243c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png new file mode 100644 index 0000000000..952aa2aa54 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png new file mode 100644 index 0000000000..3830432e4f Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png new file mode 100644 index 0000000000..8fa92e537d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png new file mode 100644 index 0000000000..286795f87f Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png new file mode 100644 index 0000000000..7d37a27105 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png new file mode 100644 index 0000000000..091f53243c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000000..b6b5a942cd --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +A factory-based sandbox tower defense game. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 0000000000..2beb939017 --- /dev/null +++ b/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Mindustry \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/video.txt b/fastlane/metadata/android/en-US/video.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt new file mode 100644 index 0000000000..03219413cc --- /dev/null +++ b/fastlane/metadata/android/ru-RU/full_description.txt @@ -0,0 +1,15 @@ +Создавайте сложные логистические сети для переноса боеприпасов для турелей, добывайте ресурсы для строительства, и защищайте их от различных волн врагов. +Играйте с друзьями в кроссплатформенные кооперативные игры, или бросьте им вызов в командных PvP-матчах. + +Особенности игры: +- 24 встроенные карты +- Кампания, с полноценным технологическим древом и прогрессией карт +- 4 вида мощных боссов +- Системы для транспорта электроэнергии, жидкостей и ресурсов +- 19 видов дронов, кораблей и наземных боевых единиц +- Более 120 блоков в техногическом древе +- Более 75 видов натуральных блоков +- Кроссплатформенный мультиплеер с поддержкой как и локальных сетей, так и серверов +- Пользовательские настройки игры — изменяйте цену блоков, силу врагов, количество стартовых ресурсов, интервал между волнами и т.д. +- Редактор карт с бесчисленными возможностями, инструментами для случайной генерации руд, рельефа, декораций, а также для симметрии карт +- Настраиваемые раскладки волн для карт \ No newline at end of file diff --git a/fastlane/metadata/android/ru-RU/short_description.txt b/fastlane/metadata/android/ru-RU/short_description.txt new file mode 100644 index 0000000000..b6b5a942cd --- /dev/null +++ b/fastlane/metadata/android/ru-RU/short_description.txt @@ -0,0 +1 @@ +A factory-based sandbox tower defense game. \ No newline at end of file diff --git a/fastlane/metadata/android/ru-RU/title.txt b/fastlane/metadata/android/ru-RU/title.txt new file mode 100644 index 0000000000..2beb939017 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/title.txt @@ -0,0 +1 @@ +Mindustry \ No newline at end of file diff --git a/fastlane/metadata/android/ru-RU/video.txt b/fastlane/metadata/android/ru-RU/video.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ios/convert_audio.sh b/ios/convert_audio.sh index c4772fadc3..8e27eb60df 100755 --- a/ios/convert_audio.sh +++ b/ios/convert_audio.sh @@ -1,7 +1,15 @@ #!/usr/bin/bash -#convert ogg to .caf files for iOS -for i in $1/*.ogg; do +cd $1 + +#convert ogg to .mp3 files for iOS +for i in *.ogg; do echo $i - ffmpeg -i "$i" "${i%.*}.caf" -done \ No newline at end of file + ffmpeg -i "$i" "OUT_${i%.*}.mp3" +done + +find . -type f ! -name "OUT_*" -delete + +for file in OUT_*; do mv "$file" "${file#OUT_}"; done; + +cd ../../ diff --git a/ios/data/Default-568h@2x~iphone.png b/ios/data/Default-568h@2x~iphone.png index 153c6efdf4..9a0e377f65 100644 Binary files a/ios/data/Default-568h@2x~iphone.png and b/ios/data/Default-568h@2x~iphone.png differ diff --git a/ios/data/Default-667h.png b/ios/data/Default-667h.png index b3acc10e67..b5a4b57681 100644 Binary files a/ios/data/Default-667h.png and b/ios/data/Default-667h.png differ diff --git a/ios/data/Default-736h.png b/ios/data/Default-736h.png index f5a22468a3..8d05a25f44 100644 Binary files a/ios/data/Default-736h.png and b/ios/data/Default-736h.png differ diff --git a/ios/data/Default-Landscape-736h.png b/ios/data/Default-Landscape-736h.png index 04e32d4d7e..70badabb86 100644 Binary files a/ios/data/Default-Landscape-736h.png and b/ios/data/Default-Landscape-736h.png differ diff --git a/ios/data/Default-Landscape@2x~ipad.png b/ios/data/Default-Landscape@2x~ipad.png index 239e218934..da2884cfd1 100644 Binary files a/ios/data/Default-Landscape@2x~ipad.png and b/ios/data/Default-Landscape@2x~ipad.png differ diff --git a/ios/data/Default-Landscape~ipad.png b/ios/data/Default-Landscape~ipad.png index 7374a80f75..3b101edc7e 100644 Binary files a/ios/data/Default-Landscape~ipad.png and b/ios/data/Default-Landscape~ipad.png differ diff --git a/ios/data/Default-Portrait@2x~ipad.png b/ios/data/Default-Portrait@2x~ipad.png index c00e06f4e4..0989e62081 100644 Binary files a/ios/data/Default-Portrait@2x~ipad.png and b/ios/data/Default-Portrait@2x~ipad.png differ diff --git a/ios/data/Default-Portrait~ipad.png b/ios/data/Default-Portrait~ipad.png index 030e88c898..9f980c961a 100644 Binary files a/ios/data/Default-Portrait~ipad.png and b/ios/data/Default-Portrait~ipad.png differ diff --git a/ios/data/Default@2x~iphone.png b/ios/data/Default@2x~iphone.png index 5724809f89..710599b407 100644 Binary files a/ios/data/Default@2x~iphone.png and b/ios/data/Default@2x~iphone.png differ diff --git a/ios/data/Default~iphone.png b/ios/data/Default~iphone.png index fa72316347..527d691cc0 100644 Binary files a/ios/data/Default~iphone.png and b/ios/data/Default~iphone.png differ diff --git a/ios/src/io/anuke/mindustry/IOSLauncher.java b/ios/src/io/anuke/mindustry/IOSLauncher.java index 15988c3424..c8dcc9b510 100644 --- a/ios/src/io/anuke/mindustry/IOSLauncher.java +++ b/ios/src/io/anuke/mindustry/IOSLauncher.java @@ -26,8 +26,8 @@ public class IOSLauncher extends IOSApplication.Delegate{ @Override protected IOSApplication createApplication(){ - Net.setClientProvider(new ArcNetClient()); - Net.setServerProvider(new ArcNetServer()); + Net.setClientProvider(new MClient()); + Net.setServerProvider(new MServer()); if(UIDevice.getCurrentDevice().getUserInterfaceIdiom() == UIUserInterfaceIdiom.Pad){ UnitScl.dp.addition = 0.5f; @@ -40,7 +40,7 @@ public class IOSLauncher extends IOSApplication.Delegate{ @Override public void shareFile(FileHandle file){ Log.info("Attempting to share file " + file); - FileHandle to = Core.files.absolute(getDocumentsDirectory()).child(file.name()/* + ".png"*/); + FileHandle to = Core.files.absolute(getDocumentsDirectory()).child(file.name()); file.copyTo(to); NSURL url = new NSURL(to.file()); @@ -53,7 +53,6 @@ public class IOSLauncher extends IOSApplication.Delegate{ @Override public void beginForceLandscape(){ - Log.info("begin force landscape"); forced = true; UINavigationController.attemptRotationToDeviceOrientation(); } diff --git a/net/src/io/anuke/mindustry/net/ArcNetClient.java b/net/src/io/anuke/mindustry/net/ArcNetClient.java index 485235724f..9f8f09459d 100644 --- a/net/src/io/anuke/mindustry/net/ArcNetClient.java +++ b/net/src/io/anuke/mindustry/net/ArcNetClient.java @@ -4,10 +4,10 @@ import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.net.*; +import io.anuke.arc.util.async.*; import io.anuke.arc.util.pooling.*; import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Packets.*; -import net.jpountz.lz4.*; import java.io.*; import java.net.*; @@ -19,7 +19,6 @@ import static io.anuke.mindustry.Vars.*; public class ArcNetClient implements ClientProvider{ final Client client; final Supplier packetSupplier = () -> new DatagramPacket(new byte[256], 256); - final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor(); public ArcNetClient(){ client = new Client(8192, 4096, new PacketSerializer()); @@ -74,17 +73,9 @@ public class ArcNetClient implements ClientProvider{ } } - - @Override - public byte[] decompressSnapshot(byte[] input, int size){ - byte[] result = new byte[size]; - decompressor.decompress(input, result); - return result; - } - @Override public void connect(String ip, int port, Runnable success){ - runAsync(() -> { + Threads.daemon(() -> { try{ //just in case client.stop(); @@ -99,7 +90,7 @@ public class ArcNetClient implements ClientProvider{ updateThread.setDaemon(true); updateThread.start(); - client.connect(5000, ip, port, port); + client.connect(5000, ip, port); success.run(); }catch(Exception e){ handleException(e); @@ -115,11 +106,7 @@ public class ArcNetClient implements ClientProvider{ @Override public void send(Object object, SendMode mode){ try{ - if(mode == SendMode.tcp){ - client.sendTCP(object); - }else{ - client.sendUDP(object); - } + client.sendTCP(object); //sending things can cause an under/overflow, catch it and disconnect instead of crashing }catch(BufferOverflowException | BufferUnderflowException e){ Net.showError(e); @@ -140,7 +127,7 @@ public class ArcNetClient implements ClientProvider{ @Override public void pingHost(String address, int port, Consumer valid, Consumer invalid){ - runAsync(() -> { + Threads.daemon(() -> { try{ DatagramSocket socket = new DatagramSocket(); socket.send(new DatagramPacket(new byte[]{-2, 1}, 2, InetAddress.getByName(address), port)); @@ -189,12 +176,6 @@ public class ArcNetClient implements ClientProvider{ } } - private void runAsync(Runnable run){ - Thread thread = new Thread(run, "Client Async Run"); - thread.setDaemon(true); - thread.start(); - } - private void handleException(Exception e){ if(e instanceof ArcNetException){ Core.app.post(() -> Net.showError(new IOException("mismatch"))); diff --git a/net/src/io/anuke/mindustry/net/ArcNetServer.java b/net/src/io/anuke/mindustry/net/ArcNetServer.java index ed8496a8e2..219d37b437 100644 --- a/net/src/io/anuke/mindustry/net/ArcNetServer.java +++ b/net/src/io/anuke/mindustry/net/ArcNetServer.java @@ -1,14 +1,11 @@ package io.anuke.mindustry.net; -import com.dosse.upnp.*; import io.anuke.arc.*; -import io.anuke.arc.collection.*; import io.anuke.arc.net.*; import io.anuke.arc.util.*; -import io.anuke.mindustry.*; +import io.anuke.arc.util.async.*; import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Packets.*; -import net.jpountz.lz4.*; import java.io.*; import java.nio.*; @@ -19,14 +16,9 @@ import static io.anuke.mindustry.Vars.*; public class ArcNetServer implements ServerProvider{ final Server server; - final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); - final CopyOnWriteArraySet missing = new CopyOnWriteArraySet<>(); - final Array array = new Array<>(); - final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); + final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); Thread serverThread; - int lastconnection = 0; - public ArcNetServer(){ server = new Server(4096 * 2, 4096, new PacketSerializer()); server.setMulticast(multicastGroup, multicastPort); @@ -42,7 +34,7 @@ public class ArcNetServer implements ServerProvider{ public void connected(Connection connection){ String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress(); - KryoConnection kn = new KryoConnection(lastconnection++, ip, connection); + ArcConnection kn = new ArcConnection(ip, connection); Connect c = new Connect(); c.id = kn.id; @@ -56,7 +48,7 @@ public class ArcNetServer implements ServerProvider{ @Override public void disconnected(Connection connection){ - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(k == null) return; Disconnect c = new Disconnect(); @@ -70,7 +62,7 @@ public class ArcNetServer implements ServerProvider{ @Override public void received(Connection connection, Object object){ - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(object instanceof FrameworkMessage || k == null) return; Core.app.post(() -> { @@ -94,23 +86,14 @@ public class ArcNetServer implements ServerProvider{ } @Override - public byte[] compressSnapshot(byte[] input){ - return compressor.compress(input); + public Iterable getConnections(){ + return connections; } @Override - public Array getConnections(){ - array.clear(); - for(KryoConnection c : connections){ - array.add(c); - } - return array; - } - - @Override - public KryoConnection getByID(int id){ + public ArcConnection getByID(int id){ for(int i = 0; i < connections.size(); i++){ - KryoConnection con = connections.get(i); + ArcConnection con = connections.get(i); if(con.id == id){ return con; } @@ -121,28 +104,14 @@ public class ArcNetServer implements ServerProvider{ @Override public void host(int port) throws IOException{ - //attempt to open default ports if they're not already open - //this only opens the default port due to security concerns (?) - if(port == Vars.port){ - async(() -> { - try{ - if(!UPnP.isMappedTCP(port)) UPnP.openPortTCP(port); - if(!UPnP.isMappedUDP(port)) UPnP.openPortUDP(port); - }catch(Throwable ignored){ - } - }); - } - - lastconnection = 0; connections.clear(); - missing.clear(); - server.bind(port, port); + server.bind(port); serverThread = new Thread(() -> { try{ server.run(); }catch(Throwable e){ - if(!(e instanceof ClosedSelectorException)) handleException(e); + if(!(e instanceof ClosedSelectorException)) Threads.throwAppException(e); } }, "Net Server"); serverThread.setDaemon(true); @@ -152,102 +121,12 @@ public class ArcNetServer implements ServerProvider{ @Override public void close(){ connections.clear(); - lastconnection = 0; - - async(server::stop); + Threads.daemon(server::stop); } - @Override - public void sendStream(int id, Streamable stream){ - KryoConnection connection = getByID(id); - if(connection == null) return; - try{ - - if(connection.connection != null){ - - connection.connection.addListener(new InputStreamSender(stream.stream, 512){ - int id; - - protected void start(){ - //send an object so the receiving side knows how to handle the following chunks - StreamBegin begin = new StreamBegin(); - begin.total = stream.stream.available(); - begin.type = Registrator.getID(stream.getClass()); - connection.connection.sendTCP(begin); - id = begin.id; - } - - protected Object next(byte[] bytes){ - StreamChunk chunk = new StreamChunk(); - chunk.id = id; - chunk.data = bytes; - return chunk; //wrap the byte[] with an object so the receiving side knows how to handle it. - } - }); - }else{ - int cid; - StreamBegin begin = new StreamBegin(); - begin.total = stream.stream.available(); - begin.type = Registrator.getID(stream.getClass()); - connection.send(begin, SendMode.tcp); - cid = begin.id; - - while(stream.stream.available() > 0){ - byte[] bytes = new byte[Math.min(512, stream.stream.available())]; - stream.stream.read(bytes); - - StreamChunk chunk = new StreamChunk(); - chunk.id = cid; - chunk.data = bytes; - connection.send(chunk, SendMode.tcp); - } - } - }catch(IOException e){ - throw new RuntimeException(e); - } - } - - @Override - public void send(Object object, SendMode mode){ + ArcConnection getByKryoID(int id){ for(int i = 0; i < connections.size(); i++){ - connections.get(i).send(object, mode); - } - } - - @Override - public void sendTo(int id, Object object, SendMode mode){ - NetConnection conn = getByID(id); - if(conn == null){ - if(!missing.contains(id)) - Log.err("Failed to find connection with ID {0}.", id); - missing.add(id); - return; - } - conn.send(object, mode); - } - - @Override - public void sendExcept(int id, Object object, SendMode mode){ - for(int i = 0; i < connections.size(); i++){ - KryoConnection conn = connections.get(i); - if(conn.id != id) conn.send(object, mode); - } - } - - @Override - public void dispose(){ - close(); - } - - private void handleException(Throwable e){ - Time.run(0f, () -> { - throw new RuntimeException(e); - }); - } - - KryoConnection getByKryoID(int id){ - for(int i = 0; i < connections.size(); i++){ - KryoConnection con = connections.get(i); + ArcConnection con = connections.get(i); if(con.connection != null && con.connection.getID() == id){ return con; } @@ -256,17 +135,11 @@ public class ArcNetServer implements ServerProvider{ return null; } - void async(Runnable run){ - Thread thread = new Thread(run); - thread.setDaemon(true); - thread.start(); - } - - class KryoConnection extends NetConnection{ + class ArcConnection extends NetConnection{ public final Connection connection; - public KryoConnection(int id, String address, Connection connection){ - super(id, address); + public ArcConnection(String address, Connection connection){ + super(address); this.connection = connection; } @@ -278,17 +151,13 @@ public class ArcNetServer implements ServerProvider{ @Override public void send(Object object, SendMode mode){ try{ - if(mode == SendMode.tcp){ - connection.sendTCP(object); - }else{ - connection.sendUDP(object); - } + connection.sendTCP(object); }catch(Exception e){ Log.err(e); Log.info("Error sending packet. Disconnecting invalid client!"); connection.close(); - KryoConnection k = getByKryoID(connection.getID()); + ArcConnection k = getByKryoID(connection.getID()); if(k != null) connections.remove(k); } } diff --git a/net/src/io/anuke/mindustry/net/MClient.java b/net/src/io/anuke/mindustry/net/MClient.java new file mode 100644 index 0000000000..b9aca40e87 --- /dev/null +++ b/net/src/io/anuke/mindustry/net/MClient.java @@ -0,0 +1,177 @@ +package io.anuke.mindustry.net; + +import io.anuke.arc.*; +import io.anuke.arc.function.*; +import io.anuke.arc.util.async.*; +import io.anuke.arc.util.pooling.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.net.Net.*; +import io.anuke.mindustry.net.Packets.*; +import io.anuke.mnet.*; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.util.*; + +import static io.anuke.mindustry.Vars.*; + +public class MClient implements ClientProvider, ApplicationListener{ + MSocket socket; + + public MClient(){ + Events.on(AppLoadEvent.class, event -> Core.app.addListener(this)); + } + + public void connect(String ip, int port, Runnable success) throws IOException{ + socket = new MSocket(InetAddress.getByName(ip), port, PacketSerializer::new); + socket.addDcListener((sock, reason) -> Core.app.post(() -> Net.handleClientReceived(new Disconnect()))); + socket.connectAsync(null, 2000, response -> { + if(response.getType() == ResponseType.ACCEPTED){ + Core.app.post(() -> { + success.run(); + Net.handleClientReceived(new Connect()); + }); + }else if(response.getType() == ResponseType.WRONG_STATE){ + Core.app.post(() -> Net.showError(new IOException("alreadyconnected"))); + }else{ + Core.app.post(() -> Net.showError(new IOException("connection refused"))); + } + }); + } + + @Override + public void update(){ + if(socket == null) return; + + socket.update((sock, object) -> { + try{ + Net.handleClientReceived(object); + }catch(Exception e){ + Net.showError(e); + netClient.disconnectQuietly(); + } + }); + } + + @Override + public void updatePing(){ + + } + + @Override + public void dispose(){ + disconnect(); + } + + public void send(Object object, SendMode mode){ + if(mode == SendMode.tcp){ + socket.send(object); + }else{ + socket.sendUnreliable(object); + } + + Pools.free(object); + } + + public int getPing(){ + return socket == null ? 0 : (int)socket.getPing(); + } + + public void disconnect(){ + if(socket != null) socket.close(); + } + + public void discover(Consumer callback, Runnable done){ + Threads.daemon(() -> { + byte[] bytes = new byte[512]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + DatagramPacket packet = new DatagramPacket(bytes, bytes.length); + ArrayList foundAddresses = new ArrayList<>(); + + try(DatagramSocket socket = new DatagramSocket()){ + broadcast(port, socket); + + socket.setSoTimeout(4000); + + outer: + while(true){ + + try{ + socket.receive(packet); + }catch(SocketTimeoutException ex){ + done.run(); + return; + } + + buffer.position(0); + + InetAddress address = ((InetSocketAddress)packet.getSocketAddress()).getAddress(); + + for(InetAddress other : foundAddresses){ + if(other.equals(address) || (isLocal(other) && isLocal(address))){ + continue outer; + } + } + + Host host = NetworkIO.readServerData(address.getHostName(), buffer); + callback.accept(host); + foundAddresses.add(address); + } + }catch(IOException ex){ + done.run(); + } + }); + } + + public void pingHost(String address, int port, Consumer valid, Consumer failed){ + Threads.daemon(() -> { + try{ + DatagramPacket packet = new DatagramPacket(new byte[512], 512); + + DatagramSocket socket = new DatagramSocket(); + socket.send(new DatagramPacket(new byte[]{-2}, 1, InetAddress.getByName(address), port)); + socket.setSoTimeout(4000); + socket.receive(packet); + + ByteBuffer buffer = ByteBuffer.wrap(packet.getData()); + Host host = NetworkIO.readServerData(packet.getAddress().getHostAddress(), buffer); + + Core.app.post(() -> valid.accept(host)); + }catch(Exception e){ + Core.app.post(() -> failed.accept(e)); + } + }); + } + private void broadcast (int udpPort, DatagramSocket socket) throws IOException{ + byte[] data = {-2}; + + for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())){ + for (InetAddress address : Collections.list(iface.getInetAddresses())){ + + byte[] ip = address.getAddress(); //255.255.255.255 + try{ + socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), udpPort)); + }catch (Exception ignored){} + ip[3] = -1; //255.255.255.0 + try{ + socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), udpPort)); + }catch (Exception ignored){} + ip[2] = -1; //255.255.0.0 + try{ + socket.send(new DatagramPacket(data, data.length, InetAddress.getByAddress(ip), udpPort)); + }catch (Exception ignored){} + } + } + } + + private boolean isLocal(InetAddress addr) { + if (addr.isAnyLocalAddress() || addr.isLoopbackAddress()) return true; + + try { + return NetworkInterface.getByInetAddress(addr) != null; + } catch (Exception e) { + return false; + } + } +} diff --git a/net/src/io/anuke/mindustry/net/MServer.java b/net/src/io/anuke/mindustry/net/MServer.java new file mode 100644 index 0000000000..80c21cd285 --- /dev/null +++ b/net/src/io/anuke/mindustry/net/MServer.java @@ -0,0 +1,127 @@ +package io.anuke.mindustry.net; + +import io.anuke.arc.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.net.Net.*; +import io.anuke.mindustry.net.Packets.*; +import io.anuke.mnet.*; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.util.concurrent.*; + +public class MServer implements ServerProvider, ApplicationListener{ + final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); + MServerSocket socket; + + public MServer(){ + Events.on(AppLoadEvent.class, event -> Core.app.addListener(this)); + } + + @Override + public void update(){ + if(socket == null) return; + + socket.update(); + for(MSocket socket : socket.getSockets()){ + MConnectionImpl c = socket.getUserData(); + socket.update((s, msg) -> Core.app.post(() -> { + try{ + Net.handleServerReceived(c.id, msg); + }catch(Exception e){ + e.printStackTrace(); + } + })); + } + } + + @Override + public void host(int port) throws IOException{ + close(); + + socket = new MServerSocket(port, con -> { + MSocket sock = con.accept(null); + + MConnectionImpl kn = new MConnectionImpl(sock); + sock.setUserData(kn); + + String ip = sock.getRemoteAddress().getHostAddress(); + + Connect c = new Connect(); + c.id = kn.id; + c.addressTCP = ip; + + Log.info("&bRecieved connection: {0} / {1}", c.id, c.addressTCP); + + connections.add(kn); + Core.app.post(() -> Net.handleServerReceived(kn.id, c)); + + sock.addDcListener((socket, message) -> { + Log.info("&bLost connection {0}. Reason: {1}", kn.id, message); + + Disconnect dc = new Disconnect(); + dc.id = kn.id; + + Core.app.post(() -> { + Net.handleServerReceived(kn.id, dc); + connections.remove(kn); + }); + }); + }, PacketSerializer::new, () -> { + ByteBuffer buf = NetworkIO.writeServerData(); + byte[] bytes = buf.array(); + return new DatagramPacket(bytes, bytes.length); + }); + + connections.clear(); + } + + @Override + public void close(){ + if(socket != null){ + socket.close(); + socket = null; + } + } + + @Override + public Iterable getConnections(){ + return connections; + } + + @Override + public MConnectionImpl getByID(int id){ + for(MConnectionImpl n : connections){ + if(n.id == id){ + return n; + } + } + return null; + } + + class MConnectionImpl extends NetConnection{ + private final MSocket sock; //sock. + + public MConnectionImpl(MSocket con){ + super(con.getRemoteAddress().getHostAddress()); + this.sock = con; + } + + @Override + public void send(Object object, SendMode mode){ + if(mode == SendMode.tcp){ + sock.send(object); + }else{ + sock.sendUnreliable(object); + } + + } + + @Override + public void close(){ + sock.close(); + } + } +} diff --git a/net/src/io/anuke/mindustry/net/PacketSerializer.java b/net/src/io/anuke/mindustry/net/PacketSerializer.java index e79d5457aa..ad3c8f6e27 100644 --- a/net/src/io/anuke/mindustry/net/PacketSerializer.java +++ b/net/src/io/anuke/mindustry/net/PacketSerializer.java @@ -1,42 +1,85 @@ package io.anuke.mindustry.net; -import io.anuke.arc.function.Supplier; -import io.anuke.arc.net.FrameworkMessage; +import io.anuke.arc.function.*; +import io.anuke.arc.net.*; import io.anuke.arc.net.FrameworkMessage.*; -import io.anuke.arc.net.NetSerializer; -import io.anuke.arc.util.pooling.Pools; +import io.anuke.arc.util.pooling.*; +import io.anuke.mnet.*; -import java.nio.ByteBuffer; +import java.nio.*; +import java.util.*; @SuppressWarnings("unchecked") -public class PacketSerializer implements NetSerializer{ +public class PacketSerializer implements NetSerializer, MSerializer{ + private ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024 * 8); @Override public void write(ByteBuffer byteBuffer, Object o){ - if(o instanceof FrameworkMessage){ - byteBuffer.put((byte)-2); //code for framework message - writeFramework(byteBuffer, (FrameworkMessage)o); - }else{ - if(!(o instanceof Packet)) - throw new RuntimeException("All sent objects must implement be Packets! Class: " + o.getClass()); - byte id = Registrator.getID(o.getClass()); - if(id == -1) - throw new RuntimeException("Unregistered class: " + o.getClass()); - byteBuffer.put(id); - ((Packet)o).write(byteBuffer); + if(o == null){ + byteBuffer.put((byte)-1); + return; } + + if (!(o instanceof Packet)) + throw new RuntimeException("All sent objects must implement be Packets! Class: " + o.getClass()); + byte id = Registrator.getID(o.getClass()); + if (id == -1) + throw new RuntimeException("Unregistered class: " + o.getClass()); + byteBuffer.put(id); + ((Packet) o).write(byteBuffer); } @Override public Object read(ByteBuffer byteBuffer){ byte id = byteBuffer.get(); - if(id == -2){ - return readFramework(byteBuffer); - }else{ - Packet packet = Pools.obtain((Class)Registrator.getByID(id).type, (Supplier)Registrator.getByID(id).constructor); - packet.read(byteBuffer); - return packet; + if(id == -1){ + return null; } + Packet packet = Pools.obtain((Class) Registrator.getByID(id).type, (Supplier) Registrator.getByID(id).constructor); + packet.read(byteBuffer); + return packet; + } + + @Override + public byte[] serialize(Object o){ + buffer.position(0); + write(buffer, o); + return Arrays.copyOfRange(buffer.array(), 0, buffer.position()); + } + + @Override + public byte[] serialize(Object o, int offset){ + buffer.position(0); + write(buffer, o); + int length = buffer.position(); + byte[] bytes = new byte[length + offset]; + System.arraycopy(buffer.array(), 0, bytes, offset, length); + return bytes; + } + + @Override + public int serialize(Object o, byte[] bytes, int offset){ + buffer.position(0); + write(buffer, o); + int length = buffer.position(); + System.arraycopy(buffer.array(), 0, bytes, offset, length); + return length; + } + + @Override + public Object deserialize(byte[] bytes){ + buffer.position(0); + buffer.put(bytes); + buffer.position(0); + return read(buffer); + } + + @Override + public Object deserialize(byte[] bytes, int offset, int length){ + buffer.position(0); + buffer.put(bytes, offset, length); + buffer.position(0); + return read(buffer); } diff --git a/server/build.gradle b/server/build.gradle index 0118bb0f76..383ab91441 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -39,7 +39,9 @@ task dist(type: Jar){ exclude("music/**") exclude("sounds/**") exclude("fonts/**") + exclude("zones/**") exclude("com/badlogic/gdx/**") + exclude("icons/**") exclude("bundles/**") writeVersion() diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 8eeeba9e1c..934ba7e1fe 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -157,7 +157,7 @@ public class ServerControl implements ApplicationListener{ info("Selected next map to be {0}.", map.name()); - play(true, () -> world.loadMap(map)); + play(true, () -> world.loadMap(map, map.applyRules(lastMode))); } }else{ netServer.kickAll(KickReason.gameover); @@ -231,7 +231,7 @@ public class ServerControl implements ApplicationListener{ logic.reset(); lastMode = preset; try{ - world.loadMap(result); + world.loadMap(result, result.applyRules(lastMode)); state.rules = result.applyRules(preset); logic.play(); @@ -643,6 +643,13 @@ public class ServerControl implements ApplicationListener{ info("Nobody with that name could be found."); } }); + + handler.register("gc", "Trigger a grabage collection. Testing onlu.", arg -> { + int pre = (int)(Core.app.getJavaHeap() / 1024 / 1024); + System.gc(); + int post = (int)(Core.app.getJavaHeap() / 1024 / 1024); + info("&ly{0}&lg MB collected. Memory usage now at &ly{1}&lg MB.", pre - post, post); + }); } private void readCommands(){ @@ -700,10 +707,11 @@ public class ServerControl implements ApplicationListener{ run.run(); logic.play(); state.rules = world.getMap().applyRules(lastMode); + for(Player p : players){ p.reset(); if(state.rules.pvp){ - p.setTeam(netServer.assignTeam(new ArrayIterable<>(players))); + p.setTeam(netServer.assignTeam(p, new ArrayIterable<>(players))); } netServer.sendWorldData(p, p.con.id); } diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index 06f842c418..fdcbd5a61b 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -8,8 +8,8 @@ public class ServerLauncher{ public static void main(String[] args){ try{ - Net.setClientProvider(new ArcNetClient()); - Net.setServerProvider(new ArcNetServer()); + Net.setClientProvider(new MClient()); + Net.setServerProvider(new MServer()); new HeadlessApplication(new MindustryServer(args), null, throwable -> CrashSender.send(throwable, f -> {})); }catch(Throwable t){ CrashSender.send(t, f -> {}); diff --git a/settings.gradle b/settings.gradle index 20085aa2fb..ea39bf3078 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include 'desktop', 'desktop-sdl', 'core', 'net', 'server', 'ios', 'annotations', 'tools', 'tests' +include 'desktop', 'core', 'net', 'server', 'ios', 'annotations', 'tools', 'tests' def use = { String name -> include(name) @@ -27,6 +27,7 @@ if(!hasProperty("release")){ use(':Arc:extensions:freetype') use(':Arc:extensions:recorder') use(':Arc:extensions:arcnet') + use(':Arc:extensions:mnet') use(':Arc:extensions:packer') use(':Arc:backends') use(':Arc:backends:backend-sdl')