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 extends NetConnection> getConnections();
+ Iterable extends NetConnection> 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 extends NetConnection> 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')