diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 12314ac093..c701a41356 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -627,9 +627,12 @@ setting.conveyorpathfinding.name = Conveyor Placement Pathfinding setting.sensitivity.name = Controller Sensitivity setting.saveinterval.name = Save Interval setting.seconds = {0} seconds +setting.blockselecttimeout.name = Block Select Timeout +setting.milliseconds = {0} milliseconds setting.fullscreen.name = Fullscreen setting.borderlesswindow.name = Borderless Window[lightgray] (may require restart) setting.fps.name = Show FPS & Ping +setting.blockselectkeys.name = Show Block Select Keys setting.vsync.name = VSync setting.pixelate.name = Pixelate[lightgray] (disables animations) setting.minimap.name = Show Minimap @@ -658,6 +661,7 @@ category.multiplayer.name = Multiplayer command.attack = Attack command.rally = Rally command.retreat = Retreat +placement.blockselectkeys = \n[lightgray]Key: [{0}, keybind.clear_building.name = Clear Building keybind.press = Press a key... keybind.press.axis = Press an axis or key... @@ -665,10 +669,27 @@ keybind.screenshot.name = Map Screenshot keybind.move_x.name = Move X keybind.move_y.name = Move Y keybind.mouse_move.name = Follow Mouse +keybind.dash.name = Dash keybind.schematic_select.name = Select Region keybind.schematic_menu.name = Schematic Menu keybind.schematic_flip_x.name = Flip Schematic X keybind.schematic_flip_y.name = Flip Schematic Y +keybind.category_prev.name = Previous Category +keybind.category_next.name = Next Category +keybind.block_select_left.name = Block Select Left +keybind.block_select_right.name = Block Select Right +keybind.block_select_up.name = Block Select Up +keybind.block_select_down.name = Block Select Down +keybind.block_select_01.name = Category/Block Select 1 +keybind.block_select_02.name = Category/Block Select 2 +keybind.block_select_03.name = Category/Block Select 3 +keybind.block_select_04.name = Category/Block Select 4 +keybind.block_select_05.name = Category/Block Select 5 +keybind.block_select_06.name = Category/Block Select 6 +keybind.block_select_07.name = Category/Block Select 7 +keybind.block_select_08.name = Category/Block Select 8 +keybind.block_select_09.name = Category/Block Select 9 +keybind.block_select_10.name = Category/Block Select 10 keybind.fullscreen.name = Toggle Fullscreen keybind.select.name = Select/Shoot keybind.diagonal_placement.name = Diagonal Placement @@ -682,7 +703,6 @@ keybind.menu.name = Menu keybind.pause.name = Pause keybind.pause_building.name = Pause/Resume Building keybind.minimap.name = Minimap -keybind.dash.name = Dash keybind.chat.name = Chat keybind.player_list.name = Player List keybind.console.name = Console @@ -1019,7 +1039,7 @@ unit.reaper.name = Reaper tutorial.next = [lightgray] tutorial.intro = You have entered the[scarlet] Mindustry Tutorial.[]\nUse [accent][[WASD][] to move.\n[accent]Hold [[Ctrl] while scrolling[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper tutorial.intro.mobile = You have entered the[scarlet] Mindustry Tutorial.[]\nSwipe the screen to move.\n[accent]Pinch with 2 fingers[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper -tutorial.drill = Mining manually is inefficient.\n[accent]Drills[] can mine automatically.\nClick the drill tab in the bottom right.\nSelect the[accent] mechanical drill[]. Place it on a copper vein by clicking.\n[accent]Right-click[] to stop building. +tutorial.drill = Mining manually is inefficient.\n[accent]Drills[] can mine automatically.\nClick the drill tab in the bottom right.\nSelect the[accent] mechanical drill[]. Place it on a copper vein by clicking.\nYou can also select the drill by tapping [accent][[2][] then [accent][[1][] quickly, regardless of which tab is open.\n[accent]Right-click[] to stop building. tutorial.drill.mobile = Mining manually is inefficient.\n[accent]Drills[] can mine automatically.\nTap the drill tab in the bottom right.\nSelect the[accent] mechanical drill[].\nPlace it on a copper vein by tapping, then press the[accent] checkmark[] below to confirm your selection.\nPress the[accent] X button[] to cancel placement. tutorial.blockinfo = Each block has different stats. Each drill can only mine certain ores.\nTo check a block's info and stats,[accent] tap the "?" button while selecting it in the build menu.[]\n\n[accent]Access the Mechanical Drill's stats now.[] tutorial.conveyor = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent]Hold down the mouse to place in a line.[]\nHold[accent] CTRL[] while selecting a line to place diagonally.\nUse the scrollwheel to rotate blocks before placing them.\n[accent]Place 2 conveyors with the line tool, then deliver an item into the core. diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index ed5808c78d..9add2d5e8c 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -53,9 +53,9 @@ public class Blocks implements ContentList{ powerSource, powerVoid, itemSource, itemVoid, liquidSource, message, illuminator, //defense - scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge, phaseWall, phaseWallLarge, surgeWall, surgeWallLarge, mender, mendProjector, overdriveProjector, forceProjector, shockMine, + scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet //transport conveyor, titaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router, overflowGate, massDriver, @@ -712,75 +712,11 @@ public class Blocks implements ContentList{ consumes.power(0.50f); }}; - //endregion - //region sandbox - - powerSource = new PowerSource("power-source"){{ - requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with()); - alwaysUnlocked = true; - }}; - powerVoid = new PowerVoid("power-void"){{ - requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with()); - alwaysUnlocked = true; - }}; - itemSource = new ItemSource("item-source"){{ - requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with()); - alwaysUnlocked = true; - }}; - itemVoid = new ItemVoid("item-void"){{ - requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with()); - alwaysUnlocked = true; - }}; - liquidSource = new LiquidSource("liquid-source"){{ - requirements(Category.liquid, BuildVisibility.sandboxOnly, ItemStack.with()); - alwaysUnlocked = true; - }}; - message = new MessageBlock("message"){{ - requirements(Category.effect, ItemStack.with(Items.graphite, 5)); - }}; - illuminator = new LightBlock("illuminator"){{ - requirements(Category.effect, BuildVisibility.lightingOnly, ItemStack.with(Items.graphite, 4, Items.silicon, 2)); - brightness = 0.67f; - radius = 120f; - consumes.power(0.05f); - }}; - //endregion //region defense int wallHealthMultiplier = 4; - scrapWall = new Wall("scrap-wall"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); - health = 60 * wallHealthMultiplier; - variants = 5; - }}; - - scrapWallLarge = new Wall("scrap-wall-large"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); - health = 60 * 4 * wallHealthMultiplier; - size = 2; - variants = 4; - }}; - - scrapWallHuge = new Wall("scrap-wall-huge"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); - health = 60 * 9 * wallHealthMultiplier; - size = 3; - variants = 3; - }}; - - scrapWallGigantic = new Wall("scrap-wall-gigantic"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); - health = 60 * 16 * wallHealthMultiplier; - size = 4; - }}; - - thruster = new Wall("thruster"){{ - health = 55 * 16 * wallHealthMultiplier; - size = 4; - }}; - copperWall = new Wall("copper-wall"){{ requirements(Category.defense, ItemStack.with(Items.copper, 6)); health = 80 * wallHealthMultiplier; @@ -862,6 +798,37 @@ public class Blocks implements ContentList{ size = 2; }}; + scrapWall = new Wall("scrap-wall"){{ + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); + health = 60 * wallHealthMultiplier; + variants = 5; + }}; + + scrapWallLarge = new Wall("scrap-wall-large"){{ + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); + health = 60 * 4 * wallHealthMultiplier; + size = 2; + variants = 4; + }}; + + scrapWallHuge = new Wall("scrap-wall-huge"){{ + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); + health = 60 * 9 * wallHealthMultiplier; + size = 3; + variants = 3; + }}; + + scrapWallGigantic = new Wall("scrap-wall-gigantic"){{ + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.with()); + health = 60 * 16 * wallHealthMultiplier; + size = 4; + }}; + + thruster = new Wall("thruster"){{ + health = 55 * 16 * wallHealthMultiplier; + size = 4; + }}; + mender = new MendProjector("mender"){{ requirements(Category.effect, ItemStack.with(Items.lead, 30, Items.copper, 25)); consumes.power(0.3f); @@ -1825,6 +1792,45 @@ public class Blocks implements ContentList{ consumes.power(1.2f); }}; + //endregion + //region sandbox + + powerSource = new PowerSource("power-source"){{ + requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with()); + alwaysUnlocked = true; + }}; + + powerVoid = new PowerVoid("power-void"){{ + requirements(Category.power, BuildVisibility.sandboxOnly, ItemStack.with()); + alwaysUnlocked = true; + }}; + + itemSource = new ItemSource("item-source"){{ + requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with()); + alwaysUnlocked = true; + }}; + + itemVoid = new ItemVoid("item-void"){{ + requirements(Category.distribution, BuildVisibility.sandboxOnly, ItemStack.with()); + alwaysUnlocked = true; + }}; + + liquidSource = new LiquidSource("liquid-source"){{ + requirements(Category.liquid, BuildVisibility.sandboxOnly, ItemStack.with()); + alwaysUnlocked = true; + }}; + + message = new MessageBlock("message"){{ + requirements(Category.effect, ItemStack.with(Items.graphite, 5)); + }}; + + illuminator = new LightBlock("illuminator"){{ + requirements(Category.effect, BuildVisibility.lightingOnly, ItemStack.with(Items.graphite, 4, Items.silicon, 2)); + brightness = 0.67f; + radius = 120f; + consumes.power(0.05f); + }}; + //endregion } } diff --git a/core/src/io/anuke/mindustry/input/Binding.java b/core/src/io/anuke/mindustry/input/Binding.java index c96b58a1cc..45383cb6ef 100644 --- a/core/src/io/anuke/mindustry/input/Binding.java +++ b/core/src/io/anuke/mindustry/input/Binding.java @@ -24,6 +24,22 @@ public enum Binding implements KeyBind{ schematic_flip_x(KeyCode.Z), schematic_flip_y(KeyCode.X), schematic_menu(KeyCode.T), + category_prev(KeyCode.COMMA), + category_next(KeyCode.PERIOD), + block_select_left(KeyCode.LEFT), + block_select_right(KeyCode.RIGHT), + block_select_up(KeyCode.UP), + block_select_down(KeyCode.DOWN), + block_select_01(KeyCode.NUM_1), + block_select_02(KeyCode.NUM_2), + block_select_03(KeyCode.NUM_3), + block_select_04(KeyCode.NUM_4), + block_select_05(KeyCode.NUM_5), + block_select_06(KeyCode.NUM_6), + block_select_07(KeyCode.NUM_7), + block_select_08(KeyCode.NUM_8), + block_select_09(KeyCode.NUM_9), + block_select_10(KeyCode.NUM_0), zoom_hold(KeyCode.CONTROL_LEFT, "view"), zoom(new Axis(KeyCode.SCROLL)), menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE), diff --git a/core/src/io/anuke/mindustry/type/Category.java b/core/src/io/anuke/mindustry/type/Category.java index a22b742345..c75451bc64 100644 --- a/core/src/io/anuke/mindustry/type/Category.java +++ b/core/src/io/anuke/mindustry/type/Category.java @@ -23,4 +23,12 @@ public enum Category{ effect; public static final Category[] all = values(); + + public Category prev(){ + return all[(this.ordinal() - 1 + all.length) % all.length]; + } + + public Category next(){ + return all[(this.ordinal() + 1) % all.length]; + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 4fd9d9b224..28e83c63c8 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -221,6 +221,8 @@ public class SettingsMenuDialog extends SettingsDialog{ game.sliderPref("saveinterval", 60, 10, 5 * 120, 10, i -> Core.bundle.format("setting.seconds", i)); if(!mobile){ + game.sliderPref("blockselecttimeout", 750, 0, 2000, 50, i -> Core.bundle.format("setting.milliseconds", i)); + game.checkPref("crashreport", true); } @@ -306,6 +308,9 @@ public class SettingsMenuDialog extends SettingsDialog{ graphics.checkPref("minimap", !mobile); graphics.checkPref("position", false); graphics.checkPref("fps", false); + if(!mobile){ + graphics.checkPref("blockselectkeys", true); + } graphics.checkPref("indicators", true); graphics.checkPref("animatedwater", !mobile); if(Shaders.shield != null){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index fd088ae3b6..95bdd5affa 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -36,6 +36,25 @@ public class PlacementFragment extends Fragment{ Tile hoverTile; Table blockTable, toggler, topTable; boolean lastGround; + boolean blockSelectEnd; + int blockSelectSeq; + long blockSelectSeqMillis; + Binding[] blockSelect = { + Binding.block_select_01, + Binding.block_select_02, + Binding.block_select_03, + Binding.block_select_04, + Binding.block_select_05, + Binding.block_select_06, + Binding.block_select_07, + Binding.block_select_08, + Binding.block_select_09, + Binding.block_select_10, + Binding.block_select_left, + Binding.block_select_right, + Binding.block_select_up, + Binding.block_select_down + }; public PlacementFragment(){ Events.on(WorldLoadEvent.class, event -> { @@ -83,6 +102,78 @@ public class PlacementFragment extends Fragment{ return true; } } + + if(ui.chatfrag.chatOpen()) return false; + for(int i = 0; i < blockSelect.length; i++){ + if(Core.input.keyTap(blockSelect[i])){ + if(i > 9) { //select block directionally + Array blocks = getByCategory(currentCategory); + Block currentBlock = getSelectedBlock(currentCategory); + for(int j = 0; j < blocks.size; j++){ + if(blocks.get(j) == currentBlock){ + switch(i){ + case 10: //left + j = (j - 1 + blocks.size) % blocks.size; + break; + case 11: //right + j = (j + 1) % blocks.size; + break; + case 12: //up + j = (j > 3 ? j - 4 : blocks.size - blocks.size % 4 + j); + j -= (j < blocks.size ? 0 : 4); + break; + case 13: //down + j = (j < blocks.size - 4 ? j + 4 : j % 4); + } + input.block = blocks.get(j); + selectedBlocks.put(currentCategory, input.block); + break; + } + } + }else if(blockSelectEnd || Time.timeSinceMillis(blockSelectSeqMillis) > Core.settings.getInt("blockselecttimeout")){ //1st number of combo, select category + //select only visible categories + if(!getByCategory(Category.all[i]).isEmpty()){ + currentCategory = Category.all[i]; + if(input.block != null){ + input.block = getSelectedBlock(currentCategory); + } + blockSelectEnd = false; + blockSelectSeq = 0; + blockSelectSeqMillis = Time.millis(); + } + }else{ //select block + if(blockSelectSeq == 0){ //2nd number of combo + blockSelectSeq = i + 1; + }else{ //3rd number of combo + //entering "X,1,0" selects the same block as "X,0" + i += (blockSelectSeq - (i != 9 ? 0 : 1)) * 10; + blockSelectEnd = true; + } + Array blocks = getByCategory(currentCategory); + input.block = (i < blocks.size) ? blocks.get(i) : null; + selectedBlocks.put(currentCategory, input.block); + blockSelectSeqMillis = Time.millis(); + } + return true; + } + } + + if(Core.input.keyTap(Binding.category_prev)){ + do{ + currentCategory = currentCategory.prev(); + }while(categoryEmpty[currentCategory.ordinal()]); + input.block = getSelectedBlock(currentCategory); + return true; + } + + if(Core.input.keyTap(Binding.category_next)){ + do{ + currentCategory = currentCategory.next(); + }while(categoryEmpty[currentCategory.ordinal()]); + input.block = getSelectedBlock(currentCategory); + return true; + } + return false; } @@ -109,11 +200,6 @@ public class PlacementFragment extends Fragment{ blockTable.row(); } - if(!unlocked(block)){ - blockTable.add().size(46); - continue; - } - ImageButton button = blockTable.addImageButton(Icon.lockedSmall, Styles.selecti, () -> { if(unlocked(block)){ control.input.block = control.input.block == block ? null : block; @@ -170,9 +256,22 @@ public class PlacementFragment extends Fragment{ lastGround = false; topTable.table(header -> { + String keyCombo = ""; + if(!mobile && Core.settings.getBool("blockselectkeys")){ + Array blocks = getByCategory(currentCategory); + for(int i = 0; i < blocks.size; i++){ + if(blocks.get(i) == lastDisplay){ + 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() + "]"; + break; + } + } + } + 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) + header.labelWrap(() -> !unlocked(lastDisplay) ? Core.bundle.get("block.unknown") : lastDisplay.localizedName + keyComboFinal) .left().width(190f).padLeft(5); header.add().growX(); if(unlocked(lastDisplay)){ @@ -265,7 +364,7 @@ public class PlacementFragment extends Fragment{ //update category empty values for(Category cat : Category.all){ Array blocks = getByCategory(cat); - categoryEmpty[cat.ordinal()] = blocks.isEmpty() || !unlocked(blocks.first()); + categoryEmpty[cat.ordinal()] = blocks.isEmpty(); } int f = 0; @@ -280,10 +379,7 @@ public class PlacementFragment extends Fragment{ categories.addImageButton(Core.atlas.drawable("icon-" + cat.name() + "-smaller"), Styles.clearToggleTransi, () -> { currentCategory = cat; if(control.input.block != null){ - if(selectedBlocks.get(currentCategory) == null){ - selectedBlocks.put(currentCategory, getByCategory(currentCategory).find(this::unlocked)); - } - control.input.block = selectedBlocks.get(currentCategory); + control.input.block = getSelectedBlock(currentCategory); } rebuildCategory.run(); }).group(group).update(i -> i.setChecked(currentCategory == cat)).name("category-" + cat.name()); @@ -308,7 +404,7 @@ public class PlacementFragment extends Fragment{ Array getByCategory(Category cat){ returnArray.clear(); for(Block block : content.blocks()){ - if(block.category == cat && block.isVisible()){ + if(block.category == cat && block.isVisible() && unlocked(block)){ returnArray.add(block); } } @@ -320,6 +416,13 @@ public class PlacementFragment extends Fragment{ return returnArray; } + Block getSelectedBlock(Category cat){ + if(selectedBlocks.get(cat) == null){ + selectedBlocks.put(cat, getByCategory(cat).find(this::unlocked)); + } + return selectedBlocks.get(cat); + } + boolean unlocked(Block block){ return !world.isZone() || data.isUnlocked(block); } @@ -359,4 +462,4 @@ public class PlacementFragment extends Fragment{ Block tileDisplayBlock(){ return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.drop() != null ? hoverTile.overlay().itemDrop != null ? hoverTile.overlay() : hoverTile.floor() : null; } -} \ No newline at end of file +}