diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 53c61daca8..f3ec661d70 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1836,7 +1836,7 @@ hint.desktopPause = Press [accent][[Space][] to pause and unpause the game. hint.breaking = [accent]Right-click[] and drag to break blocks. hint.breaking.mobile = Activate the \uE817 [accent]hammer[] in the bottom right and tap to break blocks.\n\nHold down your finger for a second and drag to break in a selection. hint.blockInfo = View information of a block by selecting it in the [accent]build menu[], then selecting the [accent][[?][] button at the right. -hint.derelict = [accent]Derelict[] structures are broken remnants of old bases that no longer function.\n\nThese structures can be [accent]deconstructed[] for resources. +hint.derelict = [accent]Derelict[] structures are broken remnants of old bases that no longer function.\n\nThese structures can be [accent]deconstructed[] for resources, or repaired. hint.research = Use the \uE875 [accent]Research[] button to research new technology. hint.research.mobile = Use the \uE875 [accent]Research[] button in the \uE88C [accent]Menu[] to research new technology. hint.unitControl = Hold [accent][[L-ctrl][] and [accent]click[] to manually control friendly units or turrets. diff --git a/core/assets/cursors/repair.png b/core/assets/cursors/repair.png new file mode 100644 index 0000000000..0604903f1d Binary files /dev/null and b/core/assets/cursors/repair.png differ diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index e151c6f05a..1b6b550461 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -78,7 +78,7 @@ public class UI implements ApplicationListener, Loadable{ public IntMap followUpMenus; - public Cursor drillCursor, unloadCursor, targetCursor; + public Cursor drillCursor, unloadCursor, targetCursor, repairCursor; private @Nullable Element lastAnnouncement; @@ -142,6 +142,7 @@ public class UI implements ApplicationListener, Loadable{ drillCursor = Core.graphics.newCursor("drill", Fonts.cursorScale()); unloadCursor = Core.graphics.newCursor("unload", Fonts.cursorScale()); targetCursor = Core.graphics.newCursor("target", Fonts.cursorScale()); + repairCursor = Core.graphics.newCursor("repair", Fonts.cursorScale()); } @Override diff --git a/core/src/mindustry/entities/comp/BuilderComp.java b/core/src/mindustry/entities/comp/BuilderComp.java index 5b7ea1678a..64e9e155fa 100644 --- a/core/src/mindustry/entities/comp/BuilderComp.java +++ b/core/src/mindustry/entities/comp/BuilderComp.java @@ -61,8 +61,12 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{ while(it.hasNext()){ BuildPlan plan = it.next(); Tile tile = world.tile(plan.x, plan.y); - if(tile == null || (plan.breaking && tile.block() == Blocks.air) || (!plan.breaking && ((tile.build != null && tile.build.rotation == plan.rotation) || !plan.block.rotate) && - (tile.block() == plan.block || (plan.block != null && (plan.block.isOverlay() && plan.block == tile.overlay() || (plan.block.isFloor() && plan.block == tile.floor())))))){ + boolean isSameDerelict = (tile != null && tile.build != null && tile.block() == plan.block && tile.build.tileX() == plan.x && tile.build.tileY() == plan.y && tile.team() == Team.derelict); + if(tile == null || (plan.breaking && tile.block() == Blocks.air) || (!plan.breaking && ((tile.build != null && tile.build.rotation == plan.rotation && !isSameDerelict) || !plan.block.rotate) && + //th block must be the same, but not derelict and the same + ((tile.block() == plan.block && !isSameDerelict) || + //same floor or overlay + (plan.block != null && (plan.block.isOverlay() && plan.block == tile.overlay() || (plan.block.isFloor() && plan.block == tile.floor())))))){ it.remove(); } diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index 760c8a0dcd..feea195e29 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -447,10 +447,14 @@ public class DesktopInput extends InputHandler{ Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY()); if(cursor != null){ - if(cursor.build != null){ + if(cursor.build != null && cursor.build.interactable(player.team())){ cursorType = cursor.build.getCursor(); } + if(cursor.build != null && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){ + cursorType = ui.repairCursor; + } + if((isPlacing() && player.isBuilder()) || !selectPlans.isEmpty()){ cursorType = SystemCursor.hand; } @@ -672,7 +676,7 @@ public class DesktopInput extends InputHandler{ commandRect = true; commandRectX = input.mouseWorldX(); commandRectY = input.mouseWorldY(); - }else if(!checkConfigTap() && selected != null){ + }else if(!checkConfigTap() && selected != null && !tryRepairDerelict(selected)){ //only begin shooting if there's no cursor event if(!tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !tileTapped(selected.build) && !player.unit().activelyBuilding() && !droppingItem && !(tryStopMine(selected) || (!settings.getBool("doubletapmine") || selected == prevSelected && Time.timeSinceMillis(selectMillis) < 500) && tryBeginMine(selected)) && !Core.scene.hasKeyboard()){ diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index af04caa5c0..d38f7f9ec2 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -1641,6 +1641,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ return false; } + boolean tryRepairDerelict(Tile selected){ + if(selected != null && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){ + player.unit().addBuild(new BuildPlan(selected.build.tileX(), selected.build.tileY(), selected.build.rotation, selected.block(), selected.build.config())); + return true; + } + return false; + } + boolean canMine(Tile tile){ return !Core.scene.hasMouse() && player.unit().validMine(tile) diff --git a/core/src/mindustry/input/MobileInput.java b/core/src/mindustry/input/MobileInput.java index 047d32013f..5b410a11b7 100644 --- a/core/src/mindustry/input/MobileInput.java +++ b/core/src/mindustry/input/MobileInput.java @@ -712,7 +712,7 @@ public class MobileInput extends InputHandler implements GestureListener{ buildingTapped = selectedControlBuild(); //prevent mining if placing/breaking blocks - if(!tryStopMine() && !canTapPlayer(worldx, worldy) && !checkConfigTap() && !tileTapped(linked.build) && mode == none && !Core.settings.getBool("doubletapmine")){ + if(!tryRepairDerelict(cursor) && !tryStopMine() && !canTapPlayer(worldx, worldy) && !checkConfigTap() && !tileTapped(linked.build) && mode == none && !Core.settings.getBool("doubletapmine")){ tryBeginMine(cursor); } } diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index d1efde7d1d..39080e2c0a 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -336,6 +336,8 @@ public class Block extends UnlockableContent implements Senseable{ public boolean instantTransfer = false; /** Whether you can rotate this block after it is placed. */ public boolean quickRotate = true; + /** If true, this derelict block can be repair by clicking it. */ + public boolean allowDerelictRepair = true; /** Main subclass. Non-anonymous. */ public @Nullable Class subclass; /** Scroll position for certain blocks. */ diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java index e5a9064930..ebfa7300e3 100644 --- a/core/src/mindustry/world/Build.java +++ b/core/src/mindustry/world/Build.java @@ -86,6 +86,31 @@ public class Build{ return; } + //repair derelict tile + if(tile.team() == Team.derelict && tile.block == result && tile.build != null && tile.block.allowDerelictRepair){ + float healthf = tile.build.healthf(); + var config = tile.build.config(); + + tile.setBlock(result, team, rotation); + + if(unit != null && unit.getControllerName() != null) tile.build.lastAccessed = unit.getControllerName(); + + if(config != null){ + tile.build.configured(unit, config); + } + //keep health + tile.build.health = result.health * healthf; + + if(fogControl.isVisibleTile(team, tile.x, tile.y)){ + result.placeEffect.at(tile.drawx(), tile.drawy(), result.size); + Fx.rotateBlock.at(tile.build.x, tile.build.y, tile.build.block.size); + //doesn't play a sound + } + + Events.fire(new BlockBuildEndEvent(tile, unit, team, false, config)); + return; + } + //break all props in the way tile.getLinkedTilesAs(result, out -> { if(out.block != Blocks.air && out.block.alwaysReplace){ @@ -193,11 +218,11 @@ public class Build{ (type.size == 2 && world.getDarkness(wx, wy) >= 3) || (state.rules.staticFog && state.rules.fog && !fogControl.isDiscovered(team, wx, wy)) || (check.floor().isDeep() && !type.floating && !type.requiresWater && !type.placeableLiquid) || //deep water - (type == check.block() && check.build != null && rotation == check.build.rotation && type.rotate) || //same block, same rotation + (type == check.block() && check.build != null && rotation == check.build.rotation && type.rotate && !((type == check.block && check.team() == Team.derelict))) || //same block, same rotation !check.interactable(team) || //cannot interact !check.floor().placeableOn || //solid wall (!checkVisible && !check.block().alwaysReplace) || //replacing a block that should be replaced (e.g. payload placement) - !((type.canReplace(check.block()) || //can replace type + !(((type.canReplace(check.block()) || (type == check.block && check.team() == Team.derelict)) || //can replace type OR can replace derelict block of same type (check.build instanceof ConstructBuild build && build.current == type && check.centerX() == tile.x && check.centerY() == tile.y)) && //same type in construction type.bounds(tile.x, tile.y, Tmp.r1).grow(0.01f).contains(check.block.bounds(check.centerX(), check.centerY(), Tmp.r2))) || //no replacement (type.requiresWater && check.floor().liquidDrop != Liquids.water) //requires water but none found