mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-12-15 15:20:57 -08:00
RTS Control groups
This commit is contained in:
parent
e6b2e27d2a
commit
aecd220574
8 changed files with 119 additions and 18 deletions
|
|
@ -1068,6 +1068,7 @@ setting.backgroundpause.name = Pause In Background
|
||||||
setting.buildautopause.name = Auto-Pause Building
|
setting.buildautopause.name = Auto-Pause Building
|
||||||
setting.doubletapmine.name = Double-Tap to Mine
|
setting.doubletapmine.name = Double-Tap to Mine
|
||||||
setting.commandmodehold.name = Hold For Command Mode
|
setting.commandmodehold.name = Hold For Command Mode
|
||||||
|
setting.distinctcontrolgroups.name = Limit One Control Group Per Unit
|
||||||
setting.modcrashdisable.name = Disable Mods On Startup Crash
|
setting.modcrashdisable.name = Disable Mods On Startup Crash
|
||||||
setting.animatedwater.name = Animated Surfaces
|
setting.animatedwater.name = Animated Surfaces
|
||||||
setting.animatedshields.name = Animated Shields
|
setting.animatedshields.name = Animated Shields
|
||||||
|
|
@ -1158,7 +1159,8 @@ keybind.mouse_move.name = Follow Mouse
|
||||||
keybind.pan.name = Pan View
|
keybind.pan.name = Pan View
|
||||||
keybind.boost.name = Boost
|
keybind.boost.name = Boost
|
||||||
keybind.command_mode.name = Command Mode
|
keybind.command_mode.name = Command Mode
|
||||||
keybind.command_queue.name = Unit Command Queue
|
keybind.command_queue.name = Queue Unit Command
|
||||||
|
keybind.create_control_group.name = Create Control Group
|
||||||
keybind.rebuild_select.name = Rebuild Region
|
keybind.rebuild_select.name = Rebuild Region
|
||||||
keybind.schematic_select.name = Select Region
|
keybind.schematic_select.name = Select Region
|
||||||
keybind.schematic_menu.name = Schematic Menu
|
keybind.schematic_menu.name = Schematic Menu
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ public enum Binding implements KeyBind{
|
||||||
boost(KeyCode.shiftLeft),
|
boost(KeyCode.shiftLeft),
|
||||||
command_mode(KeyCode.shiftLeft),
|
command_mode(KeyCode.shiftLeft),
|
||||||
command_queue(KeyCode.mouseMiddle),
|
command_queue(KeyCode.mouseMiddle),
|
||||||
|
create_control_group(KeyCode.controlLeft),
|
||||||
control(KeyCode.controlLeft),
|
control(KeyCode.controlLeft),
|
||||||
respawn(KeyCode.v),
|
respawn(KeyCode.v),
|
||||||
select(KeyCode.mouseLeft),
|
select(KeyCode.mouseLeft),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.scene.*;
|
import arc.scene.*;
|
||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.core.*;
|
import mindustry.core.*;
|
||||||
|
|
@ -22,6 +23,7 @@ import mindustry.graphics.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
|
||||||
|
import static arc.Core.camera;
|
||||||
import static arc.Core.*;
|
import static arc.Core.*;
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
import static mindustry.input.PlaceMode.*;
|
import static mindustry.input.PlaceMode.*;
|
||||||
|
|
@ -49,6 +51,11 @@ public class DesktopInput extends InputHandler{
|
||||||
/** Previously selected tile. */
|
/** Previously selected tile. */
|
||||||
public Tile prevSelected;
|
public Tile prevSelected;
|
||||||
|
|
||||||
|
/** Most recently selected control group by index */
|
||||||
|
public int lastCtrlGroup;
|
||||||
|
/** Time of most recent control group selection */
|
||||||
|
public long lastCtrlGroupSelectMillis;
|
||||||
|
|
||||||
boolean showHint(){
|
boolean showHint(){
|
||||||
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() &&
|
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() &&
|
||||||
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
|
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
|
||||||
|
|
@ -262,22 +269,86 @@ public class DesktopInput extends InputHandler{
|
||||||
//validate commanding units
|
//validate commanding units
|
||||||
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid());
|
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid());
|
||||||
|
|
||||||
if(commandMode && input.keyTap(Binding.select_all_units) && !scene.hasField() && !scene.hasDialog()){
|
if(commandMode && !scene.hasField() && !scene.hasDialog()){
|
||||||
selectedUnits.clear();
|
if(input.keyTap(Binding.select_all_units)){
|
||||||
commandBuildings.clear();
|
selectedUnits.clear();
|
||||||
for(var unit : player.team().data().units){
|
commandBuildings.clear();
|
||||||
if(unit.isCommandable()){
|
for(var unit : player.team().data().units){
|
||||||
selectedUnits.add(unit);
|
if(unit.isCommandable()){
|
||||||
|
selectedUnits.add(unit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(commandMode && input.keyTap(Binding.select_all_unit_factories) && !scene.hasField() && !scene.hasDialog()){
|
if(input.keyTap(Binding.select_all_unit_factories)){
|
||||||
selectedUnits.clear();
|
selectedUnits.clear();
|
||||||
commandBuildings.clear();
|
commandBuildings.clear();
|
||||||
for(var build : player.team().data().buildings){
|
for(var build : player.team().data().buildings){
|
||||||
if(build.block.commandable){
|
if(build.block.commandable){
|
||||||
commandBuildings.add(build);
|
commandBuildings.add(build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < controlGroupBindings.length; i++){
|
||||||
|
if(input.keyTap(controlGroupBindings[i])){
|
||||||
|
|
||||||
|
//create control group if it doesn't exist yet
|
||||||
|
if(controlGroups[i] == null) controlGroups[i] = new IntSeq();
|
||||||
|
|
||||||
|
IntSeq group = controlGroups[i];
|
||||||
|
boolean creating = input.keyDown(Binding.create_control_group);
|
||||||
|
|
||||||
|
//clear existing if making a new control group
|
||||||
|
//if any of the control group edit buttons are pressed take the current selection
|
||||||
|
if(creating){
|
||||||
|
group.clear();
|
||||||
|
|
||||||
|
IntSeq selectedUnitIds = selectedUnits.mapInt(u -> u.id);
|
||||||
|
if(Core.settings.getBool("distinctcontrolgroups", true)){
|
||||||
|
for(IntSeq cg : controlGroups){
|
||||||
|
if(cg != null){
|
||||||
|
cg.removeAll(selectedUnitIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.addAll(selectedUnitIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove invalid units
|
||||||
|
for(int j = 0; j < group.size; j++){
|
||||||
|
Unit u = Groups.unit.getByID(group.get(j));
|
||||||
|
if(u == null || !u.isCommandable() || !u.isValid()){
|
||||||
|
group.removeIndex(j);
|
||||||
|
j --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//replace the selected units with the current control group
|
||||||
|
if(!group.isEmpty() && !creating){
|
||||||
|
selectedUnits.clear();
|
||||||
|
commandBuildings.clear();
|
||||||
|
|
||||||
|
group.each(id -> {
|
||||||
|
var unit = Groups.unit.getByID(id);
|
||||||
|
if(unit != null){
|
||||||
|
selectedUnits.addAll(unit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//double tap to center camera
|
||||||
|
if(lastCtrlGroup == i && Time.timeSinceMillis(lastCtrlGroupSelectMillis) < 400){
|
||||||
|
float totalX = 0, totalY = 0;
|
||||||
|
for(Unit unit : selectedUnits){
|
||||||
|
totalX += unit.x;
|
||||||
|
totalY += unit.y;
|
||||||
|
}
|
||||||
|
panning = true;
|
||||||
|
Core.camera.position.set(totalX / selectedUnits.size, totalY / selectedUnits.size);
|
||||||
|
}
|
||||||
|
lastCtrlGroup = i;
|
||||||
|
lastCtrlGroupSelectMillis = Time.millis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||||
final static int maxLength = 100;
|
final static int maxLength = 100;
|
||||||
final static Rect r1 = new Rect(), r2 = new Rect();
|
final static Rect r1 = new Rect(), r2 = new Rect();
|
||||||
final static Seq<Unit> tmpUnits = new Seq<>(false);
|
final static Seq<Unit> tmpUnits = new Seq<>(false);
|
||||||
|
final static Binding[] controlGroupBindings = {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
/** If true, there is a cutscene currently occurring in logic. */
|
/** If true, there is a cutscene currently occurring in logic. */
|
||||||
public boolean logicCutscene;
|
public boolean logicCutscene;
|
||||||
|
|
@ -87,6 +99,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||||
public boolean commandRect = false;
|
public boolean commandRect = false;
|
||||||
public boolean tappedOne = false;
|
public boolean tappedOne = false;
|
||||||
public float commandRectX, commandRectY;
|
public float commandRectX, commandRectY;
|
||||||
|
/** Groups of units saved to different hotkeys */
|
||||||
|
public IntSeq[] controlGroups = new IntSeq[controlGroupBindings.length];
|
||||||
|
|
||||||
private Seq<BuildPlan> plansOut = new Seq<>(BuildPlan.class);
|
private Seq<BuildPlan> plansOut = new Seq<>(BuildPlan.class);
|
||||||
private QuadTree<BuildPlan> playerPlanTree = new QuadTree<>(new Rect());
|
private QuadTree<BuildPlan> playerPlanTree = new QuadTree<>(new Rect());
|
||||||
|
|
@ -124,6 +138,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||||
|
|
||||||
Events.on(ResetEvent.class, e -> {
|
Events.on(ResetEvent.class, e -> {
|
||||||
logicCutscene = false;
|
logicCutscene = false;
|
||||||
|
Arrays.fill(controlGroups, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,10 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||||
node.save();
|
node.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStringMap(stream, StringMap.of(
|
StringMap result = new StringMap();
|
||||||
|
result.putAll(tags);
|
||||||
|
|
||||||
|
writeStringMap(stream, result.merge(StringMap.of(
|
||||||
"saved", Time.millis(),
|
"saved", Time.millis(),
|
||||||
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
|
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
|
||||||
"build", Version.build,
|
"build", Version.build,
|
||||||
|
|
@ -135,13 +138,14 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||||
"stats", JsonIO.write(state.stats),
|
"stats", JsonIO.write(state.stats),
|
||||||
"rules", JsonIO.write(state.rules),
|
"rules", JsonIO.write(state.rules),
|
||||||
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
||||||
|
"controlGroups", headless || control == null ? "null" : JsonIO.write(control.input.controlGroups),
|
||||||
"width", world.width(),
|
"width", world.width(),
|
||||||
"height", world.height(),
|
"height", world.height(),
|
||||||
"viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(),
|
"viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(),
|
||||||
"controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name,
|
"controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name,
|
||||||
"nocores", state.rules.defaultTeam.cores().isEmpty(),
|
"nocores", state.rules.defaultTeam.cores().isEmpty(),
|
||||||
"playerteam", player == null ? state.rules.defaultTeam.id : player.team().id
|
"playerteam", player == null ? state.rules.defaultTeam.id : player.team().id
|
||||||
).merge(tags));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readMeta(DataInput stream, WorldContext context) throws IOException{
|
public void readMeta(DataInput stream, WorldContext context) throws IOException{
|
||||||
|
|
@ -177,6 +181,11 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||||
if(!net.client() && team != Team.derelict){
|
if(!net.client() && team != Team.derelict){
|
||||||
player.team(team);
|
player.team(team);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var groups = JsonIO.read(IntSeq[].class, map.get("controlGroups", "null"));
|
||||||
|
if(groups != null && groups.length == control.input.controlGroups.length){
|
||||||
|
control.input.controlGroups = groups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
|
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,7 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||||
if(!mobile){
|
if(!mobile){
|
||||||
game.checkPref("backgroundpause", true);
|
game.checkPref("backgroundpause", true);
|
||||||
game.checkPref("buildautopause", false);
|
game.checkPref("buildautopause", false);
|
||||||
|
game.checkPref("distinctcontrolgroups", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
game.checkPref("doubletapmine", false);
|
game.checkPref("doubletapmine", false);
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,9 @@ public class PlacementFragment{
|
||||||
|
|
||||||
rebuildCategory.run();
|
rebuildCategory.run();
|
||||||
frame.update(() -> {
|
frame.update(() -> {
|
||||||
if(gridUpdate(control.input)) rebuildCategory.run();
|
if(!control.input.commandMode && gridUpdate(control.input)){
|
||||||
|
rebuildCategory.run();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,4 @@ org.gradle.caching=true
|
||||||
#used for slow jitpack builds; TODO see if this actually works
|
#used for slow jitpack builds; TODO see if this actually works
|
||||||
org.gradle.internal.http.socketTimeout=100000
|
org.gradle.internal.http.socketTimeout=100000
|
||||||
org.gradle.internal.http.connectionTimeout=100000
|
org.gradle.internal.http.connectionTimeout=100000
|
||||||
archash=70f345cc75
|
archash=38114a3118
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue