it is done
This commit is contained in:
80
core/src/mindustry/input/Binding.java
Normal file
80
core/src/mindustry/input/Binding.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package mindustry.input;
|
||||
|
||||
import arc.Application.ApplicationType;
|
||||
import arc.Core;
|
||||
import arc.KeyBinds.*;
|
||||
import arc.input.InputDevice.DeviceType;
|
||||
import arc.input.KeyCode;
|
||||
|
||||
public enum Binding implements KeyBind{
|
||||
move_x(new Axis(KeyCode.A, KeyCode.D), "general"),
|
||||
move_y(new Axis(KeyCode.S, KeyCode.W)),
|
||||
mouse_move(KeyCode.MOUSE_BACK),
|
||||
dash(KeyCode.SHIFT_LEFT),
|
||||
select(KeyCode.MOUSE_LEFT),
|
||||
deselect(KeyCode.MOUSE_RIGHT),
|
||||
break_block(KeyCode.MOUSE_RIGHT),
|
||||
clear_building(KeyCode.Q),
|
||||
pause_building(KeyCode.E),
|
||||
rotate(new Axis(KeyCode.SCROLL)),
|
||||
rotateplaced(KeyCode.R),
|
||||
diagonal_placement(KeyCode.CONTROL_LEFT),
|
||||
pick(KeyCode.MOUSE_MIDDLE),
|
||||
schematic_select(KeyCode.F),
|
||||
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(new Axis(KeyCode.SCROLL), "view"),
|
||||
menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE),
|
||||
fullscreen(KeyCode.F11),
|
||||
pause(KeyCode.SPACE),
|
||||
minimap(KeyCode.M),
|
||||
toggle_menus(KeyCode.C),
|
||||
screenshot(KeyCode.P),
|
||||
toggle_power_lines(KeyCode.F7),
|
||||
player_list(KeyCode.TAB, "multiplayer"),
|
||||
chat(KeyCode.ENTER),
|
||||
chat_history_prev(KeyCode.UP),
|
||||
chat_history_next(KeyCode.DOWN),
|
||||
chat_scroll(new Axis(KeyCode.SCROLL)),
|
||||
console(KeyCode.F8),
|
||||
;
|
||||
|
||||
private final KeybindValue defaultValue;
|
||||
private final String category;
|
||||
|
||||
Binding(KeybindValue defaultValue, String category){
|
||||
this.defaultValue = defaultValue;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
Binding(KeybindValue defaultValue){
|
||||
this(defaultValue, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeybindValue defaultValue(DeviceType type){
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String category(){
|
||||
return category;
|
||||
}
|
||||
}
|
||||
467
core/src/mindustry/input/DesktopInput.java
Normal file
467
core/src/mindustry/input/DesktopInput.java
Normal file
@@ -0,0 +1,467 @@
|
||||
package mindustry.input;
|
||||
|
||||
import arc.*;
|
||||
import arc.Graphics.*;
|
||||
import arc.Graphics.Cursor.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.traits.BuilderTrait.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.scene;
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.input.PlaceMode.*;
|
||||
|
||||
public class DesktopInput extends InputHandler{
|
||||
/** Current cursor type. */
|
||||
private Cursor cursorType = SystemCursor.arrow;
|
||||
/** Position where the player started dragging a line. */
|
||||
private int selectX, selectY, schemX, schemY;
|
||||
/** Last known line positions.*/
|
||||
private int lastLineX, lastLineY, schematicX, schematicY;
|
||||
/** Whether selecting mode is active. */
|
||||
private PlaceMode mode;
|
||||
/** Animation scale for line. */
|
||||
private float selectScale;
|
||||
/** Selected build request for movement. */
|
||||
private @Nullable BuildRequest sreq;
|
||||
/** Whether player is currently deleting removal requests. */
|
||||
private boolean deleting = false;
|
||||
|
||||
@Override
|
||||
public void buildUI(Group group){
|
||||
group.fill(t -> {
|
||||
t.bottom().update(() -> t.getColor().a = Mathf.lerpDelta(t.getColor().a, player.isBuilding() ? 1f : 0f, 0.15f));
|
||||
t.visible(() -> Core.settings.getBool("hints") && selectRequests.isEmpty());
|
||||
t.touchable(() -> t.getColor().a < 0.1f ? Touchable.disabled : Touchable.childrenOnly);
|
||||
t.table(Styles.black6, b -> {
|
||||
b.defaults().left();
|
||||
b.label(() -> Core.bundle.format(!player.isBuilding ? "resumebuilding" : "pausebuilding", Core.keybinds.get(Binding.pause_building).key.toString())).style(Styles.outlineLabel);
|
||||
b.row();
|
||||
b.label(() -> Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString())).style(Styles.outlineLabel);
|
||||
b.row();
|
||||
b.label(() -> Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString())).style(Styles.outlineLabel);
|
||||
}).margin(10f);
|
||||
});
|
||||
|
||||
group.fill(t -> {
|
||||
t.visible(() -> lastSchematic != null && !selectRequests.isEmpty());
|
||||
t.bottom();
|
||||
t.table(Styles.black6, b -> {
|
||||
b.defaults().left();
|
||||
b.label( () -> Core.bundle.format("schematic.flip",
|
||||
Core.keybinds.get(Binding.schematic_flip_x).key.toString(),
|
||||
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel);
|
||||
b.row();
|
||||
b.table(a -> {
|
||||
a.addImageTextButton("$schematic.add", Icon.saveSmall, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
|
||||
});
|
||||
}).margin(6f);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTop(){
|
||||
Lines.stroke(1f);
|
||||
int cursorX = tileX(Core.input.mouseX());
|
||||
int cursorY = tileY(Core.input.mouseY());
|
||||
|
||||
//draw selection(s)
|
||||
if(mode == placing && block != null){
|
||||
for(int i = 0; i < lineRequests.size; i++){
|
||||
BuildRequest req = lineRequests.get(i);
|
||||
if(i == lineRequests.size - 1 && req.block.rotate){
|
||||
drawArrow(block, req.x, req.y, req.rotation);
|
||||
}
|
||||
drawRequest(lineRequests.get(i));
|
||||
}
|
||||
}else if(mode == breaking){
|
||||
drawBreakSelection(selectX, selectY, cursorX, cursorY);
|
||||
}else if(isPlacing()){
|
||||
if(block.rotate){
|
||||
drawArrow(block, cursorX, cursorY, rotation);
|
||||
}
|
||||
Draw.color();
|
||||
drawRequest(cursorX, cursorY, block, rotation);
|
||||
block.drawPlace(cursorX, cursorY, rotation, validPlace(cursorX, cursorY, block, rotation));
|
||||
}
|
||||
|
||||
if(mode == none && !isPlacing()){
|
||||
BuildRequest req = getRequest(cursorX, cursorY);
|
||||
if(req != null){
|
||||
drawSelected(req.x, req.y, req.breaking ? req.tile().block() : req.block, Pal.accent);
|
||||
}
|
||||
}
|
||||
|
||||
//draw schematic requests
|
||||
for(BuildRequest request : selectRequests){
|
||||
request.animScale = 1f;
|
||||
drawRequest(request);
|
||||
}
|
||||
|
||||
if(sreq != null){
|
||||
boolean valid = validPlace(sreq.x, sreq.y, sreq.block, sreq.rotation, sreq);
|
||||
if(sreq.block.rotate){
|
||||
drawArrow(sreq.block, sreq.x, sreq.y, sreq.rotation, valid);
|
||||
}
|
||||
|
||||
sreq.block.drawRequest(sreq, allRequests(), valid);
|
||||
|
||||
drawSelected(sreq.x, sreq.y, sreq.block, getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null ? Pal.remove : Pal.accent);
|
||||
}
|
||||
|
||||
if(Core.input.keyDown(Binding.schematic_select) && !Core.scene.hasKeyboard()){
|
||||
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(net.active() && Core.input.keyTap(Binding.player_list)){
|
||||
ui.listfrag.toggle();
|
||||
}
|
||||
|
||||
if(Core.input.keyRelease(Binding.select)){
|
||||
player.isShooting = false;
|
||||
}
|
||||
|
||||
if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && (scene.getKeyboardFocus() == ui.minimap || !scene.hasDialog()) && !Core.scene.hasKeyboard() && !(scene.getKeyboardFocus() instanceof TextField)){
|
||||
if(!ui.minimap.isShown()){
|
||||
ui.minimap.show();
|
||||
}else{
|
||||
ui.minimap.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if(state.is(State.menu) || Core.scene.hasDialog()) return;
|
||||
|
||||
//zoom camera
|
||||
if(!Core.scene.hasScroll() && !ui.chatfrag.shown() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
|
||||
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
|
||||
}
|
||||
|
||||
if(player.isDead()){
|
||||
cursorType = SystemCursor.arrow;
|
||||
return;
|
||||
}
|
||||
|
||||
pollInput();
|
||||
|
||||
//deselect if not placing
|
||||
if(!isPlacing() && mode == placing){
|
||||
mode = none;
|
||||
}
|
||||
|
||||
if(player.isShooting && !canShoot()){
|
||||
player.isShooting = false;
|
||||
}
|
||||
|
||||
if(isPlacing()){
|
||||
cursorType = SystemCursor.hand;
|
||||
selectScale = Mathf.lerpDelta(selectScale, 1f, 0.2f);
|
||||
}else{
|
||||
selectScale = 0f;
|
||||
}
|
||||
|
||||
if(!Core.input.keyDown(Binding.diagonal_placement) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){
|
||||
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
|
||||
|
||||
if(sreq != null){
|
||||
sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4);
|
||||
}
|
||||
|
||||
if(isPlacing() && mode == placing){
|
||||
updateLine(selectX, selectY);
|
||||
}else if(!selectRequests.isEmpty()){
|
||||
rotateRequests(selectRequests, (int)Core.input.axisTap(Binding.rotate));
|
||||
}
|
||||
}
|
||||
|
||||
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||
|
||||
if(cursor != null){
|
||||
cursor = cursor.link();
|
||||
|
||||
cursorType = cursor.block().getCursor(cursor);
|
||||
|
||||
if(isPlacing() || !selectRequests.isEmpty()){
|
||||
cursorType = SystemCursor.hand;
|
||||
}
|
||||
|
||||
if(!isPlacing() && canMine(cursor)){
|
||||
cursorType = ui.drillCursor;
|
||||
}
|
||||
|
||||
if(getRequest(cursor.x, cursor.y) != null && mode == none){
|
||||
cursorType = SystemCursor.hand;
|
||||
}
|
||||
|
||||
if(canTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y)){
|
||||
cursorType = ui.unloadCursor;
|
||||
}
|
||||
|
||||
if(!isPlacing() && Math.abs(Core.input.axisTap(Binding.rotate)) > 0 && Core.input.keyDown(Binding.rotateplaced) && cursor.block().rotate){
|
||||
Call.rotateBlock(player, cursor, Core.input.axisTap(Binding.rotate) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(!Core.scene.hasMouse()){
|
||||
Core.graphics.cursor(cursorType);
|
||||
}
|
||||
|
||||
cursorType = SystemCursor.arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useSchematic(Schematic schem){
|
||||
block = null;
|
||||
schematicX = tileX(getMouseX());
|
||||
schematicY = tileY(getMouseY());
|
||||
|
||||
selectRequests.clear();
|
||||
selectRequests.addAll(schematics.toRequests(schem, schematicX, schematicY));
|
||||
mode = none;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBreaking(){
|
||||
return mode == breaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildPlacementUI(Table table){
|
||||
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
|
||||
table.row();
|
||||
table.left().margin(0f).defaults().size(48f).left();
|
||||
|
||||
table.addImageButton(Icon.pasteSmall, Styles.clearPartiali, () -> {
|
||||
ui.schematics.show();
|
||||
});
|
||||
}
|
||||
|
||||
void pollInput(){
|
||||
if(scene.getKeyboardFocus() instanceof TextField) return;
|
||||
|
||||
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||
int cursorX = tileX(Core.input.mouseX());
|
||||
int cursorY = tileY(Core.input.mouseY());
|
||||
int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y);
|
||||
|
||||
// automatically pause building if the current build queue is empty
|
||||
if(Core.settings.getBool("buildautopause") && player.isBuilding && !player.isBuilding()){
|
||||
player.isBuilding = false;
|
||||
player.buildWasAutoPaused = true;
|
||||
}
|
||||
|
||||
if(!selectRequests.isEmpty()){
|
||||
int shiftX = rawCursorX - schematicX, shiftY = rawCursorY - schematicY;
|
||||
|
||||
selectRequests.each(s -> {
|
||||
s.x += shiftX;
|
||||
s.y += shiftY;
|
||||
});
|
||||
|
||||
schematicX += shiftX;
|
||||
schematicY += shiftY;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.deselect)){
|
||||
player.setMineTile(null);
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.clear_building)){
|
||||
player.clearBuilding();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard()){
|
||||
schemX = rawCursorX;
|
||||
schemY = rawCursorY;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.schematic_menu) && !Core.scene.hasKeyboard()){
|
||||
if(ui.schematics.isShown()){
|
||||
ui.schematics.hide();
|
||||
}else{
|
||||
ui.schematics.show();
|
||||
}
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.clear_building)){
|
||||
lastSchematic = null;
|
||||
selectRequests.clear();
|
||||
}
|
||||
|
||||
if(Core.input.keyRelease(Binding.schematic_select) && !Core.scene.hasKeyboard()){
|
||||
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
|
||||
useSchematic(lastSchematic);
|
||||
if(selectRequests.isEmpty()){
|
||||
lastSchematic = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(!selectRequests.isEmpty()){
|
||||
if(Core.input.keyTap(Binding.schematic_flip_x)){
|
||||
flipRequests(selectRequests, true);
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.schematic_flip_y)){
|
||||
flipRequests(selectRequests, false);
|
||||
}
|
||||
}
|
||||
|
||||
if(sreq != null){
|
||||
float offset = ((sreq.block.size + 2) % 2) * tilesize / 2f;
|
||||
float x = Core.input.mouseWorld().x + offset;
|
||||
float y = Core.input.mouseWorld().y + offset;
|
||||
sreq.x = (int)(x / tilesize);
|
||||
sreq.y = (int)(y / tilesize);
|
||||
}
|
||||
|
||||
if(block == null || mode != placing){
|
||||
lineRequests.clear();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.pause_building)){
|
||||
player.isBuilding = !player.isBuilding;
|
||||
player.buildWasAutoPaused = false;
|
||||
}
|
||||
|
||||
if((cursorX != lastLineX || cursorY != lastLineY) && isPlacing() && mode == placing){
|
||||
updateLine(selectX, selectY);
|
||||
lastLineX = cursorX;
|
||||
lastLineY = cursorY;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){
|
||||
BuildRequest req = getRequest(cursorX, cursorY);
|
||||
|
||||
if(Core.input.keyDown(Binding.break_block)){
|
||||
mode = none;
|
||||
}else if(!selectRequests.isEmpty()){
|
||||
flushRequests(selectRequests);
|
||||
}else if(isPlacing()){
|
||||
selectX = cursorX;
|
||||
selectY = cursorY;
|
||||
lastLineX = cursorX;
|
||||
lastLineY = cursorY;
|
||||
mode = placing;
|
||||
updateLine(selectX, selectY);
|
||||
}else if(req != null && !req.breaking && mode == none && !req.initialized){
|
||||
sreq = req;
|
||||
}else if(req != null && req.breaking){
|
||||
deleting = true;
|
||||
}else if(selected != null){
|
||||
//only begin shooting if there's no cursor event
|
||||
if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && (player.buildQueue().size == 0 || !player.isBuilding) && !droppingItem &&
|
||||
!tryBeginMine(selected) && player.getMineTile() == null && !Core.scene.hasKeyboard()){
|
||||
player.isShooting = true;
|
||||
}
|
||||
}else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine
|
||||
player.isShooting = true;
|
||||
}
|
||||
}else if(Core.input.keyTap(Binding.deselect) && isPlacing()){
|
||||
block = null;
|
||||
mode = none;
|
||||
}else if(Core.input.keyTap(Binding.deselect) && !selectRequests.isEmpty()){
|
||||
selectRequests.clear();
|
||||
lastSchematic = null;
|
||||
}else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse()){
|
||||
//is recalculated because setting the mode to breaking removes potential multiblock cursor offset
|
||||
deleting = false;
|
||||
mode = breaking;
|
||||
selectX = tileX(Core.input.mouseX());
|
||||
selectY = tileY(Core.input.mouseY());
|
||||
}
|
||||
|
||||
if(Core.input.keyDown(Binding.select) && mode == none && !isPlacing() && deleting){
|
||||
BuildRequest req = getRequest(cursorX, cursorY);
|
||||
if(req != null && req.breaking){
|
||||
player.buildQueue().remove(req);
|
||||
}
|
||||
}else{
|
||||
deleting = false;
|
||||
}
|
||||
|
||||
if(mode == placing && block != null){
|
||||
if(!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int) Core.input.axisTap(Binding.rotate) != 0)){
|
||||
rotation = ((int)((Angles.angle(selectX, selectY, cursorX, cursorY) + 45) / 90f)) % 4;
|
||||
overrideLineRotation = true;
|
||||
}
|
||||
}else{
|
||||
overrideLineRotation = false;
|
||||
}
|
||||
|
||||
if(Core.input.keyRelease(Binding.break_block) || Core.input.keyRelease(Binding.select)){
|
||||
|
||||
if(mode == placing && block != null){ //touch up while placing, place everything in selection
|
||||
flushRequests(lineRequests);
|
||||
lineRequests.clear();
|
||||
Events.fire(new LineConfirmEvent());
|
||||
}else if(mode == breaking){ //touch up while breaking, break everything in selection
|
||||
removeSelection(selectX, selectY, cursorX, cursorY);
|
||||
}
|
||||
|
||||
if(selected != null){
|
||||
tryDropItems(selected.link(), Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
}
|
||||
|
||||
if(sreq != null){
|
||||
if(getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null){
|
||||
player.buildQueue().remove(sreq, true);
|
||||
}
|
||||
sreq = null;
|
||||
}
|
||||
|
||||
mode = none;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.toggle_power_lines)){
|
||||
if(Core.settings.getInt("lasersopacity") == 0){
|
||||
Core.settings.put("lasersopacity", Core.settings.getInt("preferredlaseropacity", 100));
|
||||
}else{
|
||||
Core.settings.put("preferredlaseropacity", Core.settings.getInt("lasersopacity"));
|
||||
Core.settings.put("lasersopacity", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selectedBlock(){
|
||||
return isPlacing() && mode != breaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMouseX(){
|
||||
return Core.input.mouseX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMouseY(){
|
||||
return Core.input.mouseY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(){
|
||||
if(state.is(State.menu)){
|
||||
droppingItem = false;
|
||||
mode = none;
|
||||
block = null;
|
||||
sreq = null;
|
||||
selectRequests.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
884
core/src/mindustry/input/InputHandler.java
Normal file
884
core/src/mindustry/input/InputHandler.java
Normal file
@@ -0,0 +1,884 @@
|
||||
package mindustry.input;
|
||||
|
||||
import arc.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import arc.struct.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.input.GestureDetector.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.effect.*;
|
||||
import mindustry.entities.traits.BuilderTrait.*;
|
||||
import mindustry.entities.type.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.input.Placement.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.fragments.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.BuildBlock.*;
|
||||
import mindustry.world.blocks.power.PowerNode;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
/** Used for dropping items. */
|
||||
final static float playerSelectRange = mobile ? 17f : 11f;
|
||||
/** Maximum line length. */
|
||||
final static int maxLength = 100;
|
||||
final static Vector2 stackTrns = new Vector2();
|
||||
final static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
|
||||
/** Distance on the back from where items originate. */
|
||||
final static float backTrns = 3f;
|
||||
|
||||
public final OverlayFragment frag = new OverlayFragment();
|
||||
|
||||
public Block block;
|
||||
public boolean overrideLineRotation;
|
||||
public int rotation;
|
||||
public boolean droppingItem;
|
||||
public Group uiGroup;
|
||||
|
||||
protected @Nullable Schematic lastSchematic;
|
||||
protected GestureDetector detector;
|
||||
protected PlaceLine line = new PlaceLine();
|
||||
protected BuildRequest resultreq;
|
||||
protected BuildRequest brequest = new BuildRequest();
|
||||
protected Array<BuildRequest> lineRequests = new Array<>();
|
||||
protected Array<BuildRequest> selectRequests = new Array<>();
|
||||
|
||||
//methods to override
|
||||
|
||||
@Remote(targets = Loc.client, called = Loc.server)
|
||||
public static void dropItem(Player player, float angle){
|
||||
if(net.server() && player.item().amount <= 0){
|
||||
throw new ValidateException(player, "Player cannot drop an item.");
|
||||
}
|
||||
|
||||
Effects.effect(Fx.dropItem, Color.white, player.x, player.y, angle, player.item().item);
|
||||
player.clearItem();
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
|
||||
public static void rotateBlock(Player player, Tile tile, boolean direction){
|
||||
if(net.server() && !Units.canInteract(player, tile)){
|
||||
throw new ValidateException(player, "Player cannot drop an item.");
|
||||
}
|
||||
|
||||
tile.rotation(Mathf.mod(tile.rotation() + Mathf.sign(direction), 4));
|
||||
|
||||
if(tile.entity != null){
|
||||
tile.entity.updateProximity();
|
||||
tile.entity.noSleep();
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, forward = true, called = Loc.server)
|
||||
public static void transferInventory(Player player, Tile tile){
|
||||
if(player == null || player.timer == null || !player.timer.get(Player.timerTransfer, 40)) return;
|
||||
if(net.server() && (player.item().amount <= 0 || player.isTransferring|| !Units.canInteract(player, tile))){
|
||||
throw new ValidateException(player, "Player cannot transfer an item.");
|
||||
}
|
||||
|
||||
if(tile.entity == null) return;
|
||||
|
||||
player.isTransferring = true;
|
||||
|
||||
Item item = player.item().item;
|
||||
int amount = player.item().amount;
|
||||
int accepted = tile.block().acceptStack(item, amount, tile, player);
|
||||
player.item().amount -= accepted;
|
||||
|
||||
int sent = Mathf.clamp(accepted / 4, 1, 8);
|
||||
int removed = accepted / sent;
|
||||
int[] remaining = {accepted, accepted};
|
||||
Block block = tile.block();
|
||||
|
||||
Core.app.post(() -> Events.fire(new DepositEvent(tile, player, item, accepted)));
|
||||
|
||||
for(int i = 0; i < sent; i++){
|
||||
boolean end = i == sent - 1;
|
||||
Time.run(i * 3, () -> {
|
||||
tile.block().getStackOffset(item, tile, stackTrns);
|
||||
|
||||
ItemTransfer.create(item,
|
||||
player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns),
|
||||
new Vector2(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> {
|
||||
if(tile.block() != block || tile.entity == null || tile.entity.items == null) return;
|
||||
|
||||
tile.block().handleStack(item, removed, tile, player);
|
||||
remaining[1] -= removed;
|
||||
|
||||
if(end && remaining[1] > 0){
|
||||
tile.block().handleStack(item, remaining[1], tile, player);
|
||||
}
|
||||
});
|
||||
|
||||
remaining[0] -= removed;
|
||||
|
||||
if(end){
|
||||
player.isTransferring = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
public static void onTileTapped(Player player, Tile tile){
|
||||
if(tile == null || player == null) return;
|
||||
if(!Units.canInteract(player, tile)) return;
|
||||
tile.block().tapped(tile, player);
|
||||
Core.app.post(() -> Events.fire(new TapEvent(tile, player)));
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.both, forward = true)
|
||||
public static void onTileConfig(Player player, Tile tile, int value){
|
||||
if(tile == null || !Units.canInteract(player, tile)) return;
|
||||
tile.block().configured(tile, player, value);
|
||||
Core.app.post(() -> Events.fire(new TapConfigEvent(tile, player, value)));
|
||||
}
|
||||
|
||||
public Eachable<BuildRequest> allRequests(){
|
||||
return cons -> {
|
||||
for(BuildRequest request : player.buildQueue()) cons.get(request);
|
||||
for(BuildRequest request : selectRequests) cons.get(request);
|
||||
for(BuildRequest request : lineRequests) cons.get(request);
|
||||
};
|
||||
}
|
||||
|
||||
public OverlayFragment getFrag(){
|
||||
return frag;
|
||||
}
|
||||
|
||||
public void update(){
|
||||
|
||||
}
|
||||
|
||||
public float getMouseX(){
|
||||
return Core.input.mouseX();
|
||||
}
|
||||
|
||||
public float getMouseY(){
|
||||
return Core.input.mouseY();
|
||||
}
|
||||
|
||||
public void buildPlacementUI(Table table){
|
||||
|
||||
}
|
||||
|
||||
public void buildUI(Group group){
|
||||
|
||||
}
|
||||
|
||||
public void updateState(){
|
||||
|
||||
}
|
||||
|
||||
public void drawBottom(){
|
||||
|
||||
}
|
||||
|
||||
public void drawTop(){
|
||||
|
||||
}
|
||||
|
||||
public void drawSelected(int x, int y, Block block, Color color){
|
||||
Draw.color(color);
|
||||
for(int i = 0; i < 4; i++){
|
||||
Point2 p = Geometry.d8edge[i];
|
||||
float offset = -Math.max(block.size - 1, 0) / 2f * tilesize;
|
||||
Draw.rect("block-select",
|
||||
x*tilesize + block.offset() + offset * p.x,
|
||||
y*tilesize + block.offset() + offset * p.y, i * 90);
|
||||
}
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public void drawBreaking(BuildRequest request){
|
||||
if(request.breaking){
|
||||
drawBreaking(request.x, request.y);
|
||||
}else{
|
||||
drawSelected(request.x, request.y, request.block, Pal.remove);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requestMatches(BuildRequest request){
|
||||
Tile tile = world.tile(request.x, request.y);
|
||||
return tile != null && tile.block() instanceof BuildBlock && tile.<BuildEntity>ent().cblock == request.block;
|
||||
}
|
||||
|
||||
public void drawBreaking(int x, int y){
|
||||
Tile tile = world.ltile(x, y);
|
||||
if(tile == null) return;
|
||||
Block block = tile.block();
|
||||
|
||||
drawSelected(x, y, block, Pal.remove);
|
||||
}
|
||||
|
||||
public void useSchematic(Schematic schem){
|
||||
selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y)));
|
||||
}
|
||||
|
||||
protected void showSchematicSave(){
|
||||
if(lastSchematic == null) return;
|
||||
|
||||
ui.showTextInput("$schematic.add", "$name", "", text -> {
|
||||
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
|
||||
if(replacement != null){
|
||||
ui.showConfirm("$confirm", "$schematic.replace", () -> {
|
||||
schematics.overwrite(replacement, lastSchematic);
|
||||
ui.showInfoFade("$schematic.saved");
|
||||
ui.schematics.showInfo(replacement);
|
||||
});
|
||||
}else{
|
||||
lastSchematic.tags.put("name", text);
|
||||
schematics.add(lastSchematic);
|
||||
ui.showInfoFade("$schematic.saved");
|
||||
ui.schematics.showInfo(lastSchematic);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void rotateRequests(Array<BuildRequest> requests, int direction){
|
||||
int ox = schemOriginX(), oy = schemOriginY();
|
||||
|
||||
requests.each(req -> {
|
||||
//rotate config position
|
||||
if(req.block.posConfig){
|
||||
int cx = Pos.x(req.config) - req.originalX, cy = Pos.y(req.config) - req.originalY;
|
||||
int lx = cx;
|
||||
|
||||
if(direction >= 0){
|
||||
cx = -cy;
|
||||
cy = lx;
|
||||
}else{
|
||||
cx = cy;
|
||||
cy = -lx;
|
||||
}
|
||||
req.config = Pos.get(cx + req.originalX, cy + req.originalY);
|
||||
}
|
||||
|
||||
//rotate actual request, centered on its multiblock position
|
||||
float wx = (req.x - ox) * tilesize + req.block.offset(), wy = (req.y - oy) * tilesize + req.block.offset();
|
||||
float x = wx;
|
||||
if(direction >= 0){
|
||||
wx = -wy;
|
||||
wy = x;
|
||||
}else{
|
||||
wx = wy;
|
||||
wy = -x;
|
||||
}
|
||||
req.x = world.toTile(wx - req.block.offset()) + ox;
|
||||
req.y = world.toTile(wy - req.block.offset()) + oy;
|
||||
req.rotation = Mathf.mod(req.rotation + direction, 4);
|
||||
});
|
||||
}
|
||||
|
||||
public void flipRequests(Array<BuildRequest> requests, boolean x){
|
||||
int origin = (x ? schemOriginX() : schemOriginY()) * tilesize;
|
||||
|
||||
requests.each(req -> {
|
||||
float value = -((x ? req.x : req.y) * tilesize - origin + req.block.offset()) + origin;
|
||||
|
||||
if(x){
|
||||
req.x = (int)((value - req.block.offset()) / tilesize);
|
||||
}else{
|
||||
req.y = (int)((value - req.block.offset()) / tilesize);
|
||||
}
|
||||
|
||||
if(req.block.posConfig){
|
||||
int corigin = x ? req.originalWidth/2 : req.originalHeight/2;
|
||||
int nvalue = -((x ? Pos.x(req.config) : Pos.y(req.config)) - corigin) + corigin;
|
||||
if(x){
|
||||
req.originalX = -(req.originalX - corigin) + corigin;
|
||||
req.config = Pos.get(nvalue, Pos.y(req.config));
|
||||
}else{
|
||||
req.originalY = -(req.originalY - corigin) + corigin;
|
||||
req.config = Pos.get(Pos.x(req.config), nvalue);
|
||||
}
|
||||
}
|
||||
|
||||
//flip rotation
|
||||
if(x == (req.rotation % 2 == 0)){
|
||||
req.rotation = Mathf.mod(req.rotation + 2, 4);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected int schemOriginX(){
|
||||
return rawTileX();
|
||||
}
|
||||
|
||||
protected int schemOriginY(){
|
||||
return rawTileY();
|
||||
}
|
||||
|
||||
/** Returns the selection request that overlaps this position, or null. */
|
||||
protected BuildRequest getRequest(int x, int y){
|
||||
return getRequest(x, y, 1, null);
|
||||
}
|
||||
|
||||
/** Returns the selection request that overlaps this position, or null. */
|
||||
protected BuildRequest getRequest(int x, int y, int size, BuildRequest skip){
|
||||
float offset = ((size + 1) % 2) * tilesize / 2f;
|
||||
r2.setSize(tilesize * size);
|
||||
r2.setCenter(x * tilesize + offset, y * tilesize + offset);
|
||||
resultreq = null;
|
||||
|
||||
Boolf<BuildRequest> test = req -> {
|
||||
if(req == skip) return false;
|
||||
Tile other = req.tile();
|
||||
|
||||
if(other == null) return false;
|
||||
|
||||
if(!req.breaking){
|
||||
r1.setSize(req.block.size * tilesize);
|
||||
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
|
||||
}else{
|
||||
r1.setSize(other.block().size * tilesize);
|
||||
r1.setCenter(other.worldx() + other.block().offset(), other.worldy() + other.block().offset());
|
||||
}
|
||||
|
||||
return r2.overlaps(r1);
|
||||
};
|
||||
|
||||
for(BuildRequest req : player.buildQueue()){
|
||||
if(test.get(req)) return req;
|
||||
}
|
||||
|
||||
for(BuildRequest req : selectRequests){
|
||||
if(test.get(req)) return req;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
|
||||
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
|
||||
NormalizeResult dresult = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
|
||||
|
||||
for(int x = dresult.x; x <= dresult.x2; x++){
|
||||
for(int y = dresult.y; y <= dresult.y2; y++){
|
||||
Tile tile = world.ltile(x, y);
|
||||
if(tile == null || !validBreak(tile.x, tile.y)) continue;
|
||||
|
||||
drawBreaking(tile.x, tile.y);
|
||||
}
|
||||
}
|
||||
|
||||
Tmp.r1.set(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
|
||||
|
||||
Draw.color(Pal.remove);
|
||||
Lines.stroke(1f);
|
||||
|
||||
for(BuildRequest req : player.buildQueue()){
|
||||
if(req.breaking) continue;
|
||||
if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){
|
||||
drawBreaking(req);
|
||||
}
|
||||
}
|
||||
|
||||
for(BuildRequest req : selectRequests){
|
||||
if(req.breaking) continue;
|
||||
if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){
|
||||
drawBreaking(req);
|
||||
}
|
||||
}
|
||||
|
||||
for(BrokenBlock req : state.teams.get(player.getTeam()).brokenBlocks){
|
||||
Block block = content.block(req.block);
|
||||
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
drawSelected(req.x, req.y, content.block(req.block), Pal.remove);
|
||||
}
|
||||
}
|
||||
|
||||
Lines.stroke(2f);
|
||||
|
||||
Draw.color(Pal.removeBack);
|
||||
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
|
||||
Draw.color(Pal.remove);
|
||||
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
|
||||
}
|
||||
|
||||
protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){
|
||||
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
|
||||
|
||||
Lines.stroke(2f);
|
||||
|
||||
Draw.color(Pal.accentBack);
|
||||
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
|
||||
Draw.color(Pal.accent);
|
||||
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
|
||||
}
|
||||
|
||||
protected void flushSelectRequests(Array<BuildRequest> requests){
|
||||
for(BuildRequest req : requests){
|
||||
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
|
||||
BuildRequest other = getRequest(req.x, req.y, req.block.size, null);
|
||||
if(other == null){
|
||||
selectRequests.add(req.copy());
|
||||
}else if(!other.breaking && other.x == req.x && other.y == req.y && other.block.size == req.block.size){
|
||||
selectRequests.remove(other);
|
||||
selectRequests.add(req.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushRequests(Array<BuildRequest> requests){
|
||||
for(BuildRequest req : requests){
|
||||
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
|
||||
BuildRequest copy = req.copy();
|
||||
if(copy.hasConfig && copy.block.posConfig){
|
||||
copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY);
|
||||
}
|
||||
player.addBuildRequest(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawRequest(BuildRequest request){
|
||||
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation));
|
||||
}
|
||||
|
||||
/** Draws a placement icon for a specific block. */
|
||||
protected void drawRequest(int x, int y, Block block, int rotation){
|
||||
brequest.set(x, y, rotation, block);
|
||||
brequest.animScale = 1f;
|
||||
block.drawRequest(brequest, allRequests(), validPlace(x, y, block, rotation));
|
||||
}
|
||||
|
||||
/** Remove everything from the queue in a selection. */
|
||||
protected void removeSelection(int x1, int y1, int x2, int y2){
|
||||
removeSelection(x1, y1, x2, y2, false);
|
||||
}
|
||||
|
||||
/** Remove everything from the queue in a selection. */
|
||||
protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush){
|
||||
NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
|
||||
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
|
||||
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
|
||||
int wx = x1 + x * Mathf.sign(x2 - x1);
|
||||
int wy = y1 + y * Mathf.sign(y2 - y1);
|
||||
|
||||
Tile tile = world.ltile(wx, wy);
|
||||
|
||||
if(tile == null) continue;
|
||||
|
||||
if(!flush){
|
||||
tryBreakBlock(wx, wy);
|
||||
}else if(validBreak(tile.x, tile.y) && !selectRequests.contains(r -> r.tile() != null && r.tile().link() == tile)){
|
||||
selectRequests.add(new BuildRequest(tile.x, tile.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//remove build requests
|
||||
Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize);
|
||||
|
||||
Iterator<BuildRequest> it = player.buildQueue().iterator();
|
||||
while(it.hasNext()){
|
||||
BuildRequest req = it.next();
|
||||
if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
it = selectRequests.iterator();
|
||||
while(it.hasNext()){
|
||||
BuildRequest req = it.next();
|
||||
if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
//remove blocks to rebuild
|
||||
Iterator<BrokenBlock> broken = state.teams.get(player.getTeam()).brokenBlocks.iterator();
|
||||
while(broken.hasNext()){
|
||||
BrokenBlock req = broken.next();
|
||||
Block block = content.block(req.block);
|
||||
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
broken.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateLine(int x1, int y1, int x2, int y2){
|
||||
lineRequests.clear();
|
||||
iterateLine(x1, y1, x2, y2, l -> {
|
||||
rotation = l.rotation;
|
||||
BuildRequest req = new BuildRequest(l.x, l.y, l.rotation, block);
|
||||
req.animScale = 1f;
|
||||
lineRequests.add(req);
|
||||
});
|
||||
|
||||
if(Core.settings.getBool("blockreplace")){
|
||||
lineRequests.each(req -> {
|
||||
Block replace = req.block.getReplacement(req, lineRequests);
|
||||
if(replace.unlockedCur()){
|
||||
req.block = replace;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateLine(int x1, int y1){
|
||||
updateLine(x1, y1, tileX(getMouseX()), tileY(getMouseY()));
|
||||
}
|
||||
|
||||
/** Handles tile tap events that are not platform specific. */
|
||||
boolean tileTapped(Tile tile){
|
||||
tile = tile.link();
|
||||
|
||||
boolean consumed = false, showedInventory = false;
|
||||
|
||||
//check if tapped block is configurable
|
||||
if(tile.block().configurable && tile.interactable(player.getTeam())){
|
||||
consumed = true;
|
||||
if(((!frag.config.isShown() && tile.block().shouldShowConfigure(tile, player)) //if the config fragment is hidden, show
|
||||
//alternatively, the current selected block can 'agree' to switch config tiles
|
||||
|| (frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)))){
|
||||
Sounds.click.at(tile);
|
||||
frag.config.showConfig(tile);
|
||||
}
|
||||
//otherwise...
|
||||
}else if(!frag.config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor
|
||||
//then, if it's shown and the current block 'agrees' to hide, hide it.
|
||||
if(frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)){
|
||||
consumed = true;
|
||||
frag.config.hideConfig();
|
||||
}
|
||||
|
||||
if(frag.config.isShown()){
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//call tapped event
|
||||
if(!consumed && tile.interactable(player.getTeam())){
|
||||
Call.onTileTapped(player, tile);
|
||||
}
|
||||
|
||||
//consume tap event if necessary
|
||||
if(tile.interactable(player.getTeam()) && tile.block().consumesTap){
|
||||
consumed = true;
|
||||
}else if(tile.interactable(player.getTeam()) && tile.block().synthetic() && !consumed){
|
||||
if(tile.block().hasItems && tile.entity.items.total() > 0){
|
||||
frag.inv.showFor(tile);
|
||||
consumed = true;
|
||||
showedInventory = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!showedInventory){
|
||||
frag.inv.hide();
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** Tries to select the player to drop off items, returns true if successful. */
|
||||
boolean tryTapPlayer(float x, float y){
|
||||
if(canTapPlayer(x, y)){
|
||||
droppingItem = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean canTapPlayer(float x, float y){
|
||||
return Mathf.dst(x, y, player.x, player.y) <= playerSelectRange && player.item().amount > 0;
|
||||
}
|
||||
|
||||
/** Tries to begin mining a tile, returns true if successful. */
|
||||
boolean tryBeginMine(Tile tile){
|
||||
if(canMine(tile)){
|
||||
//if a block is clicked twice, reset it
|
||||
player.setMineTile(player.getMineTile() == tile ? null : tile);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean canMine(Tile tile){
|
||||
return !Core.scene.hasMouse()
|
||||
&& tile.drop() != null && tile.drop().hardness <= player.mech.drillPower
|
||||
&& !(tile.floor().playerUnmineable && tile.overlay().itemDrop == null)
|
||||
&& player.acceptsItem(tile.drop())
|
||||
&& tile.block() == Blocks.air && player.dst(tile.worldx(), tile.worldy()) <= Player.mineDistance;
|
||||
}
|
||||
|
||||
/** Returns the tile at the specified MOUSE coordinates. */
|
||||
Tile tileAt(float x, float y){
|
||||
return world.tile(tileX(x), tileY(y));
|
||||
}
|
||||
|
||||
int rawTileX(){
|
||||
return world.toTile(Core.input.mouseWorld().x);
|
||||
}
|
||||
|
||||
int rawTileY(){
|
||||
return world.toTile(Core.input.mouseWorld().y);
|
||||
}
|
||||
|
||||
int tileX(float cursorX){
|
||||
Vector2 vec = Core.input.mouseWorld(cursorX, 0);
|
||||
if(selectedBlock()){
|
||||
vec.sub(block.offset(), block.offset());
|
||||
}
|
||||
return world.toTile(vec.x);
|
||||
}
|
||||
|
||||
int tileY(float cursorY){
|
||||
Vector2 vec = Core.input.mouseWorld(0, cursorY);
|
||||
if(selectedBlock()){
|
||||
vec.sub(block.offset(), block.offset());
|
||||
}
|
||||
return world.toTile(vec.y);
|
||||
}
|
||||
|
||||
public boolean selectedBlock(){
|
||||
return isPlacing();
|
||||
}
|
||||
|
||||
public boolean isPlacing(){
|
||||
return block != null;
|
||||
}
|
||||
|
||||
public boolean isBreaking(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public float mouseAngle(float x, float y){
|
||||
return Core.input.mouseWorld(getMouseX(), getMouseY()).sub(x, y).angle();
|
||||
}
|
||||
|
||||
public void remove(){
|
||||
Core.input.removeProcessor(this);
|
||||
frag.remove();
|
||||
if(Core.scene != null){
|
||||
Table table = (Table)Core.scene.find("inputTable");
|
||||
if(table != null){
|
||||
table.clear();
|
||||
}
|
||||
}
|
||||
if(detector != null){
|
||||
Core.input.removeProcessor(detector);
|
||||
}
|
||||
if(uiGroup != null){
|
||||
uiGroup.remove();
|
||||
uiGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void add(){
|
||||
Core.input.addProcessor(detector = new GestureDetector(20, 0.5f, 0.4f, 0.15f, this));
|
||||
Core.input.addProcessor(this);
|
||||
if(Core.scene != null){
|
||||
Table table = (Table)Core.scene.find("inputTable");
|
||||
if(table != null){
|
||||
table.clear();
|
||||
buildPlacementUI(table);
|
||||
}
|
||||
|
||||
uiGroup = new WidgetGroup();
|
||||
uiGroup.touchable(Touchable.childrenOnly);
|
||||
uiGroup.setFillParent(true);
|
||||
ui.hudGroup.addChild(uiGroup);
|
||||
buildUI(uiGroup);
|
||||
|
||||
frag.add();
|
||||
}
|
||||
|
||||
if(player != null){
|
||||
player.isBuilding = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canShoot(){
|
||||
return block == null && !Core.scene.hasMouse() && !onConfigurable() && !isDroppingItem();
|
||||
}
|
||||
|
||||
public boolean onConfigurable(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDroppingItem(){
|
||||
return droppingItem;
|
||||
}
|
||||
|
||||
public void tryDropItems(Tile tile, float x, float y){
|
||||
if(!droppingItem || player.item().amount <= 0 || canTapPlayer(x, y) || state.isPaused() || !player.timer.check(Player.timerTransfer, 40)){
|
||||
droppingItem = false;
|
||||
return;
|
||||
}
|
||||
|
||||
droppingItem = false;
|
||||
|
||||
ItemStack stack = player.item();
|
||||
|
||||
if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.interactable(player.getTeam()) && tile.block().hasItems && player.item().amount > 0 && !player.isTransferring && tile.interactable(player.getTeam())){
|
||||
Call.transferInventory(player, tile);
|
||||
}else{
|
||||
Call.dropItem(player.angleTo(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
public void tryPlaceBlock(int x, int y){
|
||||
if(block != null && validPlace(x, y, block, rotation)){
|
||||
placeBlock(x, y, block, rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public void tryBreakBlock(int x, int y){
|
||||
if(validBreak(x, y)){
|
||||
breakBlock(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validPlace(int x, int y, Block type, int rotation){
|
||||
return validPlace(x, y, type, rotation, null);
|
||||
}
|
||||
|
||||
public boolean validPlace(int x, int y, Block type, int rotation, BuildRequest ignore){
|
||||
for(BuildRequest req : player.buildQueue()){
|
||||
if(req != ignore
|
||||
&& !req.breaking
|
||||
&& req.block.bounds(req.x, req.y, Tmp.r1).overlaps(type.bounds(x, y, Tmp.r2))
|
||||
&& !(type.canReplace(req.block) && Tmp.r1.equals(Tmp.r2))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Build.validPlace(player.getTeam(), x, y, type, rotation);
|
||||
}
|
||||
|
||||
public boolean validBreak(int x, int y){
|
||||
return Build.validBreak(player.getTeam(), x, y);
|
||||
}
|
||||
|
||||
public void placeBlock(int x, int y, Block block, int rotation){
|
||||
BuildRequest req = getRequest(x, y);
|
||||
if(req != null){
|
||||
player.buildQueue().remove(req);
|
||||
}
|
||||
player.addBuildRequest(new BuildRequest(x, y, rotation, block));
|
||||
}
|
||||
|
||||
public void breakBlock(int x, int y){
|
||||
Tile tile = world.ltile(x, y);
|
||||
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
|
||||
}
|
||||
|
||||
public void drawArrow(Block block, int x, int y, int rotation){
|
||||
drawArrow(block, x, y, rotation, validPlace(x, y, block, rotation));
|
||||
}
|
||||
|
||||
public void drawArrow(Block block, int x, int y, int rotation, boolean valid){
|
||||
Draw.color(!valid ? Pal.removeBack : Pal.accentBack);
|
||||
Draw.rect(Core.atlas.find("place-arrow"),
|
||||
x * tilesize + block.offset(),
|
||||
y * tilesize + block.offset() - 1,
|
||||
Core.atlas.find("place-arrow").getWidth() * Draw.scl,
|
||||
Core.atlas.find("place-arrow").getHeight() * Draw.scl, rotation * 90 - 90);
|
||||
|
||||
Draw.color(!valid ? Pal.remove : Pal.accent);
|
||||
Draw.rect(Core.atlas.find("place-arrow"),
|
||||
x * tilesize + block.offset(),
|
||||
y * tilesize + block.offset(),
|
||||
Core.atlas.find("place-arrow").getWidth() * Draw.scl,
|
||||
Core.atlas.find("place-arrow").getHeight() * Draw.scl, rotation * 90 - 90);
|
||||
}
|
||||
|
||||
void iterateLine(int startX, int startY, int endX, int endY, Cons<PlaceLine> cons){
|
||||
Array<Point2> points;
|
||||
boolean diagonal = Core.input.keyDown(Binding.diagonal_placement);
|
||||
|
||||
if(Core.settings.getBool("swapdiagonal") && mobile){
|
||||
diagonal = !diagonal;
|
||||
}
|
||||
|
||||
if(block instanceof PowerNode){
|
||||
diagonal = !diagonal;
|
||||
}
|
||||
|
||||
if(diagonal){
|
||||
points = Placement.pathfindLine(block != null && block.conveyorPlacement, startX, startY, endX, endY);
|
||||
}else{
|
||||
points = Placement.normalizeLine(startX, startY, endX, endY);
|
||||
}
|
||||
|
||||
if(block instanceof PowerNode){
|
||||
Array<Point2> skip = new Array<>();
|
||||
|
||||
for(int i = 1; i < points.size; i++){
|
||||
int overlaps = 0;
|
||||
Point2 point = points.get(i);
|
||||
|
||||
//check with how many powernodes the *next* tile will overlap
|
||||
for(int j = 0; j < i; j++){
|
||||
if(!skip.contains(points.get(j)) && ((PowerNode)block).overlaps(world.ltile(point.x, point.y), world.ltile(points.get(j).x, points.get(j).y))){
|
||||
overlaps++;
|
||||
}
|
||||
}
|
||||
|
||||
//if it's more than one, it can bridge the gap
|
||||
if(overlaps > 1){
|
||||
skip.add(points.get(i-1));
|
||||
}
|
||||
}
|
||||
//remove skipped points
|
||||
points.removeAll(skip);
|
||||
}
|
||||
|
||||
float angle = Angles.angle(startX, startY, endX, endY);
|
||||
int baseRotation = rotation;
|
||||
if(!overrideLineRotation || diagonal){
|
||||
baseRotation = (startX == endX && startY == endY) ? rotation : ((int)((angle + 45) / 90f)) % 4;
|
||||
}
|
||||
|
||||
Tmp.r3.set(-1, -1, 0, 0);
|
||||
|
||||
for(int i = 0; i < points.size; i++){
|
||||
Point2 point = points.get(i);
|
||||
|
||||
if(block != null && Tmp.r2.setSize(block.size * tilesize).setCenter(point.x * tilesize + block.offset(), point.y * tilesize + block.offset()).overlaps(Tmp.r3)){
|
||||
continue;
|
||||
}
|
||||
|
||||
Point2 next = i == points.size - 1 ? null : points.get(i + 1);
|
||||
line.x = point.x;
|
||||
line.y = point.y;
|
||||
if(!overrideLineRotation || diagonal){
|
||||
line.rotation = next != null ? Tile.relativeTo(point.x, point.y, next.x, next.y) : baseRotation;
|
||||
}else{
|
||||
line.rotation = rotation;
|
||||
}
|
||||
line.last = next == null;
|
||||
cons.get(line);
|
||||
|
||||
Tmp.r3.setSize(block.size * tilesize).setCenter(point.x * tilesize + block.offset(), point.y * tilesize + block.offset());
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceLine{
|
||||
public int x, y, rotation;
|
||||
public boolean last;
|
||||
}
|
||||
}
|
||||
770
core/src/mindustry/input/MobileInput.java
Normal file
770
core/src/mindustry/input/MobileInput.java
Normal file
@@ -0,0 +1,770 @@
|
||||
package mindustry.input;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.GestureDetector.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.traits.BuilderTrait.*;
|
||||
import mindustry.entities.traits.*;
|
||||
import mindustry.entities.type.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.input.PlaceMode.*;
|
||||
|
||||
public class MobileInput extends InputHandler implements GestureListener{
|
||||
/** Maximum speed the player can pan. */
|
||||
private static final float maxPanSpeed = 1.3f;
|
||||
/** Distance to edge of screen to start panning. */
|
||||
private final float edgePan = Scl.scl(60f);
|
||||
|
||||
//gesture data
|
||||
private Vector2 vector = new Vector2();
|
||||
private float lastZoom = -1;
|
||||
|
||||
/** Position where the player started dragging a line. */
|
||||
private int lineStartX, lineStartY, lastLineX, lastLineY;
|
||||
|
||||
/** Animation scale for line. */
|
||||
private float lineScale;
|
||||
/** Animation data for crosshair. */
|
||||
private float crosshairScale;
|
||||
private TargetTrait lastTarget;
|
||||
/** Used for shifting build requests. */
|
||||
private float shiftDeltaX, shiftDeltaY;
|
||||
|
||||
/** Place requests to be removed. */
|
||||
private Array<BuildRequest> removals = new Array<>();
|
||||
/** Whether or not the player is currently shifting all placed tiles. */
|
||||
private boolean selecting;
|
||||
/** Whether the player is currently in line-place mode. */
|
||||
private boolean lineMode, schematicMode;
|
||||
/** Current place mode. */
|
||||
private PlaceMode mode = none;
|
||||
/** Whether no recipe was available when switching to break mode. */
|
||||
private Block lastBlock;
|
||||
/** Last placed request. Used for drawing block overlay. */
|
||||
private BuildRequest lastPlaced;
|
||||
/** Down tracking for panning.*/
|
||||
private boolean down = false;
|
||||
|
||||
//region utility methods
|
||||
|
||||
/** Check and assign targets for a specific position. */
|
||||
void checkTargets(float x, float y){
|
||||
Unit unit = Units.closestEnemy(player.getTeam(), x, y, 20f, u -> !u.isDead());
|
||||
|
||||
if(unit != null){
|
||||
player.setMineTile(null);
|
||||
player.target = unit;
|
||||
}else{
|
||||
Tile tile = world.ltileWorld(x, y);
|
||||
|
||||
if(tile != null && tile.synthetic() && state.teams.areEnemies(player.getTeam(), tile.getTeam())){
|
||||
TileEntity entity = tile.entity;
|
||||
player.setMineTile(null);
|
||||
player.target = entity;
|
||||
}else if(tile != null && player.mech.canHeal && tile.entity != null && tile.getTeam() == player.getTeam() && tile.entity.damaged()){
|
||||
player.setMineTile(null);
|
||||
player.target = tile.entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether this tile is in the list of requests, or at least colliding with one. */
|
||||
boolean hasRequest(Tile tile){
|
||||
return getRequest(tile) != null;
|
||||
}
|
||||
|
||||
/** Returns whether this block overlaps any selection requests. */
|
||||
boolean checkOverlapPlacement(int x, int y, Block block){
|
||||
r2.setSize(block.size * tilesize);
|
||||
r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset());
|
||||
|
||||
for(BuildRequest req : selectRequests){
|
||||
Tile other = req.tile();
|
||||
|
||||
if(other == null || req.breaking) continue;
|
||||
|
||||
r1.setSize(req.block.size * tilesize);
|
||||
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
|
||||
|
||||
if(r2.overlaps(r1)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(BuildRequest req : player.buildQueue()){
|
||||
Tile other = world.tile(req.x, req.y);
|
||||
|
||||
if(other == null || req.breaking) continue;
|
||||
|
||||
r1.setSize(req.block.size * tilesize);
|
||||
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
|
||||
|
||||
if(r2.overlaps(r1)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the selection request that overlaps this tile, or null. */
|
||||
BuildRequest getRequest(Tile tile){
|
||||
r2.setSize(tilesize);
|
||||
r2.setCenter(tile.worldx(), tile.worldy());
|
||||
|
||||
for(BuildRequest req : selectRequests){
|
||||
Tile other = req.tile();
|
||||
|
||||
if(other == null) continue;
|
||||
|
||||
if(!req.breaking){
|
||||
r1.setSize(req.block.size * tilesize);
|
||||
r1.setCenter(other.worldx() + req.block.offset(), other.worldy() + req.block.offset());
|
||||
|
||||
if(r2.overlaps(r1)){
|
||||
return req;
|
||||
}
|
||||
}else{
|
||||
r1.setSize(other.block().size * tilesize);
|
||||
r1.setCenter(other.worldx() + other.block().offset(), other.worldy() + other.block().offset());
|
||||
|
||||
if(r2.overlaps(r1)){
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeRequest(BuildRequest request){
|
||||
selectRequests.removeValue(request, true);
|
||||
if(!request.breaking){
|
||||
removals.add(request);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLinePlacing(){
|
||||
return mode == placing && lineMode && Mathf.dst(lineStartX * tilesize, lineStartY * tilesize, Core.input.mouseWorld().x, Core.input.mouseWorld().y) >= 3 * tilesize;
|
||||
}
|
||||
|
||||
boolean isAreaBreaking(){
|
||||
return mode == breaking && lineMode && Mathf.dst(lineStartX * tilesize, lineStartY * tilesize, Core.input.mouseWorld().x, Core.input.mouseWorld().y) >= 2 * tilesize;
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region UI and drawing
|
||||
|
||||
@Override
|
||||
public void buildPlacementUI(Table table){
|
||||
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
|
||||
table.row();
|
||||
table.left().margin(0f).defaults().size(48f);
|
||||
|
||||
table.addImageButton(Icon.breakSmall, Styles.clearTogglePartiali, () -> {
|
||||
mode = mode == breaking ? block == null ? none : placing : breaking;
|
||||
lastBlock = block;
|
||||
}).update(l -> l.setChecked(mode == breaking)).name("breakmode");
|
||||
|
||||
//diagonal swap button
|
||||
table.addImageButton(Icon.diagonalSmall, Styles.clearTogglePartiali, () -> {
|
||||
Core.settings.put("swapdiagonal", !Core.settings.getBool("swapdiagonal"));
|
||||
Core.settings.save();
|
||||
}).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal")));
|
||||
|
||||
//rotate button
|
||||
table.addImageButton(Icon.arrowSmall, Styles.clearTogglePartiali, () -> {
|
||||
if(block != null && block.rotate){
|
||||
rotation = Mathf.mod(rotation + 1, 4);
|
||||
}else{
|
||||
schematicMode = !schematicMode;
|
||||
if(schematicMode){
|
||||
block = null;
|
||||
mode = none;
|
||||
}
|
||||
}
|
||||
}).update(i -> {
|
||||
boolean arrow = block != null && block.rotate;
|
||||
|
||||
i.getImage().setRotationOrigin(!arrow ? 0 : rotation * 90, Align.center);
|
||||
i.getStyle().imageUp = arrow ? Icon.arrowSmall : Icon.pasteSmall;
|
||||
i.setChecked(!arrow && schematicMode);
|
||||
});
|
||||
|
||||
//confirm button
|
||||
table.addImageButton(Icon.checkSmall, Styles.clearPartiali, () -> {
|
||||
for(BuildRequest request : selectRequests){
|
||||
Tile tile = request.tile();
|
||||
|
||||
//actually place/break all selected blocks
|
||||
if(tile != null){
|
||||
if(!request.breaking){
|
||||
if(validPlace(request.x, request.y, request.block, request.rotation)){
|
||||
BuildRequest other = getRequest(request.x, request.y, request.block.size, null);
|
||||
BuildRequest copy = request.copy();
|
||||
|
||||
if(copy.hasConfig && copy.block.posConfig){
|
||||
copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY);
|
||||
}
|
||||
|
||||
if(other == null){
|
||||
player.addBuildRequest(copy);
|
||||
}else if(!other.breaking && other.x == request.x && other.y == request.y && other.block.size == request.block.size){
|
||||
player.buildQueue().remove(other);
|
||||
player.addBuildRequest(copy);
|
||||
}
|
||||
}
|
||||
|
||||
rotation = request.rotation;
|
||||
}else{
|
||||
tryBreakBlock(tile.x, tile.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//move all current requests to removal array so they fade out
|
||||
removals.addAll(selectRequests.select(r -> !r.breaking));
|
||||
selectRequests.clear();
|
||||
selecting = false;
|
||||
}).visible(() -> !selectRequests.isEmpty()).name("confirmplace");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildUI(Group group){
|
||||
Boolp schem = () -> lastSchematic != null && !selectRequests.isEmpty();
|
||||
|
||||
group.fill(t -> {
|
||||
t.bottom().left().visible(() -> (player.isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
|
||||
t.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
|
||||
player.clearBuilding();
|
||||
selectRequests.clear();
|
||||
mode = none;
|
||||
block = null;
|
||||
}).width(155f);
|
||||
});
|
||||
|
||||
group.fill(t -> {
|
||||
t.visible(schem);
|
||||
t.bottom().left();
|
||||
t.table(Tex.pane, b -> {
|
||||
b.defaults().size(50f);
|
||||
|
||||
ImageButtonStyle style = Styles.clearPartiali;
|
||||
|
||||
b.addImageButton(Icon.floppySmall, style, this::showSchematicSave).disabled(f -> lastSchematic == null || lastSchematic.file != null);
|
||||
b.addImageButton(Icon.cancelSmall, style, () -> {
|
||||
selectRequests.clear();
|
||||
});
|
||||
b.row();
|
||||
b.addImageButton(Icon.flipSmall, style, () -> flipRequests(selectRequests, true));
|
||||
b.addImageButton(Icon.flipSmall, style, () -> flipRequests(selectRequests, false)).update(i -> i.getImage().setRotationOrigin(90f, Align.center));
|
||||
b.row();
|
||||
b.addImageButton(Icon.rotateSmall, style, () -> rotateRequests(selectRequests, 1));
|
||||
|
||||
}).margin(4f);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int schemOriginX(){
|
||||
Tmp.v1.setZero();
|
||||
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
||||
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int schemOriginY(){
|
||||
Tmp.v1.setZero();
|
||||
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
||||
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlacing(){
|
||||
return super.isPlacing() && mode == placing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBottom(){
|
||||
Lines.stroke(1f);
|
||||
|
||||
//draw removals
|
||||
for(BuildRequest request : removals){
|
||||
Tile tile = request.tile();
|
||||
|
||||
if(tile == null) continue;
|
||||
|
||||
request.animScale = Mathf.lerpDelta(request.animScale, 0f, 0.2f);
|
||||
|
||||
if(request.breaking){
|
||||
drawSelected(request.x, request.y, tile.block(), Pal.remove);
|
||||
}else{
|
||||
request.block.drawRequest(request, allRequests(), true);
|
||||
}
|
||||
}
|
||||
|
||||
//draw list of requests
|
||||
for(BuildRequest request : selectRequests){
|
||||
Tile tile = request.tile();
|
||||
|
||||
if(tile == null) continue;
|
||||
|
||||
if((!request.breaking && validPlace(tile.x, tile.y, request.block, request.rotation))
|
||||
|| (request.breaking && validBreak(tile.x, tile.y))){
|
||||
request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.2f);
|
||||
}else{
|
||||
request.animScale = Mathf.lerpDelta(request.animScale, 0.6f, 0.1f);
|
||||
}
|
||||
|
||||
Tmp.c1.set(Draw.getMixColor());
|
||||
|
||||
if(!request.breaking && request == lastPlaced && request.block != null){
|
||||
Draw.mixcol();
|
||||
if(request.block.rotate) drawArrow(request.block, tile.x, tile.y, request.rotation);
|
||||
}
|
||||
|
||||
//Draw.mixcol(Tmp.c1, 1f);
|
||||
Draw.reset();
|
||||
drawRequest(request);
|
||||
|
||||
//draw last placed request
|
||||
if(!request.breaking && request == lastPlaced && request.block != null){
|
||||
Draw.mixcol();
|
||||
request.block.drawPlace(tile.x, tile.y, rotation, validPlace(tile.x, tile.y, request.block, rotation));
|
||||
}
|
||||
}
|
||||
|
||||
Draw.mixcol();
|
||||
Draw.color(Pal.accent);
|
||||
|
||||
//Draw lines
|
||||
if(lineMode){
|
||||
int tileX = tileX(Core.input.mouseX());
|
||||
int tileY = tileY(Core.input.mouseY());
|
||||
|
||||
if(mode == placing && block != null){
|
||||
//draw placing
|
||||
for(int i = 0; i < lineRequests.size; i++){
|
||||
BuildRequest req = lineRequests.get(i);
|
||||
if(i == lineRequests.size - 1 && req.block.rotate){
|
||||
drawArrow(block, req.x, req.y, req.rotation);
|
||||
}
|
||||
|
||||
BuildRequest request = lineRequests.get(i);
|
||||
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation) && getRequest(req.x, request.y, request.block.size, null) == null);
|
||||
drawSelected(request.x, request.y, request.block, Pal.accent);
|
||||
}
|
||||
}else if(mode == breaking){
|
||||
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
|
||||
}
|
||||
}
|
||||
|
||||
TargetTrait target = player.target;
|
||||
|
||||
//draw targeting crosshair
|
||||
if(target != null && !state.isEditor()){
|
||||
if(target != lastTarget){
|
||||
crosshairScale = 0f;
|
||||
lastTarget = target;
|
||||
}
|
||||
|
||||
crosshairScale = Mathf.lerpDelta(crosshairScale, 1f, 0.2f);
|
||||
|
||||
Draw.color(Pal.remove);
|
||||
Lines.stroke(1f);
|
||||
|
||||
float radius = Interpolation.swingIn.apply(crosshairScale);
|
||||
|
||||
Lines.poly(target.getX(), target.getY(), 4, 7f * radius, Time.time() * 1.5f);
|
||||
Lines.spikes(target.getX(), target.getY(), 3f * radius, 6f * radius, 4, Time.time() * 1.5f);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTop(){
|
||||
|
||||
//draw schematic selection
|
||||
if(mode == schematicSelect){
|
||||
drawSelection(lineStartX, lineStartY, lastLineX, lastLineY, Vars.maxSchematicSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawRequest(BuildRequest request){
|
||||
if(request.tile() == null) return;
|
||||
brequest.animScale = request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.1f);
|
||||
|
||||
if(request.breaking){
|
||||
drawSelected(request.x, request.y, request.tile().block(), Pal.remove);
|
||||
}else{
|
||||
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation));
|
||||
drawSelected(request.x, request.y, request.block, Pal.accent);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region input events
|
||||
|
||||
@Override
|
||||
public boolean isBreaking(){
|
||||
return mode == breaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useSchematic(Schematic schem){
|
||||
selectRequests.clear();
|
||||
selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y)));
|
||||
lastSchematic = schem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
|
||||
if(state.is(State.menu) || player.isDead()) return false;
|
||||
|
||||
down = true;
|
||||
|
||||
//get tile on cursor
|
||||
Tile cursor = tileAt(screenX, screenY);
|
||||
|
||||
float worldx = Core.input.mouseWorld(screenX, screenY).x, worldy = Core.input.mouseWorld(screenX, screenY).y;
|
||||
|
||||
//ignore off-screen taps
|
||||
if(cursor == null || Core.scene.hasMouse(screenX, screenY)) return false;
|
||||
|
||||
//only begin selecting if the tapped block is a request
|
||||
selecting = hasRequest(cursor);
|
||||
|
||||
//call tap events
|
||||
if(pointer == 0 && !selecting){
|
||||
if(schematicMode && block == null){
|
||||
mode = schematicSelect;
|
||||
//engage schematic selection mode
|
||||
int tileX = tileX(screenX);
|
||||
int tileY = tileY(screenY);
|
||||
lineStartX = tileX;
|
||||
lineStartY = tileY;
|
||||
lastLineX = tileX;
|
||||
lastLineY = tileY;
|
||||
}else if(!tryTapPlayer(worldx, worldy) && Core.settings.getBool("keyboard")){
|
||||
//shoot on touch down when in keyboard mode
|
||||
player.isShooting = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean touchUp(int screenX, int screenY, int pointer, KeyCode button){
|
||||
lastZoom = renderer.getScale();
|
||||
|
||||
if(!Core.input.isTouched()){
|
||||
down = false;
|
||||
}
|
||||
|
||||
selecting = false;
|
||||
|
||||
//place down a line if in line mode
|
||||
if(lineMode){
|
||||
int tileX = tileX(screenX);
|
||||
int tileY = tileY(screenY);
|
||||
|
||||
if(mode == placing && isPlacing()){
|
||||
flushSelectRequests(lineRequests);
|
||||
Events.fire(new LineConfirmEvent());
|
||||
}else if(mode == breaking){
|
||||
removeSelection(lineStartX, lineStartY, tileX, tileY, true);
|
||||
}
|
||||
|
||||
lineMode = false;
|
||||
}else if(mode == schematicSelect){
|
||||
selectRequests.clear();
|
||||
lastSchematic = schematics.create(lineStartX, lineStartY, lastLineX, lastLineY);
|
||||
useSchematic(lastSchematic);
|
||||
if(selectRequests.isEmpty()){
|
||||
lastSchematic = null;
|
||||
}
|
||||
schematicMode = false;
|
||||
mode = none;
|
||||
}else{
|
||||
Tile tile = tileAt(screenX, screenY);
|
||||
|
||||
if(tile == null) return false;
|
||||
|
||||
tryDropItems(tile.link(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean longPress(float x, float y){
|
||||
if(state.is(State.menu) || mode == none || player.isDead()) return false;
|
||||
|
||||
//get tile on cursor
|
||||
Tile cursor = tileAt(x, y);
|
||||
|
||||
//ignore off-screen taps
|
||||
if(cursor == null || Core.scene.hasMouse(x, y) || schematicMode) return false;
|
||||
|
||||
//remove request if it's there
|
||||
//long pressing enables line mode otherwise
|
||||
lineStartX = cursor.x;
|
||||
lineStartY = cursor.y;
|
||||
lastLineX = cursor.x;
|
||||
lastLineY = cursor.y;
|
||||
lineMode = true;
|
||||
|
||||
if(mode == breaking){
|
||||
Effects.effect(Fx.tapBlock, cursor.worldx(), cursor.worldy(), 1f);
|
||||
}else if(block != null){
|
||||
updateLine(lineStartX, lineStartY, cursor.x, cursor.y);
|
||||
Effects.effect(Fx.tapBlock, cursor.worldx() + block.offset(), cursor.worldy() + block.offset(), block.size);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tap(float x, float y, int count, KeyCode button){
|
||||
if(state.is(State.menu) || lineMode) return false;
|
||||
|
||||
float worldx = Core.input.mouseWorld(x, y).x, worldy = Core.input.mouseWorld(x, y).y;
|
||||
|
||||
//get tile on cursor
|
||||
Tile cursor = tileAt(x, y);
|
||||
|
||||
//ignore off-screen taps
|
||||
if(cursor == null || Core.scene.hasMouse(x, y)) return false;
|
||||
|
||||
checkTargets(worldx, worldy);
|
||||
|
||||
//remove if request present
|
||||
if(hasRequest(cursor)){
|
||||
removeRequest(getRequest(cursor));
|
||||
}else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){
|
||||
//add to selection queue if it's a valid place position
|
||||
selectRequests.add(lastPlaced = new BuildRequest(cursor.x, cursor.y, rotation, block));
|
||||
}else if(mode == breaking && validBreak(cursor.link().x, cursor.link().y) && !hasRequest(cursor.link())){
|
||||
//add to selection queue if it's a valid BREAK position
|
||||
cursor = cursor.link();
|
||||
selectRequests.add(new BuildRequest(cursor.x, cursor.y));
|
||||
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.link())){
|
||||
tryBeginMine(cursor);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(state.is(State.menu) ){
|
||||
selectRequests.clear();
|
||||
removals.clear();
|
||||
mode = none;
|
||||
}
|
||||
|
||||
if(player.isDead()){
|
||||
mode = none;
|
||||
}
|
||||
|
||||
//zoom camera
|
||||
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
|
||||
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
|
||||
}
|
||||
|
||||
if(!Core.settings.getBool("keyboard")){
|
||||
//move camera around
|
||||
float camSpeed = 6f;
|
||||
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta() * camSpeed));
|
||||
}
|
||||
|
||||
if(Core.settings.getBool("keyboard")){
|
||||
if(Core.input.keyRelease(Binding.select)){
|
||||
player.isShooting = false;
|
||||
}
|
||||
|
||||
if(player.isShooting && !canShoot()){
|
||||
player.isShooting = false;
|
||||
}
|
||||
}
|
||||
|
||||
//reset state when not placing
|
||||
if(mode == none){
|
||||
lineMode = false;
|
||||
}
|
||||
|
||||
if(lineMode && mode == placing && block == null){
|
||||
lineMode = false;
|
||||
}
|
||||
|
||||
//if there is no mode and there's a recipe, switch to placing
|
||||
if(block != null && mode == none){
|
||||
mode = placing;
|
||||
}
|
||||
|
||||
if(block == null && mode == placing){
|
||||
mode = none;
|
||||
}
|
||||
|
||||
//stop schematic when in block mode
|
||||
if(block != null){
|
||||
schematicMode = false;
|
||||
}
|
||||
|
||||
//stop select when not in schematic mode
|
||||
if(!schematicMode && mode == schematicSelect){
|
||||
mode = none;
|
||||
}
|
||||
|
||||
if(mode == schematicSelect){
|
||||
lastLineX = rawTileX();
|
||||
lastLineY = rawTileY();
|
||||
autoPan();
|
||||
}
|
||||
|
||||
//automatically switch to placing after a new recipe is selected
|
||||
if(lastBlock != block && mode == breaking && block != null){
|
||||
mode = placing;
|
||||
lastBlock = block;
|
||||
}
|
||||
|
||||
if(lineMode){
|
||||
lineScale = Mathf.lerpDelta(lineScale, 1f, 0.1f);
|
||||
|
||||
//When in line mode, pan when near screen edges automatically
|
||||
if(Core.input.isTouched(0)){
|
||||
autoPan();
|
||||
}
|
||||
|
||||
int lx = tileX(Core.input.mouseX()), ly = tileY(Core.input.mouseY());
|
||||
|
||||
if((lastLineX != lx || lastLineY != ly) && isPlacing()){
|
||||
lastLineX = lx;
|
||||
lastLineY = ly;
|
||||
updateLine(lineStartX, lineStartY, lx, ly);
|
||||
}
|
||||
}else{
|
||||
lineRequests.clear();
|
||||
lineScale = 0f;
|
||||
}
|
||||
|
||||
//remove place requests that have disappeared
|
||||
for(int i = removals.size - 1; i >= 0; i--){
|
||||
BuildRequest request = removals.get(i);
|
||||
|
||||
if(request.animScale <= 0.0001f){
|
||||
removals.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void autoPan(){
|
||||
float screenX = Core.input.mouseX(), screenY = Core.input.mouseY();
|
||||
|
||||
float panX = 0, panY = 0;
|
||||
|
||||
if(screenX <= edgePan){
|
||||
panX = -(edgePan - screenX);
|
||||
}
|
||||
|
||||
if(screenX >= Core.graphics.getWidth() - edgePan){
|
||||
panX = (screenX - Core.graphics.getWidth()) + edgePan;
|
||||
}
|
||||
|
||||
if(screenY <= edgePan){
|
||||
panY = -(edgePan - screenY);
|
||||
}
|
||||
|
||||
if(screenY >= Core.graphics.getHeight() - edgePan){
|
||||
panY = (screenY - Core.graphics.getHeight()) + edgePan;
|
||||
}
|
||||
|
||||
vector.set(panX, panY).scl((Core.camera.width) / Core.graphics.getWidth());
|
||||
vector.limit(maxPanSpeed);
|
||||
|
||||
//pan view
|
||||
Core.camera.position.x += vector.x;
|
||||
Core.camera.position.y += vector.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pan(float x, float y, float deltaX, float deltaY){
|
||||
if(Core.scene.hasDialog() || Core.settings.getBool("keyboard")) return false;
|
||||
|
||||
float scale = Core.camera.width / Core.graphics.getWidth();
|
||||
deltaX *= scale;
|
||||
deltaY *= scale;
|
||||
|
||||
//can't pan in line mode with one finger or while dropping items!
|
||||
if((lineMode && !Core.input.isTouched(1)) || droppingItem || schematicMode){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!down) return false;
|
||||
|
||||
if(selecting){ //pan all requests
|
||||
shiftDeltaX += deltaX;
|
||||
shiftDeltaY += deltaY;
|
||||
|
||||
int shiftedX = (int)(shiftDeltaX / tilesize);
|
||||
int shiftedY = (int)(shiftDeltaY / tilesize);
|
||||
|
||||
if(Math.abs(shiftedX) > 0 || Math.abs(shiftedY) > 0){
|
||||
for(BuildRequest req : selectRequests){
|
||||
if(req.breaking) continue; //don't shift removal requests
|
||||
req.x += shiftedX;
|
||||
req.y += shiftedY;
|
||||
}
|
||||
|
||||
shiftDeltaX %= tilesize;
|
||||
shiftDeltaY %= tilesize;
|
||||
}
|
||||
}else{
|
||||
//pan player
|
||||
Core.camera.position.x -= deltaX;
|
||||
Core.camera.position.y -= deltaY;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean panStop(float x, float y, int pointer, KeyCode button){
|
||||
shiftDeltaX = shiftDeltaY = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean zoom(float initialDistance, float distance){
|
||||
if(Core.settings.getBool("keyboard")) return false;
|
||||
if(lastZoom < 0){
|
||||
lastZoom = renderer.getScale();
|
||||
}
|
||||
|
||||
renderer.setScale(distance / initialDistance * lastZoom);
|
||||
return true;
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
5
core/src/mindustry/input/PlaceMode.java
Normal file
5
core/src/mindustry/input/PlaceMode.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package mindustry.input;
|
||||
|
||||
public enum PlaceMode{
|
||||
none, breaking, placing, schematicSelect
|
||||
}
|
||||
286
core/src/mindustry/input/Placement.java
Normal file
286
core/src/mindustry/input/Placement.java
Normal file
@@ -0,0 +1,286 @@
|
||||
package mindustry.input;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.pooling.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Placement{
|
||||
private static final NormalizeResult result = new NormalizeResult();
|
||||
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
|
||||
private static Bresenham2 bres = new Bresenham2();
|
||||
private static Array<Point2> points = new Array<>();
|
||||
|
||||
//for pathfinding
|
||||
private static IntFloatMap costs = new IntFloatMap();
|
||||
private static IntIntMap parents = new IntIntMap();
|
||||
private static IntSet closed = new IntSet();
|
||||
|
||||
/** Normalize a diagonal line into points. */
|
||||
public static Array<Point2> pathfindLine(boolean conveyors, int startX, int startY, int endX, int endY){
|
||||
Pools.freeAll(points);
|
||||
|
||||
points.clear();
|
||||
if(conveyors && Core.settings.getBool("conveyorpathfinding")){
|
||||
if(astar(startX, startY, endX, endY)){
|
||||
return points;
|
||||
}else{
|
||||
return normalizeLine(startX, startY, endX, endY);
|
||||
}
|
||||
}else{
|
||||
return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points);
|
||||
}
|
||||
}
|
||||
|
||||
/** Normalize two points into one straight line, no diagonals. */
|
||||
public static Array<Point2> normalizeLine(int startX, int startY, int endX, int endY){
|
||||
Pools.freeAll(points);
|
||||
points.clear();
|
||||
if(Math.abs(startX - endX) > Math.abs(startY - endY)){
|
||||
//go width
|
||||
for(int i = 0; i <= Math.abs(startX - endX); i++){
|
||||
points.add(Pools.obtain(Point2.class, Point2::new).set(startX + i * Mathf.sign(endX - startX), startY));
|
||||
}
|
||||
}else{
|
||||
//go height
|
||||
for(int i = 0; i <= Math.abs(startY - endY); i++){
|
||||
points.add(Pools.obtain(Point2.class, Point2::new).set(startX, startY + i * Mathf.sign(endY - startY)));
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private static float tileHeuristic(Tile tile, Tile other){
|
||||
Block block = control.input.block;
|
||||
|
||||
if((!other.block().alwaysReplace && !(block != null && block.canReplace(other.block()))) || other.floor().isDeep()){
|
||||
return 20;
|
||||
}else{
|
||||
if(parents.containsKey(tile.pos())){
|
||||
Tile prev = world.tile(parents.get(tile.pos(), 0));
|
||||
if(tile.relativeTo(prev) != other.relativeTo(tile)){
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static float distanceHeuristic(int x1, int y1, int x2, int y2){
|
||||
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
|
||||
}
|
||||
|
||||
private static boolean validNode(Tile tile, Tile other){
|
||||
Block block = control.input.block;
|
||||
if(block != null && block.canReplace(other.block())){
|
||||
return true;
|
||||
}else{
|
||||
return other.block().alwaysReplace;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean astar(int startX, int startY, int endX, int endY){
|
||||
Tile start = world.tile(startX, startY);
|
||||
Tile end = world.tile(endX, endY);
|
||||
if(start == end || start == null || end == null) return false;
|
||||
|
||||
costs.clear();
|
||||
closed.clear();
|
||||
parents.clear();
|
||||
|
||||
int nodeLimit = 1000;
|
||||
int totalNodes = 0;
|
||||
|
||||
PriorityQueue<Tile> queue = new PriorityQueue<>(10, (a, b) -> Float.compare(costs.get(a.pos(), 0f) + distanceHeuristic(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + distanceHeuristic(b.x, b.y, end.x, end.y)));
|
||||
queue.add(start);
|
||||
boolean found = false;
|
||||
while(!queue.isEmpty() && totalNodes++ < nodeLimit){
|
||||
Tile next = queue.poll();
|
||||
float baseCost = costs.get(next.pos(), 0f);
|
||||
if(next == end){
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
closed.add(Pos.get(next.x, next.y));
|
||||
for(Point2 point : Geometry.d4){
|
||||
int newx = next.x + point.x, newy = next.y + point.y;
|
||||
Tile child = world.tile(newx, newy);
|
||||
if(child != null && validNode(next, child)){
|
||||
if(closed.add(child.pos())){
|
||||
parents.put(child.pos(), next.pos());
|
||||
costs.put(child.pos(), tileHeuristic(next, child) + baseCost);
|
||||
queue.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) return false;
|
||||
int total = 0;
|
||||
|
||||
points.add(Pools.obtain(Point2.class, Point2::new).set(endX, endY));
|
||||
|
||||
Tile current = end;
|
||||
while(current != start && total++ < nodeLimit){
|
||||
if(current == null) return false;
|
||||
int newPos = parents.get(current.pos(), Pos.invalid);
|
||||
|
||||
if(newPos == Pos.invalid) return false;
|
||||
|
||||
points.add(Pools.obtain(Point2.class, Point2::new).set(Pos.x(newPos), Pos.y(newPos)));
|
||||
current = world.tile(newPos);
|
||||
}
|
||||
|
||||
points.reverse();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a placement area and returns the result, ready to be used for drawing a rectangle.
|
||||
* Returned x2 and y2 will <i>always</i> be greater than x and y.
|
||||
* @param block block that will be drawn
|
||||
* @param startx starting X coordinate
|
||||
* @param starty starting Y coordinate
|
||||
* @param endx ending X coordinate
|
||||
* @param endy ending Y coordinate
|
||||
* @param snap whether to snap to a line
|
||||
* @param maxLength maximum length of area
|
||||
*/
|
||||
public static NormalizeDrawResult normalizeDrawArea(Block block, int startx, int starty, int endx, int endy, boolean snap, int maxLength, float scaling){
|
||||
normalizeArea(startx, starty, endx, endy, 0, snap, maxLength);
|
||||
|
||||
float offset = block.offset();
|
||||
|
||||
drawResult.x = result.x * tilesize;
|
||||
drawResult.y = result.y * tilesize;
|
||||
drawResult.x2 = result.x2 * tilesize;
|
||||
drawResult.y2 = result.y2 * tilesize;
|
||||
|
||||
drawResult.x -= block.size * scaling * tilesize / 2;
|
||||
drawResult.x2 += block.size * scaling * tilesize / 2;
|
||||
|
||||
|
||||
drawResult.y -= block.size * scaling * tilesize / 2;
|
||||
drawResult.y2 += block.size * scaling * tilesize / 2;
|
||||
|
||||
drawResult.x += offset;
|
||||
drawResult.y += offset;
|
||||
drawResult.x2 += offset;
|
||||
drawResult.y2 += offset;
|
||||
|
||||
return drawResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a placement area and returns the result.
|
||||
* Returned x2 and y2 will <i>always</i> be greater than x and y.
|
||||
* @param tilex starting X coordinate
|
||||
* @param tiley starting Y coordinate
|
||||
* @param endx ending X coordinate
|
||||
* @param endy ending Y coordinate
|
||||
* @param snap whether to snap to a line
|
||||
* @param rotation placement rotation
|
||||
* @param maxLength maximum length of area
|
||||
*/
|
||||
public static NormalizeResult normalizeArea(int tilex, int tiley, int endx, int endy, int rotation, boolean snap, int maxLength){
|
||||
|
||||
if(snap){
|
||||
if(Math.abs(tilex - endx) > Math.abs(tiley - endy)){
|
||||
endy = tiley;
|
||||
}else{
|
||||
endx = tilex;
|
||||
}
|
||||
}
|
||||
|
||||
if(Math.abs(endx - tilex) > maxLength){
|
||||
endx = Mathf.sign(endx - tilex) * maxLength + tilex;
|
||||
}
|
||||
|
||||
if(Math.abs(endy - tiley) > maxLength){
|
||||
endy = Mathf.sign(endy - tiley) * maxLength + tiley;
|
||||
}
|
||||
|
||||
int dx = endx - tilex, dy = endy - tiley;
|
||||
|
||||
if(Math.abs(dx) > Math.abs(dy)){
|
||||
if(dx >= 0){
|
||||
rotation = 0;
|
||||
}else{
|
||||
rotation = 2;
|
||||
}
|
||||
}else if(Math.abs(dx) < Math.abs(dy)){
|
||||
if(dy >= 0){
|
||||
rotation = 1;
|
||||
}else{
|
||||
rotation = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if(endx < tilex){
|
||||
int t = endx;
|
||||
endx = tilex;
|
||||
tilex = t;
|
||||
}
|
||||
if(endy < tiley){
|
||||
int t = endy;
|
||||
endy = tiley;
|
||||
tiley = t;
|
||||
}
|
||||
|
||||
result.x2 = endx;
|
||||
result.y2 = endy;
|
||||
result.x = tilex;
|
||||
result.y = tiley;
|
||||
result.rotation = rotation;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class NormalizeDrawResult{
|
||||
float x, y, x2, y2;
|
||||
}
|
||||
|
||||
public static class NormalizeResult{
|
||||
public int x, y, x2, y2, rotation;
|
||||
|
||||
boolean isX(){
|
||||
return Math.abs(x2 - x) > Math.abs(y2 - y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of greater edge of the selection.
|
||||
*/
|
||||
int getLength(){
|
||||
return Math.max(x2 - x, y2 - y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X position of a specific index along this area as a line.
|
||||
*/
|
||||
int getScaledX(int i){
|
||||
return x + (x2 - x > y2 - y ? i : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Y position of a specific index along this area as a line.
|
||||
*/
|
||||
int getScaledY(int i){
|
||||
return y + (x2 - x > y2 - y ? 0 : i);
|
||||
}
|
||||
}
|
||||
|
||||
public interface DistanceHeuristic{
|
||||
float cost(int x1, int y1, int x2, int y2);
|
||||
}
|
||||
|
||||
public interface TileHueristic{
|
||||
float cost(Tile tile, Tile other);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user