RTS Control groups

This commit is contained in:
Anuken 2023-09-19 23:48:40 -04:00
parent e6b2e27d2a
commit aecd220574
8 changed files with 119 additions and 18 deletions

View file

@ -1068,6 +1068,7 @@ setting.backgroundpause.name = Pause In Background
setting.buildautopause.name = Auto-Pause Building
setting.doubletapmine.name = Double-Tap to Mine
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.animatedwater.name = Animated Surfaces
setting.animatedshields.name = Animated Shields
@ -1158,7 +1159,8 @@ keybind.mouse_move.name = Follow Mouse
keybind.pan.name = Pan View
keybind.boost.name = Boost
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.schematic_select.name = Select Region
keybind.schematic_menu.name = Schematic Menu

View file

@ -14,6 +14,7 @@ public enum Binding implements KeyBind{
boost(KeyCode.shiftLeft),
command_mode(KeyCode.shiftLeft),
command_queue(KeyCode.mouseMiddle),
create_control_group(KeyCode.controlLeft),
control(KeyCode.controlLeft),
respawn(KeyCode.v),
select(KeyCode.mouseLeft),

View file

@ -11,6 +11,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.*;
@ -22,6 +23,7 @@ import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.*;
import static arc.Core.camera;
import static arc.Core.*;
import static mindustry.Vars.*;
import static mindustry.input.PlaceMode.*;
@ -49,6 +51,11 @@ public class DesktopInput extends InputHandler{
/** Previously selected tile. */
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(){
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() &&
(!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
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid());
if(commandMode && input.keyTap(Binding.select_all_units) && !scene.hasField() && !scene.hasDialog()){
selectedUnits.clear();
commandBuildings.clear();
for(var unit : player.team().data().units){
if(unit.isCommandable()){
selectedUnits.add(unit);
if(commandMode && !scene.hasField() && !scene.hasDialog()){
if(input.keyTap(Binding.select_all_units)){
selectedUnits.clear();
commandBuildings.clear();
for(var unit : player.team().data().units){
if(unit.isCommandable()){
selectedUnits.add(unit);
}
}
}
}
if(commandMode && input.keyTap(Binding.select_all_unit_factories) && !scene.hasField() && !scene.hasDialog()){
selectedUnits.clear();
commandBuildings.clear();
for(var build : player.team().data().buildings){
if(build.block.commandable){
commandBuildings.add(build);
if(input.keyTap(Binding.select_all_unit_factories)){
selectedUnits.clear();
commandBuildings.clear();
for(var build : player.team().data().buildings){
if(build.block.commandable){
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();
}
}
}
}

View file

@ -53,6 +53,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
final static int maxLength = 100;
final static Rect r1 = new Rect(), r2 = new Rect();
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. */
public boolean logicCutscene;
@ -87,6 +99,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean commandRect = false;
public boolean tappedOne = false;
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 QuadTree<BuildPlan> playerPlanTree = new QuadTree<>(new Rect());
@ -124,6 +138,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.on(ResetEvent.class, e -> {
logicCutscene = false;
Arrays.fill(controlGroups, null);
});
}

View file

@ -124,7 +124,10 @@ public abstract class SaveVersion extends SaveFileReader{
node.save();
}
writeStringMap(stream, StringMap.of(
StringMap result = new StringMap();
result.putAll(tags);
writeStringMap(stream, result.merge(StringMap.of(
"saved", Time.millis(),
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
"build", Version.build,
@ -135,13 +138,14 @@ public abstract class SaveVersion extends SaveFileReader{
"stats", JsonIO.write(state.stats),
"rules", JsonIO.write(state.rules),
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
"controlGroups", headless || control == null ? "null" : JsonIO.write(control.input.controlGroups),
"width", world.width(),
"height", world.height(),
"viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(),
"controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name,
"nocores", state.rules.defaultTeam.cores().isEmpty(),
"playerteam", player == null ? state.rules.defaultTeam.id : player.team().id
).merge(tags));
)));
}
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){
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", "\\\\\\"));

View file

@ -341,6 +341,7 @@ public class SettingsMenuDialog extends BaseDialog{
if(!mobile){
game.checkPref("backgroundpause", true);
game.checkPref("buildautopause", false);
game.checkPref("distinctcontrolgroups", true);
}
game.checkPref("doubletapmine", false);

View file

@ -611,7 +611,9 @@ public class PlacementFragment{
rebuildCategory.run();
frame.update(() -> {
if(gridUpdate(control.input)) rebuildCategory.run();
if(!control.input.commandMode && gridUpdate(control.input)){
rebuildCategory.run();
}
});
});
});

View file

@ -25,4 +25,4 @@ org.gradle.caching=true
#used for slow jitpack builds; TODO see if this actually works
org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000
archash=70f345cc75
archash=38114a3118