Merge branch 'master' of https://github.com/Anuken/Mindustry into 4.0

# Conflicts:
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
#	core/assets/version.properties
#	core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java
This commit is contained in:
Anuken
2018-02-27 19:58:34 -05:00
59 changed files with 2419 additions and 1120 deletions

View File

@@ -16,7 +16,7 @@
android:theme="@style/GdxTheme" > android:theme="@style/GdxTheme" >
<activity <activity
android:name="io.anuke.mindustry.AndroidLauncher" android:name="io.anuke.mindustry.AndroidLauncher"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"> android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
<intent-filter> <intent-filter>

View File

@@ -20,12 +20,12 @@ allprojects {
version = 'release' version = 'release'
ext { ext {
versionNumber = '3.3' versionNumber = '3.4'
versionType = 'release' versionType = 'release'
appName = 'Mindustry' appName = 'Mindustry'
gdxVersion = '1.9.8' gdxVersion = '1.9.8'
aiVersion = '1.8.1' aiVersion = '1.8.1'
uCoreVersion = '23e8c1c' uCoreVersion = '0a15aeb'
getVersionString = { getVersionString = {
String buildVersion = getBuildVersion() String buildVersion = getBuildVersion()

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

@@ -25,6 +25,7 @@ text.server.kicked.kick=You have been kicked from the server!
text.server.kicked.invalidPassword=Invalid password! text.server.kicked.invalidPassword=Invalid password!
text.server.kicked.clientOutdated=Outdated client! Update your game! text.server.kicked.clientOutdated=Outdated client! Update your game!
text.server.kicked.serverOutdated=Outdated server! Ask the host to update! text.server.kicked.serverOutdated=Outdated server! Ask the host to update!
text.server.kicked.banned=You are banned on this server.
text.server.connected={0} has joined. text.server.connected={0} has joined.
text.server.disconnected={0} has disconnected. text.server.disconnected={0} has disconnected.
text.nohost=Can't host server on a custom map! text.nohost=Can't host server on a custom map!
@@ -37,10 +38,27 @@ text.server.refreshing=Refreshing server
text.hosts.none=[lightgray]No LAN games found! text.hosts.none=[lightgray]No LAN games found!
text.host.invalid=[scarlet]Can't connect to host. text.host.invalid=[scarlet]Can't connect to host.
text.server.friendlyfire=Friendly Fire text.server.friendlyfire=Friendly Fire
text.trace=Trace Player
text.trace.playername=Player name: [accent]{0}
text.trace.ip=IP: [accent]{0}
text.trace.modclient=Custom Client: [accent]{0}
text.trace.totalblocksbroken=Total blocks broken: [accent]{0}
text.trace.structureblocksbroken=Structure blocks broken: [accent]{0}
text.trace.lastblockbroken=Last block broken: [accent]{0}
text.trace.totalblocksplaced=Total blocks placed: [accent]{0}
text.trace.lastblockplaced=Last block placed: [accent]{0}
text.server.bans=Bans
text.server.bans.none=No banned players found!
text.server.admins=Admins
text.server.admins.none=No admins found!
text.server.add=Add Server text.server.add=Add Server
text.server.delete=Are you sure you want to delete this server? text.server.delete=Are you sure you want to delete this server?
text.server.hostname=Host: {0} text.server.hostname=Host: {0}
text.server.edit=Edit Server text.server.edit=Edit Server
text.confirmban=Are you sure you want to ban this player?
text.confirmunban=Are you sure you want to unban this player?
text.confirmadmin=Are you sure you want to make this player an admin?
text.confirmunadmin=Are you sure you want to remove admin status from this player?
text.joingame.byip=Join by IP... text.joingame.byip=Join by IP...
text.joingame.title=Join Game text.joingame.title=Join Game
text.joingame.ip=IP: text.joingame.ip=IP:

View File

@@ -0,0 +1,479 @@
text.about = Erstellt von [ROYAL] Anuken. [] \nUrsprünglich ein Eintrag im [orange] GDL [] MM Jam.\n\nCredits: \n- SFX gemacht mit [yellow] bfxr [] - Musik gemacht von [green] RoccoW [] / gefunden auf [lime] FreeMusicArchive.org [] \n\nBesonderer Dank geht an: \n- [coral] MitchellFJN []: Umfangreicher Spieletest und Feedback \n- [sky] Luxray5474 []: Wiki-Arbeit, Code-Beiträge \n- Alle Beta-Tester auf itch.io und Google Play\n
text.discord = Trete dem Mindustry Discord bei!
text.gameover = Der Kern wurde zerstört.
text.highscore = [YELLOW] Neuer Highscore!
text.lasted = Du hast bis zur folgenden Welle überlebt
text.level.highscore = High Score: [accent] {0}
text.level.delete.title = Löschen bestätigen
text.level.delete = Bist du sicher, dass du die Karte \"[orange] {0}\" löschen möchtest?
text.level.select = Level Auswahl
text.level.mode = Spielmodus:
text.savegame = Spiel speichern
text.loadgame = Spiel laden
text.joingame = Spiel beitreten
text.quit = Verlassen
text.about.button = Info
text.name = Name:
text.public = Öffentlich
text.players = {0} Spieler online
text.players.single = {0} Spieler online
text.server.mismatch = Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben!
.server.closing = [accent] Server wird geschlossen...
text.server.kicked.kick = Du wurdest vom Server gekickt!
text.server.kicked.invalidPassword = Falsches Passwort.
text.server.connected = {0} ist beigetreten
text.server.disconnected = {0} hat die Verbindung getrennt.
text.nohost = Server kann nicht auf einer benutzerdefinierten Karte gehostet werden!
text.hostserver = Server hosten
text.host = Host
text.hosting = [accent] Server wird geöffnet...
text.hosts.refresh = Aktualisieren
text.hosts.discovering = Suche nach LAN-Spielen
text.server.refreshing = Server wird aktualisiert
text.hosts.none = [lightgray] Keine LAN Spiele gefunden!
text.host.invalid = [scarlet] Kann keine Verbindung zum Host herstellen.
text.server.add = Server hinzufügen
text.server.delete = Bist du dir sicher das du diesen Server löschen möchtest?
text.server.hostname = Host: {0}
text.server.edit = Server bearbeiten
text.joingame.byip = Über IP beitreten ...
text.joingame.title = Spiel beitreten
text.joingame.ip = IP:
text.disconnect = Verbindung unterbrochen.
text.connecting = [accent] Verbindet...
text.connecting.data = [accent] Weltdaten werden geladen...
text.connectfail = [crimson] Verbindung zum Server konnte nicht hergestellt werden: [orange]{0}
text.server.port = Port:
text.server.invalidport = Falscher Port!
text.server.error = [crimson] Fehler beim Hosten des Servers: [orange] {0}
text.tutorial.back = < Zurück
text.tutorial.next = Weiter >
text.save.new = Neuer Spielstand
text.save.overwrite = Möchten du diesen Spielstand wirklich überschreiben?
text.overwrite = Überschreiben
text.save.none = Keine Spielstände gefunden!
text.saveload = [accent] Speichern ...
text.savefail = Fehler beim Speichern des Spiels!
text.save.delete.confirm = Möchtest du diesen Spielstand wirklich löschen?
text.save.delete = Löschen
text.save.export = Spielstand exportieren
text.save.import.invalid = [orange] Dieser Spielstand ist ungültig!
text.save.import.fail = [crimson] Spielstand konnte nicht importiert werden: [orange] {0}
text.save.export.fail = [crimson] Spielstand konnte nicht exportiert werden: [orange] {0}
text.save.import = Spielstand importieren
text.save.newslot = Name speichern:
text.save.rename = Umbenennen
text.save.rename.text = Neuer Name
text.selectslot = Wähle einen Spielstand
text.slot = [accent] Platz {0}
text.save.corrupted = [orange] Datei beschädigt oder ungültig!
text.empty = <leer>
text.on = An
text.off = Aus
text.save.autosave = Automatisches Speichern: {0}
text.save.map = Karte: {0}
text.save.wave = Welle: {0}
text.save.date = Zuletzt gespeichert: {0}
text.confirm = Bestätigen
text.delete = Löschen
text.ok = OK
text.open = Öffnen
text.cancel = Abbruch
text.openlink = Link öffnen
text.back = Zurück
text.quit.confirm = Willst du wirklich aufhören?
text.loading = [accent] Wird geladen ...
text.wave = [orange] Welle {0}
text.wave.waiting = Welle in {0}
text.waiting = Warten...
text.enemies = {0} Feinde
text.enemies.single = {0} Feind
text.loadimage = Bild laden
text.saveimage = Bild speichern
text.editor.badsize = [orange]Ungültige Bildabmessungen! [] Gültige Kartenabmessungen: {0}
text.editor.errorimageload = Fehler beim Laden des Bildes: [orange] {0}
text.editor.errorimagesave = Fehler beim Speichern des Bildes: [orange] {0}
text.editor.generate = Generieren
text.editor.resize = Grösse\nanpassen
text.editor.loadmap = Karte\nladen
text.editor.savemap = Karte\nspeichern
text.editor.loadimage = Bild\nladen
text.editor.saveimage = Bild\nspeichern
text.editor.unsaved = [crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören?
text.editor.brushsize = Pinselgrösse: {0}
text.editor.noplayerspawn = Diese Karte hat keinen Spielerspawnpunkt!
text.editor.manyplayerspawns = Maps können nicht mehr als einen Spawnpunkt des Spielers haben!
text.editor.manyenemyspawns = Kann nicht mehr als {0} feindliche Spawnpunkte haben!
text.editor.resizemap = Grösse der Karte ändern
text.editor.resizebig = [crimson] Warnung! [] Karten, die grösser als 256 Einheiten sind, können ruckeln und instabil sein.
text.editor.mapname = Map Name
text.editor.overwrite = [accent] Warnung! Dies überschreibt eine vorhandene Map.
text.editor.failoverwrite = [crimson] Die Standardkarte kann nicht überschrieben werden!
text.editor.selectmap = Wähle eine Map zum Laden:
text.width = Breite:
text.height = Höhe:
text.randomize = Zufällig
text.apply = Anwenden
text.update = Aktualisieren
text.menu = Menü
text.play = Spielen
text.load = Laden
text.save = Speichern
text.settings = Einstellungen
text.tutorial = Tutorial
text.editor = Bearbeiter
text.mapeditor = Karten Bearbeiter
text.donate = Spenden
text.settings.reset = Auf Standard zurücksetzen
text.settings.controls = Steuerung
text.settings.game = Spiel
text.settings.sound = Audio
text.settings.graphics = Grafiken
text.upgrades = Verbesserungen
text.purchased = [LIME] Erstellt!
text.weapons = Waffen
text.paused = Pausiert
text.respawn = Respawn in
text.error.title = [crimson] Ein Fehler ist aufgetreten
text.error.crashmessage = [SCARLET] Es ist ein unerwarteter Fehler aufgetreten, der einen Absturz verursacht hätte. [] Bitte geben Sie die genauen Umstände an, unter denen dieser Fehler passiert ist, für den Entwickler: [ORANGE] anukendev@gmail.com []
text.error.crashtitle = EIn Fehler ist aufgetreten!
text.mode.break = Zerstörungsmodus: {0}
text.mode.place = Platzierungsmodus: {0}
placemode.hold.name = Zeile
placemode.areadelete.name = Gebiet
placemode.touchdelete.name = berühren
placemode.holddelete.name = halten
placemode.none.name = keine
placemode.touch.name = berühren
placemode.cursor.name = Mauszeiger
text.blocks.extrainfo = [accent] Extra Blockinfo:
text.blocks.blockinfo = Blockinfo:
text.blocks.powercapacity = Energiekapazität
text.blocks.powershot = Energie / Schuss
text.blocks.powersecond = Energie / Sekunde
text.blocks.powerdraindamage = Energieabnahme / Schaden
text.blocks.shieldradius = Schildradius
text.blocks.itemspeedsecond = Gegenstands Geschwindigkeit / Sekunde
text.blocks.range = Reichweite
text.blocks.size = Grösse
text.blocks.powerliquid = Energie / Flüssigkeit
text.blocks.maxliquidsecond = Max Flüssigkeit / Sekunde
text.blocks.liquidcapacity = Flüssigkeitskapazität
text.blocks.liquidsecond = Flüssigkeit / Sekunde
text.blocks.damageshot = Schaden / Schuss
text.blocks.ammocapacity = Munitionskapazität
text.blocks.ammo = Munition
text.blocks.ammoitem = Munition / Gegenstand
text.blocks.maxitemssecond = Max Gegenstände / Sekunde
text.blocks.powerrange = Energiereichweite
text.blocks.lasertilerange = Laser Reichweite
text.blocks.capacity = Kapazität
text.blocks.itemcapacity = Gegenstand Kapazität
text.blocks.maxpowergenerationsecond = Max Energieerzeugung / Sekunde
text.blocks.powergenerationsecond = Energieerzeugung / Sekunde
text.blocks.generationsecondsitem = Generation Sekunden / Gegenstand
text.blocks.input = Eingabe
text.blocks.inputliquid = Flüssigkeiten Eingabe
text.blocks.inputitem = Eingabe Gegenstand
text.blocks.output = Ausgabe
text.blocks.secondsitem = Sekunden / Item
text.blocks.maxpowertransfersecond = Max Energieübertragung / Sekunde
text.blocks.explosive = Hochexplosiv!
text.blocks.repairssecond = Reparaturen / Sekunde
text.blocks.health = Lebenspunkte
text.blocks.inaccuracy = Ungenauigkeit
text.blocks.shots = Schüsse
text.blocks.shotssecond = Schüsse / Sekunde
text.blocks.fuel = Treibstoff
text.blocks.fuelduration = Treibstoffdauer
text.blocks.maxoutputsecond = Max Ausgabe / Sekunde
text.blocks.inputcapacity = Eingabekapazität
text.blocks.outputcapacity = Ausgabekapazität
text.blocks.poweritem = Energie / Gegenstand
text.placemode = Platzierungsmodus
text.breakmode = Zerstörungsmodus
text.health = Lebenspunkte
setting.difficulty.easy = Leicht
setting.difficulty.normal = Normal
setting.difficulty.hard = Schwer
setting.difficulty.insane = Unmöglich
setting.difficulty.purge = Auslöschung
setting.difficulty.name = Schwierigkeit
setting.screenshake.name = Bildschirm wackeln
setting.smoothcam.name = Glatte Kamera
setting.indicators.name = Feind Indikatoren
setting.effects.name = Effekte anzeigen
setting.sensitivity.name = Kontroller Empfindlichkeit
setting.saveinterval.name = Autosave Häufigkeit
setting.seconds = {0} Sekunden
setting.fps.name = Zeige FPS
setting.vsync.name = VSync
setting.lasers.name = Zeige Energielaser
setting.healthbars.name = Zeige Objekt Lebensbalken
setting.pixelate.name = Pixel Bildschirm
setting.musicvol.name = Musiklautstärke
setting.mutemusic.name = Musik stummschalten
setting.sfxvol.name = Audioeffekte Lautstärke
setting.mutesound.name = Audioeffekte stummschalten
map.maze.name = Labyrinth
map.fortress.name = Festung
map.sinkhole.name = Sinkloch
map.caves.name = Höhlen
map.volcano.name = Vulkan
map.caldera.name = Lavakessel
map.scorch.name = Flammen
map.desert.name = Wüste
map.island.name = Insel
map.grassland.name = Grasland
map.tundra.name = Kältesteppe
map.spiral.name = Spirale
map.tutorial.name = Tutorial
tutorial.intro.text = [gelb] Willkommen zum Tutorial [] Um zu beginnen, drücke 'weiter'.
tutorial.moveDesktop.text = Verwende zum Verschieben die Tasten [orange] [[WASD] []. Halte [orange] Shift [] gedrückt, um zu erhöhen. Halte [orange] CTRL/STRG [] gedrückt, während du das [orange] Scrollrad [] zum Vergrössern oder Verkleinern verwendest.
tutorial.shoot.text = Ziele mit der Maus, halte die [orange] linke Maustaste [] gedrückt, um zu schiessen. Versuche es mit dem [gelben] Ziel [].
tutorial.moveAndroid.text = Um die Ansicht zu verschieben, ziehe einen Finger über den Bildschirm. Drücke und ziehe, um zu vergrössern oder zu verkleinern.
tutorial.placeSelect.text = Versuche, ein [gelbes] Förderband [] aus dem Blockmenü unten rechts auszuwählen.
tutorial.placeConveyorDesktop.text = Verwende das [orange] [[scrollwheel] [], um das Förderband nach vorne zu bewegen [orange] vorwärts [], und platziere es dann an der [gelben] markierten Stelle [] mit [orange] [[linke Maustaste] [].
tutorial.placeConveyorAndroid.text = Verwende die [orange] [[rotieren-Taste] [], um das Förderband nach vorne [orange] zu drehen [], ziehe es mit einem Finger in Position und platziere es dann an der [gelben] markierten Stelle [] mit der [Orange] [[Häkchen][].
tutorial.placeConveyorAndroidInfo.text = Alternativ kannst du das Fadenkreuzsymbol unten links drücken, um zum [orange] [[touch mode] [] zu wechseln, und Blöcke durch Tippen auf den Bildschirm platzieren. Im Touch-Modus können Blöcke mit dem Pfeil unten links gedreht werden. Drücke [gelb] neben [], um es auszuprobieren.
tutorial.placeDrill.text = Wähle nun einen [gelben] Steinbohrer [] an der markierten Stelle aus und platziere ihn.
tutorial.blockInfo.text = Wenn du mehr über einen Block erfahren möchtest, tippe oben rechts auf das [orange] Fragezeichen [], um dessen Beschreibung zu lesen.
tutorial.deselectDesktop.text = Du kannst einen Block mit [Orange] [[Rechte Maustaste] [] abwählen.
tutorial.deselectAndroid.text = Du kannst einen Block abwählen, indem du die [orange] X [] -Taste drücken.
tutorial.drillPlaced.text = Der Bohrer erzeugt nun [gelben] Stein, [] gib den Stein nun auf das Förderband aus und bewege ihn dann in den [gelben] Kern [].
tutorial.drillInfo.text = Verschiedene Erze benötigen unterschiedliche Bohrer. Stein erfordert Steinbohrer, Eisen erfordert Eisenbohrer usw.
tutorial.drillPlaced2.text = Wenn du Gegenstände in den Kern verschiebst, steckst du sie in dein [gelbes] Inventar [] oben links. Das Platzieren von Blöcken verwendet Gegenstände aus deinem Inventar.
tutorial.moreDrills.text = Du kannst so viele Bohrer und Förderer miteinander verbinden wie du lust hast.
tutorial.deleteBlock.text = Du kannst Blöcke löschen, indem du mit der [orange] rechte Maustaste [] auf dem Block klickst, den du löschen möchtest. Versuche, dieses Förderband zu löschen.
tutorial.deleteBlockAndroid.text = Du kannst Blöcke löschen, indem du [orange] das Fadenkreuz [] im [orange] Pausenmodusmenü [] links unten auswählst und auf einen Block tippst. Versuche, dieses Fliessband zu löschen.
tutorial.placeTurret.text = Wähle nun einen [gelben] Turm [] an der [gelben] markierten Stelle [] und platziere ihn.
tutorial.placedTurretAmmo.text = Dieser Turm nimmt nun [gelbe] Munition [] vom Förderband an. Du kannst sehen, wie viel Munition es hat, indem du darüber schweben und den [grünen] grünen Balken [] prüfen.
tutorial.turretExplanation.text = Geschütze schiessen automatisch auf den nächsten Feind in Reichweite, solange sie genug Munition haben.
tutorial.waves.text = Jede [yellow] 60 [] Sekunden wird eine Welle von [coral] Feinden [] an einem bestimmten Orten erscheinen und versuchen, den Kern zu zerstören.
tutorial.coreDestruction.text = Dein Ziel ist es, den Kern [yellow] zu verteidigen. Wenn der Kern zerstört wird, verlierst du [coral] das Spiel [].
tutorial.pausingDesktop.text = Wenn du jemals eine Pause machen möchtest, drücke die [orange] Pause-Taste [] oben links oder [orange]space[], um das Spiel anzuhalten. Du kannst auch Blöcke immer noch auswählen und platzieren, während du das Spiel pausiert ist, aber Sie können sich nicht bewegen oder schiessen.
tutorial.pausingAndroid.text = Wenn du jemals eine Pause machen willst, drück einfach die [orange] Pause-Taste [] oben links, um das Spiel anzuhalten. Sie können immer noch Sachen auswählen und Blöcke während der Pause platzieren.
tutorial.purchaseWeapons.text = Du kannst neue [yellow] Waffen [] für deinen Mech kaufen, indem du das Verbesserungs-Menü unten links öffnest.
tutorial.switchWeapons.text = Schalte Waffen, indem du entweder auf das Symbol unten links klickst oder Nummern verwendest [orange] [[1-9] [].
tutorial.spawnWave.text = Hier kommt die erste Welle. Zerstöre sie.
tutorial.pumpDesc.text = In späteren Wellen müsst du möglicherweise [yellow] Pumpen [] verwenden, um Flüssigkeiten für Generatoren oder Extraktoren zu verteilen.
tutorial.pumpPlace.text = Pumpen arbeiten ähnlich wie Bohrer, ausser dass sie anstelle von Gegenständen Flüssigkeiten produzieren. Versuch mal, eine Pumpe auf das [yellow] gekennzeichnete Öl [] zu setzen.
tutorial.conduitUse.text = Stellen Sie nun eine [orange] Leitungsrohr [] von der Pumpe weg.
tutorial.conduitUse2.text = Und noch ein paar mehr ...
tutorial.conduitUse3.text = Und noch ein paar mehr ...
tutorial.generator.text = Stellen Sie nun einen [orange] Verbrennungsgenerator [] Block am Ende des Leitungsrohres auf.
tutorial.generatorExplain.text = Dieser Generator erzeugt nun [yellow] Energie [] aus dem Öl.
tutorial.lasers.text = Die Energie wird mit [yellow] Energielasern [] verteilt. Drehe und platziere einen hier.
tutorial.laserExplain.text = Der Generator wird nun Energie in den Laserblock bewegen. Ein [yellow] undurchsichtiger [] Strahl bedeutet, dass er gerade Leistung überträgt, und ein [yellow] transparenter [] Strahl bedeutet, dass dies nicht der Fall ist.
tutorial.laserMore.text = Du kannst überprüfen, wie viel Energie ein Block hat, indem du darüber schweben und den [yellow] gelben Balken [] oben prüfen.
tutorial.healingTurret.text = Dieser Laser kann verwendet werden, um einen [lime] -Reparaturgeschütz [] anzutreiben. Platziere einen hier.
tutorial.healingTurretExplain.text = Solange er Kraft hat, repariert dieser Turm in der Nähe Blöcke. [] Wenn du spielst, stelle sicher, dass du so schnell wie möglich einen in deiner Basis bekommst!
tutorial.smeltery.text = Viele Blöcke benötigen [orange] Stahl [], um eine [orange] Schmelzer [] herzustellen. Platziere einen hier.
tutorial.smelterySetup.text = Diese Schmelzer wird nun [orange] Stahl [] aus dem Eingangs-Eisen produzieren, wobei Kohle als Brennstoff verwendet wird.
tutorial.end.text = Und damit ist das Tutorial abgeschlossen! Viel Glück!
keybind.move_x.name = bewege_x
keybind.move_y.name = bewege_y
keybind.select.name = wählen
keybind.break.name = Unterbrechung
keybind.shoot.name = Schiess
keybind.zoom_hold.name = zoomen_halten
keybind.zoom.name = zoomen
keybind.menu.name = Menü
keybind.pause.name = Pause
keybind.dash.name = Bindestrich
keybind.rotate_alt.name = drehen_alt
keybind.rotate.name = Drehen
keybind.weapon_1.name = Waffe_1
keybind.weapon_2.name = Waffe_2
keybind.weapon_3.name = Waffe_3
keybind.weapon_4.name = Waffe_4
keybind.weapon_5.name = Waffe_5
keybind.weapon_6.name = Waffe_6
mode.waves.name = Wellen
mode.sandbox.name = Sandkasten
mode.freebuild.name = Freier Bau
upgrade.standard.name = Standard
upgrade.standard.description = Der Standardmech.
upgrade.blaster.name = Blaster
upgrade.blaster.description = Schiesst eine langsame, schwache Kugel.
upgrade.triblaster.name = Dreifach-Blaster
upgrade.triblaster.description = Schiesst 3 Kugeln in einer Ausbreitung.
upgrade.clustergun.name = Klumpen-Waffe
upgrade.clustergun.description = Schiesst eine ungenaue Verbreitung von explosiven Granaten.
upgrade.beam.name = Strahlkanone
upgrade.beam.description = Schiesst einen weitreichenden durchdringenden Laserstrahl.
upgrade.vulcan.name = Vulkan
upgrade.vulcan.description = Schiesst eine Flut von schnellen Kugeln.
upgrade.shockgun.name = Schock-Waffe
upgrade.shockgun.description = Erschiesst eine verheerende Explosion von geladenen Granatsplittern.
item.stone.name = Stein
item.iron.name = Eisen
item.coal.name = Kohle
item.steel.name = Stahl
item.titanium.name = Titan
item.dirium.name = Dirium
item.uranium.name = Uran
item.sand.name = Sand
liquid.water.name = Wasser
liquid.plasma.name = Plasma
liquid.lava.name = Lava
liquid.oil.name = Öl
block.weaponfactory.name = Waffenfabrik
block.air.name = Luft
block.blockpart.name = Blockteil
block.deepwater.name = tiefes Wasser
block.water.name = Wasser
block.lava.name = Lava
block.oil.name = Öl
block.stone.name = Stein
block.blackstone.name = schwarzer Stein
block.iron.name = Eisen
block.coal.name = Kohle
block.titanium.name = Titan
block.uranium.name = Uran
block.dirt.name = Erde
block.sand.name = Sand
block.ice.name = Eis
block.snow.name = Schnee
block.grass.name = Gras
block.sandblock.name = Sandstein
block.snowblock.name = Schneeblock
block.stoneblock.name = Steinblock
block.blackstoneblock.name = Schwarzer Stein
block.grassblock.name = Grasblock
block.mossblock.name = Moosblock
block.shrub.name = Busch
block.rock.name = Felsen
block.icerock.name = Eisfelsen
block.blackrock.name = Schwarzer Felsen
block.dirtblock.name = Erdblock
block.stonewall.name = Steinwand
block.stonewall.fulldescription = Ein billiger Verteidigungsblock. Nützlich zum Schutz des Kerns und der Geschütztürme in den ersten Wellen.
block.ironwall.name = Eisenwand
block.ironwall.fulldescription = Ein grundlegender Verteidigungsblock. Bietet Schutz vor Feinden.
block.steelwall.name = Stahlwand
block.steelwall.fulldescription = Ein Standard-Verteidigungsblock. Bietet angemessen Schutz vor Feinden.
block.titaniumwall.name = Titanwand
block.titaniumwall.fulldescription = Eine starke Abwehrblockade. Bietet Schutz vor Feinden.
block.duriumwall.name = Diriumwand
block.duriumwall.fulldescription = Eine sehr starke Abwehrblockade. Bietet guten Schutz vor Feinden.
block.compositewall.name = Verbundende Wand
block.steelwall-large.name = grosse Stahlwand
block.steelwall-large.fulldescription = Ein Standard-Verteidigungsblock. Mehrere Blöcke gross.
block.titaniumwall-large.name = grosse Titanwand
block.titaniumwall-large.fulldescription = Eine starke Abwehrblockade. Mehrere Blöcke gross.
block.duriumwall-large.name = grosse Diriumwand
block.duriumwall-large.fulldescription = Eine sehr starke Abwehrblockade. Mehrere Blöcke gross.
block.titaniumshieldwall.name = geschützte Wand
block.titaniumshieldwall.fulldescription = Ein starker Abwehrblock mit einem extra eingebauten Schild. Benötigt Energie. Verwendet Energie, um feindliche Kugeln zu absorbieren. Es wird empfohlen, Verstärker zu verwenden, um diesem Block Energie zuzuführen.
block.repairturret.name = Reparaturgeschütz
block.repairturret.fulldescription = Repariert beschädigte Blöcke in der Nähe in einem langsamen Tempo. Nutzt geringe Mengen an Energie.
block.megarepairturret.name = Reparaturgeschütz II
block.megarepairturret.fulldescription = Repariert in der Nähe beschädigte Blöcke in Reichweite zu einem vernünftigen Preis. Verwendet Energie.
block.shieldgenerator.name = Schildgenerator
block.shieldgenerator.fulldescription = Ein fortgeschrittener Verteidigungsblock. Schützt alle Blöcke in einem Radius vor Angriffen. Bei keinem Betrieb langsamer Strom verbrauch, verliert bei Kugelkontakt jedoch schnell Energie.
block.door.name = Tür
block.door.fulldescription = Ein Block, der durch Antippen geöffnet und geschlossen werden kann.
block.door-large.name = grosse Tür
block.door-large.fulldescription = Ein Block der mehrere Felder gross ist und der durch Antippen geöffnet und geschlossen werden kann.
block.conduit.name = Leitungsrohr
block.conduit.fulldescription = Grundlegender Flüssigkeitstransportblock. Funktioniert wie ein Förderband, aber mit Flüssigkeiten. Am besten mit Pumpen oder anderen Leitungen verwenden. Kann als Brücke für Gegner und Spieler verwendet werden.
block.pulseconduit.name = Pulsleitungsrohr
block.pulseconduit.fulldescription = Fortschrittlicher Flüssigkeitstransportblock. Transportiert Flüssigkeiten schneller und speichert mehr als normale Leitungsrohre.
block.liquidrouter.name = flüssigkeiten verteiler
block.liquidrouter.fulldescription = Funktioniert ähnlich wie ein Router. Akzeptiert Flüssigkeit von einer Seite und gibt sie auf die anderen Seiten aus. Nützlich zum Aufspalten von Flüssigkeit aus eines einzelnen Leitungsrohres in mehrere andere Leitungensrohre.
block.conveyor.name = Förderband
block.conveyor.fulldescription = Grundelement Transport Block. Bewegt Gegenstände nach vorne und legt sie automatisch in Türmen oder ähnliches. Drehbar. Kann als Brücke für Gegner und Spieler verwendet werden.
block.steelconveyor.name = Stahlförderband
block.steelconveyor.fulldescription = Erweiterter Transportblock Bewegt Gegenstände schneller als Standardförderer.
block.poweredconveyor.name = Impulsförderband
block.poweredconveyor.fulldescription = Der ultimative Transportblock für Gegenstände. Bewegt Gegenstände noch schneller als Stahlförderer.
block.router.name = Verteiler
block.router.fulldescription = Akzeptiert Objekte aus einer Richtung und gibt sie in 3 andere Richtungen aus. Kann auch eine bestimmte Anzahl von Gegenständen speichern. Geeignet zum Aufteilen der Materialien von einem Bohrer in mehrere Geschütze.
block.junction.name = Kreuzung
block.junction.fulldescription = Fungiert als Brücke für zwei kreuzende Förderbänder. Nützlich in Situationen mit zwei verschiedenen Förderbänder, die unterschiedliche Materialien zu verschiedenen Orten transportieren.
block.conveyortunnel.name = Förderbandtunnel
block.conveyortunnel.fulldescription = Transportiert Artikel unter Blöcken. Verwendung für einen Tunnel, der in den zu untertunnelnden Block führt, und einen auf der anderen Seite. Stellen Sie sicher, dass beide Tunnel in entgegengesetzte Richtungen weisen, das heisst das die Tunnel voneinander weggucken.
block.liquidjunction.name = flüssigkeite Kreuzung
block.liquidjunction.fulldescription = Funktioniert als Brücke für zwei kreuzende Leitungsrohre. Nützlich in Situationen mit zwei verschiedenen Leitungen, die unterschiedliche Flüssigkeiten zu verschiedenen Orten transportieren.
block.liquiditemjunction.name = Flüssigkeit-Gegenstand-Kreuzung
block.liquiditemjunction.fulldescription = Fungiert als Brücke zum Überqueren von Leitungsrohre und Förderbändern.
block.powerbooster.name = Energieverstärker
block.powerbooster.fulldescription = Verteilt die Energie an alle Blöcke innerhalb seines Radius.
block.powerlaser.name = Energielaser
block.powerlaser.fulldescription = Erzeugt einen Laser, der die Kraft auf den Block davor überträgt. Erzeugt selbst keine Energie. Am besten mit Generatoren oder anderen Lasern verwendet.
block.powerlaserrouter.name = Laser Verteiler
block.powerlaserrouter.fulldescription = Laser, der die Kraft in drei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen.
block.powerlasercorner.name = Laser-Ecke
block.powerlasercorner.fulldescription = Laser, der die Kraft in zwei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen und ein Router ungenau ist.
block.teleporter.name = Teleporter
block.teleporter.fulldescription = Erweiterter Transportblock Teleporter geben Gegenstände in andere Teleporter derselben Farbe ein. Tut nichts, wenn keine Teleporter derselben Farbe existieren. Wenn mehrere Teleporter mit derselben Farbe existieren, wird eine zufällige ausgewählt. Verwendet Energie. Tippen, um die Farbe zu ändern.
block.sorter.name = Sortierer
block.sorter.fulldescription = Sortiert den Gegenstand nach Materialart. Das zu akzeptierende Material wird durch die Farbe im Block angezeigt. Alle Artikel, die dem Sortiermaterial entsprechen, werden vorwärts ausgegeben, alles andere wird nach links und rechts ausgegeben.
block.core.name = Kern
block.pump.name = Pumpe
block.pump.fulldescription = Pumpen Flüssigkeiten aus einem Quellblock - meist Wasser, Lava oder Öl. Gibt Flüssigkeit in benachbarte Leitungsrohre aus.
block.fluxpump.name = flux Pumpe
block.fluxpump.fulldescription = Eine erweiterte Version der Pumpe. Speichert mehr Flüssigkeit und pumpt Flüssigkeit schneller.
block.smelter.name = Schmelzer
block.smelter.fulldescription = Der essentielle Bastelblock. Wenn 1x Eisen und 1x Kohle eingegeben wird, wird 1x Stahl ausgegeben.
block.crucible.name = Tiegel
block.crucible.fulldescription = Ein fortgeschrittener Handwerksblock. Braucht Kohle um zu funktionieren. Wenn 1x Titan und 1x Stahl eingegeben wird, wird 1x Dirium ausgegeben.
block.coalpurifier.name = Kohle-Extraktor
block.coalpurifier.fulldescription = Ein einfacher Extraktorblock. Gibt Kohle aus, wenn der Block mit grossen Mengen Wasser und Stein gefüllt wird.
block.titaniumpurifier.name = Titan-Extraktor
block.titaniumpurifier.fulldescription = Ein Standard-Extraktorblock. Gibt Titan aus wenn er mit grossen Mengen Wasser und Eisen gefüllt wird.
block.oilrefinery.name = Ölraffinerie
block.oilrefinery.fulldescription = Veredelt grosse Mengen Öl zu Kohle. Nützlich für die Betankung von Blöcken die Kohle benutzen wie z.b. Waffentürme, wenn Kohleadern knapp sind.
block.stoneformer.name = Steinformer
block.stoneformer.fulldescription = Verfestigt flüssige Lava zu Stein. Nützlich für die Herstellung von grossen Mengen von Stein für Kohle-Extraktor.
block.lavasmelter.name = Lava-Schmelzer
block.lavasmelter.fulldescription = Verwendet Lava, um Eisen zu Stahl schmelzen. Eine Alternative zum Schmelzer. Nützlich in Situationen, in denen Kohle knapp ist.
block.stonedrill.name = Steinbohrer
block.stonedrill.fulldescription = Der wesentliche Bohrer. Wenn er auf Steinplatten gelegt wird, gibt er Steine mit einer langsamen Geschwindigkeit auf endlosen Zeit aus.
block.irondrill.name = Eisenbohrer
block.irondrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Eisenerz gelegt wird, gibt er Eisen auf endloser Zeit langsam aus.
block.coaldrill.name = Kohlenbohrer
block.coaldrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Kohleerz platziert wird, gibt er für endloser Zeit langsam Kohle aus.
block.uraniumdrill.name = Uran-Bohrer
block.uraniumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Uranerz platziert wird, gibt er Uran mit einer langsamen Geschwindigkeit auf endloser Zeit ab.
block.titaniumdrill.name = Titan-Bohrer
block.titaniumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Titanerz platziert wird, gibt er Titan langsam aus für undendlich langer Zeit
block.omnidrill.name = Omni-Bohrer
block.omnidrill.fulldescription = Der ultimative Bohrer. Baut in schnellem Tempo jegliches Erz ab.
block.coalgenerator.name = Kohle-Generator
block.coalgenerator.fulldescription = Der wesentliche Generator. Erzeugt Energie aus Kohle. Gibt Energie als Laser an seine 4 Seiten aus.
block.thermalgenerator.name = thermischer Generator
block.thermalgenerator.fulldescription = Erzeugt Energie aus Lava. Gibt Energie als Laser an seine 4 Seiten aus.
block.combustiongenerator.name = Verbrennungsgenerator
block.combustiongenerator.fulldescription = Erzeugt Energie aus Öl. Gibt Energie als Laser an seine 4 Seiten aus.
block.rtgenerator.name = RTG-Generator
block.rtgenerator.fulldescription = Erzeugt geringe Mengen an Energie aus dem radioaktiven Zerfall von Uran. Gibt Energie als Laser an seine 4 Seiten aus.
block.nuclearreactor.name = Kernreaktor
block.nuclearreactor.fulldescription = Eine erweiterte Version des RTG-Generators und der ultimative Energie Generator. Erzeugt Strom aus Uran. Erfordert konstante Wasserkühlung. Sehr heiss; explodiert heftig, wenn zu wenig Kühlmittel zugeführt wird.
block.turret.name = Geschütz
block.turret.fulldescription = Ein einfacher, billiger Turm. Verwendet Stein für Munition. Hat etwas mehr Reichweite als das Doppelgeschütz.
block.doubleturret.name = Doppelgeschütz
block.doubleturret.fulldescription = Eine etwas stärkere Version des Geschützes. Verwendet Stein für Munition. Hat deutlich mehr Schaden, hat aber eine geringere Reichweite. Schiesst zwei Kugeln.
block.machineturret.name = Gatling Geschütz
block.machineturret.fulldescription = Ein Standard-Allround-Turm. Verwendet Eisen für Munition. Hat eine schnelle Feuerrate mit gutem Schaden.
block.shotgunturret.name = Splittergeschütz
block.shotgunturret.fulldescription = Ein Standard-Turm. Verwendet Eisen für Munition. Schiesst 7 Kugeln auf einmal. Geringere Reichweite, aber höhere Schadensleistung als das Gatling Geschütz
block.flameturret.name = Flammenwerfer
block.flameturret.fulldescription = Fortschrittlicher Nahbereichswaffe. Verwendet Kohle für Munition. Hat eine sehr geringe Reichweite, aber sehr hohen Schaden. Gut für Nahkampf. Empfohlen für den Einsatz hinter Mauern.
block.sniperturret.name = Schienenkanone
block.sniperturret.fulldescription = Fortschrittliches Reichweitengeschütz. Verwendet Stahl für Munition. Sehr hoher Schaden, aber niedrige Feuerrate. Teuer zu verwenden, kann aber aufgrund seiner Reichweite weit entfernt von den feindlichen Linien platziert werden.
block.mortarturret.name = Flakgeschütz
block.mortarturret.fulldescription = Fortschrittlicher Flächen-Schaden Turm. Verwendet Kohle für Munition. Sehr langsame Feuerrate und Geschosse, aber sehr hoher Einzelziel- und Flächenschaden. Nützlich gegen grosse Mengen von Feinden.
block.laserturret.name = Laserturm
block.laserturret.fulldescription = Fortschrittlicher Einzelziel-Turm. Verwendet Strom. Guter Allround-Revolver für mittlere Reichweiten. Nur Einzelziel. Verfehlt nie.
block.waveturret.name = Teslakanone
block.waveturret.fulldescription = Erweiterter Mehrfach-Ziele-Turm. Verwendet Strom. Mittlere Reichweite. Verfehlt nie. Im Durchschnitt zu wenig Schaden, aber kann mehrere Feinde gleichzeitig mit Kettenblitz treffen.
block.plasmaturret.name = Plasmageschütz
block.plasmaturret.fulldescription = Hochentwickelte Version des Flammenwerfers. Verwendet Kohle als Munition. Sehr hoher Schaden, niedriger bis mittlerer Reichweite.
block.chainturret.name = Kettengeschütz
block.chainturret.fulldescription = Die ultimative Schnellfeuer Waffe. Verwendet Uran als Munition. Schiesst grosse Kugeln mit hoher Feuerrate. Mittlere Reichweite. Mehrere Felder gross. Hält extrem viel aus.
block.titancannon.name = Titan Kanone
block.titancannon.fulldescription = Die ultimative Langstrecken Kanone. Verwendet Uran als Munition. Schiesst grosse Flächen-Schadenden-Granaten mit einer mittleren Feuerrate ab. Lange Reichweite. Ist mehrere Felder gross. Hält extrem viel aus.
block.playerspawn.name = Spielerspawn
block.enemyspawn.name = Gegnerspawn

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -62,7 +62,7 @@ io.anuke.ucore.scene.ui.ImageButton$ImageButtonStyle: {
emptytoggle: {imageCheckedColor: white, imageDownColor: white, imageUpColor: lightgray}, emptytoggle: {imageCheckedColor: white, imageDownColor: white, imageUpColor: lightgray},
static: {up: button }, static: {up: button },
static-down: {up: button-down }, static-down: {up: button-down },
toggle: {checked: button-down, down: button-down, up: button }, toggle: {checked: button-down, down: button-down, up: button, imageDisabledColor: lightgray, imageUpColor: white },
togglemap: {down: button-map-down, up: button-map }, togglemap: {down: button-map-down, up: button-map },
select: {checked: button-select, up: clear }, select: {checked: button-select, up: clear },
close-window: {up: button, imageUp: icon-close, imageOver: icon-close-over, imageDown: icon-close-down, disabled: button } close-window: {up: button, imageUp: icon-close, imageOver: icon-close-over, imageDown: icon-close-down, disabled: button }
@@ -83,7 +83,7 @@ io.anuke.ucore.scene.ui.Window$WindowStyle: {
dialog: {stageBackground: dialogDim, titleFont: default-font, background: window-empty, titleFontColor: accent } dialog: {stageBackground: dialogDim, titleFont: default-font, background: window-empty, titleFontColor: accent }
}, },
io.anuke.ucore.scene.ui.KeybindDialog$KeybindDialogStyle: { io.anuke.ucore.scene.ui.KeybindDialog$KeybindDialogStyle: {
default: {keyColor: accent, keyNameColor: white, paneStyle: clear}, default: {keyColor: accent, keyNameColor: white, controllerColor: menuitem, paneStyle: clear},
}, },
io.anuke.ucore.scene.ui.Slider$SliderStyle: { io.anuke.ucore.scene.ui.Slider$SliderStyle: {
default-horizontal: {background: slider, knob: slider-knob, knobOver: slider-knob-over, knobDown: slider-knob-down}, default-horizontal: {background: slider, knob: slider-knob, knobOver: slider-knob-over, knobDown: slider-knob-down},

View File

@@ -1,7 +1,7 @@
#Autogenerated file. Do not modify. #Autogenerated file. Do not modify.
#Thu Feb 22 23:52:21 EST 2018 #Tue Feb 27 19:35:27 EST 2018
version=release version=release
androidBuildCode=282 androidBuildCode=313
name=Mindustry name=Mindustry
code=3.3 code=3.4
build=custom build build=29

View File

@@ -15,4 +15,5 @@
<extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.net.Packet" /> <extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.net.Packet" />
<extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.net.Streamable" /> <extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.net.Streamable" />
<extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.world.BlockBar" /> <extend-configuration-property name="gdx.reflect.include" value="io.anuke.mindustry.world.BlockBar" />
<extend-configuration-property name="gdx.reflect.include" value="com.badlogic.gdx.utils.Predicate" />
</module> </module>

View File

@@ -93,7 +93,7 @@ public class Vars{
public static final int tilesize = 8; public static final int tilesize = 8;
public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("pl", "PL"), public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("pl", "PL"),
new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")}; new Locale("de"), new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")};
public static final Color[] playerColors = { public static final Color[] playerColors = {
Color.valueOf("82759a"), Color.valueOf("82759a"),

View File

@@ -169,8 +169,8 @@ public class Pathfind{
/**Reset and clear the paths.*/ /**Reset and clear the paths.*/
public void resetPaths(){ public void resetPaths(){
for(SpawnPoint point : world.getSpawns()){ for(int i = 0; i < world.getSpawns().size; i ++){
resetPathFor(point); resetPathFor(world.getSpawns().get(i));
} }
} }

View File

@@ -121,7 +121,7 @@ public class Logic extends Module {
if(world.getCore() != null && world.getCore().block() != ProductionBlocks.core && !state.gameOver){ if(world.getCore() != null && world.getCore().block() != ProductionBlocks.core && !state.gameOver){
state.gameOver = true; state.gameOver = true;
NetEvents.handleGameOver(); if(Net.server()) NetEvents.handleGameOver();
Events.fire(GameOverEvent.class); Events.fire(GameOverEvent.class);
} }

View File

@@ -17,7 +17,9 @@ import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Map; import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.ProductionBlocks; import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
@@ -49,6 +51,7 @@ public class NetClient extends Module {
public NetClient(){ public NetClient(){
Net.handleClient(Connect.class, packet -> { Net.handleClient(Connect.class, packet -> {
player.isAdmin = false;
Net.setClientLoaded(false); Net.setClientLoaded(false);
recieved.clear(); recieved.clear();
@@ -159,12 +162,22 @@ public class NetClient extends Module {
state.wavetime = packet.countdown; state.wavetime = packet.countdown;
state.wave = packet.wave; state.wave = packet.wave;
//removed: messing with time isn't necessary anymore
//Timers.resetTime(packet.time + (float) (TimeUtils.timeSinceMillis(packet.timestamp) / 1000.0 * 60.0));
ui.hudfrag.updateItems(); ui.hudfrag.updateItems();
}); });
Net.handleClient(PlacePacket.class, (packet) -> {
Placement.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false);
if(packet.playerid == player.id){
Tile tile = world.tile(packet.x, packet.y);
if(tile != null) Block.getByID(packet.block).placed(tile);
}
});
Net.handleClient(BreakPacket.class, (packet) -> {
Placement.breakBlock(packet.x, packet.y, true, false);
});
Net.handleClient(EntitySpawnPacket.class, packet -> { Net.handleClient(EntitySpawnPacket.class, packet -> {
EntityGroup group = packet.group; EntityGroup group = packet.group;
@@ -316,6 +329,22 @@ public class NetClient extends Module {
r.run(); r.run();
} }
}); });
Net.handleClient(NetErrorPacket.class, packet -> {
ui.showError(packet.message);
disconnectQuietly();
});
Net.handleClient(PlayerAdminPacket.class, packet -> {
Player player = playerGroup.getByID(packet.id);
player.isAdmin = packet.admin;
ui.listfrag.rebuild();
});
Net.handleClient(TracePacket.class, packet -> {
Player player = playerGroup.getByID(packet.info.playerid);
ui.traces.show(player, packet.info);
});
} }
@Override @Override
@@ -329,12 +358,6 @@ public class NetClient extends Module {
} }
} }
//TODO remove.
public void test(){
gotData = false;
connecting = true;
}
public boolean hasData(){ public boolean hasData(){
return gotData; return gotData;
} }

View File

@@ -4,11 +4,8 @@ import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.Weapon; import io.anuke.mindustry.resource.Weapon;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.modules.Module; import io.anuke.ucore.modules.Module;
@@ -25,23 +22,6 @@ public class NetCommon extends Module {
weapon.shoot(player, packet.x, packet.y, packet.rotation); weapon.shoot(player, packet.x, packet.y, packet.rotation);
}); });
Net.handle(PlacePacket.class, (packet) -> {
if(headless)
world.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation);
else
control.input().placeBlockInternal(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false);
Recipe recipe = Recipes.getByResult(Block.getByID(packet.block));
if (recipe != null) state.inventory.removeItems(recipe.requirements);
});
Net.handle(BreakPacket.class, (packet) -> {
if(headless)
world.removeBlock(world.tile(packet.x, packet.y));
else
control.input().breakBlockInternal(packet.x, packet.y, false);
});
Net.handle(ChatPacket.class, (packet) -> { Net.handle(ChatPacket.class, (packet) -> {
ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name)); ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name));
}); });

View File

@@ -7,14 +7,15 @@ import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.game.EventType.GameOverEvent; import io.anuke.mindustry.game.EventType.GameOverEvent;
import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.io.Platform;
import io.anuke.mindustry.io.Version; import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Administration;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.*;
import io.anuke.mindustry.resource.UpgradeRecipes; import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.resource.Weapon; import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Events;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
@@ -41,6 +42,8 @@ public class NetServer extends Module{
private final static int timerStateSync = 1; private final static int timerStateSync = 1;
private final static int timerBlockSync = 2; private final static int timerBlockSync = 2;
public final Administration admins = new Administration();
/**Maps connection IDs to players.*/ /**Maps connection IDs to players.*/
private IntMap<Player> connections = new IntMap<>(); private IntMap<Player> connections = new IntMap<>();
private ObjectMap<String, ByteArray> weapons = new ObjectMap<>(); private ObjectMap<String, ByteArray> weapons = new ObjectMap<>();
@@ -51,18 +54,33 @@ public class NetServer extends Module{
Events.on(GameOverEvent.class, () -> weapons.clear()); Events.on(GameOverEvent.class, () -> weapons.clear());
Net.handleServer(Connect.class, (id, connect) -> {}); Net.handleServer(Connect.class, (id, connect) -> {
if(admins.isBanned(connect.addressTCP)){
Net.kickConnection(id, KickReason.banned);
}
});
Net.handleServer(ConnectPacket.class, (id, packet) -> { Net.handleServer(ConnectPacket.class, (id, packet) -> {
if(Net.getConnection(id) == null ||
admins.isBanned(Net.getConnection(id).address)) return;
if(packet.version != Version.build && packet.version != -1 && Version.build != -1){ //ignore 'custom builds' on both ends String ip = Net.getConnection(id).address;
admins.setKnownName(ip, packet.name);
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
Net.kickConnection(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated); Net.kickConnection(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
return; return;
} }
if(packet.version == -1){
admins.getTrace(ip).modclient = true;
}
Log.info("Sending data to player '{0}' / {1}", packet.name, id); Log.info("Sending data to player '{0}' / {1}", packet.name, id);
Player player = new Player(); Player player = new Player();
player.isAdmin = admins.isAdmin(Net.getConnection(id).address);
player.clientid = id; player.clientid = id;
player.name = packet.name; player.name = packet.name;
player.isAndroid = packet.android; player.isAndroid = packet.android;
@@ -72,6 +90,8 @@ public class NetServer extends Module{
player.color.set(packet.color); player.color.set(packet.color);
connections.put(id, player); connections.put(id, player);
admins.getTrace(ip).playerid = player.id;
if(world.getMap().custom){ if(world.getMap().custom){
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeMap(world.getMap(), stream); NetworkIO.writeMap(world.getMap(), stream);
@@ -92,7 +112,7 @@ public class NetServer extends Module{
Player player = connections.get(id); Player player = connections.get(id);
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeWorld(player.id, weapons.get(player.name, new ByteArray()), stream); NetworkIO.writeWorld(player, weapons.get(player.name, new ByteArray()), stream);
WorldData data = new WorldData(); WorldData data = new WorldData();
data.stream = new ByteArrayInputStream(stream.toByteArray()); data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(id, data); Net.sendStream(id, data);
@@ -145,19 +165,53 @@ public class NetServer extends Module{
Net.handleServer(PlacePacket.class, (id, packet) -> { Net.handleServer(PlacePacket.class, (id, packet) -> {
packet.playerid = connections.get(id).id; packet.playerid = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp);
Block block = Block.getByID(packet.block);
if(!Placement.validPlace(packet.x, packet.y, block)) return;
Recipe recipe = Recipes.getByResult(block);
if(recipe == null) return;
state.inventory.removeItems(recipe.requirements);
Placement.placeBlock(packet.x, packet.y, block, packet.rotation, true, false);
admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block;
admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++;
Net.send(packet, SendMode.tcp);
}); });
Net.handleServer(BreakPacket.class, (id, packet) -> { Net.handleServer(BreakPacket.class, (id, packet) -> {
packet.playerid = connections.get(id).id; packet.playerid = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp);
if(!Placement.validBreak(packet.x, packet.y)) return;
Block block = Placement.breakBlock(packet.x, packet.y, true, false);
if(block != null) {
admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block;
admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++;
if (block.update || block.destructible)
admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++;
}
Net.send(packet, SendMode.tcp);
}); });
Net.handleServer(ChatPacket.class, (id, packet) -> { Net.handleServer(ChatPacket.class, (id, packet) -> {
if(!Timers.get("chatFlood" + id, 20)){
ChatPacket warn = new ChatPacket();
warn.text = "[scarlet]You are sending messages too quickly.";
Net.sendTo(id, warn, SendMode.tcp);
return;
}
Player player = connections.get(id); Player player = connections.get(id);
packet.name = player.name; packet.name = player.name;
packet.id = player.id; packet.id = player.id;
Net.sendExcept(player.clientid, packet, SendMode.tcp); Net.send(packet, SendMode.tcp);
}); });
Net.handleServer(UpgradePacket.class, (id, packet) -> { Net.handleServer(UpgradePacket.class, (id, packet) -> {
@@ -200,6 +254,39 @@ public class NetServer extends Module{
packet.id = connections.get(id).id; packet.id = connections.get(id).id;
Net.sendExcept(id, packet, SendMode.tcp); Net.sendExcept(id, packet, SendMode.tcp);
}); });
Net.handleServer(AdministerRequestPacket.class, (id, packet) -> {
Player player = connections.get(id);
if(!player.isAdmin){
Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.",
player.name, Net.getConnection(player.clientid).address);
return;
}
Player other = playerGroup.getByID(packet.id);
if(other == null || other.isAdmin){
Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name);
return;
}
String ip = Net.getConnection(other.clientid).address;
if(packet.action == AdminAction.ban){
admins.banPlayer(ip);
Net.kickConnection(other.clientid, KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(packet.action == AdminAction.kick){
Net.kickConnection(other.clientid, KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(packet.action == AdminAction.trace){
TracePacket trace = new TracePacket();
trace.info = admins.getTrace(ip);
Net.sendTo(id, trace, SendMode.tcp);
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
}
});
} }
public void update(){ public void update(){
@@ -221,6 +308,7 @@ public class NetServer extends Module{
public void reset(){ public void reset(){
weapons.clear(); weapons.clear();
admins.clearTraces();
} }
void sync(){ void sync(){

View File

@@ -264,8 +264,14 @@ public class Renderer extends RendererModule{
Draw.color(); Draw.color();
Draw.tcolor(player.getColor()); Draw.tcolor(player.getColor());
Draw.text(player.name, player.x, player.y + 8); Draw.text(player.name, player.x, player.y + 8);
Draw.tcolor();
} if(player.isAdmin){
Draw.color(player.getColor());
float s = 3f;
Draw.rect("icon-admin-small", player.x + layout.width/2f + 2 + 1, player.y + 7f, s, s);
}
Draw.reset();
}
} }
Pools.free(layout); Pools.free(layout);
Draw.tscl(fontscale); Draw.tscl(fontscale);
@@ -274,6 +280,7 @@ public class Renderer extends RendererModule{
void drawEnemyMarkers(){ void drawEnemyMarkers(){
Graphics.surface(indicatorSurface); Graphics.surface(indicatorSurface);
Draw.color(Color.RED); Draw.color(Color.RED);
for(Enemy enemy : enemyGroup.all()) { for(Enemy enemy : enemyGroup.all()) {
if (rect.setSize(camera.viewportWidth, camera.viewportHeight).setCenter(camera.position.x, camera.position.y) if (rect.setSize(camera.viewportWidth, camera.viewportHeight).setCenter(camera.position.x, camera.position.y)
@@ -409,8 +416,10 @@ public class Renderer extends RendererModule{
Lines.dashCircle(spawn.start.worldx(), spawn.start.worldy(), enemyspawnspace); Lines.dashCircle(spawn.start.worldx(), spawn.start.worldy(), enemyspawnspace);
} }
Draw.color(Color.LIME); if(world.getCore() != null) {
Lines.poly(world.getSpawnX(), world.getSpawnY(), 4, 6f, Timers.time()*2f); Draw.color(Color.LIME);
Lines.poly(world.getSpawnX(), world.getSpawnY(), 4, 6f, Timers.time() * 2f);
}
if(input.breakMode == PlaceMode.holdDelete) if(input.breakMode == PlaceMode.holdDelete)
input.breakMode.draw(tilex, tiley, 0, 0); input.breakMode.draw(tilex, tiley, 0, 0);

View File

@@ -27,7 +27,10 @@ public class ThreadHandler {
public ThreadHandler(ThreadProvider impl){ public ThreadHandler(ThreadProvider impl){
this.impl = impl; this.impl = impl;
Timers.setDeltaProvider(() -> impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f); Timers.setDeltaProvider(() ->{
float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f;
return Float.isNaN(result) ? 1f : result;
});
} }
public void run(Runnable r){ public void run(Runnable r){

View File

@@ -45,6 +45,9 @@ public class UI extends SceneModule{
public ControlsDialog controls; public ControlsDialog controls;
public MapEditorDialog editor; public MapEditorDialog editor;
public LanguageDialog language; public LanguageDialog language;
public BansDialog bans;
public AdminsDialog admins;
public TraceDialog traces;
public final MenuFragment menufrag = new MenuFragment(); public final MenuFragment menufrag = new MenuFragment();
public final ToolFragment toolfrag = new ToolFragment(); public final ToolFragment toolfrag = new ToolFragment();
@@ -150,6 +153,9 @@ public class UI extends SceneModule{
paused = new PausedDialog(); paused = new PausedDialog();
about = new AboutDialog(); about = new AboutDialog();
host = new HostDialog(); host = new HostDialog();
bans = new BansDialog();
admins = new AdminsDialog();
traces = new TraceDialog();
build.begin(scene); build.begin(scene);

View File

@@ -8,10 +8,7 @@ import io.anuke.mindustry.ai.Pathfind;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.game.SpawnPoint; import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.io.Maps; import io.anuke.mindustry.io.Maps;
import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.WorldGenerator;
import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.DistributionBlocks; import io.anuke.mindustry.world.blocks.DistributionBlocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks; import io.anuke.mindustry.world.blocks.ProductionBlocks;
@@ -22,7 +19,8 @@ import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Tmp; import io.anuke.ucore.util.Tmp;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.control;
import static io.anuke.mindustry.Vars.tilesize;
public class World extends Module{ public class World extends Module{
private int seed; private int seed;
@@ -182,7 +180,7 @@ public class World extends Module{
core = WorldGenerator.generate(map.pixmap, tiles, spawns); core = WorldGenerator.generate(map.pixmap, tiles, spawns);
placeBlock(core.x, core.y, ProductionBlocks.core, 0); Placement.placeBlock(core.x, core.y, ProductionBlocks.core, 0, false, false);
if(!map.name.equals("tutorial")){ if(!map.name.equals("tutorial")){
setDefaultBlocks(); setDefaultBlocks();
@@ -233,7 +231,7 @@ public class World extends Module{
public int getSeed(){ public int getSeed(){
return seed; return seed;
} }
public void removeBlock(Tile tile){ public void removeBlock(Tile tile){
if(!tile.block().isMultiblock() && !tile.isLinked()){ if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air); tile.setBlock(Blocks.air);
@@ -246,32 +244,6 @@ public class World extends Module{
} }
} }
} }
public void placeBlock(int x, int y, Block result, int rotation){
Tile tile = world.tile(x, y);
//just in case
if(tile == null) return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
}
}
}
}
public TileEntity findTileTarget(float x, float y, Tile tile, float range, boolean damaged){ public TileEntity findTileTarget(float x, float y, Tile tile, float range, boolean damaged){
Entity closest = null; Entity closest = null;

View File

@@ -34,6 +34,7 @@ public class Player extends SyncEntity{
public String name = "name"; public String name = "name";
public boolean isAndroid; public boolean isAndroid;
public boolean isAdmin;
public Color color = new Color(); public Color color = new Color();
public Weapon weaponLeft = Weapon.blaster; public Weapon weaponLeft = Weapon.blaster;
@@ -44,7 +45,7 @@ public class Player extends SyncEntity{
public float stucktime = 0f; public float stucktime = 0f;
public boolean dashing = false; public boolean dashing = false;
public int clientid; public int clientid = -1;
public boolean isLocal = false; public boolean isLocal = false;
public Timer timer = new Timer(4); public Timer timer = new Timer(4);
@@ -83,7 +84,7 @@ public class Player extends SyncEntity{
@Override @Override
public void onDeath(){ public void onDeath(){
remove(); dead = true;
if(Net.active()){ if(Net.active()){
NetEvents.handlePlayerDeath(); NetEvents.handlePlayerDeath();
} }
@@ -112,7 +113,7 @@ public class Player extends SyncEntity{
@Override @Override
public void drawSmooth(){ public void drawSmooth(){
if((debug && (!showPlayer || !showUI)) || (isAndroid && isLocal) || (dead && !isLocal)) return; if((debug && (!showPlayer || !showUI)) || (isAndroid && isLocal) || dead) return;
boolean snap = snapCamera && Settings.getBool("smoothcam") && Settings.getBool("pixelate") && isLocal; boolean snap = snapCamera && Settings.getBool("smoothcam") && Settings.getBool("pixelate") && isLocal;
String part = isAndroid ? "ship" : "mech"; String part = isAndroid ? "ship" : "mech";
@@ -155,6 +156,8 @@ public class Player extends SyncEntity{
return; return;
} }
if(isDead()) return;
Tile tile = world.tileWorld(x, y); Tile tile = world.tileWorld(x, y);
//if player is in solid block //if player is in solid block
@@ -240,6 +243,7 @@ public class Player extends SyncEntity{
buffer.put(weaponLeft.id); buffer.put(weaponLeft.id);
buffer.put(weaponRight.id); buffer.put(weaponRight.id);
buffer.put(isAndroid ? 1 : (byte)0); buffer.put(isAndroid ? 1 : (byte)0);
buffer.put(isAdmin ? 1 : (byte)0);
buffer.putInt(Color.rgba8888(color)); buffer.putInt(Color.rgba8888(color));
buffer.putFloat(x); buffer.putFloat(x);
buffer.putFloat(y); buffer.putFloat(y);
@@ -254,6 +258,7 @@ public class Player extends SyncEntity{
weaponLeft = (Weapon) Upgrade.getByID(buffer.get()); weaponLeft = (Weapon) Upgrade.getByID(buffer.get());
weaponRight = (Weapon) Upgrade.getByID(buffer.get()); weaponRight = (Weapon) Upgrade.getByID(buffer.get());
isAndroid = buffer.get() == 1; isAndroid = buffer.get() == 1;
isAdmin = buffer.get() == 1;
color.set(buffer.getInt()); color.set(buffer.getInt());
x = buffer.getFloat(); x = buffer.getFloat();
y = buffer.getFloat(); y = buffer.getFloat();

View File

@@ -17,6 +17,7 @@ import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.graphics.Lines;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Strings; import io.anuke.ucore.util.Strings;
@@ -82,6 +83,13 @@ public class EnemyType {
Graphics.flush(); Graphics.flush();
if(isCalculating(enemy)){
Draw.color(Color.SKY);
Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f);
Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f + 180f);
Draw.color();
}
if(showPaths){ if(showPaths){
Draw.tscl(0.25f); Draw.tscl(0.25f);
Draw.text((int)enemy.idletime + " " + enemy.node + " " + enemy.id + "\n" + Strings.toFixed(enemy.totalMove.x, 2) + ", " Draw.text((int)enemy.idletime + " " + enemy.node + " " + enemy.id + "\n" + Strings.toFixed(enemy.totalMove.x, 2) + ", "
@@ -100,9 +108,10 @@ public class EnemyType {
enemy.hitTime -= Timers.delta(); enemy.hitTime -= Timers.delta();
} }
if(enemy.lane >= world.getSpawns().size) enemy.lane = 0; if(enemy.lane >= world.getSpawns().size || enemy.lane < 0) enemy.lane = 0;
boolean waiting = world.getSpawns().get(enemy.lane).pathTiles == null || enemy.node <= 0; boolean waiting = enemy.lane >= world.getSpawns().size || enemy.lane < 0
|| world.getSpawns().get(enemy.lane).pathTiles == null || enemy.node <= 0;
move(enemy); move(enemy);
@@ -156,6 +165,8 @@ public class EnemyType {
Tile core = world.getCore(); Tile core = world.getCore();
if(core == null) return;
if(enemy.idletime > maxIdleLife && enemy.node > 0){ if(enemy.idletime > maxIdleLife && enemy.node > 0){
enemy.onDeath(); enemy.onDeath();
return; return;
@@ -189,7 +200,7 @@ public class EnemyType {
}else if(dst < avoidRange){ }else if(dst < avoidRange){
calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed); calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(calc.scl(1.1f)); shift.add(calc.scl(1.1f));
}else if(dst < attractRange && !nearCore){ }else if(dst < attractRange && !nearCore && !isCalculating(enemy)){
calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed); calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(calc.scl(-1)); shift.add(calc.scl(-1));
} }
@@ -217,7 +228,8 @@ public class EnemyType {
//no tile found //no tile found
if(enemy.target == null){ if(enemy.target == null){
enemy.target = Entities.getClosest(playerGroup, enemy.x, enemy.y, range, e -> !((Player)e).isAndroid); enemy.target = Entities.getClosest(playerGroup, enemy.x, enemy.y, range, e -> !((Player)e).isAndroid &&
!((Player)e).isDead());
} }
}else if(nearCore){ }else if(nearCore){
enemy.target = world.getCore().entity; enemy.target = world.getCore().entity;
@@ -267,6 +279,10 @@ public class EnemyType {
} }
} }
public boolean isCalculating(Enemy enemy){
return enemy.node < 0 && !Net.client();
}
public static EnemyType getByID(byte id){ public static EnemyType getByID(byte id){
return types.get(id); return types.get(id);
} }

View File

@@ -56,4 +56,9 @@ public class TargetType extends EnemyType {
new Enemy(EnemyTypes.target).set(enemy.x, enemy.y).add(); new Enemy(EnemyTypes.target).set(enemy.x, enemy.y).add();
}); });
} }
@Override
public boolean isCalculating(Enemy enemy){
return false;
}
} }

View File

@@ -177,7 +177,7 @@ public class BlockRenderer{
OrthographicCamera camera = Core.camera; OrthographicCamera camera = Core.camera;
Graphics.end(); if(Graphics.drawing()) Graphics.end();
int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1; int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1;
int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1; int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1;

View File

@@ -2,26 +2,16 @@ package io.anuke.mindustry.input;
import com.badlogic.gdx.InputAdapter; import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetEvents; import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.resource.ItemStack; import io.anuke.mindustry.resource.ItemStack;
import io.anuke.mindustry.resource.Recipe; import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Placement;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Sounds;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.SolidEntity;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
@@ -35,8 +25,6 @@ public abstract class InputHandler extends InputAdapter{
public PlaceMode lastPlaceMode = placeMode; public PlaceMode lastPlaceMode = placeMode;
public PlaceMode lastBreakMode = breakMode; public PlaceMode lastBreakMode = breakMode;
private Rectangle rect = new Rectangle();
public abstract void update(); public abstract void update();
public abstract float getCursorX(); public abstract float getCursorX();
public abstract float getCursorY(); public abstract float getCursorY();
@@ -88,39 +76,6 @@ public abstract class InputHandler extends InputAdapter{
public boolean validPlace(int x, int y, Block type){ public boolean validPlace(int x, int y, Block type){
for(SpawnPoint spawn : world.getSpawns()){
if(Vector2.dst(x * tilesize, y * tilesize, spawn.start.worldx(), spawn.start.worldy()) < enemyspawnspace){
return false;
}
}
rect.setSize(type.width * tilesize, type.height * tilesize);
Vector2 offset = type.getPlaceOffset();
rect.setCenter(offset.x + x * tilesize, offset.y + y * tilesize);
synchronized (Entities.entityLock) {
for (SolidEntity e : Entities.getNearby(enemyGroup, x * tilesize, y * tilesize, tilesize * 2f)) {
if (e == null) continue; //not sure why this happens?
Rectangle rect = e.hitbox.getRect(e.x, e.y);
if (this.rect.overlaps(rect)) {
return false;
}
}
}
if(type.solid || type.solidifes) {
for (Player player : playerGroup.all()) {
if (!player.isAndroid && rect.overlaps(player.hitbox.getRect(player.x, player.y))) {
return false;
}
}
}
Tile tile = world.tile(x, y);
if(tile == null || (isSpawnPoint(tile) && (type.solidifes || type.solid))) return false;
if(!type.isMultiblock() && control.tutorial().active() && if(!type.isMultiblock() && control.tutorial().active() &&
control.tutorial().showBlock()){ control.tutorial().showBlock()){
@@ -135,40 +90,11 @@ public abstract class InputHandler extends InputAdapter{
}else if(control.tutorial().active()){ }else if(control.tutorial().active()){
return false; return false;
} }
if(type.isMultiblock()){
int offsetx = -(type.width-1)/2;
int offsety = -(type.height-1)/2;
for(int dx = 0; dx < type.width; dx ++){
for(int dy = 0; dy < type.height; dy ++){
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
if(other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) || isSpawnPoint(other)){
return false;
}
}
}
return true;
}else{
if(tile.block() != type && (type.canReplace(tile.block()) || tile.block().alwaysReplace) && tile.block().isMultiblock() == type.isMultiblock()){
return true;
}
return tile.block() == Blocks.air;
}
}
public boolean isSpawnPoint(Tile tile){ return Placement.validPlace(x, y, type);
return tile != null && tile.x == world.getCore().x && tile.y == world.getCore().y - 2;
} }
public boolean validBreak(int x, int y){ public boolean validBreak(int x, int y){
Tile tile = world.tile(x, y);
if(tile == null || tile.block() == ProductionBlocks.core) return false;
if(tile.isLinked() && tile.getLinked().block() == ProductionBlocks.core){
return false;
}
if(control.tutorial().active()){ if(control.tutorial().active()){
if(control.tutorial().showBlock()){ if(control.tutorial().showBlock()){
@@ -185,102 +111,26 @@ public abstract class InputHandler extends InputAdapter{
} }
} }
return tile.breakable(); return Placement.validBreak(x, y);
} }
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){ public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
if(!Net.client()){
Placement.placeBlock(x, y, result, rotation, effects, sound);
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
}
placeBlockInternal(x, y, result, rotation, effects, sound); if(Net.active()){
Tile tile = world.tile(x, y);
if(tile != null) result.placed(tile);
if(Net.active() && result != ProductionBlocks.core){
NetEvents.handlePlace(x, y, result, rotation); NetEvents.handlePlace(x, y, result, rotation);
} }
} }
public void placeBlockInternal(int x, int y, Block result, int rotation, boolean effects, boolean sound){
Tile tile = world.tile(x, y);
//just in case
if(tile == null)
return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
if(effects) Effects.effect(Fx.place, worldx * tilesize, worldy * tilesize);
}
}
}else{
if(effects) Effects.effect(Fx.place, x * tilesize, y * tilesize);
}
if(effects && sound) Sounds.play("place");
}
public void breakBlock(int x, int y, boolean sound){ public void breakBlock(int x, int y, boolean sound){
breakBlockInternal(x, y, sound); if(!Net.client()) Placement.breakBlock(x, y, true, sound);
if(Net.active()){ if(Net.active()){
NetEvents.handleBreak(x, y); NetEvents.handleBreak(x, y);
} }
} }
public void breakBlockInternal(int x, int y, boolean sound){
Tile tile = world.tile(x, y);
if(tile == null) return;
Block block = tile.isLinked() ? tile.getLinked().block() : tile.block();
Recipe result = null;
for(Recipe recipe : Recipes.all()){
if(recipe.result == block){
result = recipe;
break;
}
}
if(result != null){
for(ItemStack stack : result.requirements){
state.inventory.addItem(stack.item, (int)(stack.amount * breakDropAmount));
}
}
if(tile.block().drops != null){
state.inventory.addItem(tile.block().drops.item, tile.block().drops.amount);
}
//Effects.shake(3f, 1f, player);
if(sound) Sounds.play("break");
if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air);
Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
}else{
Tile target = tile.isLinked() ? tile.getLinked() : tile;
Array<Tile> removals = target.getLinkedTiles();
for(Tile toremove : removals){
//note that setting a new block automatically unlinks it
toremove.setBlock(Blocks.air);
Effects.effect(Fx.breakBlock, toremove.worldx(), toremove.worldy());
}
}
}
} }

View File

@@ -75,13 +75,14 @@ public class Maps implements Disposable{
} }
public void loadMaps(){ public void loadMaps(){
if(!loadMapFile(Gdx.files.internal("maps/maps.json"))){ if(!loadMapFile(Gdx.files.internal("maps/maps.json"), true)){
throw new RuntimeException("Failed to load maps!"); throw new RuntimeException("Failed to load maps!");
} }
if(!gwt) { if(!gwt) {
if (!loadMapFile(customMapDirectory.child("maps.json"))) { if (!loadMapFile(customMapDirectory.child("maps.json"), false)) {
try { try {
Log.info("Failed to find custom map directory.");
customMapDirectory.child("maps.json").writeString("{}", false); customMapDirectory.child("maps.json").writeString("{}", false);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to create custom map directory!"); throw new RuntimeException("Failed to create custom map directory!");
@@ -159,25 +160,30 @@ public class Maps implements Disposable{
saveMaps(out, customMapDirectory.child("maps.json")); saveMaps(out, customMapDirectory.child("maps.json"));
} }
private boolean loadMapFile(FileHandle file){ private boolean loadMapFile(FileHandle file, boolean logException){
try{ try {
Array<Map> arr = json.fromJson(ArrayContainer.class, file).maps; Array<Map> arr = json.fromJson(ArrayContainer.class, file).maps;
if(arr != null){ //can be an empty map file if (arr != null) { //can be an empty map file
for(Map map : arr){ for (Map map : arr) {
map.pixmap = new Pixmap(file.sibling(map.name + ".png")); map.pixmap = new Pixmap(file.sibling(map.name + ".png"));
if(!headless) map.texture = new Texture(map.pixmap); if (!headless) map.texture = new Texture(map.pixmap);
maps.put(map.id, map); maps.put(map.id, map);
mapNames.put(map.name, map); mapNames.put(map.name, map);
lastID = Math.max(lastID, map.id); lastID = Math.max(lastID, map.id);
if(!map.custom){ if (!map.custom) {
defaultMaps.add(map); defaultMaps.add(map);
} }
} }
} }
return true; return true;
}catch(Exception e){ }catch (GdxRuntimeException e){
Log.err(e); Log.err(e);
Log.err("Failed loading map file: {0}", file); return true;
}catch(Exception e){
if(logException) {
Log.err(e);
Log.err("Failed loading map file: {0}", file);
}
return false; return false;
} }
} }

View File

@@ -307,7 +307,7 @@ public class Save15 extends SaveFileVersion {
for(int y = 0; y < world.height(); y ++){ for(int y = 0; y < world.height(); y ++){
Tile tile = world.tile(x, y); Tile tile = world.tile(x, y);
if(tile.breakable()){ if(tile != null && tile.breakable()){
if(tile.block() instanceof Rock){ if(tile.block() instanceof Rock){
totalrocks ++; totalrocks ++;
}else{ }else{
@@ -325,7 +325,7 @@ public class Save15 extends SaveFileVersion {
for (int y = 0; y < world.height(); y++) { for (int y = 0; y < world.height(); y++) {
Tile tile = world.tile(x, y); Tile tile = world.tile(x, y);
if (tile.block() instanceof Rock) { if (tile != null && tile.block() instanceof Rock) {
stream.writeInt(tile.packedPosition()); stream.writeInt(tile.packedPosition());
} }
} }
@@ -338,7 +338,7 @@ public class Save15 extends SaveFileVersion {
for(int y = 0; y < world.height(); y ++){ for(int y = 0; y < world.height(); y ++){
Tile tile = world.tile(x, y); Tile tile = world.tile(x, y);
if(tile.breakable() && !(tile.block() instanceof Rock)){ if(tile != null && tile.breakable() && !(tile.block() instanceof Rock)){
stream.writeInt(x + y*world.width()); //tile pos stream.writeInt(x + y*world.width()); //tile pos
stream.writeInt(tile.block().id); //block ID stream.writeInt(tile.block().id); //block ID

View File

@@ -0,0 +1,125 @@
package io.anuke.mindustry.net;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ObjectMap;
import io.anuke.ucore.core.Settings;
public class Administration {
private Json json = new Json();
private Array<String> bannedIPS = new Array<>();
private Array<String> admins = new Array<>();
private ObjectMap<String, String> known = new ObjectMap<>();
private ObjectMap<String, TraceInfo> traces = new ObjectMap<>();
public Administration(){
Settings.defaultList(
"bans", "{}",
"admins", "{}",
"knownIPs", "{}"
);
load();
}
public TraceInfo getTrace(String ip){
if(!traces.containsKey(ip)) traces.put(ip, new TraceInfo(ip));
return traces.get(ip);
}
public void clearTraces(){
traces.clear();
}
/**Sets last known name for an IP.*/
public void setKnownName(String ip, String name){
known.put(ip, name);
saveKnown();
}
/**Returns the last known name for an IP. Returns 'unknown' if this IP has an unknown username.*/
public String getLastName(String ip){
return known.get(ip, "unknown");
}
/**Returns list of banned IPs.*/
public Array<String> getBanned(){
return bannedIPS;
}
/**Bans a player by IP; returns whether this player was already banned.*/
public boolean banPlayer(String ip){
if(bannedIPS.contains(ip, false))
return false;
bannedIPS.add(ip);
saveBans();
return true;
}
/**Unbans a player by IP; returns whether this player was banned in the first place..*/
public boolean unbanPlayer(String ip){
if(!bannedIPS.contains(ip, false))
return false;
bannedIPS.removeValue(ip, false);
saveBans();
return true;
}
/**Returns list of banned IPs.*/
public Array<String> getAdmins(){
return admins;
}
/**Makes a player an admin. Returns whether this player was already an admin.*/
public boolean adminPlayer(String ip){
if(admins.contains(ip, false))
return false;
admins.add(ip);
saveAdmins();
return true;
}
/**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/
public boolean unAdminPlayer(String ip){
if(!admins.contains(ip, false))
return false;
admins.removeValue(ip, false);
saveAdmins();
return true;
}
public boolean isBanned(String ip){
return bannedIPS.contains(ip, false);
}
public boolean isAdmin(String ip){
return admins.contains(ip, false);
}
private void saveKnown(){
Settings.putString("knownIPs", json.toJson(known));
Settings.save();
}
private void saveBans(){
Settings.putString("bans", json.toJson(bannedIPS));
Settings.save();
}
private void saveAdmins(){
Settings.putString("admins", json.toJson(admins));
Settings.save();
}
private void load(){
bannedIPS = json.fromJson(Array.class, Settings.getString("bans"));
admins = json.fromJson(Array.class, Settings.getString("admins"));
known = json.fromJson(ObjectMap.class, Settings.getString("knownIPs"));
}
}

View File

@@ -3,11 +3,15 @@ package io.anuke.mindustry.net;
public class Host { public class Host {
public final String name; public final String name;
public final String address; public final String address;
public final String mapname;
public final int wave;
public final int players; public final int players;
public Host(String name, String address, int players){ public Host(String name, String address, String mapname, int wave, int players){
this.name = name; this.name = name;
this.address = address; this.address = address;
this.players = players; this.players = players;
this.mapname = mapname;
this.wave = wave;
} }
} }

View File

@@ -210,7 +210,7 @@ public class Net{
} }
/**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */ /**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */
public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed){ public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed){
clientProvider.pingHost(address, port, valid, failed); clientProvider.pingHost(address, port, valid, failed);
} }
@@ -285,7 +285,7 @@ public class Net{
* Callback should be run on libGDX main thread.*/ * Callback should be run on libGDX main thread.*/
void discover(Consumer<Array<Host>> callback); void discover(Consumer<Array<Host>> callback);
/**Ping a host. If an error occured, failed() should be called with the exception. */ /**Ping a host. If an error occured, failed() should be called with the exception. */
void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed); void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed);
/**Close all connections.*/ /**Close all connections.*/
void dispose(); void dispose();
} }

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.net;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
@@ -12,8 +13,7 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.entities.Entity; import io.anuke.ucore.entities.Entity;
import static io.anuke.mindustry.Vars.netCommon; import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.ui;
public class NetEvents { public class NetEvents {
@@ -99,8 +99,6 @@ public class NetEvents {
packet.name = Vars.player.name; packet.name = Vars.player.name;
packet.id = Vars.player.id; packet.id = Vars.player.id;
Net.send(packet, SendMode.tcp); Net.send(packet, SendMode.tcp);
ui.chatfrag.addMessage(packet.text, netCommon.colorizeName(Vars.player.id, Vars.player.name));
} }
public static void handleShoot(Weapon weapon, float x, float y, float angle){ public static void handleShoot(Weapon weapon, float x, float y, float angle){
@@ -152,4 +150,27 @@ public class NetEvents {
packet.itemid = (byte)item.id; packet.itemid = (byte)item.id;
Net.send(packet, SendMode.udp); Net.send(packet, SendMode.udp);
} }
public static void handleAdminSet(Player player, boolean admin){
PlayerAdminPacket packet = new PlayerAdminPacket();
packet.admin = admin;
packet.id = player.id;
player.isAdmin = admin;
Net.send(packet, SendMode.tcp);
}
public static void handleAdministerRequest(Player target, AdminAction action){
AdministerRequestPacket packet = new AdministerRequestPacket();
packet.id = target.id;
packet.action = action;
Net.send(packet, SendMode.tcp);
}
public static void handleTraceRequest(Player target){
if(Net.client()) {
handleAdministerRequest(target, AdminAction.trace);
}else{
ui.traces.show(target, netServer.admins.getTrace(Net.getConnection(target.clientid).address));
}
}
} }

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.utils.ByteArray; import com.badlogic.gdx.utils.ByteArray;
import com.badlogic.gdx.utils.TimeUtils; import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.game.GameMode;
import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.Weapon; import io.anuke.mindustry.resource.Weapon;
@@ -100,7 +101,7 @@ public class NetworkIO {
} }
} }
public static void writeWorld(int playerID, ByteArray upgrades, OutputStream os){ public static void writeWorld(Player player, ByteArray upgrades, OutputStream os){
try(DataOutputStream stream = new DataOutputStream(os)){ try(DataOutputStream stream = new DataOutputStream(os)){
@@ -116,7 +117,8 @@ public class NetworkIO {
stream.writeInt(state.enemies); //enemy amount stream.writeInt(state.enemies); //enemy amount
stream.writeBoolean(state.friendlyFire); //friendly fire state stream.writeBoolean(state.friendlyFire); //friendly fire state
stream.writeInt(playerID); //player remap ID stream.writeInt(player.id); //player remap ID
stream.writeBoolean(player.isAdmin);
//--INVENTORY-- //--INVENTORY--
@@ -246,6 +248,7 @@ public class NetworkIO {
state.friendlyFire = friendlyfire; state.friendlyFire = friendlyfire;
int pid = stream.readInt(); int pid = stream.readInt();
boolean admin = stream.readBoolean();
//inventory //inventory
for(int i = 0; i < state.inventory.getItems().length; i ++){ for(int i = 0; i < state.inventory.getItems().length; i ++){
@@ -268,6 +271,7 @@ public class NetworkIO {
Entities.clear(); Entities.clear();
player.id = pid; player.id = pid;
player.isAdmin = admin;
player.add(); player.add();
//map //map

View File

@@ -8,6 +8,7 @@ import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Packet.ImportantPacket; import io.anuke.mindustry.net.Packet.ImportantPacket;
import io.anuke.mindustry.net.Packet.UnimportantPacket; import io.anuke.mindustry.net.Packet.UnimportantPacket;
import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.world.Block;
import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.EntityGroup;
@@ -371,7 +372,7 @@ public class Packets {
} }
public enum KickReason{ public enum KickReason{
kick, invalidPassword, clientOutdated, serverOutdated kick, invalidPassword, clientOutdated, serverOutdated, banned
} }
public static class UpgradePacket implements Packet{ public static class UpgradePacket implements Packet{
@@ -558,4 +559,97 @@ public class Packets {
itemid = buffer.get(); itemid = buffer.get();
} }
} }
public static class NetErrorPacket implements Packet{
public String message;
@Override
public void write(ByteBuffer buffer) {
buffer.putShort((short)message.getBytes().length);
buffer.put(message.getBytes());
}
@Override
public void read(ByteBuffer buffer) {
short length = buffer.getShort();
byte[] bytes = new byte[length];
buffer.get(bytes);
message = new String(bytes);
}
}
public static class PlayerAdminPacket implements Packet{
public boolean admin;
public int id;
@Override
public void write(ByteBuffer buffer) {
buffer.put(admin ? (byte)1 : 0);
buffer.putInt(id);
}
@Override
public void read(ByteBuffer buffer) {
admin = buffer.get() == 1;
id = buffer.getInt();
}
}
public static class AdministerRequestPacket implements Packet{
public AdminAction action;
public int id;
@Override
public void write(ByteBuffer buffer) {
buffer.put((byte)action.ordinal());
buffer.putInt(id);
}
@Override
public void read(ByteBuffer buffer) {
action = AdminAction.values()[buffer.get()];
id = buffer.getInt();
}
}
public enum AdminAction{
kick, ban, trace
}
public static class TracePacket implements Packet{
public TraceInfo info;
@Override
public void write(ByteBuffer buffer) {
buffer.putInt(info.playerid);
buffer.putShort((short)info.ip.getBytes().length);
buffer.put(info.ip.getBytes());
buffer.put(info.modclient ? (byte)1 : 0);
buffer.putInt(info.totalBlocksBroken);
buffer.putInt(info.structureBlocksBroken);
buffer.putInt(info.lastBlockBroken.id);
buffer.putInt(info.totalBlocksPlaced);
buffer.putInt(info.lastBlockPlaced.id);
}
@Override
public void read(ByteBuffer buffer) {
int id = buffer.getInt();
short iplen = buffer.getShort();
byte[] ipb = new byte[iplen];
buffer.get(ipb);
info = new TraceInfo(new String(ipb));
info.playerid = id;
info.modclient = buffer.get() == 1;
info.totalBlocksBroken = buffer.getInt();
info.structureBlocksBroken = buffer.getInt();
info.lastBlockBroken = Block.getByID(buffer.getInt());
info.totalBlocksPlaced = buffer.getInt();
info.lastBlockPlaced = Block.getByID(buffer.getInt());
}
}
} }

View File

@@ -40,7 +40,11 @@ public class Registrator {
EntitySpawnPacket.class, EntitySpawnPacket.class,
ItemTransferPacket.class, ItemTransferPacket.class,
ItemSetPacket.class, ItemSetPacket.class,
ItemOffloadPacket.class ItemOffloadPacket.class,
NetErrorPacket.class,
PlayerAdminPacket.class,
AdministerRequestPacket.class,
TracePacket.class,
}; };
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>(); private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();

View File

@@ -0,0 +1,21 @@
package io.anuke.mindustry.net;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.blocks.Blocks;
public class TraceInfo {
public int playerid;
public String ip;
public boolean modclient;
public int totalBlocksBroken;
public int structureBlocksBroken;
public Block lastBlockBroken = Blocks.air;
public int totalBlocksPlaced;
public Block lastBlockPlaced = Blocks.air;
public TraceInfo(String ip){
this.ip = ip;
}
}

View File

@@ -0,0 +1,65 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetEvents;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.*;
public class AdminsDialog extends FloatingDialog {
public AdminsDialog(){
super("$text.server.admins");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
content().clear();
if(gwt) return;
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table, "clear");
pane.setFadeScrollBars(false);
if(netServer.admins.getAdmins().size == 0){
table.add("$text.server.admins.none");
}
for(String ip : netServer.admins.getAdmins()){
Table res = new Table("button");
res.margin(14f);
res.labelWrap("[LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
res.add().growX();
res.addImageButton("icon-cancel", 14*3, () -> {
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
netServer.admins.unAdminPlayer(ip);
for(Player player : playerGroup.all()){
NetConnection c = Net.getConnection(player.clientid);
if(c != null){
NetEvents.handleAdminSet(player, false);
break;
}
}
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
content().add(pane);
}
}

View File

@@ -0,0 +1,55 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.*;
public class BansDialog extends FloatingDialog {
public BansDialog(){
super("$text.server.bans");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
content().clear();
if(gwt) return;
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table, "clear");
pane.setFadeScrollBars(false);
if(netServer.admins.getBanned().size == 0){
table.add("$text.server.bans.none");
}
for(String ip : netServer.admins.getBanned()){
Table res = new Table("button");
res.margin(14f);
res.labelWrap("IP: [LIGHT_GRAY]" + ip + "\n[]Name: [LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
res.add().growX();
res.addImageButton("icon-cancel", 14*3, () -> {
ui.showConfirm("$text.confirm", "$text.confirmunban", () -> {
netServer.admins.unbanPlayer(ip);
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
content().add(pane);
}
}

View File

@@ -14,7 +14,6 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.player; import static io.anuke.mindustry.Vars.player;
import static io.anuke.mindustry.Vars.ui; import static io.anuke.mindustry.Vars.ui;
//TODO add port specification
public class HostDialog extends FloatingDialog{ public class HostDialog extends FloatingDialog{
float w = 300; float w = 300;
@@ -31,13 +30,13 @@ public class HostDialog extends FloatingDialog{
Settings.put("name", text); Settings.put("name", text);
Settings.save(); Settings.save();
ui.listfrag.rebuild(); ui.listfrag.rebuild();
}).grow().pad(8); }).grow().pad(8).get().setMaxLength(40);
ImageButton button = t.addImageButton("white", 40, () -> { ImageButton button = t.addImageButton("white", 40, () -> {
new ColorPickDialog().show(color -> { new ColorPickDialog().show(color -> {
player.color.set(color); player.color.set(color);
Settings.putInt("color", Color.rgba8888(color)); Settings.putInt("color", Color.rgba8888(color));
Settings.save();; Settings.save();
}); });
}).size(50f, 54f).get(); }).size(50f, 54f).get();
button.update(() -> button.getStyle().imageUpColor = player.getColor()); button.update(() -> button.getStyle().imageUpColor = player.getColor());
@@ -50,6 +49,7 @@ public class HostDialog extends FloatingDialog{
Timers.runTask(5f, () -> { Timers.runTask(5f, () -> {
try{ try{
Net.host(Vars.port); Net.host(Vars.port);
player.isAdmin = true;
}catch (IOException e){ }catch (IOException e){
ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false))); ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false)));
} }
@@ -58,19 +58,4 @@ public class HostDialog extends FloatingDialog{
}); });
}).width(w).height(70f); }).width(w).height(70f);
} }
/*
showTextInput("$text.hostserver", "$text.server.port", Vars.port + "", new DigitsOnlyFilter(), text -> {
int result = Strings.parseInt(text);
if(result == Integer.MIN_VALUE || result >= 65535){
ui.showError("$text.server.invalidport");
}else{
try{
Net.host(result);
}catch (IOException e){
ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false)));
}
}
});
*/
} }

View File

@@ -56,7 +56,6 @@ public class JoinDialog extends FloatingDialog {
setupRemote(); setupRemote();
refreshRemote(); refreshRemote();
}else{ }else{
//renaming.port = Strings.parseInt(Settings.getString("port"));
renaming.ip = Settings.getString("ip"); renaming.ip = Settings.getString("ip");
saveServers(); saveServers();
setupRemote(); setupRemote();
@@ -85,7 +84,7 @@ public class JoinDialog extends FloatingDialog {
TextButton button = buttons[0] = remote.addButton("[accent]"+server.ip, "clear", () -> { TextButton button = buttons[0] = remote.addButton("[accent]"+server.ip, "clear", () -> {
if(!buttons[0].childrenPressed()) connect(server.ip, Vars.port); if(!buttons[0].childrenPressed()) connect(server.ip, Vars.port);
}).width(w).height(120f).pad(4f).get(); }).width(w).height(140f).pad(4f).get();
button.getLabel().setWrap(true); button.getLabel().setWrap(true);
@@ -134,10 +133,14 @@ public class JoinDialog extends FloatingDialog {
Net.pingHost(server.ip, server.port, host -> { Net.pingHost(server.ip, server.port, host -> {
server.content.clear(); server.content.clear();
server.content.add("[lightgray]" + Bundles.format("text.server.hostname", host.name)).pad(4); server.content.add("[lightgray]" + Bundles.format("text.server.hostname", host.name)).left();
server.content.row(); server.content.row();
server.content.add("[lightgray]" + (host.players != 1 ? Bundles.format("text.players", host.players) : server.content.add("[lightgray]" + (host.players != 1 ? Bundles.format("text.players", host.players) :
Bundles.format("text.players.single", host.players))); Bundles.format("text.players.single", host.players))).left();
server.content.row();
server.content.add("[lightgray]" + Bundles.format("text.save.map", host.mapname)).left();
server.content.row();
server.content.add("[lightgray]" + Bundles.format("text.save.wave", host.wave)).left();
}, e -> { }, e -> {
server.content.clear(); server.content.clear();
server.content.add("$text.host.invalid"); server.content.add("$text.host.invalid");
@@ -175,7 +178,7 @@ public class JoinDialog extends FloatingDialog {
Vars.player.name = text; Vars.player.name = text;
Settings.put("name", text); Settings.put("name", text);
Settings.save(); Settings.save();
}).grow().pad(8); }).grow().pad(8).get().setMaxLength(40);
ImageButton button = t.addImageButton("white", 40, () -> { ImageButton button = t.addImageButton("white", 40, () -> {
new ColorPickDialog().show(color -> { new ColorPickDialog().show(color -> {

View File

@@ -32,18 +32,18 @@ public class SettingsMenuDialog extends SettingsDialog{
public SettingsMenuDialog(){ public SettingsMenuDialog(){
setStyle(Core.skin.get("dialog", WindowStyle.class)); setStyle(Core.skin.get("dialog", WindowStyle.class));
hidden(()->{ hidden(() -> {
if(!state.is(State.menu)){ if(!state.is(State.menu)){
if(!wasPaused || Net.active()) if(!wasPaused || Net.active())
state.set(State.playing); state.set(State.playing);
} }
}); });
shown(()->{ shown(() -> {
if(!state.is(State.menu)){ if(!state.is(State.menu)){
wasPaused = state.is(State.paused); wasPaused = state.is(State.paused);
if(menu.getScene() != null){ if(ui.paused.getScene() != null){
wasPaused = ((PausedDialog)menu).wasPaused; wasPaused = ui.paused.wasPaused;
} }
if(!Net.active()) state.set(State.paused); if(!Net.active()) state.set(State.paused);
ui.paused.hide(); ui.paused.hide();

View File

@@ -0,0 +1,53 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.TraceInfo;
import io.anuke.ucore.scene.ui.layout.Table;
import io.anuke.ucore.util.Bundles;
public class TraceDialog extends FloatingDialog {
public TraceDialog(){
super("$text.trace");
addCloseButton();
}
public void show(Player player, TraceInfo info){
content().clear();
Table table = new Table("button");
table.margin(14);
table.defaults().pad(1);
table.defaults().left();
table.add(Bundles.format("text.trace.playername", player.name));
table.row();
table.add(Bundles.format("text.trace.ip", info.ip));
table.row();
table.add(Bundles.format("text.trace.modclient", info.modclient));
table.row();
table.add().pad(5);
table.row();
table.add(Bundles.format("text.trace.totalblocksbroken", info.totalBlocksBroken));
table.row();
table.add(Bundles.format("text.trace.structureblocksbroken", info.structureBlocksBroken));
table.row();
table.add(Bundles.format("text.trace.lastblockbroken", info.lastBlockBroken.formalName));
table.row();
table.add().pad(5);
table.row();
table.add(Bundles.format("text.trace.totalblocksplaced", info.totalBlocksPlaced));
table.row();
table.add(Bundles.format("text.trace.lastblockplaced", info.lastBlockPlaced.formalName));
table.row();
content().add(table);
show();
}
}

View File

@@ -24,13 +24,14 @@ public class BackgroundFragment implements Fragment {
Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2 +240f, h/2 - back.getRegionHeight()*backscl/2 + 250f, Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2 +240f, h/2 - back.getRegionHeight()*backscl/2 + 250f,
back.getRegionWidth()*backscl, back.getRegionHeight()*backscl); back.getRegionWidth()*backscl, back.getRegionHeight()*backscl);
float logoscl = (int)Unit.dp.scl(7); boolean portrait = Gdx.graphics.getWidth() < Gdx.graphics.getHeight();
float logoscl = (int)Unit.dp.scl(7) * (portrait ? 5f/7f : 1f);
TextureRegion logo = Core.skin.getRegion("logotext"); TextureRegion logo = Core.skin.getRegion("logotext");
float logow = logo.getRegionWidth()*logoscl; float logow = logo.getRegionWidth()*logoscl;
float logoh = logo.getRegionHeight()*logoscl; float logoh = logo.getRegionHeight()*logoscl;
Draw.color(); Draw.color();
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15, logow, logoh); Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15 + (portrait ? -Unit.dp.scl(30f) : 0f), logow, logoh);
}).visible(() -> state.is(State.menu)).grow(); }).visible(() -> state.is(State.menu)).grow();
} }
} }

View File

@@ -57,11 +57,22 @@ public class DebugFragment implements Fragment {
row(); row();
new button("wave", () -> state.wavetime = 0f); new button("wave", () -> state.wavetime = 0f);
row(); row();
new button("time 0", () -> Timers.resetTime(0f));
row();
new button("time max", () -> Timers.resetTime(1080000 - 60*10));
row();
new button("clear", () -> {
enemyGroup.clear();
state.enemies = 0;
netClient.clearRecieved();
});
row();
new button("spawn", () -> { new button("spawn", () -> {
for(int i = 0; i < 30; i ++){ for(int i = 0; i < 30; i ++){
new Enemy(EnemyTypes.healer).set(player.x + Mathf.range(50f), player.y + Mathf.range(50f)).add(); new Enemy(EnemyTypes.healer).set(player.x + Mathf.range(50f), player.y + Mathf.range(50f)).add();
} }
}); });
row();
}}.end(); }}.end();
row(); row();

View File

@@ -16,10 +16,10 @@ import static io.anuke.mindustry.Vars.*;
public class MenuFragment implements Fragment{ public class MenuFragment implements Fragment{
public void build(){ public void build(){
if(!android){ new table(){{
//menu table visible(() -> state.is(State.menu));
new table(){{
if(!android){
new table(){{ new table(){{
PressGroup group = new PressGroup(); PressGroup group = new PressGroup();
@@ -53,37 +53,32 @@ public class MenuFragment implements Fragment{
} }
get().margin(16); get().margin(16);
}}.end(); }}.end();
visible(() -> state.is(State.menu)); }else {
}}.end(); new table() {{
}else{
new table(){{
new table(){{
defaults().size(120f).pad(5); defaults().size(120f).pad(5);
float isize = 14f*4; float isize = 14f * 4;
new imagebutton("icon-play-2", isize, ui.levels::show).text("$text.play").padTop(4f); new imagebutton("icon-play-2", isize, ui.levels::show).text("$text.play").padTop(4f);
new imagebutton("icon-tutorial", isize, () -> control.playMap(world.maps().getMap("tutorial"))).text("$text.tutorial").padTop(4f); new imagebutton("icon-tutorial", isize, () -> control.playMap(world.maps().getMap("tutorial"))).text("$text.tutorial").padTop(4f);
new imagebutton("icon-load", isize, ui.load::show).text("$text.load").padTop(4f); new imagebutton("icon-load", isize, ui.load::show).text("$text.load").padTop(4f);
new imagebutton("icon-add", isize, ui.join::show).text("$text.joingame").padTop(4f); new imagebutton("icon-add", isize, ui.join::show).text("$text.joingame").padTop(4f);
row(); row();
new imagebutton("icon-editor", isize, ui.editor::show).text("$text.editor").padTop(4f); new imagebutton("icon-editor", isize, ui.editor::show).text("$text.editor").padTop(4f);
new imagebutton("icon-tools", isize, ui.settings::show).text("$text.settings").padTop(4f); new imagebutton("icon-tools", isize, ui.settings::show).text("$text.settings").padTop(4f);
new imagebutton("icon-info", isize, ui.about::show).text("$text.about.button").padTop(4f); new imagebutton("icon-info", isize, ui.about::show).text("$text.about.button").padTop(4f);
new imagebutton("icon-donate", isize, Platform.instance::openDonations).text("$text.donate").padTop(4f); new imagebutton("icon-donate", isize, Platform.instance::openDonations).text("$text.donate").padTop(4f);
visible(() -> state.is(State.menu));
}}.end(); }}.end();
}}.end(); }
} }}.end();
//extra icons in top right //extra icons in top right
new table(){{ new table(){{

View File

@@ -214,6 +214,7 @@ public class PlacementFragment implements Fragment{
breaktable.getParent().swapActor(breaktable, next); breaktable.getParent().swapActor(breaktable, next);
if(!show){ if(!show){
control.input().breakMode = PlaceMode.none;
breaktable.actions(Actions.translateBy(-breaktable.getWidth() - 5, 0, dur, in), Actions.call(() -> shown = false)); breaktable.actions(Actions.translateBy(-breaktable.getWidth() - 5, 0, dur, in), Actions.call(() -> shown = false));
}else{ }else{
shown = true; shown = true;

View File

@@ -1,16 +1,21 @@
package io.anuke.mindustry.ui.fragments; package io.anuke.mindustry.ui.fragments;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.NetEvents; import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.net.Packets.AdminAction;
import io.anuke.mindustry.net.Packets.KickReason; import io.anuke.mindustry.net.Packets.KickReason;
import io.anuke.mindustry.ui.BorderImage; import io.anuke.mindustry.ui.BorderImage;
import io.anuke.ucore.core.Inputs; import io.anuke.ucore.core.Inputs;
import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.scene.Element; import io.anuke.ucore.scene.Element;
import io.anuke.ucore.scene.builders.button;
import io.anuke.ucore.scene.builders.label; import io.anuke.ucore.scene.builders.label;
import io.anuke.ucore.scene.builders.table; import io.anuke.ucore.scene.builders.table;
import io.anuke.ucore.scene.event.Touchable;
import io.anuke.ucore.scene.ui.ScrollPane; import io.anuke.ucore.scene.ui.ScrollPane;
import io.anuke.ucore.scene.ui.layout.Stack; import io.anuke.ucore.scene.ui.layout.Stack;
import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.ui.layout.Table;
@@ -40,10 +45,20 @@ public class PlayerListFragment implements Fragment{
row(); row();
new table("pane"){{ new table("pane"){{
margin(12f); margin(12f);
get().addCheck("$text.server.friendlyfire", b -> { get().addCheck("$text.server.friendlyfire", b -> {
state.friendlyFire = b; state.friendlyFire = b;
NetEvents.handleFriendlyFireChange(b); NetEvents.handleFriendlyFireChange(b);
}).growX().update(i -> i.setChecked(state.friendlyFire)).disabled(b -> Net.client()); }).growX().update(i -> i.setChecked(state.friendlyFire)).disabled(b -> Net.client()).padRight(5);
new button("$text.server.bans", () -> {
ui.bans.show();
}).padTop(-12).padBottom(-12).fillY().cell.disabled(b -> Net.client());
new button("$text.server.admins", () -> {
ui.admins.show();
}).padTop(-12).padBottom(-12).padRight(-12).fillY().cell.disabled(b -> Net.client());
}}.pad(10f).growX().end(); }}.pad(10f).growX().end();
}}.end(); }}.end();
@@ -69,9 +84,13 @@ public class PlayerListFragment implements Fragment{
public void rebuild(){ public void rebuild(){
content.clear(); content.clear();
float h = 60f; float h = 74f;
for(Player player : playerGroup.all()){ for(Player player : playerGroup.all()){
NetConnection connection = gwt ? null : Net.getConnection(player.clientid);
if(connection == null && Net.server() && !player.isLocal) continue;
Table button = new Table("button"); Table button = new Table("button");
button.left(); button.left();
button.margin(5).marginBottom(10); button.margin(5).marginBottom(10);
@@ -93,19 +112,68 @@ public class PlayerListFragment implements Fragment{
} }
}); });
} }
button.add(stack).size(h); button.add(stack).size(h);
button.add("[#" + player.getColor().toString().toUpperCase() + "]" + player.name).pad(10); button.labelWrap("[#" + player.getColor().toString().toUpperCase() + "]" + player.name).width(170f).pad(10);
button.add().grow(); button.add().grow();
if(Net.server() && !player.isLocal){ button.addImage("icon-admin").size(14*2).visible(() -> player.isAdmin && !(!player.isLocal && Net.server())).padRight(5);
if((Net.server() || Vars.player.isAdmin) && !player.isLocal && (!player.isAdmin || Net.server())){
button.add().growY(); button.add().growY();
button.addImageButton("icon-cancel", 14*3, () ->
Net.kickConnection(player.clientid, KickReason.kick) float bs = (h + 14)/2f;
).pad(-5).padBottom(-10).size(h+10, h+14);
button.table(t -> {
t.defaults().size(bs - 1, bs + 3);
t.addImageButton("icon-ban", 14*2, () -> {
ui.showConfirm("$text.confirm", "$text.confirmban", () -> {
if(Net.server()) {
netServer.admins.banPlayer(connection.address);
Net.kickConnection(player.clientid, KickReason.banned);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.ban);
}
});
}).padBottom(-5.1f);
t.addImageButton("icon-cancel", 14*2, () -> {
if(Net.server()) {
Net.kickConnection(player.clientid, KickReason.kick);
}else{
NetEvents.handleAdministerRequest(player, AdminAction.kick);
}
}).padBottom(-5.1f);
t.row();
t.addImageButton("icon-admin", "toggle", 14*2, () -> {
if(Net.client()) return;
if(netServer.admins.isAdmin(connection.address)){
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
netServer.admins.unAdminPlayer(connection.address);
NetEvents.handleAdminSet(player, false);
});
}else{
ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> {
netServer.admins.adminPlayer(connection.address);
NetEvents.handleAdminSet(player, true);
});
}
}).update(b ->{
b.setChecked(player.isAdmin);
b.setDisabled(Net.client());
}).get().setTouchable(() -> Net.client() ? Touchable.disabled : Touchable.enabled);
t.addImageButton("icon-zoom-small", 14*2, () -> NetEvents.handleTraceRequest(player));
}).padRight(12).padTop(-5).padLeft(0).padBottom(-10).size(bs + 10f, bs);
} }
content.add(button).padBottom(-5).width(350f); content.add(button).padBottom(-6).width(350f).maxHeight(h + 14);
content.row(); content.row();
} }

View File

@@ -0,0 +1,166 @@
package io.anuke.mindustry.world;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.SpawnPoint;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.resource.ItemStack;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.ProductionBlocks;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Sounds;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.SolidEntity;
import static io.anuke.mindustry.Vars.*;
public class Placement {
private static final Rectangle rect = new Rectangle();
/**Returns block type that was broken, or null if unsuccesful.*/
public static Block breakBlock(int x, int y, boolean effect, boolean sound){
Tile tile = world.tile(x, y);
if(tile == null) return null;
Block block = tile.isLinked() ? tile.getLinked().block() : tile.block();
Recipe result = Recipes.getByResult(block);
if(result != null){
for(ItemStack stack : result.requirements){
state.inventory.addItem(stack.item, (int)(stack.amount * breakDropAmount));
}
}
if(tile.block().drops != null){
state.inventory.addItem(tile.block().drops.item, tile.block().drops.amount);
}
if(sound) Sounds.play("break");
if(!tile.block().isMultiblock() && !tile.isLinked()){
tile.setBlock(Blocks.air);
if(effect) Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
}else{
Tile target = tile.isLinked() ? tile.getLinked() : tile;
Array<Tile> removals = target.getLinkedTiles();
for(Tile toremove : removals){
//note that setting a new block automatically unlinks it
toremove.setBlock(Blocks.air);
if(effect) Effects.effect(Fx.breakBlock, toremove.worldx(), toremove.worldy());
}
}
return block;
}
public static void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
Tile tile = world.tile(x, y);
//just in case
if(tile == null) return;
tile.setBlock(result, rotation);
if(result.isMultiblock()){
int offsetx = -(result.width-1)/2;
int offsety = -(result.height-1)/2;
for(int dx = 0; dx < result.width; dx ++){
for(int dy = 0; dy < result.height; dy ++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null)
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
}
if(effects) Effects.effect(Fx.place, worldx * tilesize, worldy * tilesize);
}
}
}else if(effects) Effects.effect(Fx.place, x * tilesize, y * tilesize);
if(effects && sound) Sounds.play("place");
}
public static boolean validPlace(int x, int y, Block type){
for(int i = 0; i < world.getSpawns().size; i ++){
SpawnPoint spawn = world.getSpawns().get(i);
if(Vector2.dst(x * tilesize, y * tilesize, spawn.start.worldx(), spawn.start.worldy()) < enemyspawnspace){
return false;
}
}
Recipe recipe = Recipes.getByResult(type);
if(recipe == null || !state.inventory.hasItems(recipe.requirements)){
return false;
}
rect.setSize(type.width * tilesize, type.height * tilesize);
Vector2 offset = type.getPlaceOffset();
rect.setCenter(offset.x + x * tilesize, offset.y + y * tilesize);
synchronized (Entities.entityLock) {
for (SolidEntity e : Entities.getNearby(enemyGroup, x * tilesize, y * tilesize, tilesize * 2f)) {
if (e == null) continue; //not sure why this happens?
Rectangle rect = e.hitbox.getRect(e.x, e.y);
if (Placement.rect.overlaps(rect)) {
return false;
}
}
}
if(type.solid || type.solidifes) {
for (Player player : playerGroup.all()) {
if (!player.isAndroid && rect.overlaps(player.hitbox.getRect(player.x, player.y))) {
return false;
}
}
}
Tile tile = world.tile(x, y);
if(tile == null || (isSpawnPoint(tile) && (type.solidifes || type.solid))) return false;
if(type.isMultiblock()){
int offsetx = -(type.width-1)/2;
int offsety = -(type.height-1)/2;
for(int dx = 0; dx < type.width; dx ++){
for(int dy = 0; dy < type.height; dy ++){
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
if(other == null || (other.block() != Blocks.air && !other.block().alwaysReplace) || isSpawnPoint(other)){
return false;
}
}
}
return true;
}else {
return tile.block() != type
&& (type.canReplace(tile.block()) || tile.block().alwaysReplace)
&& tile.block().isMultiblock() == type.isMultiblock() || tile.block() == Blocks.air;
}
}
public static boolean isSpawnPoint(Tile tile){
return tile != null && tile.x == world.getCore().x && tile.y == world.getCore().y - 2;
}
public static boolean validBreak(int x, int y){
Tile tile = world.tile(x, y);
if(tile == null || tile.block() == ProductionBlocks.core) return false;
if(tile.isLinked() && tile.getLinked().block() == ProductionBlocks.core){
return false;
}
return tile.breakable();
}
}

View File

@@ -43,13 +43,14 @@ public class LaserTurret extends PowerTurret{
@Override @Override
public void drawLayer2(Tile tile){ public void drawLayer2(Tile tile){
TurretEntity entity = tile.entity(); TurretEntity entity = tile.entity();
Enemy enemy = entity.target;
if(entity.target != null && if(enemy != null &&
Angles.angleDist(entity.rotation, Angles.angle(tile.drawx(), tile.drawy(), entity.target.x, entity.target.y)) <= cone){ Angles.angleDist(entity.rotation, Angles.angle(tile.drawx(), tile.drawy(), enemy.x, enemy.y)) <= cone){
float len = 4f; float len = 4f;
float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len); float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len);
float x2 = entity.target.x, y2 = entity.target.y; float x2 = enemy.x, y2 = enemy.y;
float lighten = (MathUtils.sin(Timers.time()/1.2f) + 1f) / 10f; float lighten = (MathUtils.sin(Timers.time()/1.2f) + 1f) / 10f;

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.world.blocks.types.defense;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.world.Layer; import io.anuke.mindustry.world.Layer;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
@@ -80,9 +81,10 @@ public class RepairTurret extends PowerTurret{
@Override @Override
public void drawLayer2(Tile tile){ public void drawLayer2(Tile tile){
PowerTurretEntity entity = tile.entity(); PowerTurretEntity entity = tile.entity();
TileEntity target = entity.blockTarget;
if(entity.power >= powerUsed && entity.blockTarget != null && Angles.angleDist(entity.angleTo(entity.blockTarget), entity.rotation) < 10){ if(entity.power >= powerUsed && target != null && Angles.angleDist(entity.angleTo(target), entity.rotation) < 10){
Tile targetTile = entity.blockTarget.tile; Tile targetTile = target.tile;
float len = 4f; float len = 4f;
float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len); float x = tile.drawx() + Angles.trnsx(entity.rotation, len), y = tile.drawy() + Angles.trnsy(entity.rotation, len);

View File

@@ -84,6 +84,7 @@ public class Junction extends Block{
return new JunctionEntity(); return new JunctionEntity();
} }
@Override
public Array<Object> getDebugInfo(Tile tile){ public Array<Object> getDebugInfo(Tile tile){
JunctionEntity entity = tile.entity(); JunctionEntity entity = tile.entity();
Array<Object> arr = super.getDebugInfo(tile); Array<Object> arr = super.getDebugInfo(tile);

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.world.blocks.types.distribution; package io.anuke.mindustry.world.blocks.types.distribution;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.NumberUtils; import com.badlogic.gdx.utils.NumberUtils;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Item;
@@ -90,10 +91,35 @@ public class TunnelConveyor extends Block{
return new TunnelEntity(); return new TunnelEntity();
} }
@Override
public Array<Object> getDebugInfo(Tile tile){
TunnelEntity entity = tile.entity();
Array<Object> arr = super.getDebugInfo(tile);
for(int i = 0; i < 4; i ++){
arr.add("nearby." + i);
arr.add(tile.getNearby(i));
}
arr.add("buffer");
arr.add(entity.index);
for(int i = 0; i < entity.index; i++){
long l = entity.items[i];
float time = NumberUtils.intBitsToFloat(Bits.getLeftInt(l));
Item item = Item.getByID(Bits.getRightInt(l));
Tile dest = getDestTunnel(tile, item);
arr.add(" buffer.item");
arr.add(time + " | " + item.name + " | " + ( dest == null ? "no dest" : dest.block() + ":" + dest.floor()));
}
return arr;
}
Tile getDestTunnel(Tile tile, Item item){ Tile getDestTunnel(Tile tile, Item item){
Tile dest = tile; Tile dest = tile;
int rel = (tile.getRotation() + 2)%4; int rel = (tile.getRotation() + 2)%4;
for(int i = 0; i < maxdist; i ++){ for(int i = 0; i < maxdist; i ++){
if(dest == null) return null;
dest = dest.getNearby(rel); dest = dest.getNearby(rel);
if(dest != null && dest.block() instanceof TunnelConveyor && dest.getRotation() == rel if(dest != null && dest.block() instanceof TunnelConveyor && dest.getRotation() == rel
&& dest.getNearby(rel) != null && dest.getNearby(rel) != null

View File

@@ -103,7 +103,7 @@ public class WebsocketClient implements ClientProvider {
} }
@Override @Override
public void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed) { public void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed) {
if(!Platform.instance.canJoinGame()) { if(!Platform.instance.canJoinGame()) {
failed.accept(new IOException()); failed.accept(new IOException());
}else { }else {
@@ -119,7 +119,7 @@ public class WebsocketClient implements ClientProvider {
public void onMessage(String msg) { public void onMessage(String msg) {
if(!msg.startsWith("---")) return; if(!msg.startsWith("---")) return;
String[] text = msg.substring(3).split("\\|"); String[] text = msg.substring(3).split("\\|");
Host host = new Host(text[1], address, Strings.parseInt(text[0])); Host host = new Host(text[1], address, text[2], Strings.parseInt(text[3]), Strings.parseInt(text[0]));
valid.accept(host); valid.accept(host);
accepted[0] = true; accepted[0] = true;
socket.close(); socket.close();

View File

@@ -13,6 +13,7 @@ import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Packets.Connect; import io.anuke.mindustry.net.Packets.Connect;
import io.anuke.mindustry.net.Packets.Disconnect; import io.anuke.mindustry.net.Packets.Disconnect;
import io.anuke.ucore.function.Consumer; import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.util.Strings;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -34,7 +35,7 @@ public class KryoClient implements ClientProvider{
handler = new ClientDiscoveryHandler() { handler = new ClientDiscoveryHandler() {
@Override @Override
public DatagramPacket onRequestNewDatagramPacket() { public DatagramPacket onRequestNewDatagramPacket() {
return new DatagramPacket(new byte[32], 32); return new DatagramPacket(new byte[128], 128);
} }
@Override @Override
@@ -140,7 +141,7 @@ public class KryoClient implements ClientProvider{
} }
@Override @Override
public void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> invalid){ public void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> invalid){
runAsync(() -> { runAsync(() -> {
try { try {
DatagramSocket socket = new DatagramSocket(); DatagramSocket socket = new DatagramSocket();
@@ -162,7 +163,7 @@ public class KryoClient implements ClientProvider{
} else { } else {
Gdx.app.postRunnable(() -> invalid.accept(new IOException("Outdated server."))); Gdx.app.postRunnable(() -> invalid.accept(new IOException("Outdated server.")));
} }
} catch (IOException e) { } catch (Exception e) {
Gdx.app.postRunnable(() -> invalid.accept(e)); Gdx.app.postRunnable(() -> invalid.accept(e));
} }
}); });
@@ -209,7 +210,7 @@ public class KryoClient implements ClientProvider{
if(e instanceof KryoNetException){ if(e instanceof KryoNetException){
Gdx.app.postRunnable(() -> Net.showError("$text.server.mismatch")); Gdx.app.postRunnable(() -> Net.showError("$text.server.mismatch"));
}else{ }else{
//TODO better exception handling. Net.showError(Strings.parseException(e, true));
disconnect(); disconnect();
} }
} }

View File

@@ -2,7 +2,6 @@ package io.anuke.kryonet;
import com.esotericsoftware.minlog.Log; import com.esotericsoftware.minlog.Log;
import com.esotericsoftware.minlog.Log.Logger; import com.esotericsoftware.minlog.Log.Logger;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.net.Host; import io.anuke.mindustry.net.Host;
import io.anuke.ucore.util.ColorCodes; import io.anuke.ucore.util.ColorCodes;
@@ -11,8 +10,7 @@ import java.io.StringWriter;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.headless; import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.playerGroup;
public class KryoRegistrator { public class KryoRegistrator {
public static boolean fakeLag = false; public static boolean fakeLag = false;
@@ -49,24 +47,44 @@ public class KryoRegistrator {
} }
public static ByteBuffer writeServerData(){ public static ByteBuffer writeServerData(){
String host = headless ? "Server" : Vars.player.name; int maxlen = 32;
String host = (headless ? "Server" : player.name);
String map = world.getMap().name;
host = host.substring(0, Math.min(host.length(), maxlen));
map = map.substring(0, Math.min(map.length(), maxlen));
ByteBuffer buffer = ByteBuffer.allocate(128);
ByteBuffer buffer = ByteBuffer.allocate(1 + host.getBytes().length + 4);
buffer.put((byte)host.getBytes().length); buffer.put((byte)host.getBytes().length);
buffer.put(host.getBytes()); buffer.put(host.getBytes());
buffer.put((byte)map.getBytes().length);
buffer.put(map.getBytes());
buffer.putInt(playerGroup.size()); buffer.putInt(playerGroup.size());
buffer.putInt(state.wave);
return buffer; return buffer;
} }
public static Host readServerData(InetAddress ia, ByteBuffer buffer){ public static Host readServerData(InetAddress ia, ByteBuffer buffer){
//old version address. if(buffer.capacity() < 128) return null; //old version address.
if(buffer.capacity() == 4) return null;
byte hlength = buffer.get();
byte[] hb = new byte[hlength];
buffer.get(hb);
byte mlength = buffer.get();
byte[] mb = new byte[mlength];
buffer.get(mb);
String host = new String(hb);
String map = new String(mb);
byte length = buffer.get();
byte[] sname = new byte[length];
buffer.get(sname);
int players = buffer.getInt(); int players = buffer.getInt();
int wave = buffer.getInt();
return new Host(new String(sname), ia.getHostAddress(), players); return new Host(host, ia.getHostAddress(), map, wave, players);
} }
} }

View File

@@ -14,10 +14,7 @@ import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.Net.ServerProvider; import io.anuke.mindustry.net.Net.ServerProvider;
import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.net.Packets.Connect; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.net.Packets.Disconnect;
import io.anuke.mindustry.net.Packets.KickPacket;
import io.anuke.mindustry.net.Packets.KickReason;
import io.anuke.mindustry.net.Registrator; import io.anuke.mindustry.net.Registrator;
import io.anuke.mindustry.net.Streamable; import io.anuke.mindustry.net.Streamable;
import io.anuke.mindustry.net.Streamable.StreamBegin; import io.anuke.mindustry.net.Streamable.StreamBegin;
@@ -25,6 +22,7 @@ import io.anuke.mindustry.net.Streamable.StreamChunk;
import io.anuke.ucore.UCore; import io.anuke.ucore.UCore;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Strings;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshake;
@@ -66,13 +64,15 @@ public class KryoServer implements ServerProvider {
@Override @Override
public void connected (Connection connection) { public void connected (Connection connection) {
KryoConnection kn = new KryoConnection(lastconnection ++, connection.getRemoteAddressTCP().toString(), connection); String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress();
KryoConnection kn = new KryoConnection(lastconnection ++, ip, connection);
Connect c = new Connect(); Connect c = new Connect();
c.id = kn.id; c.id = kn.id;
c.addressTCP = connection.getRemoteAddressTCP().toString(); c.addressTCP = ip;
Log.info("&bRecieved connection: {0} {1}", c.id, c.addressTCP); Log.info("&bRecieved connection: {0} / {1}", c.id, c.addressTCP);
connections.add(kn); connections.add(kn);
Gdx.app.postRunnable(() -> Net.handleServerReceived(kn.id, c)); Gdx.app.postRunnable(() -> Net.handleServerReceived(kn.id, c));
@@ -141,12 +141,15 @@ public class KryoServer implements ServerProvider {
if(con == null){ if(con == null){
Log.err("Cannot kick unknown player!"); Log.err("Cannot kick unknown player!");
return; return;
}else{
Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason);
} }
KickPacket p = new KickPacket(); KickPacket p = new KickPacket();
p.reason = reason; p.reason = reason;
con.send(p, SendMode.tcp); con.send(p, SendMode.tcp);
Timers.runTask(2f, con::close);
} }
@Override @Override
@@ -371,9 +374,21 @@ public class KryoServer implements ServerProvider {
connection.sendUDP(object); connection.sendUDP(object);
} }
}catch (Exception e){ }catch (Exception e){
e.printStackTrace(); Log.err(e);
Log.info("Disconnecting invalid client!"); Log.info("Disconnecting invalid client!");
try{
NetErrorPacket packet = new NetErrorPacket();
packet.message = Strings.parseException(e, true);
Timers.runTask(5f, connection::close);
}catch (Exception e2){
Log.err(e2);
connection.close();
}
connection.close(); connection.close();
KryoConnection k = getByKryoID(connection.getID());
if(k != null) connections.remove(k);
Log.info("Connection removed {0}", k);
} }
} }
} }
@@ -398,7 +413,7 @@ public class KryoServer implements ServerProvider {
@Override @Override
public void onOpen(WebSocket conn, ClientHandshake handshake) { public void onOpen(WebSocket conn, ClientHandshake handshake) {
Connect connect = new Connect(); Connect connect = new Connect();
connect.addressTCP = conn.getRemoteSocketAddress().toString(); connect.addressTCP = conn.getRemoteSocketAddress().getAddress().getHostAddress();
KryoConnection kn = new KryoConnection(lastconnection ++, connect.addressTCP, conn); KryoConnection kn = new KryoConnection(lastconnection ++, connect.addressTCP, conn);
Log.info("&bRecieved web connection: {0} {1}", kn.id, connect.addressTCP); Log.info("&bRecieved web connection: {0} {1}", kn.id, connect.addressTCP);

View File

@@ -13,6 +13,7 @@ import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetEvents; import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.net.Packets.ChatPacket; import io.anuke.mindustry.net.Packets.ChatPacket;
import io.anuke.mindustry.net.Packets.KickReason; import io.anuke.mindustry.net.Packets.KickReason;
import io.anuke.mindustry.net.TraceInfo;
import io.anuke.mindustry.ui.fragments.DebugFragment; import io.anuke.mindustry.ui.fragments.DebugFragment;
import io.anuke.mindustry.world.Map; import io.anuke.mindustry.world.Map;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
@@ -38,7 +39,12 @@ public class ServerControl extends Module {
private ShuffleMode mode; private ShuffleMode mode;
public ServerControl(){ public ServerControl(){
Settings.defaults("shufflemode", "normal"); Settings.defaultList(
"shufflemode", "normal",
"bans", "",
"admins", ""
);
mode = ShuffleMode.valueOf(Settings.getString("shufflemode")); mode = ShuffleMode.valueOf(Settings.getString("shufflemode"));
Effects.setScreenShakeProvider((a, b) -> {}); Effects.setScreenShakeProvider((a, b) -> {});
@@ -99,7 +105,7 @@ public class ServerControl extends Module {
handler.register("help", "Displays this command list.", arg -> { handler.register("help", "Displays this command list.", arg -> {
info("Commands:"); info("Commands:");
for(Command command : handler.getCommandList()){ for(Command command : handler.getCommandList()){
print(" &y" + command.text + (command.params.isEmpty() ? "" : " ") + command.params + " - &lm" + command.description); print(" &y" + command.text + (command.paramText.isEmpty() ? "" : " ") + command.paramText + " - &lm" + command.description);
} }
}); });
@@ -184,7 +190,22 @@ public class ServerControl extends Module {
} }
}); });
handler.register("say", "<message>", "Send a message to all players.", arg -> { handler.register("players", "Display player info.", arg -> {
if(state.is(State.menu)){
info("&lyServer is closed.");
}else{
if(playerGroup.size() > 0) {
info("&lyPlayers: {0}", playerGroup.size());
for (Player p : playerGroup.all()) {
print(" &y{0} / Connection {1} / IP: {2}", p.name, p.clientid, Net.getConnection(p.clientid).address);
}
}else{
info("&lyNo players connected.");
}
}
});
handler.register("say", "<message...>", "Send a message to all players.", arg -> {
if(!state.is(State.playing)) { if(!state.is(State.playing)) {
err("Not hosting. Host a game first."); err("Not hosting. Host a game first.");
return; return;
@@ -192,7 +213,7 @@ public class ServerControl extends Module {
netCommon.sendMessage("[GRAY][[Server]:[] " + arg[0]); netCommon.sendMessage("[GRAY][[Server]:[] " + arg[0]);
info("&lyServer: &lb{0}", arg[0]); info("&lyServer: &lb{0}", arg[0]);
}).mergeArgs(); });
handler.register("difficulty", "<difficulty>", "Set game difficulty.", arg -> { handler.register("difficulty", "<difficulty>", "Set game difficulty.", arg -> {
try{ try{
@@ -237,11 +258,6 @@ public class ServerControl extends Module {
return; return;
} }
if(playerGroup.size() == 0){
err("But this server is empty. A barren wasteland.");
return;
}
Player target = null; Player target = null;
for(Player player : playerGroup.all()){ for(Player player : playerGroup.all()){
@@ -259,6 +275,130 @@ public class ServerControl extends Module {
} }
}); });
handler.register("ban", "<username>", "Ban a person by name.", arg -> {
if(!state.is(State.playing)) {
err("Can't ban people by name with no players.");
return;
}
Player target = null;
for(Player player : playerGroup.all()){
if(player.name.equalsIgnoreCase(arg[0])){
target = player;
break;
}
}
if(target != null){
String ip = Net.getConnection(player.clientid).address;
netServer.admins.banPlayer(ip);
Net.kickConnection(target.clientid, KickReason.banned);
info("Banned player by IP: {0}", ip);
}else{
info("Nobody with that name could be found.");
}
});
handler.register("bans", "List all banned IPs.", arg -> {
Array<String> bans = netServer.admins.getBanned();
if(bans.size == 0){
Log.info("No banned players have been found.");
}else{
Log.info("&lyBanned players:");
for(String string : bans){
Log.info(" &luy {0} / Last known name: '{1}'", string, netServer.admins.getLastName(string));
}
}
});
handler.register("banip", "<ip>", "Ban a person by IP.", arg -> {
if(netServer.admins.banPlayer(arg[0])) {
info("Banned player by IP: {0}.", arg[0]);
for(Player player : playerGroup.all()){
if(Net.getConnection(player.clientid).address.equals(arg[0])){
Net.kickConnection(player.clientid, KickReason.banned);
break;
}
}
}else{
err("That IP is already banned!");
}
});
handler.register("unbanip", "<ip>", "Unban a person by IP.", arg -> {
if(netServer.admins.unbanPlayer(arg[0])) {
info("Unbanned player by IP: {0}.", arg[0]);
}else{
err("That IP is not banned!");
}
});
handler.register("admin", "<username>", "Make a user admin", arg -> {
if(!state.is(State.playing)) {
err("Open the server first.");
return;
}
Player target = null;
for(Player player : playerGroup.all()){
if(player.name.equalsIgnoreCase(arg[0])){
target = player;
break;
}
}
if(target != null){
String ip = Net.getConnection(player.clientid).address;
netServer.admins.adminPlayer(ip);
NetEvents.handleAdminSet(target, true);
info("Admin-ed player by IP: {0} / {1}", ip, arg[0]);
}else{
info("Nobody with that name could be found.");
}
});
handler.register("unadmin", "<username>", "Removes admin status from a player", arg -> {
if(!state.is(State.playing)) {
err("Open the server first.");
return;
}
Player target = null;
for(Player player : playerGroup.all()){
if(player.name.equalsIgnoreCase(arg[0])){
target = player;
break;
}
}
if(target != null){
String ip = Net.getConnection(player.clientid).address;
netServer.admins.unAdminPlayer(ip);
NetEvents.handleAdminSet(target, false);
info("Un-admin-ed player by IP: {0} / {1}", ip, arg[0]);
}else{
info("Nobody with that name could be found.");
}
});
handler.register("admins", "List all banned IPs.", arg -> {
Array<String> admins = netServer.admins.getAdmins();
if(admins.size == 0){
Log.info("No admins have been found.");
}else{
Log.info("&lyAdmins:");
for(String string : admins){
Log.info(" &luy {0} / Name: '{1}'", string, netServer.admins.getLastName(string));
}
}
});
handler.register("runwave", "Trigger the next wave.", arg -> { handler.register("runwave", "Trigger the next wave.", arg -> {
if(!state.is(State.playing)) { if(!state.is(State.playing)) {
err("Not hosting. Host a game first."); err("Not hosting. Host a game first.");
@@ -317,7 +457,7 @@ public class ServerControl extends Module {
info(DebugFragment.debugInfo()); info(DebugFragment.debugInfo());
}); });
handler.register("trace", "<x> <y>", "Prints debug info about a block", arg -> { handler.register("traceblock", "<x> <y>", "Prints debug info about a block", arg -> {
try{ try{
int x = Integer.parseInt(arg[0]); int x = Integer.parseInt(arg[0]);
int y = Integer.parseInt(arg[1]); int y = Integer.parseInt(arg[1]);
@@ -343,6 +483,39 @@ public class ServerControl extends Module {
Log.err("Invalid coordinates passed."); Log.err("Invalid coordinates passed.");
} }
}); });
handler.register("trace", "<username>", "Trace a player's actions", arg -> {
if(!state.is(State.playing)) {
err("Open the server first.");
return;
}
Player target = null;
for(Player player : playerGroup.all()){
if(player.name.equalsIgnoreCase(arg[0])){
target = player;
break;
}
}
if(target != null){
TraceInfo info = netServer.admins.getTrace(Net.getConnection(target.clientid).address);
Log.info("&lcTrace info for player '{0}':", target.name);
Log.info(" &lyEntity ID: {0}", info. playerid);
Log.info(" &lyIP: {0}", info.ip);
Log.info(" &lycustom client: {0}", info.modclient);
Log.info("");
Log.info(" &lytotal blocks broken: {0}", info.totalBlocksBroken);
Log.info(" &lystructure blocks broken: {0}", info.structureBlocksBroken);
Log.info(" &lylast block broken: {0}", info.lastBlockBroken.formalName);
Log.info("");
Log.info(" &lytotal blocks placed: {0}", info.totalBlocksPlaced);
Log.info(" &lylast block placed: {0}", info.lastBlockPlaced.formalName);
}else{
info("Nobody with that name could be found.");
}
});
} }
private void readCommands(){ private void readCommands(){
@@ -355,8 +528,10 @@ public class ServerControl extends Module {
if (response.type == ResponseType.unknownCommand) { if (response.type == ResponseType.unknownCommand) {
err("Invalid command. Type 'help' for help."); err("Invalid command. Type 'help' for help.");
} else if (response.type == ResponseType.invalidArguments) { }else if (response.type == ResponseType.fewArguments) {
err("Invalid command arguments. Usage: " + response.command.text + " " + response.command.params); err("Too few command arguments. Usage: " + response.command.text + " " + response.command.paramText);
}else if (response.type == ResponseType.manyArguments) {
err("Too many command arguments. Usage: " + response.command.text + " " + response.command.paramText);
} }
}); });
} }