mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-10-04 12:20:21 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08690b9d9e | ||
|
|
d3eb9c9d99 | ||
|
|
a0ef3a0e44 | ||
|
|
ca3bfa2239 | ||
|
|
35a3a89d2d | ||
|
|
a647efe3fd | ||
|
|
c79c23899f | ||
|
|
fcb74d4fba | ||
|
|
e58194ac3c | ||
|
|
31f83dfb40 | ||
|
|
022b16c8e6 | ||
|
|
d462d9c89b | ||
|
|
86360049c3 | ||
|
|
f524be6738 | ||
|
|
519d2c1714 | ||
|
|
c19fcb831f | ||
|
|
ff6268e616 | ||
|
|
b0f1ad63ee | ||
|
|
a7b017f307 | ||
|
|
29c3df8ba7 | ||
|
|
ba0c7907ca | ||
|
|
4c6ae83d4b | ||
|
|
a3268eefbf | ||
|
|
5a77eebb34 | ||
|
|
68af32bc9f | ||
|
|
380a955e7a | ||
|
|
b3adf7b331 | ||
|
|
7f2c2d9d6b |
@@ -9,6 +9,7 @@
|
||||
/desktop/mindustry-maps/
|
||||
/desktop/gifexport/
|
||||
/core/lib/
|
||||
/annotations/build/
|
||||
/kryonet/build/
|
||||
/server/build/
|
||||
/android/assets/mindustry-maps/
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ allprojects {
|
||||
gdxVersion = '1.9.8'
|
||||
roboVMVersion = '2.3.0'
|
||||
aiVersion = '1.8.1'
|
||||
uCoreVersion = '55f0d90'
|
||||
uCoreVersion = 'd5af97f93813d8767423521b1fcc5a5e0f7241d9'
|
||||
|
||||
getVersionString = {
|
||||
String buildVersion = getBuildVersion()
|
||||
@@ -85,7 +85,7 @@ project(":desktop") {
|
||||
compile "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion"
|
||||
compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
|
||||
compile "com.badlogicgames.gdx:gdx-controllers-lwjgl3:$gdxVersion"
|
||||
compile 'com.github.MinnDevelopment:java-discord-rpc:v1.3.2'
|
||||
compile 'com.github.MinnDevelopment:java-discord-rpc:v2.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ project(":core") {
|
||||
apply plugin: "java"
|
||||
|
||||
dependencies {
|
||||
boolean comp = false//System.properties["release"] == null || System.properties["release"] == "false"
|
||||
boolean comp = System.properties["release"] == null || System.properties["release"] == "false"
|
||||
|
||||
if(!comp){
|
||||
println("NOTICE: Compiling release build.")
|
||||
|
||||
@@ -70,6 +70,8 @@ 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.rollback=Rollback
|
||||
text.server.rollback.numberfield=Rollback Amount:
|
||||
text.server.add=Add Server
|
||||
text.server.delete=Are you sure you want to delete this server?
|
||||
text.server.hostname=Host: {0}
|
||||
@@ -209,6 +211,8 @@ placemode.touch.name=touch
|
||||
placemode.cursor.name=cursor
|
||||
text.blocks.extrainfo=[accent]extra block info:
|
||||
text.blocks.blockinfo=Block Info
|
||||
text.blocks.editlogs=Edit Logs
|
||||
text.block.editlogsnotfound=[red]There are no edit logs for this location.
|
||||
text.blocks.powercapacity=Power Capacity
|
||||
text.blocks.powershot=Power/shot
|
||||
text.blocks.powersecond=Power/second
|
||||
|
||||
@@ -71,19 +71,19 @@ text.server.admins = 관리자
|
||||
text.server.admins.none = 관리자가 없습니다.
|
||||
text.server.add = 서버 추가
|
||||
text.server.delete = 이 서버를 삭제 하시겠습니까?
|
||||
text.server.hostname = 호스트:
|
||||
text.server.hostname = 호스트: {0}
|
||||
text.server.edit = 서버 수정
|
||||
text.server.outdated = [crimson]서버 버전이 낮습니다![]
|
||||
text.server.outdated.client = [Crimson]클라이언트 버전이 낮습니다![]
|
||||
text.server.version = [lightgray] 버전 : {0}
|
||||
text.server.custombuild = [노란색]수정된 빌드
|
||||
text.server.custombuild = [yellow]custom server
|
||||
text.confirmban = 이 플레이어를 차단하시겠습니까?
|
||||
text.confirmunban = 이 플레이어를 차단 해제 하시겠습니까?
|
||||
text.confirmadmin = 이 플레이어를 관리자로 설정 하시겠습니까?
|
||||
text.confirmunadmin = 이 플레이어에서 관리자 상태를 삭제 하시겠습니까?
|
||||
text.joingame.byip = IP로 참가하기...
|
||||
text.joingame.title = 게임 참가
|
||||
text.joingame.ip = IP:
|
||||
text.joingame.ip = IP:
|
||||
text.disconnect = 서버와의 연결이 해제되었습니다.
|
||||
text.disconnect.data = 맵 데이터를 불러오지 못했습니다!
|
||||
text.connecting = [accent]연결중...
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,8 @@ import com.badlogic.gdx.Application.ApplicationType;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import io.anuke.mindustry.core.*;
|
||||
import io.anuke.mindustry.entities.Bullet;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
@@ -11,6 +13,7 @@ import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.entities.effect.Shield;
|
||||
import io.anuke.mindustry.entities.enemies.Enemy;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.net.EditLog;
|
||||
import io.anuke.mindustry.net.ClientDebug;
|
||||
import io.anuke.mindustry.net.ServerDebug;
|
||||
import io.anuke.ucore.UCore;
|
||||
@@ -18,7 +21,7 @@ import io.anuke.ucore.entities.EffectEntity;
|
||||
import io.anuke.ucore.entities.Entities;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||
|
||||
import io.anuke.ucore.util.OS;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Vars{
|
||||
@@ -49,12 +52,17 @@ public class Vars{
|
||||
public static final String discordURL = "https://discord.gg/BKADYds";
|
||||
|
||||
public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases";
|
||||
public static final String macAppDir = UCore.getProperty("user.home") + "/Library/Application Support/";
|
||||
//directory for user-created map data
|
||||
public static final FileHandle customMapDirectory = gwt ? null : UCore.isAssets() ?
|
||||
Gdx.files.local("../../desktop/mindustry-maps") : Gdx.files.local("mindustry-maps/");
|
||||
Gdx.files.local("../../desktop/mindustry-maps") :
|
||||
OS.isMac ? (Gdx.files.absolute(macAppDir).child("maps/")) :
|
||||
Gdx.files.local("mindustry-maps/");
|
||||
//save file directory
|
||||
public static final FileHandle saveDirectory = gwt ? null : UCore.isAssets() ?
|
||||
Gdx.files.local("../../desktop/mindustry-saves") : Gdx.files.local("mindustry-saves/");
|
||||
Gdx.files.local("../../desktop/mindustry-saves") :
|
||||
OS.isMac ? (Gdx.files.absolute(macAppDir).child("saves/")) :
|
||||
Gdx.files.local("mindustry-saves/");
|
||||
//scale of the font
|
||||
public static float fontscale = Math.max(Unit.dp.scl(1f)/2f, 0.5f);
|
||||
//camera zoom displayed on startup
|
||||
@@ -90,6 +98,8 @@ public class Vars{
|
||||
//amount of drops that are left when breaking a block
|
||||
public static final float breakDropAmount = 0.5f;
|
||||
|
||||
public static Array<EditLog> currentEditLogs = new Array<>();
|
||||
|
||||
//only if smoothCamera
|
||||
public static boolean snapCamera = true;
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ import io.anuke.ucore.core.Inputs.DeviceType;
|
||||
import io.anuke.ucore.entities.Entities;
|
||||
import io.anuke.ucore.modules.Module;
|
||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||
import io.anuke.ucore.util.*;
|
||||
import io.anuke.ucore.util.Atlas;
|
||||
import io.anuke.ucore.util.InputProxy;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
@@ -384,7 +386,7 @@ public class Control extends Module{
|
||||
|
||||
if(respawntime > 0){
|
||||
|
||||
respawntime -= delta();
|
||||
respawntime -= Timers.delta();
|
||||
|
||||
if(respawntime <= 0){
|
||||
player.set(world.getSpawnX(), world.getSpawnY());
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.IntSet;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Bullet;
|
||||
import io.anuke.mindustry.entities.BulletType;
|
||||
@@ -166,6 +167,10 @@ public class NetClient extends Module {
|
||||
|
||||
ui.hudfrag.updateItems();
|
||||
});
|
||||
|
||||
Net.handleClient(BlockLogRequestPacket.class, packet -> {
|
||||
currentEditLogs = packet.editlogs;
|
||||
});
|
||||
|
||||
Net.handleClient(PlacePacket.class, (packet) -> {
|
||||
Placement.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, Timers.get("placeblocksound", 10));
|
||||
|
||||
@@ -25,7 +25,6 @@ import io.anuke.ucore.util.Timer;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class NetServer extends Module{
|
||||
@@ -44,7 +43,10 @@ public class NetServer extends Module{
|
||||
|
||||
public NetServer(){
|
||||
|
||||
Events.on(GameOverEvent.class, () -> weapons.clear());
|
||||
Events.on(GameOverEvent.class, () -> {
|
||||
weapons.clear();
|
||||
admins.getEditLogs().clear();
|
||||
});
|
||||
|
||||
Net.handleServer(Connect.class, (id, connect) -> {
|
||||
if(admins.isIPBanned(connect.addressTCP)){
|
||||
@@ -211,6 +213,7 @@ public class NetServer extends Module{
|
||||
|
||||
Placement.placeBlock(packet.x, packet.y, block, packet.rotation, true, false);
|
||||
|
||||
admins.logEdit(packet.x, packet.y, connections.get(id), block, packet.rotation, EditLog.EditAction.PLACE);
|
||||
admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block;
|
||||
admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++;
|
||||
admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlockPlaced ++;
|
||||
@@ -235,6 +238,7 @@ public class NetServer extends Module{
|
||||
Block block = Placement.breakBlock(packet.x, packet.y, true, false);
|
||||
|
||||
if(block != null) {
|
||||
admins.logEdit(packet.x, packet.y, connections.get(id), block, tile.getRotation(), EditLog.EditAction.BREAK);
|
||||
admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block;
|
||||
admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++;
|
||||
admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlocksBroken ++;
|
||||
@@ -312,7 +316,7 @@ public class NetServer extends Module{
|
||||
packet.id = connections.get(id).id;
|
||||
Net.sendExcept(id, packet, SendMode.tcp);
|
||||
});
|
||||
|
||||
|
||||
Net.handleServer(AdministerRequestPacket.class, (id, packet) -> {
|
||||
Player player = connections.get(id);
|
||||
|
||||
@@ -345,6 +349,24 @@ public class NetServer extends Module{
|
||||
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
|
||||
}
|
||||
});
|
||||
|
||||
Net.handleServer(BlockLogRequestPacket.class, (id, packet) -> {
|
||||
packet.editlogs = admins.getEditLogs().get(packet.x + packet.y * world.width(), new Array<>());
|
||||
Net.sendTo(id, packet, SendMode.udp);
|
||||
});
|
||||
|
||||
Net.handleServer(RollbackRequestPacket.class, (id, packet) -> {
|
||||
Player player = connections.get(id);
|
||||
|
||||
if(!player.isAdmin){
|
||||
Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform a rollback without proper security access.",
|
||||
player.name, Net.getConnection(player.clientid).address);
|
||||
return;
|
||||
}
|
||||
|
||||
admins.rollbackWorld(packet.rollbackTimes);
|
||||
Log.info("&lc{0} has rolled back the world {1} times.", player.name, packet.rollbackTimes);
|
||||
});
|
||||
}
|
||||
|
||||
public void update(){
|
||||
@@ -474,7 +496,7 @@ public class NetServer extends Module{
|
||||
packet.wave = state.wave;
|
||||
packet.time = Timers.time();
|
||||
packet.timestamp = TimeUtils.millis();
|
||||
|
||||
|
||||
Net.send(packet, SendMode.udp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
|
||||
import io.anuke.ucore.core.Settings;
|
||||
import io.anuke.ucore.entities.Entity;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
import io.anuke.ucore.function.Consumer;
|
||||
import io.anuke.ucore.scene.ui.TextField;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -63,6 +64,16 @@ public abstract class Platform {
|
||||
}
|
||||
/**Only used for iOS or android: open the share menu for a map or save.*/
|
||||
public void shareFile(FileHandle file){}
|
||||
|
||||
/**Show a file chooser. Desktop only.
|
||||
*
|
||||
* @param text File chooser title text
|
||||
* @param content Type of files to be loaded
|
||||
* @param cons Selection listener
|
||||
* @param open Whether to open or save files.
|
||||
* @param filetype File extensions to filter.
|
||||
*/
|
||||
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, String filetype){}
|
||||
/**Use the default thread provider from the kryonet module for this.*/
|
||||
public ThreadProvider getThreadProvider(){
|
||||
return new ThreadProvider() {
|
||||
|
||||
@@ -489,6 +489,12 @@ public class Renderer extends RendererModule{
|
||||
Lines.crect(target.drawx(), target.drawy(), target.block().width * tilesize, target.block().height * tilesize);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
if(Inputs.keyDown("block_logs")){
|
||||
Draw.color(Colors.get("accent"));
|
||||
Lines.crect(target.drawx(), target.drawy(), target.block().width * tilesize, target.block().height * tilesize);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
if(target.entity != null) {
|
||||
int bot = 0, top = 0;
|
||||
|
||||
@@ -48,6 +48,7 @@ public class UI extends SceneModule{
|
||||
public BansDialog bans;
|
||||
public AdminsDialog admins;
|
||||
public TraceDialog traces;
|
||||
public RollbackDialog rollback;
|
||||
public ChangelogDialog changelog;
|
||||
|
||||
public final MenuFragment menufrag = new MenuFragment();
|
||||
@@ -159,6 +160,7 @@ public class UI extends SceneModule{
|
||||
bans = new BansDialog();
|
||||
admins = new AdminsDialog();
|
||||
traces = new TraceDialog();
|
||||
rollback = new RollbackDialog();
|
||||
|
||||
build.begin(scene);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public class DefaultKeybinds {
|
||||
"rotate", new Axis(Input.SCROLL),
|
||||
"toggle_menus", Input.C,
|
||||
"block_info", Input.CONTROL_LEFT,
|
||||
"block_logs", Input.I,
|
||||
"player_list", Input.TAB,
|
||||
"chat", Input.ENTER,
|
||||
"chat_history_prev", Input.UP,
|
||||
|
||||
@@ -107,6 +107,17 @@ public class DesktopInput extends InputHandler{
|
||||
Cursors.restoreCursor();
|
||||
}
|
||||
}
|
||||
|
||||
if(recipe == null && !ui.hasMouse() && Inputs.keyDown("block_logs")) {
|
||||
showCursor = true;
|
||||
if(Inputs.keyTap("select")){
|
||||
NetEvents.handleBlockLogRequest(getBlockX(), getBlockY());
|
||||
Timers.runTask(20f, () -> {
|
||||
ui.hudfrag.blockfrag.showBlockLogs(getBlockX(), getBlockY());
|
||||
Cursors.restoreCursor();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(target != null && target.block().isConfigurable(target)){
|
||||
showCursor = true;
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.ui.dialogs.FileChooser;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.ColorMapper;
|
||||
import io.anuke.mindustry.world.ColorMapper.BlockPair;
|
||||
@@ -43,7 +42,6 @@ public class MapEditorDialog extends Dialog{
|
||||
private MapSaveDialog saveDialog;
|
||||
private MapResizeDialog resizeDialog;
|
||||
private ScrollPane pane;
|
||||
private FileChooser openFile, saveFile;
|
||||
private boolean saved = false;
|
||||
|
||||
private ButtonGroup<ImageButton> blockgroup;
|
||||
@@ -56,25 +54,6 @@ public class MapEditorDialog extends Dialog{
|
||||
dialog = new MapGenerateDialog(editor);
|
||||
view = new MapView(editor);
|
||||
|
||||
openFile = new FileChooser("$text.loadimage", FileChooser.pngFilter, true, this::tryLoadMap);
|
||||
|
||||
saveFile = new FileChooser("$saveimage", false, file -> {
|
||||
if(!file.extension().toLowerCase().equals(".png")){
|
||||
file = file.parent().child(file.nameWithoutExtension() + ".png");
|
||||
}
|
||||
FileHandle result = file;
|
||||
ui.loadfrag.show();
|
||||
Timers.run(3f, () -> {
|
||||
try{
|
||||
Pixmaps.write(editor.pixmap(), result);
|
||||
}catch (Exception e){
|
||||
ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false)));
|
||||
if(!mobile) Log.err(e);
|
||||
}
|
||||
ui.loadfrag.hide();
|
||||
});
|
||||
});
|
||||
|
||||
loadDialog = new MapLoadDialog(map -> {
|
||||
saveDialog.setFieldText(map.name);
|
||||
ui.loadfrag.show();
|
||||
@@ -225,9 +204,9 @@ public class MapEditorDialog extends Dialog{
|
||||
//iOS does not support loading raw files.
|
||||
if(!ios) {
|
||||
|
||||
new imagebutton("icon-load-image", isize, () ->
|
||||
openFile.show()
|
||||
).text("$text.editor.loadimage");
|
||||
new imagebutton("icon-load-image", isize, () -> {
|
||||
Platform.instance.showFileChooser(Bundles.get("text.loadimage"), "Image Files", MapEditorDialog.this::tryLoadMap, true, "png");
|
||||
}).text("$text.editor.loadimage");
|
||||
|
||||
row();
|
||||
}
|
||||
@@ -235,7 +214,22 @@ public class MapEditorDialog extends Dialog{
|
||||
new imagebutton("icon-save-image", isize, () -> {
|
||||
//iOS doesn't really support saving raw files. Sharing is used instead.
|
||||
if(!ios){
|
||||
saveFile.show();
|
||||
Platform.instance.showFileChooser(Bundles.get("text.saveimage"), "Image Files", file -> {
|
||||
if(!file.extension().toLowerCase().equals(".png")){
|
||||
file = file.parent().child(file.nameWithoutExtension() + ".png");
|
||||
}
|
||||
FileHandle result = file;
|
||||
ui.loadfrag.show();
|
||||
Timers.run(3f, () -> {
|
||||
try{
|
||||
Pixmaps.write(editor.pixmap(), result);
|
||||
}catch (Exception e){
|
||||
ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false)));
|
||||
if(!mobile) Log.err(e);
|
||||
}
|
||||
ui.loadfrag.hide();
|
||||
});
|
||||
}, false, "png");
|
||||
}else{
|
||||
try{
|
||||
FileHandle file = Gdx.files.local(("map-" + ((editor.getMap().name == null) ? "unknown" : editor.getMap().name) + ".png"));
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.Json;
|
||||
import com.badlogic.gdx.utils.ObjectMap;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Placement;
|
||||
import io.anuke.mindustry.world.blocks.types.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.types.Floor;
|
||||
import io.anuke.mindustry.world.blocks.types.Rock;
|
||||
import io.anuke.mindustry.world.blocks.types.StaticBlock;
|
||||
import io.anuke.ucore.core.Settings;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class Administration {
|
||||
public static final int defaultMaxBrokenBlocks = 15;
|
||||
@@ -15,6 +24,9 @@ public class Administration {
|
||||
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
|
||||
/**Maps UUIDs to trace infos. This is wiped when a player logs off.*/
|
||||
private ObjectMap<String, TraceInfo> traceInfo = new ObjectMap<>();
|
||||
/**Maps packed coordinates to logs for that coordinate */
|
||||
private IntMap<Array<EditLog>> editLogs = new IntMap<>();
|
||||
|
||||
private Array<String> bannedIPs = new Array<>();
|
||||
|
||||
public Administration(){
|
||||
@@ -48,6 +60,68 @@ public class Administration {
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public IntMap<Array<EditLog>> getEditLogs() {
|
||||
return editLogs;
|
||||
}
|
||||
|
||||
public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action) {
|
||||
if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) return;
|
||||
if(editLogs.containsKey(x + y * world.width())) {
|
||||
editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action));
|
||||
}
|
||||
else {
|
||||
Array<EditLog> logs = new Array<>();
|
||||
logs.add(new EditLog(player.name, block, rotation, action));
|
||||
editLogs.put(x + y * world.width(), logs);
|
||||
}
|
||||
}
|
||||
|
||||
public void rollbackWorld(int rollbackTimes) {
|
||||
for(IntMap.Entry<Array<EditLog>> editLog : editLogs.entries()) {
|
||||
int coords = editLog.key;
|
||||
Array<EditLog> logs = editLog.value;
|
||||
|
||||
for(int i = 0; i < rollbackTimes; i++) {
|
||||
|
||||
EditLog log = logs.get(logs.size - 1);
|
||||
|
||||
int x = coords % world.width();
|
||||
int y = coords / world.width();
|
||||
Block result = log.block;
|
||||
int rotation = log.rotation;
|
||||
|
||||
if(log.action == EditLog.EditAction.PLACE) {
|
||||
Placement.breakBlock(x, y, false, false);
|
||||
|
||||
Packets.BreakPacket packet = new Packets.BreakPacket();
|
||||
packet.x = (short) x;
|
||||
packet.y = (short) y;
|
||||
packet.playerid = 0;
|
||||
|
||||
Net.send(packet, Net.SendMode.tcp);
|
||||
}
|
||||
else if(log.action == EditLog.EditAction.BREAK) {
|
||||
Placement.placeBlock(x, y, result, rotation, false, false);
|
||||
|
||||
Packets.PlacePacket packet = new Packets.PlacePacket();
|
||||
packet.x = (short) x;
|
||||
packet.y = (short) y;
|
||||
packet.rotation = (byte) rotation;
|
||||
packet.playerid = 0;
|
||||
packet.block = result.id;
|
||||
|
||||
Net.send(packet, Net.SendMode.tcp);
|
||||
}
|
||||
|
||||
logs.removeIndex(logs.size - 1);
|
||||
if(logs.size == 0) {
|
||||
editLogs.remove(coords);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateBreak(String id, String ip){
|
||||
if(!isAntiGrief() || isAdmin(id, ip)) return true;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
|
||||
public class EditLog {
|
||||
public String playername;
|
||||
public Block block;
|
||||
public int rotation;
|
||||
public EditAction action;
|
||||
|
||||
EditLog(String playername, Block block, int rotation, EditAction action) {
|
||||
this.playername = playername;
|
||||
this.block = block;
|
||||
this.rotation = rotation;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String info() {
|
||||
return String.format("Player: %s, Block: %s, Rotation: %s, Edit Action: %s", playername, block.name(), rotation, action.toString());
|
||||
}
|
||||
|
||||
public enum EditAction {
|
||||
PLACE, BREAK;
|
||||
}
|
||||
}
|
||||
@@ -177,4 +177,20 @@ public class NetEvents {
|
||||
ui.traces.show(target, netServer.admins.getTrace(Net.getConnection(target.clientid).address));
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleBlockLogRequest(int x, int y) {
|
||||
BlockLogRequestPacket packet = new BlockLogRequestPacket();
|
||||
packet.x = x;
|
||||
packet.y = y;
|
||||
packet.editlogs = Vars.currentEditLogs;
|
||||
|
||||
Net.send(packet, SendMode.udp);
|
||||
}
|
||||
|
||||
public static void handleRollbackRequest(int rollbackTimes) {
|
||||
RollbackRequestPacket packet = new RollbackRequestPacket();
|
||||
packet.rollbackTimes = rollbackTimes;
|
||||
|
||||
Net.send(packet, SendMode.udp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.Base64Coder;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.reflect.ClassReflection;
|
||||
import com.badlogic.gdx.utils.reflect.ReflectionException;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.entities.SyncEntity;
|
||||
import io.anuke.mindustry.io.Version;
|
||||
@@ -12,7 +15,6 @@ import io.anuke.mindustry.resource.Item;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.ucore.entities.Entities;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**Class for storing all packets.*/
|
||||
@@ -139,7 +141,61 @@ public class Packets {
|
||||
timestamp = buffer.getLong();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class BlockLogRequestPacket implements Packet {
|
||||
public int x;
|
||||
public int y;
|
||||
public Array<EditLog> editlogs;
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
buffer.putShort((short)x);
|
||||
buffer.putShort((short)y);
|
||||
buffer.putInt(editlogs.size);
|
||||
for(EditLog value : editlogs) {
|
||||
buffer.put((byte)value.playername.getBytes().length);
|
||||
buffer.put(value.playername.getBytes());
|
||||
buffer.putInt(value.block.id);
|
||||
buffer.put((byte) value.rotation);
|
||||
buffer.put((byte) value.action.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
x = buffer.getShort();
|
||||
y = buffer.getShort();
|
||||
editlogs = new Array<>();
|
||||
int arraySize = buffer.getInt();
|
||||
for(int a = 0; a < arraySize; a ++) {
|
||||
byte length = buffer.get();
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
String name = new String(bytes);
|
||||
|
||||
int blockid = buffer.getInt();
|
||||
int rotation = buffer.get();
|
||||
int ordinal = buffer.get();
|
||||
|
||||
editlogs.add(new EditLog(name, Block.getByID(blockid), rotation, EditLog.EditAction.values()[ordinal]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RollbackRequestPacket implements Packet {
|
||||
public int rollbackTimes;
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
buffer.putInt(rollbackTimes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
rollbackTimes = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PositionPacket implements Packet{
|
||||
public byte[] data;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ public class Registrator {
|
||||
PlacePacket.class,
|
||||
BreakPacket.class,
|
||||
StateSyncPacket.class,
|
||||
BlockLogRequestPacket.class,
|
||||
RollbackRequestPacket.class,
|
||||
BlockSyncPacket.class,
|
||||
BulletPacket.class,
|
||||
EnemyDeathPacket.class,
|
||||
@@ -44,7 +46,7 @@ public class Registrator {
|
||||
NetErrorPacket.class,
|
||||
PlayerAdminPacket.class,
|
||||
AdministerRequestPacket.class,
|
||||
TracePacket.class,
|
||||
TracePacket.class
|
||||
};
|
||||
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();
|
||||
|
||||
|
||||
@@ -2,17 +2,20 @@ package io.anuke.mindustry.ui.dialogs;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.utils.ObjectSet;
|
||||
import io.anuke.mindustry.ui.Links;
|
||||
import io.anuke.mindustry.ui.Links.LinkEntry;
|
||||
import io.anuke.ucore.core.Core;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.scene.ui.ScrollPane;
|
||||
import io.anuke.ucore.scene.ui.layout.Table;
|
||||
import io.anuke.ucore.util.OS;
|
||||
|
||||
import static io.anuke.mindustry.Vars.ios;
|
||||
import static io.anuke.mindustry.Vars.ui;
|
||||
|
||||
public class AboutDialog extends FloatingDialog {
|
||||
private static ObjectSet<String> bannedItems = ObjectSet.with("google-play", "itch.io", "dev-builds", "trello");
|
||||
|
||||
public AboutDialog(){
|
||||
super("$text.about.button");
|
||||
@@ -26,7 +29,7 @@ public class AboutDialog extends FloatingDialog {
|
||||
ScrollPane pane = new ScrollPane(in, "clear");
|
||||
|
||||
for(LinkEntry link : Links.getLinks()){
|
||||
if(ios && link.name.equals("google-play")){ //because Apple doesn't like me mentioning android
|
||||
if((ios || OS.isMac) && bannedItems.contains(link.name)){ //because Apple doesn't like me mentioning things
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -64,13 +67,14 @@ public class AboutDialog extends FloatingDialog {
|
||||
content().add(pane).growX();
|
||||
|
||||
buttons().addButton("$text.credits", this::showCredits).size(200f, 64f);
|
||||
if(!ios){
|
||||
|
||||
if(!ios && !OS.isMac){
|
||||
buttons().addButton("$text.changelog.title", ui.changelog::show).size(200f, 64f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showCredits(){
|
||||
public void showCredits(){
|
||||
FloatingDialog dialog = new FloatingDialog("$text.credits");
|
||||
dialog.addCloseButton();
|
||||
dialog.content().add("$text.about");
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.anuke.ucore.core.Settings;
|
||||
import io.anuke.ucore.scene.ui.ScrollPane;
|
||||
import io.anuke.ucore.scene.ui.layout.Table;
|
||||
import io.anuke.ucore.util.Log;
|
||||
import io.anuke.ucore.util.OS;
|
||||
|
||||
import static io.anuke.mindustry.Vars.ios;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class ChangelogDialog extends FloatingDialog{
|
||||
|
||||
content().add("$text.changelog.loading");
|
||||
|
||||
if(!ios) {
|
||||
if(!ios && !OS.isMac) {
|
||||
Changelogs.getChangelog(result -> {
|
||||
versions = result;
|
||||
Gdx.app.postRunnable(this::setup);
|
||||
@@ -55,6 +56,10 @@ public class ChangelogDialog extends FloatingDialog{
|
||||
}
|
||||
}else{
|
||||
for(VersionInfo info : versions){
|
||||
String desc = info.description;
|
||||
|
||||
desc = desc.replace("Android", "Mobile");
|
||||
|
||||
Table in = new Table("clear");
|
||||
in.top().left().margin(10);
|
||||
|
||||
@@ -67,7 +72,7 @@ public class ChangelogDialog extends FloatingDialog{
|
||||
in.add("$text.changelog.latest");
|
||||
}
|
||||
in.row();
|
||||
in.labelWrap("[lightgray]" + info.description).width(vw - 20).padTop(12);
|
||||
in.labelWrap("[lightgray]" + desc).width(vw - 20).padTop(12);
|
||||
|
||||
table.add(in).width(vw).pad(8).row();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.Align;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.Pools;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.ucore.UCore;
|
||||
import io.anuke.ucore.core.Core;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.function.Consumer;
|
||||
@@ -15,12 +16,14 @@ import io.anuke.ucore.scene.event.Touchable;
|
||||
import io.anuke.ucore.scene.ui.*;
|
||||
import io.anuke.ucore.scene.ui.layout.Table;
|
||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||
import io.anuke.ucore.util.OS;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FileChooser extends FloatingDialog {
|
||||
private Table files;
|
||||
private FileHandle homeDirectory = Gdx.files.absolute(Gdx.files.getExternalStoragePath());
|
||||
private FileHandle homeDirectory = Gdx.files.absolute(OS.isMac ? UCore.getProperty("user.home") + "/Downloads/" :
|
||||
Gdx.files.getExternalStoragePath());
|
||||
private FileHandle directory = homeDirectory;
|
||||
private ScrollPane pane;
|
||||
private TextField navigation, filefield;
|
||||
@@ -96,6 +99,11 @@ public class FileChooser extends FloatingDialog {
|
||||
updateFiles(true);
|
||||
});
|
||||
|
||||
//Macs are confined to the Downloads/ directory
|
||||
if(OS.isMac){
|
||||
up.setDisabled(true);
|
||||
}
|
||||
|
||||
ImageButton back = new ImageButton("icon-arrow-left");
|
||||
back.resizeImage(isize);
|
||||
|
||||
@@ -166,7 +174,8 @@ public class FileChooser extends FloatingDialog {
|
||||
|
||||
private void updateFiles(boolean push){
|
||||
if(push) stack.push(directory);
|
||||
navigation.setText(directory.toString());
|
||||
//if is mac, don't display extra info since you can only ever go to downloads
|
||||
navigation.setText(OS.isMac ? directory.name() : directory.toString());
|
||||
|
||||
GlyphLayout layout = Pools.obtain(GlyphLayout.class);
|
||||
|
||||
@@ -181,23 +190,25 @@ public class FileChooser extends FloatingDialog {
|
||||
Pools.free(layout);
|
||||
|
||||
files.clearChildren();
|
||||
files.top().left();
|
||||
FileHandle[] names = getFileNames();
|
||||
|
||||
Image upimage = new Image("icon-folder-parent");
|
||||
//macs are confined to the Downloads/ directory
|
||||
if(!OS.isMac) {
|
||||
Image upimage = new Image("icon-folder-parent");
|
||||
TextButton upbutton = new TextButton(".." + directory.toString());
|
||||
upbutton.clicked(() -> {
|
||||
directory = directory.parent();
|
||||
updateFiles(true);
|
||||
});
|
||||
|
||||
TextButton upbutton = new TextButton(".." + directory.toString());
|
||||
upbutton.clicked(()->{
|
||||
directory = directory.parent();
|
||||
updateFiles(true);
|
||||
});
|
||||
|
||||
upbutton.left().add(upimage).padRight(4f).size(14*2);
|
||||
upbutton.getCells().reverse();
|
||||
|
||||
files.top().left().add(upbutton).align(Align.topLeft).fillX().expandX().height(50).pad(2).colspan(2);
|
||||
upbutton.getLabel().setAlignment(Align.left);
|
||||
upbutton.left().add(upimage).padRight(4f).size(14 * 2);
|
||||
upbutton.getLabel().setAlignment(Align.left);
|
||||
upbutton.getCells().reverse();
|
||||
|
||||
files.row();
|
||||
files.add(upbutton).align(Align.topLeft).fillX().expandX().height(50).pad(2).colspan(2);
|
||||
files.row();
|
||||
}
|
||||
|
||||
ButtonGroup<TextButton> group = new ButtonGroup<TextButton>();
|
||||
group.setMinCheckCount(0);
|
||||
|
||||
@@ -87,14 +87,14 @@ public class LoadDialog extends FloatingDialog{
|
||||
if(!gwt) {
|
||||
t.addImageButton("icon-save", "empty", 14 * 3, () -> {
|
||||
if(!ios) {
|
||||
new FileChooser("$text.save.export", false, file -> {
|
||||
try {
|
||||
slot.exportFile(file);
|
||||
setup();
|
||||
} catch (IOException e) {
|
||||
ui.showError(Bundles.format("text.save.export.fail", Strings.parseException(e, false)));
|
||||
}
|
||||
}).show();
|
||||
Platform.instance.showFileChooser(Bundles.get("text.save.export"), "Mindustry Save", file -> {
|
||||
try {
|
||||
slot.exportFile(file);
|
||||
setup();
|
||||
} catch (IOException e) {
|
||||
ui.showError(Bundles.format("text.save.export.fail", Strings.parseException(e, false)));
|
||||
}
|
||||
}, false, "mins");
|
||||
}else{
|
||||
try {
|
||||
FileHandle file = Gdx.files.local("save-" + slot.getName() + ".mins");
|
||||
@@ -150,7 +150,7 @@ public class LoadDialog extends FloatingDialog{
|
||||
if(gwt || ios) return;
|
||||
|
||||
slots.addImageTextButton("$text.save.import", "icon-add", "clear", 14*3, () -> {
|
||||
new FileChooser("$text.save.import", f -> f.extension().equals("mins"), true, file -> {
|
||||
Platform.instance.showFileChooser(Bundles.get("text.save.import"), "Mindustry Save", file -> {
|
||||
if(SaveIO.isSaveValid(file)){
|
||||
try{
|
||||
control.getSaves().importSave(file);
|
||||
@@ -161,7 +161,7 @@ public class LoadDialog extends FloatingDialog{
|
||||
}else{
|
||||
ui.showError("$text.save.import.invalid");
|
||||
}
|
||||
}).show();
|
||||
}, true, "mins");
|
||||
}).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.anuke.mindustry.ui.dialogs;
|
||||
|
||||
import io.anuke.mindustry.net.NetEvents;
|
||||
import io.anuke.ucore.scene.ui.Label;
|
||||
import io.anuke.ucore.scene.ui.TextField;
|
||||
import io.anuke.ucore.scene.ui.layout.Table;
|
||||
import io.anuke.ucore.util.Strings;
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class RollbackDialog extends FloatingDialog {
|
||||
|
||||
public RollbackDialog(){
|
||||
super("$text.server.rollback");
|
||||
|
||||
setup();
|
||||
shown(this::setup);
|
||||
}
|
||||
|
||||
private void setup(){
|
||||
content().clear();
|
||||
buttons().clear();
|
||||
|
||||
if(gwt) return;
|
||||
|
||||
content().row();
|
||||
content().add("$text.server.rollback.numberfield");
|
||||
|
||||
TextField field = content().addField("", t->{}).size(200f, 48f).get();
|
||||
field.setTextFieldFilter((f, c) -> field.getText().length() < 4);
|
||||
|
||||
content().row();
|
||||
buttons().defaults().size(200f, 50f).left().pad(2f);
|
||||
buttons().addButton("$text.cancel", this::hide);
|
||||
|
||||
buttons().addButton("$text.ok", () -> {
|
||||
NetEvents.handleRollbackRequest(Integer.valueOf(field.getText()));
|
||||
hide();
|
||||
}).disabled(b -> field.getText().isEmpty() || !Strings.canParsePostiveInt(field.getText()));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.badlogic.gdx.math.Interpolation;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.input.InputHandler;
|
||||
import io.anuke.mindustry.net.EditLog;
|
||||
import io.anuke.mindustry.resource.*;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
@@ -24,7 +25,6 @@ import io.anuke.ucore.scene.ui.layout.Table;
|
||||
import io.anuke.ucore.util.Bundles;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
import io.anuke.ucore.util.Strings;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class BlocksFragment implements Fragment{
|
||||
@@ -344,7 +344,45 @@ public class BlocksFragment implements Fragment{
|
||||
|
||||
d.show();
|
||||
}
|
||||
|
||||
|
||||
public void showBlockLogs(int x, int y){
|
||||
boolean wasPaused = state.is(State.paused);
|
||||
state.set(State.paused);
|
||||
|
||||
FloatingDialog d = new FloatingDialog("$text.blocks.editlogs");
|
||||
Table table = new Table();
|
||||
table.defaults().pad(1f);
|
||||
ScrollPane pane = new ScrollPane(table, "clear");
|
||||
pane.setFadeScrollBars(false);
|
||||
Table top = new Table();
|
||||
top.left();
|
||||
top.add("[accent]Edit logs for: "+ x + ", " + y);
|
||||
table.add(top).fill().left();
|
||||
table.row();
|
||||
|
||||
d.content().add(pane).grow();
|
||||
|
||||
if(currentEditLogs == null || currentEditLogs.size == 0) {
|
||||
table.add("$text.block.editlogsnotfound").left();
|
||||
table.row();
|
||||
}
|
||||
else {
|
||||
for(int i = 0; i < currentEditLogs.size; i++) {
|
||||
EditLog log = currentEditLogs.get(i);
|
||||
table.add("[gold]" + (i + 1) + ". [white]" + log.info()).left();
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
|
||||
d.buttons().addButton("$text.ok", () -> {
|
||||
if(!wasPaused)
|
||||
state.set(State.playing);
|
||||
d.hide();
|
||||
}).size(110, 50).pad(10f);
|
||||
|
||||
d.show();
|
||||
}
|
||||
|
||||
public void updateItems(){
|
||||
|
||||
itemtable.clear();
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
import io.anuke.ucore.scene.builders.imagebutton;
|
||||
import io.anuke.ucore.scene.builders.label;
|
||||
import io.anuke.ucore.scene.builders.table;
|
||||
import io.anuke.ucore.util.OS;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
@@ -44,7 +45,13 @@ public class MenuFragment implements Fragment{
|
||||
|
||||
add(new MenuButton("icon-info", "$text.about.button", ui.about::show));
|
||||
|
||||
add(new MenuButton("icon-menu", "$text.changelog.title", ui.changelog::show));
|
||||
add(new MenuButton("icon-menu", OS.isMac ? "$text.credits" : "$text.changelog.title", () -> {
|
||||
if(OS.isMac){
|
||||
ui.about.showCredits();
|
||||
}else {
|
||||
ui.changelog.show();
|
||||
}
|
||||
}));
|
||||
|
||||
row();
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ public class PlayerListFragment implements Fragment{
|
||||
new button("$text.server.admins", () -> {
|
||||
ui.admins.show();
|
||||
}).padTop(-12).padBottom(-12).padRight(-12).fillY().cell.disabled(b -> Net.client());
|
||||
|
||||
new button("$text.server.rollback", () -> {
|
||||
ui.rollback.show();
|
||||
}).padTop(-12).padBottom(-12).padRight(-12).fillY().cell.disabled(b -> !player.isAdmin);
|
||||
|
||||
}}.pad(10f).growX().end();
|
||||
}}.end();
|
||||
|
||||
@@ -86,7 +86,7 @@ task packrCmd() {
|
||||
|
||||
commandLine("java", "-jar", PACKR_DIR + "packr.jar",
|
||||
"--verbose",
|
||||
"--bundle", getPackage(),
|
||||
"--bundle", getPackage() + ".mac",
|
||||
"--platform", getPlatform(),
|
||||
"--executable", appName,
|
||||
"--output", "packr-out/",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.anuke.mindustry.desktop;
|
||||
|
||||
import com.badlogic.gdx.Files.FileType;
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
|
||||
import io.anuke.kryonet.KryoClient;
|
||||
@@ -7,6 +8,8 @@ import io.anuke.kryonet.KryoServer;
|
||||
import io.anuke.mindustry.Mindustry;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.ucore.UCore;
|
||||
import io.anuke.ucore.util.OS;
|
||||
|
||||
public class DesktopLauncher {
|
||||
|
||||
@@ -18,6 +21,10 @@ public class DesktopLauncher {
|
||||
config.setWindowedMode(960, 540);
|
||||
config.setWindowIcon("sprites/icon.png");
|
||||
|
||||
if(OS.isMac) {
|
||||
config.setPreferencesConfig(UCore.getProperty("user.home") + "/Library/Application Support/Mindustry", FileType.Absolute);
|
||||
}
|
||||
|
||||
Platform.instance = new DesktopPlatform(arg);
|
||||
|
||||
Net.setClientProvider(new KryoClient());
|
||||
|
||||
@@ -3,14 +3,17 @@ package io.anuke.mindustry.desktop;
|
||||
import club.minnced.discord.rpc.DiscordEventHandlers;
|
||||
import club.minnced.discord.rpc.DiscordRPC;
|
||||
import club.minnced.discord.rpc.DiscordRichPresence;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.utils.Base64Coder;
|
||||
import io.anuke.kryonet.DefaultThreadImpl;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.core.ThreadHandler.ThreadProvider;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.ui.dialogs.FileChooser;
|
||||
import io.anuke.ucore.UCore;
|
||||
import io.anuke.ucore.core.Settings;
|
||||
import io.anuke.ucore.function.Consumer;
|
||||
import io.anuke.ucore.util.Strings;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -40,6 +43,11 @@ public class DesktopPlatform extends Platform {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, String filter) {
|
||||
new FileChooser(text, file -> file.extension().equalsIgnoreCase(filter), open, cons).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Date date){
|
||||
return format.format(date);
|
||||
|
||||
@@ -2,5 +2,5 @@ app.version=3.5
|
||||
app.id=io.anuke.mindustry
|
||||
app.mainclass=io.anuke.mindustry.IOSLauncher
|
||||
app.executable=IOSLauncher
|
||||
app.build=10
|
||||
app.build=11
|
||||
app.name=Mindustry
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.anuke.mindustry.server;
|
||||
import com.badlogic.gdx.ApplicationLogger;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.game.Difficulty;
|
||||
@@ -15,7 +16,9 @@ import io.anuke.mindustry.net.Administration.PlayerInfo;
|
||||
import io.anuke.mindustry.net.Packets.ChatPacket;
|
||||
import io.anuke.mindustry.net.Packets.KickReason;
|
||||
import io.anuke.mindustry.ui.fragments.DebugFragment;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Map;
|
||||
import io.anuke.mindustry.world.Placement;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.ucore.core.*;
|
||||
import io.anuke.ucore.modules.Module;
|
||||
@@ -32,8 +35,6 @@ import java.util.Scanner;
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
import static io.anuke.ucore.util.Log.*;
|
||||
|
||||
;
|
||||
|
||||
public class ServerControl extends Module {
|
||||
private final CommandHandler handler = new CommandHandler("");
|
||||
private ShuffleMode mode;
|
||||
@@ -727,6 +728,28 @@ public class ServerControl extends Module {
|
||||
info("Nobody with that name could be found.");
|
||||
}
|
||||
});
|
||||
|
||||
handler.register("rollback", "<amount>", "Rollback the block edits in the world", arg -> {
|
||||
if(!state.is(State.playing)) {
|
||||
err("Open the server first.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Strings.canParsePostiveInt(arg[0])) {
|
||||
err("Please input a valid, positive, number of times to rollback");
|
||||
return;
|
||||
}
|
||||
|
||||
int rollbackTimes = Integer.valueOf(arg[0]);
|
||||
IntMap<Array<EditLog>> editLogs = netServer.admins.getEditLogs();
|
||||
if(editLogs.size == 0){
|
||||
err("Nothing to rollback!");
|
||||
return;
|
||||
}
|
||||
|
||||
netServer.admins.rollbackWorld(rollbackTimes);
|
||||
info("Rollback done!");
|
||||
});
|
||||
}
|
||||
|
||||
private void readCommands(){
|
||||
|
||||
Reference in New Issue
Block a user