diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 9fead5492b..844447e7b8 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -230,7 +230,11 @@ public class NetServer implements ApplicationListener{ net.handleServer(InvokePacket.class, (con, packet) -> { if(con.player == null) return; - RemoteReadServer.readPacket(packet.writeBuffer, packet.type, con.player); + try{ + RemoteReadServer.readPacket(packet.writeBuffer, packet.type, con.player); + }catch(ValidateException e){ + Log.debug("Validation failed for '{0}': {1}", e.player, e.getMessage()); + } }); registerCommands(); @@ -504,6 +508,7 @@ public class NetServer implements ApplicationListener{ player.isShooting = shooting; player.isBuilding = building; player.buildQueue().clear(); + for(BuildRequest req : requests){ if(req == null) continue; Tile tile = world.tile(req.x, req.y); @@ -513,10 +518,23 @@ public class NetServer implements ApplicationListener{ continue; }else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){ continue; + }else if(connection.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so + continue; + }else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server + action.block = req.block; + action.rotation = req.rotation; + action.config = req.config; + })){ + //force the player to remove this request if that's not the case + Call.removeQueueBlock(player.con, req.x, req.y, req.breaking); + connection.rejectedRequests.add(req); + continue; } player.buildQueue().addLast(req); } + connection.rejectedRequests.clear(); + vector.set(x - player.getInterpolator().target.x, y - player.getInterpolator().target.y); vector.limit(maxMove); diff --git a/core/src/mindustry/entities/TargetPriority.java b/core/src/mindustry/entities/TargetPriority.java index ec36b03925..583710ca2c 100644 --- a/core/src/mindustry/entities/TargetPriority.java +++ b/core/src/mindustry/entities/TargetPriority.java @@ -1,5 +1,6 @@ package mindustry.entities; +/** A higher ordinal means a higher priority. Higher priority blocks will always get targeted over those of lower priority, regardless of distance. */ public enum TargetPriority{ base, turret diff --git a/core/src/mindustry/entities/traits/BuilderTrait.java b/core/src/mindustry/entities/traits/BuilderTrait.java index 02ae0bca5b..570f5eabf9 100644 --- a/core/src/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/mindustry/entities/traits/BuilderTrait.java @@ -67,9 +67,9 @@ public interface BuilderTrait extends Entity, TeamTrait{ if(!(tile.block() instanceof BuildBlock)){ if(!current.initialized && canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){ - Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation); + Build.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation); }else if(!current.initialized && canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){ - Call.beginBreak(getTeam(), current.x, current.y); + Build.beginBreak(getTeam(), current.x, current.y); }else{ buildQueue().removeFirst(); return; @@ -118,6 +118,14 @@ public interface BuilderTrait extends Entity, TeamTrait{ return request.stuck && !core.items.has(request.block.requirements); } + default void removeRequest(int x, int y, boolean breaking){ + //remove matching request + int idx = player.buildQueue().indexOf(req -> req.breaking == breaking && req.x == x && req.y == y); + if(idx != -1){ + player.buildQueue().removeIndex(idx); + } + } + /** Returns the queue for storing build requests. */ Queue buildQueue(); diff --git a/core/src/mindustry/game/GlobalData.java b/core/src/mindustry/game/GlobalData.java index 3dbf592a71..0e72ef7245 100644 --- a/core/src/mindustry/game/GlobalData.java +++ b/core/src/mindustry/game/GlobalData.java @@ -85,9 +85,15 @@ public class GlobalData{ if(amount > 0){ unlockContent(item); } + amount = Math.max(amount, 0); + modified = true; items.getAndIncrement(item, 0, amount); state.stats.itemsDelivered.getAndIncrement(item, 0, amount); + + //clamp overflow + if(items.get(item, 0) < 0) items.put(item, Integer.MAX_VALUE); + if(state.stats.itemsDelivered.get(item, 0) < 0) state.stats.itemsDelivered.put(item, Integer.MAX_VALUE); } public boolean hasItems(Array stacks){ diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index e56244928e..8000fc4e19 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -1,8 +1,6 @@ package mindustry.input; import arc.*; -import mindustry.annotations.Annotations.*; -import arc.struct.*; import arc.func.*; import arc.graphics.*; import arc.graphics.g2d.*; @@ -13,8 +11,10 @@ import arc.math.geom.*; import arc.scene.*; import arc.scene.event.*; import arc.scene.ui.layout.*; +import arc.struct.*; import arc.util.ArcAnnotate.*; import arc.util.*; +import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.effect.*; @@ -27,12 +27,13 @@ import mindustry.gen.*; import mindustry.graphics.*; import mindustry.input.Placement.*; import mindustry.net.*; +import mindustry.net.Administration.*; import mindustry.type.*; import mindustry.ui.fragments.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.blocks.BuildBlock.*; -import mindustry.world.blocks.power.PowerNode; +import mindustry.world.blocks.power.*; import java.util.*; @@ -66,6 +67,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ //methods to override + @Remote(variants = Variant.one) + public static void removeQueueBlock(int x, int y, boolean breaking){ + player.removeRequest(x, y, breaking); + } + @Remote(targets = Loc.client, called = Loc.server) public static void dropItem(Player player, float angle){ if(net.server() && player.item().amount <= 0){ @@ -78,8 +84,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ @Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true) public static void rotateBlock(Player player, Tile tile, boolean direction){ - if(net.server() && !Units.canInteract(player, tile)){ - throw new ValidateException(player, "Player cannot drop an item."); + if(net.server() && (!Units.canInteract(player, tile) || + !netServer.admins.allowAction(player, ActionType.rotate, tile, action -> action.rotation = Mathf.mod(tile.rotation() + Mathf.sign(direction), 4)))){ + throw new ValidateException(player, "Player cannot rotate a block."); } tile.rotation(Mathf.mod(tile.rotation() + Mathf.sign(direction), 4)); @@ -93,7 +100,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ @Remote(targets = Loc.both, forward = true, called = Loc.server) public static void transferInventory(Player player, Tile tile){ if(player == null || player.timer == null) return; - if(net.server() && (player.item().amount <= 0 || player.isTransferring|| !Units.canInteract(player, tile))){ + if(net.server() && (player.item().amount <= 0 || player.isTransferring|| !Units.canInteract(player, tile) || + !netServer.admins.allowAction(player, ActionType.depositItem, tile, action -> { + action.itemAmount = player.item().amount; + action.item = player.item().item; + }))){ throw new ValidateException(player, "Player cannot transfer an item."); } @@ -143,14 +154,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ @Remote(targets = Loc.both, called = Loc.server, forward = true) public static void onTileTapped(Player player, Tile tile){ if(tile == null || player == null) return; - if(!Units.canInteract(player, tile)) return; + if(net.server() && (!Units.canInteract(player, tile) || + !netServer.admins.allowAction(player, ActionType.tapTile, tile, action -> {}))) throw new ValidateException(player, "Player cannot tap a tile."); tile.block().tapped(tile, player); Core.app.post(() -> Events.fire(new TapEvent(tile, player))); } @Remote(targets = Loc.both, called = Loc.both, forward = true) public static void onTileConfig(Player player, Tile tile, int value){ - if(tile == null || !Units.canInteract(player, tile)) return; + if(tile == null) return; + + if(net.server() && (!Units.canInteract(player, tile) || + !netServer.admins.allowAction(player, ActionType.configure, tile, action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile."); tile.block().configured(tile, player, value); Core.app.post(() -> Events.fire(new TapConfigEvent(tile, player, value))); } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index aa36705c39..8a5a438e14 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -529,6 +529,10 @@ public class Administration{ public @NonNull ActionType type; public @NonNull Tile tile; + /** valid for block placement events only */ + public @Nullable Block block; + public int rotation; + /** valid for configure and rotation-type events only. */ public int config; @@ -554,7 +558,7 @@ public class Administration{ } public enum ActionType{ - breakBlock, placeBlock, rotate, configure, withdrawItem, depositItem + breakBlock, placeBlock, rotate, configure, tapTile, withdrawItem, depositItem } } diff --git a/core/src/mindustry/net/ArcNetProvider.java b/core/src/mindustry/net/ArcNetProvider.java index 19ce42e48e..68fc3a08a4 100644 --- a/core/src/mindustry/net/ArcNetProvider.java +++ b/core/src/mindustry/net/ArcNetProvider.java @@ -114,12 +114,7 @@ public class ArcNetProvider implements NetProvider{ try{ net.handleServerReceived(k, object); }catch(RuntimeException e){ - if(e.getCause() instanceof ValidateException){ - ValidateException v = (ValidateException)e.getCause(); - Log.err("Validation failed: {0} ({1})", v.player.name, v.getMessage()); - }else{ - e.printStackTrace(); - } + e.printStackTrace(); }catch(Exception e){ e.printStackTrace(); } diff --git a/core/src/mindustry/net/NetConnection.java b/core/src/mindustry/net/NetConnection.java index 104ed2d431..6555957b5b 100644 --- a/core/src/mindustry/net/NetConnection.java +++ b/core/src/mindustry/net/NetConnection.java @@ -1,7 +1,9 @@ package mindustry.net; -import arc.util.*; +import arc.struct.*; import arc.util.ArcAnnotate.*; +import arc.util.*; +import mindustry.entities.traits.BuilderTrait.*; import mindustry.entities.type.*; import mindustry.gen.*; import mindustry.net.Administration.*; @@ -21,6 +23,8 @@ public abstract class NetConnection{ public int lastRecievedClientSnapshot = -1; /** Timestamp of last recieved snapshot. */ public long lastRecievedClientTime; + /** Build requests that have been recently rejected. This is cleared every snapshot. */ + public Array rejectedRequests = new Array<>(); public boolean hasConnected, hasBegunConnecting, hasDisconnected; public float viewWidth, viewHeight, viewX, viewY; diff --git a/core/src/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/mindustry/ui/fragments/BlockInventoryFragment.java index ce2a83e5d4..926c56460a 100644 --- a/core/src/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/mindustry/ui/fragments/BlockInventoryFragment.java @@ -1,8 +1,6 @@ package mindustry.ui.fragments; import arc.*; -import mindustry.annotations.Annotations.*; -import arc.struct.*; import arc.func.*; import arc.graphics.g2d.*; import arc.input.*; @@ -13,14 +11,18 @@ import arc.scene.actions.*; import arc.scene.event.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; +import arc.struct.*; import arc.util.*; +import mindustry.annotations.Annotations.*; import mindustry.core.GameState.*; import mindustry.entities.*; import mindustry.entities.type.*; import mindustry.game.EventType.*; import mindustry.gen.*; +import mindustry.net.Administration.*; +import mindustry.net.*; import mindustry.type.*; -import mindustry.ui.Cicon; +import mindustry.ui.*; import mindustry.world.*; import static mindustry.Vars.*; @@ -37,8 +39,14 @@ public class BlockInventoryFragment extends Fragment{ @Remote(called = Loc.server, targets = Loc.both, forward = true) public static void requestItem(Player player, Tile tile, Item item, int amount){ if(player == null || tile == null || !tile.interactable(player.getTeam())) return; - if(!Units.canInteract(player, tile)) return; amount = Mathf.clamp(amount, 0, player.getItemCapacity()); + int fa = amount; + + if(net.server() && (!Units.canInteract(player, tile) || + !netServer.admins.allowAction(player, ActionType.withdrawItem, tile, action -> { + action.item = item; + action.itemAmount = fa; + }))) throw new ValidateException(player, "Player cannot request items."); int removed = tile.block().removeStack(tile, item, amount); diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java index b8d41df4f0..1e20da65e8 100644 --- a/core/src/mindustry/world/Build.java +++ b/core/src/mindustry/world/Build.java @@ -1,17 +1,15 @@ package mindustry.world; -import mindustry.annotations.Annotations.Loc; -import mindustry.annotations.Annotations.Remote; -import arc.Core; -import arc.Events; -import arc.math.Mathf; +import arc.*; +import arc.math.*; import arc.math.geom.*; -import mindustry.content.Blocks; -import mindustry.entities.Units; -import mindustry.game.EventType.BlockBuildBeginEvent; -import mindustry.game.Team; -import mindustry.world.blocks.BuildBlock; -import mindustry.world.blocks.BuildBlock.BuildEntity; +import mindustry.annotations.Annotations.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.game.EventType.*; +import mindustry.game.*; +import mindustry.world.blocks.*; +import mindustry.world.blocks.BuildBlock.*; import static mindustry.Vars.*; diff --git a/desktop/src/mindustry/desktop/steam/SNet.java b/desktop/src/mindustry/desktop/steam/SNet.java index cb23befebe..e27a9d8c52 100644 --- a/desktop/src/mindustry/desktop/steam/SNet.java +++ b/desktop/src/mindustry/desktop/steam/SNet.java @@ -76,14 +76,7 @@ public class SNet implements SteamNetworkingCallback, SteamMatchmakingCallback, } net.handleServerReceived(con, output); - }catch(RuntimeException e){ - if(e.getCause() instanceof ValidateException){ - ValidateException v = (ValidateException)e.getCause(); - Log.err("Validation failed: {0} ({1})", v.player.name, v.getMessage()); - }else{ - Log.err(e); - } - }catch(Exception e){ + }catch(Throwable e){ Log.err(e); } }else if(currentServer != null && fromID == currentServer.getAccountID()){ diff --git a/gradle.properties b/gradle.properties index 65d5d065a3..091723c9aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=097f07c939d99020b24a9b33fc3e24bf800e210d +archash=2be8dfe555b6aa4e5307ce56a92e24d8fee39a0b