diff --git a/core/src/mindustry/entities/Units.java b/core/src/mindustry/entities/Units.java index e6c4559b0e..ed42bff2a7 100644 --- a/core/src/mindustry/entities/Units.java +++ b/core/src/mindustry/entities/Units.java @@ -180,6 +180,25 @@ public class Units{ return result; } + /** Returns the closest ally of this team. Filter by predicate. + * Unlike the closest() function, this only guarantees that unit hitboxes overlap the range. */ + public static Unitc closestOverlap(Team team, float x, float y, float range, Boolf predicate){ + result = null; + cdist = 0f; + + nearby(team, x - range, y - range, range*2f, range*2f, e -> { + if(!predicate.get(e)) return; + + float dist = e.dst2(x, y); + if(result == null || dist < cdist){ + result = e; + cdist = dist; + } + }); + + return result; + } + /** Iterates over all units in a rectangle. */ public static void nearby(Team team, float x, float y, float width, float height, Cons cons){ teamIndex.tree(team).intersect(height, x, y, width, cons); diff --git a/core/src/mindustry/entities/comp/TileComp.java b/core/src/mindustry/entities/comp/TileComp.java index 0198ec1b34..e540a1a9db 100644 --- a/core/src/mindustry/entities/comp/TileComp.java +++ b/core/src/mindustry/entities/comp/TileComp.java @@ -9,6 +9,7 @@ import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; import arc.math.geom.QuadTree.*; +import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; @@ -37,7 +38,7 @@ import static mindustry.Vars.*; @EntityDef(value = {Tilec.class}, isFinal = false, genio = false, serialize = false) @Component -abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTreeObject{ +abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTreeObject, Displayable{ //region vars and initialization static final float timeToSleep = 60f * 1; static final ObjectSet tmpTiles = new ObjectSet<>(); @@ -910,7 +911,18 @@ abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTree return block.icon(Cicon.medium); } + @Override public void display(Table table){ + //display the block stuff + //TODO duplicated code? + table.table(t -> { + t.left(); + t.add(new Image(block.getDisplayIcon(tile))).size(8 * 4); + t.labelWrap(block.getDisplayName(tile)).left().width(190f).padLeft(5); + }).growX().left(); + + table.row(); + table.table(bars -> { bars.defaults().growX().height(18f).pad(4); @@ -926,12 +938,13 @@ abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTree if(items != null){ table.row(); + table.left(); table.table(l -> { Bits current = new Bits(); - l.left(); Runnable rebuild = () -> { l.clearChildren(); + l.left(); for(Item item : content.items()){ if(items.hasFlowItem(item)){ l.image(item.icon(Cicon.small)).padRight(3f); @@ -950,7 +963,7 @@ abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTree } } }); - }); + }).left(); } if(liquids != null){ diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index bdc4e15ce4..c125cdb984 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -3,8 +3,9 @@ package mindustry.entities.comp; import arc.*; import arc.math.*; import arc.math.geom.*; -import arc.util.*; +import arc.scene.ui.layout.*; import arc.util.ArcAnnotate.*; +import arc.util.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.*; @@ -13,13 +14,14 @@ import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; +import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.environment.*; import static mindustry.Vars.*; @Component -abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc{ +abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable{ @Import float x, y, rotation, elevation, maxHealth, drag, armor; @@ -187,6 +189,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I } } + @Override + public void display(Table table){ + type.display(this, table); + } + @Override public boolean isImmune(StatusEffect effect){ return type.immunities.contains(effect); diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index daaeb32938..7c49d21234 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -7,6 +7,7 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; @@ -99,6 +100,27 @@ public class UnitType extends UnlockableContent{ public void landed(Unitc unit){} + public void display(Unitc unit, Table table){ + table.table(t -> { + t.left(); + t.add(new Image(icon(Cicon.medium))).size(8 * 4); + t.labelWrap(localizedName).left().width(190f).padLeft(5); + }).growX().left(); + table.row(); + + table.table(bars -> { + bars.defaults().growX().height(18f).pad(4); + + bars.add(new Bar("blocks.health", Pal.health, unit::healthf).blink(Color.white)); + bars.row(); + + if(state.rules.unitAmmo){ + bars.add(new Bar("blocks.ammo", Pal.ammo, () -> (float)unit.ammo() / ammoCapacity)); + bars.row(); + } + }).growX(); + } + @Override public void displayInfo(Table table){ ContentDisplay.displayUnit(table, this); diff --git a/core/src/mindustry/ui/dialogs/LoadoutDialog.java b/core/src/mindustry/ui/dialogs/LoadoutDialog.java index 58d0e057d5..1b5d067bb4 100644 --- a/core/src/mindustry/ui/dialogs/LoadoutDialog.java +++ b/core/src/mindustry/ui/dialogs/LoadoutDialog.java @@ -58,7 +58,6 @@ public class LoadoutDialog extends BaseDialog{ this.updater = updater; this.capacity = capacity; this.hider = hider; - //this.filter = filter; show(); } diff --git a/core/src/mindustry/ui/fragments/PlacementFragment.java b/core/src/mindustry/ui/fragments/PlacementFragment.java index 9ca009c3d0..f5f2207cb9 100644 --- a/core/src/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/mindustry/ui/fragments/PlacementFragment.java @@ -10,11 +10,12 @@ import arc.scene.style.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; +import arc.util.ArcAnnotate.*; import arc.util.*; -import mindustry.content.*; -import mindustry.gen.*; +import mindustry.entities.*; import mindustry.entities.units.*; import mindustry.game.EventType.*; +import mindustry.gen.*; import mindustry.graphics.*; import mindustry.input.*; import mindustry.type.*; @@ -32,12 +33,11 @@ public class PlacementFragment extends Fragment{ boolean[] categoryEmpty = new boolean[Category.all.length]; ObjectMap selectedBlocks = new ObjectMap<>(); ObjectFloatMap scrollPositions = new ObjectFloatMap<>(); - Block hovered, lastDisplay; - Tile lastHover; - Tile hoverTile; + Block menuHoverBlock; + Object lastDisplayState; + boolean wasHovered; Table blockTable, toggler, topTable; ScrollPane blockPane; - boolean lastGround; boolean blockSelectEnd; int blockSelectSeq; long blockSelectSeqMillis; @@ -234,10 +234,10 @@ public class PlacementFragment extends Fragment{ } }); - button.hovered(() -> hovered = block); + button.hovered(() -> menuHoverBlock = block); button.exited(() -> { - if(hovered == block){ - hovered = null; + if(menuHoverBlock == block){ + menuHoverBlock = null; } }); } @@ -260,27 +260,32 @@ public class PlacementFragment extends Fragment{ frame.table(Tex.buttonEdge2,top -> { topTable = top; top.add(new Table()).growX().update(topTable -> { + + //find current hovered thing + Displayable hovered = hovered(); + Block displayBlock = menuHoverBlock != null ? menuHoverBlock : control.input.block; + Object displayState = displayBlock != null ? displayBlock : hovered; + boolean isHovered = displayBlock == null; //use hovered thing if displayblock is null + //don't refresh unnecessarily - if((tileDisplayBlock() == null && lastDisplay == getSelected() && !lastGround) - || (tileDisplayBlock() != null && lastHover == hoverTile && lastDisplay == tileDisplayBlock() && lastGround)) - return; + //refresh only when the hover state changes, or the displayed block changes + if(wasHovered == isHovered && lastDisplayState == displayState) return; topTable.clear(); topTable.top().left().margin(5); - lastHover = hoverTile; - lastDisplay = getSelected(); - lastGround = tileDisplayBlock() != null; + lastDisplayState = displayState; + wasHovered = isHovered; - if(lastDisplay != null){ //show selected recipe - lastGround = false; + //show details of selected block, with costs + if(displayBlock != null){ topTable.table(header -> { String keyCombo = ""; if(!mobile && Core.settings.getBool("blockselectkeys")){ Seq blocks = getByCategory(currentCategory); for(int i = 0; i < blocks.size; i++){ - if(blocks.get(i) == lastDisplay && (i + 1) / 10 - 1 < blockSelect.length){ + if(blocks.get(i) == displayBlock && (i + 1) / 10 - 1 < blockSelect.length){ keyCombo = Core.bundle.format("placement.blockselectkeys", Core.keybinds.get(blockSelect[currentCategory.ordinal()]).key.toString()) + (i < 10 ? "" : Core.keybinds.get(blockSelect[(i + 1) / 10 - 1]).key.toString() + ",") + Core.keybinds.get(blockSelect[i % 10]).key.toString() + "]"; @@ -290,13 +295,13 @@ public class PlacementFragment extends Fragment{ } final String keyComboFinal = keyCombo; header.left(); - header.add(new Image(lastDisplay.icon(Cicon.medium))).size(8 * 4); - header.labelWrap(() -> !unlocked(lastDisplay) ? Core.bundle.get("block.unknown") : lastDisplay.localizedName + keyComboFinal) + header.add(new Image(displayBlock.icon(Cicon.medium))).size(8 * 4); + header.labelWrap(() -> !unlocked(displayBlock) ? Core.bundle.get("block.unknown") : displayBlock.localizedName + keyComboFinal) .left().width(190f).padLeft(5); header.add().growX(); - if(unlocked(lastDisplay)){ + if(unlocked(displayBlock)){ header.button("?", Styles.clearPartialt, () -> { - ui.content.show(lastDisplay); + ui.content.show(displayBlock); Events.fire(new BlockInfoEvent()); }).size(8 * 5).padTop(-5).padRight(-5).right().grow().name("blockinfo"); } @@ -306,7 +311,7 @@ public class PlacementFragment extends Fragment{ topTable.table(req -> { req.top().left(); - for(ItemStack stack : lastDisplay.requirements){ + for(ItemStack stack : displayBlock.requirements){ req.table(line -> { line.left(); line.image(stack.item.icon(Cicon.small)).size(8 * 2); @@ -326,34 +331,21 @@ public class PlacementFragment extends Fragment{ } }).growX().left().margin(3); - if(!lastDisplay.isPlaceable() || !player.isBuilder()){ + if(!displayBlock.isPlaceable() || !player.isBuilder()){ topTable.row(); topTable.table(b -> { b.image(Icon.cancel).padRight(2).color(Color.scarlet); - b.add(!player.isBuilder() ? "$unit.nobuild" : lastDisplay.unplaceableMessage()).width(190f).wrap(); + b.add(!player.isBuilder() ? "$unit.nobuild" : displayBlock.unplaceableMessage()).width(190f).wrap(); b.left(); }).padTop(2).left(); } - }else if(tileDisplayBlock() != null){ //show selected tile - lastDisplay = tileDisplayBlock(); - topTable.table(t -> { - t.left(); - t.add(new Image(lastDisplay.getDisplayIcon(hoverTile))).size(8 * 4); - t.labelWrap(lastDisplay.getDisplayName(hoverTile)).left().width(190f).padLeft(5); - }).growX().left(); - if(hoverTile.team() == player.team()){ - topTable.row(); - topTable.table(t -> { - t.left().defaults().left(); - if(hoverTile.entity != null){ - hoverTile.entity.display(t); - } - }).left().growX(); - } + }else if(hovered != null){ + //show hovered item, whatever that may be + hovered.display(topTable); } }); - }).colspan(3).fillX().visible(() -> getSelected() != null || tileDisplayBlock() != null).touchable(Touchable.enabled); + }).colspan(3).fillX().visible(this::hasInfoBox).touchable(Touchable.enabled); frame.row(); frame.image().color(Pal.gray).colspan(3).height(4).growX(); frame.row(); @@ -425,28 +417,15 @@ public class PlacementFragment extends Fragment{ } Seq getByCategory(Category cat){ - returnArray.clear(); - for(Block block : content.blocks()){ - if(block.category == cat && block.isVisible()){ - returnArray.add(block); - } - } - return returnArray; + return returnArray.selectFrom(content.blocks(), block -> block.category == cat && block.isVisible()); } Seq getUnlockedByCategory(Category cat){ - returnArray.clear(); - for(Block block : content.blocks()){ - if(block.category == cat && block.isVisible() && unlocked(block)){ - returnArray.add(block); - } - } - returnArray.sort((b1, b2) -> { + return returnArray.selectFrom(content.blocks(), block -> block.category == cat && block.isVisible() && unlocked(block)).sort((b1, b2) -> { int locked = -Boolean.compare(unlocked(b1), unlocked(b2)); if(locked != 0) return locked; return Boolean.compare(!b1.isPlaceable(), !b2.isPlaceable()); }); - return returnArray; } Block getSelectedBlock(Category cat){ @@ -460,41 +439,38 @@ public class PlacementFragment extends Fragment{ return !state.isCampaign() || data.isUnlocked(block); } - /** Returns the currently displayed block in the top box. */ - Block getSelected(){ - Block toDisplay = null; + boolean hasInfoBox(){ + return control.input.block != null || menuHoverBlock != null || hovered() != null; + } + /** Returns the thing being hovered over. */ + @Nullable + Displayable hovered(){ Vec2 v = topTable.stageToLocalCoordinates(Core.input.mouse()); - //setup hovering tile - if(!Core.scene.hasMouse() && topTable.hit(v.x, v.y, false) == null){ - hoverTile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); - if(hoverTile != null && hoverTile.entity != null){ + //if the mouse intersects the table or the UI has the mouse, no hovering can occur + if(Core.scene.hasMouse() || topTable.hit(v.x, v.y, false) != null) return null; + + //check for a unit + Unitc unit = Units.closestOverlap(player.team(), Core.input.mouseWorldX(), Core.input.mouseWorldY(), 5f, u -> !u.isLocal()); + //if cursor has a unit, display it + if(unit != null) return unit; + + //check tile being hovered over + Tile hoverTile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); + if(hoverTile != null){ + //if the tile has an entity, display it + if(hoverTile.entity != null){ hoverTile.entity.updateFlow(true); + return hoverTile.entity; + } + + //if the tile has a drop, display the drop + if(hoverTile.drop() != null){ + return hoverTile; } - }else{ - hoverTile = null; } - //block currently selected - if(control.input.block != null){ - toDisplay = control.input.block; - } - - //block hovered on in build menu - if(hovered != null){ - toDisplay = hovered; - } - - return toDisplay; - } - - /** Returns the block currently being hovered over in the world. */ - Block tileDisplayBlock(){ - return hoverTile == null ? null : - hoverTile.block().synthetic() ? hoverTile.block() : - hoverTile.drop() != null && hoverTile.block() == Blocks.air ? - hoverTile.overlay().itemDrop != null ? hoverTile.overlay() : - hoverTile.floor() : null; + return null; } } diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 498340c03d..e45607e0ed 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -9,7 +9,6 @@ import arc.graphics.g2d.TextureAtlas.*; import arc.math.*; import arc.math.geom.*; import arc.scene.ui.layout.*; -import arc.struct.Seq; import arc.struct.EnumSet; import arc.struct.*; import arc.util.*; diff --git a/core/src/mindustry/world/Tile.java b/core/src/mindustry/world/Tile.java index 1078057ea7..975806bee7 100644 --- a/core/src/mindustry/world/Tile.java +++ b/core/src/mindustry/world/Tile.java @@ -4,6 +4,8 @@ import arc.func.*; import arc.math.*; import arc.math.geom.*; import arc.math.geom.QuadTree.*; +import arc.scene.ui.*; +import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.ArcAnnotate.*; import mindustry.annotations.Annotations.*; @@ -11,11 +13,12 @@ import mindustry.content.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.type.*; +import mindustry.ui.*; import mindustry.world.blocks.environment.*; import static mindustry.Vars.*; -public class Tile implements Position, QuadTreeObject{ +public class Tile implements Position, QuadTreeObject, Displayable{ static final ObjectSet tileSet = new ObjectSet<>(); /** Tile traversal cost. */ @@ -583,6 +586,17 @@ public class Tile implements Position, QuadTreeObject{ } } + @Override + public void display(Table table){ + Block toDisplay = overlay.itemDrop != null ? overlay : floor; + + table.table(t -> { + t.left(); + t.add(new Image(toDisplay.getDisplayIcon(this))).size(8 * 4); + t.labelWrap(toDisplay.getDisplayName(this)).left().width(190f).padLeft(5); + }).growX().left(); + } + @Override public float getX(){ return drawx(); diff --git a/gradle.properties b/gradle.properties index 14972e4621..22c4b51c85 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=30796daec8eba235f9cde3723537431df4e4f6f5 +archash=06c938d6dced0d9bf9f8ec98d4767b38f633f8fa