From be8632d94b27198ad14a0f799bf9819bf79cc570 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 1 Jun 2018 18:23:45 -0400 Subject: [PATCH] Added Android automatic shooting --- .../src/io/anuke/mindustry/core/Renderer.java | 69 +----- .../mindustry/entities/BlockBuilder.java | 5 + .../io/anuke/mindustry/entities/Player.java | 197 +++++++++++++----- .../io/anuke/mindustry/entities/Predict.java | 11 +- .../anuke/mindustry/entities/Targetable.java | 18 ++ .../anuke/mindustry/entities/TileEntity.java | 24 ++- .../src/io/anuke/mindustry/entities/Unit.java | 7 +- .../mindustry/entities/UnitInventory.java | 5 + .../io/anuke/mindustry/entities/Units.java | 42 +++- .../mindustry/entities/bullet/Bullet.java | 2 +- .../mindustry/entities/units/BaseUnit.java | 8 +- .../entities/units/FlyingUnitType.java | 8 +- .../entities/units/GroundUnitType.java | 3 +- .../mindustry/entities/units/types/Drone.java | 2 +- .../mindustry/graphics/OverlayRenderer.java | 2 +- .../io/anuke/mindustry/graphics/Trail.java | 53 +++++ .../anuke/mindustry/input/AndroidInput.java | 84 ++++++-- .../anuke/mindustry/input/DesktopInput.java | 2 +- .../anuke/mindustry/input/InputHandler.java | 2 +- core/src/io/anuke/mindustry/type/Weapon.java | 9 +- .../blocks/types/defense/RepairTurret.java | 2 +- .../world/blocks/types/defense/Turret.java | 2 +- .../types/defense/turrets/ItemTurret.java | 26 ++- 23 files changed, 416 insertions(+), 167 deletions(-) create mode 100644 core/src/io/anuke/mindustry/entities/Targetable.java create mode 100644 core/src/io/anuke/mindustry/graphics/Trail.java diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 0a5971d47b..e3fc72a0c5 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -20,7 +20,10 @@ import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.world.Block; -import io.anuke.ucore.core.*; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Settings; import io.anuke.ucore.entities.EffectEntity; import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entity; @@ -33,15 +36,12 @@ import io.anuke.ucore.graphics.Surface; import io.anuke.ucore.modules.RendererModule; import io.anuke.ucore.scene.utils.Cursors; import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Tmp; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.core.Core.batch; import static io.anuke.ucore.core.Core.camera; public class Renderer extends RendererModule{ - private final static float shieldHitDuration = 18f; - public Surface effectSurface; private int targetscale = baseCameraScale; @@ -143,15 +143,18 @@ public class Renderer extends RendererModule{ Graphics.clear(Color.BLACK); }else{ Vector2 position = averagePosition(); + boolean flying = players[0].isFlying(); - setCamera(position.x, position.y); + if(!flying){ + setCamera(position.x, position.y); + } + + clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); float prex = camera.position.x, prey = camera.position.y; updateShake(0.75f); - clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); float deltax = camera.position.x - prex, deltay = camera.position.y - prey; - float lastx = camera.position.x, lasty = camera.position.y; if(snapCamera){ @@ -243,6 +246,7 @@ public class Renderer extends RendererModule{ Entities.drawWith(playerGroup, p -> p.isFlying() == flying && p.team == team, Unit::drawUnder); Shaders.outline.color.set(team.color); + Shaders.mix.color.set(Color.WHITE); Graphics.beginShaders(Shaders.outline); Graphics.shader(Shaders.mix, true); @@ -328,57 +332,6 @@ public class Renderer extends RendererModule{ Draw.color(); } - void drawShield(){ - if(shieldGroup.size() == 0 && shieldDraws.size == 0) return; - - Graphics.surface(renderer.effectSurface, false); - Draw.color(Color.ROYAL); - Entities.draw(shieldGroup); - for(Callable c : shieldDraws){ - c.run(); - } - Draw.reset(); - Graphics.surface(); - - for(int i = 0; i < shieldHits.size / 3; i++){ - float time = shieldHits.get(i * 3 + 2); - - time += Timers.delta() / shieldHitDuration; - shieldHits.set(i * 3 + 2, time); - - if(time >= 1f){ - shieldHits.removeRange(i * 3, i * 3 + 2); - i--; - } - } - - Texture texture = effectSurface.texture(); - Shaders.shield.color.set(Color.SKY); - - Tmp.tr2.setRegion(texture); - Shaders.shield.region = Tmp.tr2; - Shaders.shield.hits = shieldHits; - - if(Shaders.shield.isFallback){ - Draw.color(1f, 1f, 1f, 0.3f); - Shaders.outline.color = Color.SKY; - Shaders.outline.region = Tmp.tr2; - } - - Graphics.end(); - Graphics.shader(Shaders.shield.isFallback ? Shaders.outline : Shaders.shield); - Graphics.setScreen(); - - Core.batch.draw(texture, 0, Gdx.graphics.getHeight(), Gdx.graphics.getWidth(), -Gdx.graphics.getHeight()); - - Graphics.shader(); - Graphics.end(); - Graphics.beginCam(); - - Draw.color(); - shieldDraws.clear(); - } - public BlockRenderer getBlocks() { return blocks; } diff --git a/core/src/io/anuke/mindustry/entities/BlockBuilder.java b/core/src/io/anuke/mindustry/entities/BlockBuilder.java index 3883925be2..e234b139c0 100644 --- a/core/src/io/anuke/mindustry/entities/BlockBuilder.java +++ b/core/src/io/anuke/mindustry/entities/BlockBuilder.java @@ -30,6 +30,11 @@ public interface BlockBuilder { /**Returns the queue for storing build requests.*/ Queue getPlaceQueue(); + /**Return whether this builder's place queue contains items.*/ + default boolean isBuilding(){ + return getPlaceQueue().size != 0; + } + /**If a place request matching this signature is present, it is removed. * Otherwise, a new place request is added to the queue.*/ default void replaceBuilding(int x, int y, int rotation, Recipe recipe){ diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 365f08015e..171a4ec91c 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Pools; import com.badlogic.gdx.utils.Queue; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.content.Weapons; import io.anuke.mindustry.content.fx.ExplosionFx; @@ -14,16 +15,14 @@ import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.entities.effect.DamageArea; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.graphics.Trail; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetEvents; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.types.Floor; import io.anuke.mindustry.world.blocks.types.storage.CoreBlock.CoreEntity; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Inputs; -import io.anuke.ucore.core.Timers; +import io.anuke.ucore.core.*; import io.anuke.ucore.entities.SolidEntity; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Fill; @@ -38,10 +37,14 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.*; public class Player extends Unit implements BlockBuilder { - private static final float speed = 1.1f; + private static final float walkSpeed = 1.1f; + private static final float flySpeed = 0.4f; + private static final float flyMaxSpeed = 3f; private static final float dashSpeed = 1.8f; private static final Vector2 movement = new Vector2(); + //region instance variables, constructor + public String name = "name"; public String uuid; public boolean isAdmin; @@ -51,17 +54,16 @@ public class Player extends Unit implements BlockBuilder { public Weapon weapon = Weapons.blaster; public Mech mech = Mechs.standard; - public float targetAngle = 0f; - public boolean dashing = false; - public int clientid = -1; public int playerIndex = 0; public boolean isLocal = false; public Timer timer = new Timer(4); + public Targetable target; private boolean respawning; private float walktime; private Queue placeQueue = new Queue<>(); + private Trail trail = new Trail(16); public Player(){ hitbox.setSize(5); @@ -71,11 +73,25 @@ public class Player extends Unit implements BlockBuilder { heal(); } + //endregion + + //region unit and event overrides, utility methods + @Override public float getArmor() { return 0f; } + @Override + public boolean acceptsAmmo(Item item) { + return weapon.getAmmoType(item) != null && inventory.canAcceptAmmo(weapon.getAmmoType(item)); + } + + @Override + public void addAmmo(Item item) { + inventory.addAmmo(weapon.getAmmoType(item)); + } + @Override public void onRemoteShoot(BulletType type, float x, float y, float rotation, short data) { Weapon weapon = Upgrade.getByID(Bits.getLeftByte(data)); @@ -131,14 +147,33 @@ public class Player extends Unit implements BlockBuilder { } @Override - public void onRemoteDeath(){ + public void onRemoteDeath() { dead = true; respawning = false; Effects.effect(ExplosionFx.explosion, this); Effects.shake(4f, 5f, this); Effects.sound("die", this); } - + + @Override + public Player set(float x, float y){ + this.x = x; + this.y = y; + if(isFlying() && isLocal){ + Core.camera.position.set(x, y, 0f); + } + return this; + } + + @Override + public Player add(){ + return add(playerGroup); + } + + //endregion + + //region draw methods + @Override public void drawSmooth(){ if((debug && (!showPlayer || !showUI)) || dead) return; @@ -219,6 +254,8 @@ public class Player extends Unit implements BlockBuilder { if(!isShooting() && getCurrentRequest() != null && !dead) { drawBuilding(this); } + + trail.draw(Palette.lighterOrange, Palette.lightishOrange, 5f); } public void drawName(){ @@ -284,7 +321,11 @@ public class Player extends Unit implements BlockBuilder { Draw.reset(); } - + + //endregion + + //region update methods + @Override public void update(){ hitTime = Math.max(0f, hitTime - Timers.delta()); @@ -309,6 +350,11 @@ public class Player extends Unit implements BlockBuilder { updateMech(); } + float wobblyness = 0.6f; + + trail.update(x + Angles.trnsx(rotation + 180f, 6f) + Mathf.range(wobblyness), + y + Angles.trnsy(rotation + 180f, 6f) + Mathf.range(wobblyness)); + if(!isShooting()) { updateBuilding(this); } @@ -317,26 +363,6 @@ public class Player extends Unit implements BlockBuilder { y = Mathf.clamp(y, 0, world.height() * tilesize); } - /**Resets all values of the player.*/ - public void reset(){ - weapon = Weapons.blaster; - team = Team.blue; - inventory.clear(); - upgrades.clear(); - placeQueue.clear(); - - add(); - heal(); - } - - public boolean isShooting(){ - return control.input(playerIndex).canShoot() && control.input(playerIndex).isShooting() && inventory.hasAmmo(); - } - - public Queue getPlaceQueue(){ - return placeQueue; - } - protected void updateMech(){ Tile tile = world.tileWorld(x, y); @@ -347,9 +373,7 @@ public class Player extends Unit implements BlockBuilder { if(ui.chatfrag.chatOpen()) return; - dashing = Inputs.keyDown("dash"); - - float speed = dashing ? (debug ? Player.dashSpeed * 5f : Player.dashSpeed) : Player.speed ; + float speed = Inputs.keyDown("dash") ? (debug ? Player.dashSpeed * 5f : Player.dashSpeed) : Player.walkSpeed; float carrySlowdown = 0.3f; @@ -370,8 +394,12 @@ public class Player extends Unit implements BlockBuilder { boolean shooting = isShooting(); if(shooting){ - weapon.update(this, true); - weapon.update(this, false); + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + float vx = vec.x, vy = vec.y; + + weapon.update(this, true, vx, vy); + weapon.update(this, false, vx, vy); } movement.limit(speed); @@ -396,32 +424,83 @@ public class Player extends Unit implements BlockBuilder { } protected void updateFlying(){ - rotation = Mathf.slerpDelta(rotation, targetAngle, 0.1f); - } - - @Override - public Player set(float x, float y){ - this.x = x; - this.y = y; - if(mobile && !isLocal){ - Core.camera.position.set(x, y, 0f); + if(Units.invalidateTarget(target, this)){ + target = null; + } + + float targetX = Core.camera.position.x, targetY = Core.camera.position.y; + float attractDst = 15f; + + movement.set(targetX - x, targetY - y).limit(flySpeed); + movement.setAngle(Mathf.slerpDelta(movement.angle(), velocity.angle(), 0.05f)); + + if(distanceTo(targetX, targetY) < attractDst){ + movement.setZero(); + } + + velocity.add(movement); + updateVelocityStatus(0.1f, flyMaxSpeed); + + //hovering effect + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); + + if(velocity.len() <= 0.2f){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 1f); + }else{ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len()/10f); + } + + //update shooting if not building and there's ammo left + if(!isBuilding() && inventory.hasAmmo()){ + + //autofire: mobile only! + if(mobile) { + if (target == null) { + target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + } else { + //rotate toward and shoot the target + rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f); + + Vector2 intercept = + Predict.intercept(x, y, target.getX(), target.getY(), target.getVelocity().x - velocity.x, target.getVelocity().y - velocity.y, inventory.getAmmo().bullet.speed); + + weapon.update(this, true, intercept.x, intercept.y); + weapon.update(this, false, intercept.x, intercept.y); + } + }else if(isShooting()){ //desktop shooting, TODO + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + float vx = vec.x, vy = vec.y; + + weapon.update(this, true, vx, vy); + weapon.update(this, false, vx, vy); + } } - return this; } - @Override - public boolean acceptsAmmo(Item item) { - return weapon.getAmmoType(item) != null && inventory.canAcceptAmmo(weapon.getAmmoType(item)); + //endregion + + //region utility methods + + /**Resets all values of the player.*/ + public void reset(){ + weapon = Weapons.blaster; + team = Team.blue; + inventory.clear(); + upgrades.clear(); + placeQueue.clear(); + + add(); + heal(); } - @Override - public void addAmmo(Item item) { - inventory.addAmmo(weapon.getAmmoType(item)); + public boolean isShooting(){ + return control.input(playerIndex).canShoot() && control.input(playerIndex).isShooting() && inventory.hasAmmo(); } - @Override - public Player add(){ - return add(playerGroup); + public Queue getPlaceQueue(){ + return placeQueue; } @Override @@ -429,6 +508,10 @@ public class Player extends Unit implements BlockBuilder { return "Player{" + id + ", mech=" + mech.name + ", local=" + isLocal + ", " + x + ", " + y + "}\n"; } + //endregion + + //region read and write methods + @Override public void writeSave(DataOutputStream stream) throws IOException { stream.writeBoolean(isLocal); @@ -502,7 +585,6 @@ public class Player extends Unit implements BlockBuilder { data.putFloat(rotation); data.putFloat(baseRotation); data.putShort((short)health); - data.put((byte)(dashing ? 1 : 0)); } @Override @@ -515,8 +597,9 @@ public class Player extends Unit implements BlockBuilder { byte dashing = data.get(); this.health = health; - this.dashing = dashing == 1; interpolator.read(this.x, this.y, x, y, rot, baseRot, time); } + + //endregion } diff --git a/core/src/io/anuke/mindustry/entities/Predict.java b/core/src/io/anuke/mindustry/entities/Predict.java index 88dfd8957b..64fc0cffe6 100644 --- a/core/src/io/anuke/mindustry/entities/Predict.java +++ b/core/src/io/anuke/mindustry/entities/Predict.java @@ -8,8 +8,15 @@ public class Predict { private static Vector2 vec = new Vector2(); private static Vector2 vresult = new Vector2(); - /**Returns resulting predicted vector. - * Don't call from multiple threads.*/ + /**Calculates of intercept of a stationary and moving target. Do not call from multiple threads! + * @param srcx X of shooter + * @param srcy Y of shooter + * @param dstx X of target + * @param dsty Y of target + * @param dstvx X velocity of target (subtract shooter X velocity if needed) + * @param dstvy Y velocity of target (subtract shooter Y velocity if needed) + * @param v speed of bullet + * @return the intercept location*/ public static Vector2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float v) { float tx = dstx - srcx, ty = dsty - srcy, diff --git a/core/src/io/anuke/mindustry/entities/Targetable.java b/core/src/io/anuke/mindustry/entities/Targetable.java new file mode 100644 index 0000000000..d9d7c3cdaa --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/Targetable.java @@ -0,0 +1,18 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.game.Team; +import io.anuke.ucore.util.Position; + +/**Base interface for targetable entities.*/ +public interface Targetable extends Position{ + + boolean isDead(); + Team getTeam(); + Vector2 getVelocity(); + + /**Whether this entity is a valid target.*/ + default boolean isValid(){ + return !isDead(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index 5012b2ca08..b2e968b362 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -1,7 +1,9 @@ package io.anuke.mindustry.entities; +import com.badlogic.gdx.math.Vector2; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetEvents; import io.anuke.mindustry.world.Block; @@ -23,19 +25,19 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tileGroup; import static io.anuke.mindustry.Vars.world; -public class TileEntity extends Entity{ +public class TileEntity extends Entity implements Targetable{ public static final float timeToSleep = 60f*4; //4 seconds to fall asleep public static int sleepingEntities = 0; public Tile tile; public Timer timer; public float health; - public boolean dead = false; public PowerModule power; public InventoryModule items; public LiquidModule liquids; + private boolean dead = false; private boolean sleeping; private float sleepTime; @@ -82,7 +84,11 @@ public class TileEntity extends Entity{ sleepingEntities --; } } - + + public boolean isDead() { + return dead; + } + public void write(DataOutputStream stream) throws IOException{} public void read(DataInputStream stream) throws IOException{} @@ -128,7 +134,17 @@ public class TileEntity extends Entity{ NetEvents.handleBlockDamaged(this); } } - + + @Override + public Team getTeam() { + return tile.getTeam(); + } + + @Override + public Vector2 getVelocity() { + return Vector2.Zero; + } + @Override public void update(){ synchronized (Tile.tileSetLock) { diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index ca94a3fd74..77babec970 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -21,7 +21,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; -public abstract class Unit extends SyncEntity implements SerializableEntity { +public abstract class Unit extends SyncEntity implements SerializableEntity, Targetable { /**total duration of hit flash effect*/ public static final float hitDuration = 9f; @@ -175,6 +175,11 @@ public abstract class Unit extends SyncEntity implements SerializableEntity { } } + @Override + public Vector2 getVelocity() { + return velocity; + } + public void drawUnder(){} public void drawOver(){} diff --git a/core/src/io/anuke/mindustry/entities/UnitInventory.java b/core/src/io/anuke/mindustry/entities/UnitInventory.java index 0c0cc02f04..a90c46919b 100644 --- a/core/src/io/anuke/mindustry/entities/UnitInventory.java +++ b/core/src/io/anuke/mindustry/entities/UnitInventory.java @@ -53,6 +53,11 @@ public class UnitInventory { } } + /**Returns ammo range, or MAX_VALUE if this inventory has no ammo.*/ + public float getAmmoRange(){ + return hasAmmo() ? getAmmo().getRange() : Float.MAX_VALUE; + } + public AmmoType getAmmo() { return ammos.size == 0 ? null : ammos.peek().type; } diff --git a/core/src/io/anuke/mindustry/entities/Units.java b/core/src/io/anuke/mindustry/entities/Units.java index e1c044457a..1108413154 100644 --- a/core/src/io/anuke/mindustry/entities/Units.java +++ b/core/src/io/anuke/mindustry/entities/Units.java @@ -20,6 +20,36 @@ import static io.anuke.mindustry.Vars.*; public class Units { private static Rectangle rect = new Rectangle(); + /**Validates a target. + * @param target The target to validate + * @param team The team of the thing doing tha targeting + * @param x The X position of the thing doign the targeting + * @param y The Y position of the thing doign the targeting + * @param range The maximum distance from the target X/Y the targeter can be for it to be valid + * @return whether the target is invalid + */ + public static boolean invalidateTarget(Targetable target, Team team, float x, float y, float range){ + if(target == null){ + return false; + } + + if(range != Float.MAX_VALUE && target.distanceTo(x, y) > range){ + return false; + } + + return target.getTeam() == team || !target.isValid(); + } + + /**See {@link #invalidateTarget(Targetable, Team, float, float, float)}*/ + public static boolean invalidateTarget(Targetable target, Team team, float x, float y){ + return invalidateTarget(target, team, x, y, Float.MAX_VALUE); + } + + /**See {@link #invalidateTarget(Targetable, Team, float, float, float)}*/ + public static boolean invalidateTarget(Targetable target, Unit targeter){ + return invalidateTarget(target, targeter.team, targeter.x, targeter.y, targeter.inventory.getAmmoRange()); + } + /**Returns whether there are any entities on this tile.*/ public static boolean anyEntities(Tile tile){ Block type = tile.block(); @@ -95,6 +125,16 @@ public class Units { } } + /**Returns the closest target enemy. First, units are checked, then tile entities.*/ + public static Targetable getClosestTarget(Team team, float x, float y, float range){ + Unit unit = getClosestEnemy(team, x, y, range, u -> true); + if(unit != null){ + return unit; + }else{ + return findEnemyTile(team, x, y, range, tile -> true); + } + } + /**Returns the closest enemy of this team. Filter by predicate.*/ public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate predicate){ Unit[] result = {null}; @@ -103,7 +143,7 @@ public class Units { rect.setSize(range*2f).setCenter(x, y); getNearbyEnemies(team, rect, e -> { - if (!predicate.test(e)) + if (e.isDead() || !predicate.test(e)) return; float dist = Vector2.dst(e.x, e.y, x, y); diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java index 2f6a916b95..2c8753115b 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -95,7 +95,7 @@ public class Bullet extends BulletEntity{ if (tile == null) return false; tile = tile.target(); - if (tile.entity != null && tile.entity.collide(this) && !tile.entity.dead && tile.entity.tile.getTeam() != team) { + if (tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team) { tile.entity.collision(this); remove(); type.hit(this); diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index 6ca757a6be..9a8a3fb595 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -1,15 +1,15 @@ package io.anuke.mindustry.entities.units; -import io.anuke.mindustry.entities.bullet.Bullet; -import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.Targetable; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.bullet.BulletType; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.BlockFlag; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; -import io.anuke.ucore.entities.Entity; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Timer; @@ -26,7 +26,7 @@ public class BaseUnit extends Unit{ public Timer timer = new Timer(5); public float walkTime = 0f; public StateMachine state = new StateMachine(); - public Entity target; + public Targetable target; public BaseUnit(UnitType type, Team team){ this.type = type; diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnitType.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnitType.java index 84c276f1a4..8bf22543f5 100644 --- a/core/src/io/anuke/mindustry/entities/units/FlyingUnitType.java +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnitType.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.entities.units; -import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.type.AmmoType; @@ -59,7 +58,7 @@ public class FlyingUnitType extends UnitType { } protected void circle(BaseUnit unit, float circleLength){ - vec.set(unit.target.x - unit.x, unit.target.y - unit.y); + vec.set(unit.target.getX() - unit.x, unit.target.getY() - unit.y); if(vec.len() < circleLength){ vec.rotate((circleLength-vec.len())/circleLength * 180f); @@ -71,7 +70,7 @@ public class FlyingUnitType extends UnitType { } protected void attack(BaseUnit unit, float circleLength){ - vec.set(unit.target.x - unit.x, unit.target.y - unit.y); + vec.set(unit.target.getX() - unit.x, unit.target.getY() - unit.y); float ang = unit.angleTo(unit.target); float diff = Angles.angleDist(ang, unit.rotation); @@ -113,8 +112,7 @@ public class FlyingUnitType extends UnitType { } public void update(BaseUnit unit) { - if(unit.target != null && (unit.target instanceof TileEntity && - (((TileEntity)unit.target).tile.getTeam() == unit.team || !((TileEntity)unit.target).tile.breakable()))){ + if(Units.invalidateTarget(unit.target, unit.team, unit.x, unit.y)){ unit.target = null; } diff --git a/core/src/io/anuke/mindustry/entities/units/GroundUnitType.java b/core/src/io/anuke/mindustry/entities/units/GroundUnitType.java index 4ce4250912..55976fa01c 100644 --- a/core/src/io/anuke/mindustry/entities/units/GroundUnitType.java +++ b/core/src/io/anuke/mindustry/entities/units/GroundUnitType.java @@ -89,8 +89,7 @@ public abstract class GroundUnitType extends UnitType{ public void updateTargeting(BaseUnit unit) { super.updateTargeting(unit); - if(unit.target != null && unit.inventory.hasAmmo() && - !(unit.target instanceof TileEntity) && unit.target.distanceTo(unit) > unit.inventory.getAmmo().getRange()){ + if(Units.invalidateTarget(unit.target, unit)){ unit.target = null; } } diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java index 2894def651..f69ea2d972 100644 --- a/core/src/io/anuke/mindustry/entities/units/types/Drone.java +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -69,7 +69,7 @@ public class Drone extends FlyingUnitType { Shapes.laser("beam", "beam-end", unit.x + Angles.trnsx(unit.rotation, len), unit.y + Angles.trnsy(unit.rotation, len), - unit.target.x, unit.target.y); + unit.target.getX(), unit.target.getY()); Draw.color(); } } diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index ca4571d325..f8283d0408 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -31,7 +31,7 @@ public class OverlayRenderer { Shaders.outline.color.set(Palette.accent); Graphics.beginShaders(Shaders.outline); - input.drawBottom(); + input.drawOutlined(); Graphics.endShaders(); } diff --git a/core/src/io/anuke/mindustry/graphics/Trail.java b/core/src/io/anuke/mindustry/graphics/Trail.java new file mode 100644 index 0000000000..3aa0033531 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/Trail.java @@ -0,0 +1,53 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.FloatArray; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Mathf; + +/**Class that renders a trail.*/ +public class Trail { + private final int length; + + private FloatArray points = new FloatArray(); + + public Trail(int length){ + this.length = length; + } + + public void update(float curx, float cury){ + points.add(curx, cury); + + if(points.size > length*2) { + float[] items = points.items; + System.arraycopy(items, 2, items, 0, points.size - 2); + points.size -= 2; + } + } + + public void draw(Color start, Color end, float stroke){ + + for(int i = 0; i < points.size - 2; i += 2){ + float x = points.get(i); + float y = points.get(i + 1); + float x2 = points.get(i + 2); + float y2 = points.get(i + 3); + float s = Mathf.clamp((float)(i) / points.size); + + Draw.color(start, end, s); + + Lines.stroke(s * stroke); + Lines.line(x, y, x2, y2); + } + + if(points.size >= 2){ + Fill.circle(points.get(points.size-2), points.get(points.size-1), stroke/2f); + } + + Draw.color(start); + + Draw.reset(); + } +} diff --git a/core/src/io/anuke/mindustry/input/AndroidInput.java b/core/src/io/anuke/mindustry/input/AndroidInput.java index 65b435d61e..098d5f3fa4 100644 --- a/core/src/io/anuke/mindustry/input/AndroidInput.java +++ b/core/src/io/anuke/mindustry/input/AndroidInput.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.input.GestureDetector; import com.badlogic.gdx.input.GestureDetector.GestureListener; +import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Align; @@ -13,6 +14,9 @@ import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.Targetable; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Shaders; import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult; @@ -20,16 +24,12 @@ import io.anuke.mindustry.input.PlaceUtils.NormalizeResult; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.*; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.scene.Group; import io.anuke.ucore.scene.builders.imagebutton; import io.anuke.ucore.scene.builders.table; -import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; @@ -41,7 +41,7 @@ public class AndroidInput 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 = Unit.dp.scl(60f); + private final float edgePan = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(60f); //gesture data private Vector2 pinch1 = new Vector2(-1, -1), pinch2 = pinch1.cpy(); @@ -54,6 +54,9 @@ public class AndroidInput extends InputHandler implements GestureListener{ /**Animation scale for line.*/ private float lineScale; + /**Animation data for crosshair.*/ + private float crosshairScale; + private Targetable lastTarget; /**List of currently selected tiles to place.*/ private Array selection = new Array<>(); @@ -73,6 +76,24 @@ public class AndroidInput extends InputHandler implements GestureListener{ Inputs.addProcessor(new GestureDetector(20, 0.5f, 0.4f, 0.15f, this)); } + //region utility methods + + /**Check and assign targets for a specific position.*/ + void checkTargets(float x, float y){ + Unit unit = Units.getClosestEnemy(player.team, x, y, 20f, u -> true); + + if(unit != null){ + player.target = unit; + }else{ + Tile tile = world.tileWorld(x, y); + if(tile != null) tile = tile.target(); + + if(tile != null && state.teams.areEnemies(player.team, tile.getTeam())){ + 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; @@ -156,6 +177,10 @@ public class AndroidInput extends InputHandler implements GestureListener{ } } + //endregion + + //region UI and drawing + @Override public void buildUI(Group group) { @@ -217,11 +242,11 @@ public class AndroidInput extends InputHandler implements GestureListener{ @Override public boolean isDrawing(){ - return selection.size > 0 || removals.size > 0; + return selection.size > 0 || removals.size > 0 || lineMode || player.target != null; } @Override - public void drawBottom(){ + public void drawOutlined(){ Shaders.mix.color.set(Palette.accent); Graphics.shader(Shaders.mix); @@ -321,9 +346,31 @@ public class AndroidInput extends InputHandler implements GestureListener{ } } - Draw.color(); + //draw targeting crosshair + if(player.target != null){ + if(player.target != lastTarget){ + crosshairScale = 0f; + lastTarget = player.target; + } + + crosshairScale = Mathf.lerpDelta(crosshairScale, 1f, 0.2f); + + Draw.color(Palette.remove); + Lines.stroke(1f); + + float radius = Interpolation.swingIn.apply(crosshairScale); + + Lines.poly(player.target.getX(), player.target.getY(), 4, 7f * radius, Timers.time()*1.5f); + Lines.spikes(player.target.getX(), player.target.getY(), 3f * radius, 6f * radius, 4, Timers.time()*1.5f); + } + + Draw.reset(); } + //endregion + + //region input events + @Override public boolean touchDown(int screenX, int screenY, int pointer, int button){ if(state.is(State.menu)) return false; @@ -432,6 +479,8 @@ public class AndroidInput extends InputHandler implements GestureListener{ public boolean tap(float x, float y, int count, int button) { if(state.is(State.menu) || lineMode) return false; + checkTargets(Graphics.world(x, y).x, Graphics.world(x, y).y); + //get tile on cursor Tile cursor = tileAt(x, y); @@ -502,9 +551,9 @@ public class AndroidInput extends InputHandler implements GestureListener{ vector.set(panX, panY).scl((Core.camera.viewportWidth * Core.camera.zoom) / Gdx.graphics.getWidth()); vector.limit(maxPanSpeed); - player.x += vector.x; - player.y += vector.y; - player.targetAngle = vector.angle(); + //pan view + Core.camera.position.x += vector.x; + Core.camera.position.y += vector.y; } }else{ lineScale = 0f; @@ -540,9 +589,8 @@ public class AndroidInput extends InputHandler implements GestureListener{ } }else{ //pan player - player.x -= dx; - player.y += dy; - player.targetAngle = Mathf.atan2(dx, -dy) + 180f; + Core.camera.position.x -= dx; + Core.camera.position.y += dy; } return false; @@ -577,9 +625,9 @@ public class AndroidInput extends InputHandler implements GestureListener{ initzoom = initialDistance; } - if(Math.abs(distance - initzoom) > Unit.dp.scl(100f) && !zoomed){ + if(Math.abs(distance - initzoom) > io.anuke.ucore.scene.ui.layout.Unit.dp.scl(100f) && !zoomed){ int amount = (distance > initzoom ? 1 : -1); - renderer.scaleCamera(Math.round(Unit.dp.scl(amount))); + renderer.scaleCamera(Math.round(io.anuke.ucore.scene.ui.layout.Unit.dp.scl(amount))); initzoom = distance; zoomed = true; return true; @@ -598,6 +646,8 @@ public class AndroidInput extends InputHandler implements GestureListener{ @Override public boolean touchDown(float x, float y, int pointer, int button) { return false; } @Override public boolean fling(float velocityX, float velocityY, int button) { return false; } + //endregion + class PlaceRequest{ float x, y; Recipe recipe; diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index bd02666bb1..c6a8cf3469 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -66,7 +66,7 @@ public class DesktopInput extends InputHandler{ } @Override - public void drawBottom(){ + public void drawOutlined(){ Tile cursor = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); if(cursor == null) return; diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 742e57ab7e..b0c29cca0d 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -73,7 +73,7 @@ public abstract class InputHandler extends InputAdapter{ } - public void drawBottom(){ + public void drawOutlined(){ } diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index 7aeea29c55..1373bc113a 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -1,16 +1,14 @@ package io.anuke.mindustry.type; import com.badlogic.gdx.utils.ObjectMap; -import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.fx.Fx; -import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetEvents; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; -import io.anuke.ucore.core.Graphics; import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Bits; import io.anuke.ucore.util.Mathf; @@ -44,7 +42,7 @@ public class Weapon extends Upgrade { super(name); } - public void update(Player p, boolean left){ + public void update(Player p, boolean left, float pointerX, float pointerY){ int t = left ? 1 : 2; int t2 = !left ? 1 : 2; if(p.inventory.hasAmmo() && p.timer.get(t, reload)){ @@ -52,8 +50,7 @@ public class Weapon extends Upgrade { p.timer.reset(t2, reload/2f); } - tr.set(Graphics.world(Vars.control.input(p.playerIndex).getMouseX(), - Vars.control.input(p.playerIndex).getMouseY())).sub(p.x, p.y); + tr.set(pointerX, pointerY).sub(p.x, p.y); if(tr.len() < minPlayerDist) tr.setLength(minPlayerDist); float cx = tr.x + p.x, cy = tr.y + p.y; diff --git a/core/src/io/anuke/mindustry/world/blocks/types/defense/RepairTurret.java b/core/src/io/anuke/mindustry/world/blocks/types/defense/RepairTurret.java index b66423a669..f0be3cbe0b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/defense/RepairTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/defense/RepairTurret.java @@ -38,7 +38,7 @@ public class RepairTurret extends PowerTurret { return; } - if(entity.blockTarget != null && entity.blockTarget.dead){ + if(entity.blockTarget != null && entity.blockTarget.isDead()){ entity.blockTarget = null; } diff --git a/core/src/io/anuke/mindustry/world/blocks/types/defense/Turret.java b/core/src/io/anuke/mindustry/world/blocks/types/defense/Turret.java index 3b5a6260ff..10cf02286b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/defense/Turret.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/defense/Turret.java @@ -79,7 +79,7 @@ public abstract class Turret extends Block{ super.setStats(); /* if(ammo != null) stats.add("ammo", ammo); - if(ammo != null) stats.add("ammocapacity", maxammo); + if(ammo != null) stats.add("ammocapacity", maxAmmo); if(ammo != null) stats.add("ammoitem", ammoMultiplier);*/ stats.add("range", (int)range); stats.add("inaccuracy", (int)inaccuracy); diff --git a/core/src/io/anuke/mindustry/world/blocks/types/defense/turrets/ItemTurret.java b/core/src/io/anuke/mindustry/world/blocks/types/defense/turrets/ItemTurret.java index cd5f07f993..dd8ffd0e04 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/defense/turrets/ItemTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/defense/turrets/ItemTurret.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world.blocks.types.defense.turrets; import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.type.AmmoEntry; import io.anuke.mindustry.type.AmmoType; import io.anuke.mindustry.type.Item; @@ -10,7 +11,7 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.types.defense.Turret; public class ItemTurret extends Turret { - protected int maxammo = 100; + protected int maxAmmo = 100; //TODO implement this! /**A value of 'null' means this turret does not need ammo.*/ protected AmmoType[] ammoTypes; @@ -18,6 +19,24 @@ public class ItemTurret extends Turret { public ItemTurret(String name) { super(name); + hasItems = true; + } + + @Override + public int acceptStack(Item item, int amount, Tile tile, Unit source) { + TurretEntity entity = tile.entity(); + + AmmoType type = ammoMap.get(item); + + if(type == null) return 0; + + return Math.min((int)((maxAmmo - entity.totalAmmo) / ammoMap.get(item).quantityMultiplier), amount); + } + + //currently can't remove items from turrets. + @Override + public int removeStack(Tile tile, Item item, int amount) { + return 0; } @Override @@ -26,6 +45,7 @@ public class ItemTurret extends Turret { AmmoType type = ammoMap.get(item); entity.totalAmmo += type.quantityMultiplier; + entity.items.addItem(item, 1); //find ammo entry by type for(int i = 0; i < entity.ammo.size; i ++){ @@ -48,12 +68,12 @@ public class ItemTurret extends Turret { public boolean acceptItem(Item item, Tile tile, Tile source){ TurretEntity entity = tile.entity(); - return ammoMap != null && ammoMap.get(item) != null && entity.totalAmmo + ammoMap.get(item).quantityMultiplier <= maxammo; + return ammoMap != null && ammoMap.get(item) != null && entity.totalAmmo + ammoMap.get(item).quantityMultiplier <= maxAmmo; } @Override public void setBars(){ - bars.add(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity().totalAmmo / maxammo)); + bars.replace(new BlockBar(BarType.inventory, true, tile -> (float)tile.entity().totalAmmo / maxAmmo)); } @Override