Further targeting improvements for AI
|
Before Width: | Height: | Size: 310 B After Width: | Height: | Size: 310 B |
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 266 B |
BIN
core/assets-raw/sprites/blocks/units/droppoint.png
Normal file
|
After Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
@@ -63,7 +63,7 @@ public class Vars{
|
|||||||
//whether turrets have infinite ammo (only with debug)
|
//whether turrets have infinite ammo (only with debug)
|
||||||
public static boolean infiniteAmmo = true;
|
public static boolean infiniteAmmo = true;
|
||||||
//whether to show paths of enemies
|
//whether to show paths of enemies
|
||||||
public static boolean showPaths = false;
|
public static boolean showPaths = true;
|
||||||
//if false, player is always hidden
|
//if false, player is always hidden
|
||||||
public static boolean showPlayer = true;
|
public static boolean showPlayer = true;
|
||||||
//whether to hide ui, only on debug
|
//whether to hide ui, only on debug
|
||||||
|
|||||||
@@ -3,20 +3,31 @@ package io.anuke.mindustry.ai;
|
|||||||
import com.badlogic.gdx.utils.IntMap;
|
import com.badlogic.gdx.utils.IntMap;
|
||||||
import com.badlogic.gdx.utils.ObjectMap;
|
import com.badlogic.gdx.utils.ObjectMap;
|
||||||
import com.badlogic.gdx.utils.ObjectSet;
|
import com.badlogic.gdx.utils.ObjectSet;
|
||||||
|
import io.anuke.mindustry.content.Items;
|
||||||
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
||||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
|
import io.anuke.mindustry.type.Item;
|
||||||
import io.anuke.mindustry.world.BlockFlag;
|
import io.anuke.mindustry.world.BlockFlag;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
import io.anuke.ucore.core.Events;
|
import io.anuke.ucore.core.Events;
|
||||||
import io.anuke.ucore.util.EnumSet;
|
import io.anuke.ucore.util.EnumSet;
|
||||||
|
import io.anuke.ucore.util.Mathf;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.state;
|
import static io.anuke.mindustry.Vars.state;
|
||||||
import static io.anuke.mindustry.Vars.world;
|
import static io.anuke.mindustry.Vars.world;
|
||||||
|
|
||||||
|
//TODO consider using quadtrees for finding specific types of blocks within an area
|
||||||
/**Class used for indexing special target blocks for AI.
|
/**Class used for indexing special target blocks for AI.
|
||||||
* TODO maybe use Arrays instead of ObjectSets?*/
|
* TODO maybe use Arrays instead of ObjectSets?*/
|
||||||
public class BlockIndexer {
|
public class BlockIndexer {
|
||||||
|
/**Size of one ore quadrant.*/
|
||||||
|
private final static int quadrantSize = 12;
|
||||||
|
/**Set of all ores that are being scanned.*/
|
||||||
|
private final ObjectSet<Item> scanOres = ObjectSet.with(Items.iron, Items.coal, Items.lead, Items.thorium, Items.titanium);
|
||||||
|
/**Stores all ore quadtrants on the map.*/
|
||||||
|
private ObjectMap<Item, ObjectSet<Tile>> ores = new ObjectMap<>();
|
||||||
|
|
||||||
/**Maps teams to a map of flagged tiles by type.*/
|
/**Maps teams to a map of flagged tiles by type.*/
|
||||||
private ObjectMap<BlockFlag, ObjectSet<Tile>> enemyMap = new ObjectMap<>();
|
private ObjectMap<BlockFlag, ObjectSet<Tile>> enemyMap = new ObjectMap<>();
|
||||||
/**Maps teams to a map of flagged tiles by type.*/
|
/**Maps teams to a map of flagged tiles by type.*/
|
||||||
@@ -43,22 +54,35 @@ public class BlockIndexer {
|
|||||||
enemyMap.clear();
|
enemyMap.clear();
|
||||||
allyMap.clear();
|
allyMap.clear();
|
||||||
typeMap.clear();
|
typeMap.clear();
|
||||||
|
ores.clear();
|
||||||
for(int x = 0; x < world.width(); x ++){
|
for(int x = 0; x < world.width(); x ++){
|
||||||
for (int y = 0; y < world.height(); y++) {
|
for (int y = 0; y < world.height(); y++) {
|
||||||
process(world.tile(x, y));
|
process(world.tile(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanOres();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Get all allied blocks with a flag.*/
|
||||||
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
|
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
|
||||||
return (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
|
return (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Get all enemy blocks with a flag.*/
|
||||||
public ObjectSet<Tile> getEnemy(Team team, BlockFlag type){
|
public ObjectSet<Tile> getEnemy(Team team, BlockFlag type){
|
||||||
return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
|
return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Returns a set of tiles that have ores of the specified type nearby.
|
||||||
|
* While each tile in the set is not guaranteed to have an ore directly on it,
|
||||||
|
* each tile will at least have an ore within {@link #quadrantSize} / 2 blocks of it.
|
||||||
|
* Only specific ore types are scanned. See {@link #scanOres}.*/
|
||||||
|
public ObjectSet<Tile> getOrePositions(Item item){
|
||||||
|
return ores.get(item, emptyArray);
|
||||||
|
}
|
||||||
|
|
||||||
private void process(Tile tile){
|
private void process(Tile tile){
|
||||||
if(tile.block().flags != null &&
|
if(tile.block().flags != null &&
|
||||||
tile.getTeam() != Team.none){
|
tile.getTeam() != Team.none){
|
||||||
@@ -85,6 +109,30 @@ public class BlockIndexer {
|
|||||||
return state.teams.get(team).ally ? allyMap : enemyMap;
|
return state.teams.get(team).ally ? allyMap : enemyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scanOres(){
|
||||||
|
//initialize ore map with empty sets
|
||||||
|
for(Item item : scanOres){
|
||||||
|
ores.put(item, new ObjectSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < world.width(); x ++){
|
||||||
|
for (int y = 0; y < world.height(); y++) {
|
||||||
|
int qx = (x/quadrantSize);
|
||||||
|
int qy = (y/quadrantSize);
|
||||||
|
|
||||||
|
Tile tile = world.tile(x, y);
|
||||||
|
|
||||||
|
//add position of quadrant to list when an ore is found
|
||||||
|
if(tile.floor().drops != null && scanOres.contains(tile.floor().drops.item)){
|
||||||
|
ores.get(tile.floor().drops.item).add(world.tile(
|
||||||
|
//make sure to clamp quadrant middle position, since it might go off bounds
|
||||||
|
Mathf.clamp(qx * quadrantSize + quadrantSize/2, 0, world.width() - 1),
|
||||||
|
Mathf.clamp(qy * quadrantSize + quadrantSize/2, 0, world.height() - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TileIndex{
|
private class TileIndex{
|
||||||
public final EnumSet<BlockFlag> flags;
|
public final EnumSet<BlockFlag> flags;
|
||||||
public final Team team;
|
public final Team team;
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ public class Pathfinder {
|
|||||||
if(other == null) continue;
|
if(other == null) continue;
|
||||||
|
|
||||||
if(values[dx][dy] < value && (target == null || values[dx][dy] < tl) &&
|
if(values[dx][dy] < value && (target == null || values[dx][dy] < tl) &&
|
||||||
(!other.solid() || state.teams.areEnemies(team, other.getTeam()))){
|
!other.solid() &&
|
||||||
|
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
|
||||||
target = other;
|
target = other;
|
||||||
tl = values[dx][dy];
|
tl = values[dx][dy];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,9 +110,12 @@ public class Recipes implements ContentList{
|
|||||||
new Recipe(liquid, LiquidBlocks.fluxpump, new ItemStack(Items.steel, 10), new ItemStack(Items.surgealloy, 5));
|
new Recipe(liquid, LiquidBlocks.fluxpump, new ItemStack(Items.steel, 10), new ItemStack(Items.surgealloy, 5));
|
||||||
|
|
||||||
new Recipe(units, UnitBlocks.repairPoint, new ItemStack(Items.steel, 10));
|
new Recipe(units, UnitBlocks.repairPoint, new ItemStack(Items.steel, 10));
|
||||||
|
new Recipe(units, UnitBlocks.dropPoint, new ItemStack(Items.steel, 10));
|
||||||
new Recipe(units, UnitBlocks.resupplyPoint, new ItemStack(Items.steel, 10));
|
new Recipe(units, UnitBlocks.resupplyPoint, new ItemStack(Items.steel, 10));
|
||||||
|
|
||||||
//new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.steel, 10));
|
new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.iron, 50));
|
||||||
|
|
||||||
|
|
||||||
//new Recipe(units, UnitBlocks.vtolFactory, new ItemStack(Items.steel, 10));
|
//new Recipe(units, UnitBlocks.vtolFactory, new ItemStack(Items.steel, 10));
|
||||||
//new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.steel, 10));
|
//new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.steel, 10));
|
||||||
//new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.steel, 10));
|
//new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.steel, 10));
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
package io.anuke.mindustry.content.blocks;
|
package io.anuke.mindustry.content.blocks;
|
||||||
|
|
||||||
|
import io.anuke.mindustry.content.Items;
|
||||||
|
import io.anuke.mindustry.content.UnitTypes;
|
||||||
import io.anuke.mindustry.type.ContentList;
|
import io.anuke.mindustry.type.ContentList;
|
||||||
|
import io.anuke.mindustry.type.ItemStack;
|
||||||
import io.anuke.mindustry.world.Block;
|
import io.anuke.mindustry.world.Block;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.units.DropPoint;
|
||||||
import io.anuke.mindustry.world.blocks.types.units.RepairPoint;
|
import io.anuke.mindustry.world.blocks.types.units.RepairPoint;
|
||||||
import io.anuke.mindustry.world.blocks.types.units.ResupplyPoint;
|
import io.anuke.mindustry.world.blocks.types.units.ResupplyPoint;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.units.UnitFactory;
|
||||||
|
|
||||||
public class UnitBlocks implements ContentList {
|
public class UnitBlocks implements ContentList {
|
||||||
public static Block resupplyPoint, repairPoint, droneFactory;
|
public static Block resupplyPoint, repairPoint, droneFactory, dropPoint;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
/*
|
|
||||||
droneFactory = new UnitFactory("dronefactory") {{
|
droneFactory = new UnitFactory("dronefactory") {{
|
||||||
type = UnitTypes.drone;
|
type = UnitTypes.drone;
|
||||||
produceTime = 200;
|
produceTime = 300;
|
||||||
size = 2;
|
size = 2;
|
||||||
requirements = new ItemStack[]{
|
requirements = new ItemStack[]{
|
||||||
new ItemStack(Items.stone, 5)
|
new ItemStack(Items.iron, 20)
|
||||||
};
|
};
|
||||||
}};*/
|
}};
|
||||||
|
|
||||||
resupplyPoint = new ResupplyPoint("resupplypoint") {{
|
resupplyPoint = new ResupplyPoint("resupplypoint") {{
|
||||||
shadow = "shadow-round-1";
|
shadow = "shadow-round-1";
|
||||||
itemCapacity = 30;
|
itemCapacity = 30;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
dropPoint = new DropPoint("droppoint") {{
|
||||||
|
shadow = "shadow-round-1";
|
||||||
|
itemCapacity = 40;
|
||||||
|
}};
|
||||||
|
|
||||||
repairPoint = new RepairPoint("repairpoint") {{
|
repairPoint = new RepairPoint("repairpoint") {{
|
||||||
shadow = "shadow-round-1";
|
shadow = "shadow-round-1";
|
||||||
repairSpeed = 0.1f;
|
repairSpeed = 0.1f;
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ public class ContentLoader {
|
|||||||
//weapons
|
//weapons
|
||||||
new Weapons(),
|
new Weapons(),
|
||||||
|
|
||||||
|
//units
|
||||||
|
new UnitTypes(),
|
||||||
|
|
||||||
//blocks
|
//blocks
|
||||||
new Blocks(),
|
new Blocks(),
|
||||||
new DefenseBlocks(),
|
new DefenseBlocks(),
|
||||||
@@ -68,9 +71,6 @@ public class ContentLoader {
|
|||||||
|
|
||||||
//recipes
|
//recipes
|
||||||
new Recipes(),
|
new Recipes(),
|
||||||
|
|
||||||
//units
|
|
||||||
new UnitTypes(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ public interface BlockBuilder {
|
|||||||
/**Sets the tile this builder is currently mining.*/
|
/**Sets the tile this builder is currently mining.*/
|
||||||
void setMineTile(Tile tile);
|
void setMineTile(Tile tile);
|
||||||
|
|
||||||
|
/**Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.*/
|
||||||
|
float getBuildPower(Tile tile);
|
||||||
|
|
||||||
/**Return whether this builder's place queue contains items.*/
|
/**Return whether this builder's place queue contains items.*/
|
||||||
default boolean isBuilding(){
|
default boolean isBuilding(){
|
||||||
return getPlaceQueue().size != 0;
|
return getPlaceQueue().size != 0;
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ public class Player extends Unit implements BlockBuilder {
|
|||||||
|
|
||||||
//region unit and event overrides, utility methods
|
//region unit and event overrides, utility methods
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getBuildPower(Tile tile) {
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getMaxHealth() {
|
public float getMaxHealth() {
|
||||||
return 200;
|
return 200;
|
||||||
@@ -507,6 +513,8 @@ public class Player extends Unit implements BlockBuilder {
|
|||||||
inventory.clear();
|
inventory.clear();
|
||||||
upgrades.clear();
|
upgrades.clear();
|
||||||
placeQueue.clear();
|
placeQueue.clear();
|
||||||
|
dead = true;
|
||||||
|
respawning = false;
|
||||||
|
|
||||||
add();
|
add();
|
||||||
heal();
|
heal();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public class UnitInventory {
|
|||||||
private Array<AmmoEntry> ammos = new Array<>();
|
private Array<AmmoEntry> ammos = new Array<>();
|
||||||
private int totalAmmo;
|
private int totalAmmo;
|
||||||
private ItemStack item;
|
private ItemStack item;
|
||||||
|
//TODO move these somewhere else so they're not variables?
|
||||||
private int capacity, ammoCapacity;
|
private int capacity, ammoCapacity;
|
||||||
private boolean infiniteAmmo;
|
private boolean infiniteAmmo;
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ public class UnitInventory {
|
|||||||
this.ammoCapacity = ammoCapacity;
|
this.ammoCapacity = ammoCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFull(){
|
||||||
|
return item != null && item.amount >= capacity;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInfiniteAmmo() {
|
public boolean isInfiniteAmmo() {
|
||||||
return infiniteAmmo;
|
return infiniteAmmo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ public class Units {
|
|||||||
* @return whether the target is invalid
|
* @return whether the target is invalid
|
||||||
*/
|
*/
|
||||||
public static boolean invalidateTarget(Targetable target, Team team, float x, float y, float range) {
|
public static boolean invalidateTarget(Targetable target, Team team, float x, float y, float range) {
|
||||||
if (target == null) {
|
return target == null || (range != Float.MAX_VALUE && target.distanceTo(x, y) > range) || target.getTeam() == team || !target.isValid();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (!(range != Float.MAX_VALUE) || !(target.distanceTo(x, y) > range)) && (target.getTeam() == team || !target.isValid());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import io.anuke.mindustry.content.fx.ExplosionFx;
|
|||||||
import io.anuke.mindustry.entities.Targetable;
|
import io.anuke.mindustry.entities.Targetable;
|
||||||
import io.anuke.mindustry.entities.TileEntity;
|
import io.anuke.mindustry.entities.TileEntity;
|
||||||
import io.anuke.mindustry.entities.Unit;
|
import io.anuke.mindustry.entities.Unit;
|
||||||
|
import io.anuke.mindustry.entities.Units;
|
||||||
import io.anuke.mindustry.entities.bullet.Bullet;
|
import io.anuke.mindustry.entities.bullet.Bullet;
|
||||||
import io.anuke.mindustry.entities.bullet.BulletType;
|
import io.anuke.mindustry.entities.bullet.BulletType;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
@@ -95,10 +96,25 @@ public abstract class BaseUnit extends Unit{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void targetClosestAllyFlag(BlockFlag flag){
|
public void targetClosestAllyFlag(BlockFlag flag){
|
||||||
|
if(target != null) return;
|
||||||
|
|
||||||
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag));
|
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag));
|
||||||
if (target != null) this.target = target.entity;
|
if (target != null) this.target = target.entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void targetClosestEnemyFlag(BlockFlag flag){
|
||||||
|
if(target != null) return;
|
||||||
|
|
||||||
|
Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag));
|
||||||
|
if (target != null) this.target = target.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void targetClosest(){
|
||||||
|
if(target != null) return;
|
||||||
|
|
||||||
|
target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange());
|
||||||
|
}
|
||||||
|
|
||||||
public UnitState getStartState(){
|
public UnitState getStartState(){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.anuke.mindustry.entities.units;
|
package io.anuke.mindustry.entities.units;
|
||||||
|
|
||||||
import io.anuke.mindustry.entities.Unit;
|
|
||||||
import io.anuke.mindustry.entities.Units;
|
import io.anuke.mindustry.entities.Units;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
import io.anuke.mindustry.graphics.Palette;
|
import io.anuke.mindustry.graphics.Palette;
|
||||||
@@ -48,7 +47,7 @@ public class FlyingUnit extends BaseUnit {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawOver() {
|
public void drawOver() {
|
||||||
trail.draw(Palette.lighterOrange, Palette.lightishOrange, 5f);
|
trail.draw(Palette.lightFlame, Palette.lightOrange, 5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,6 +64,11 @@ public class FlyingUnit extends BaseUnit {
|
|||||||
return attack;
|
return attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float drawSize() {
|
||||||
|
return 60;
|
||||||
|
}
|
||||||
|
|
||||||
protected void circle(float circleLength){
|
protected void circle(float circleLength){
|
||||||
vec.set(target.getX() - x, target.getY() - y);
|
vec.set(target.getX() - x, target.getY() - y);
|
||||||
|
|
||||||
@@ -111,6 +115,24 @@ public class FlyingUnit extends BaseUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
idle = new UnitState() {
|
||||||
|
public void update() {
|
||||||
|
retarget(() -> {
|
||||||
|
targetClosest();
|
||||||
|
targetClosestEnemyFlag(BlockFlag.target);
|
||||||
|
|
||||||
|
if(target != null){
|
||||||
|
setState(attack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
target = getClosestCore();
|
||||||
|
if(target != null){
|
||||||
|
circle(50f);
|
||||||
|
}
|
||||||
|
velocity.scl(0.8f);
|
||||||
|
}
|
||||||
|
},
|
||||||
attack = new UnitState(){
|
attack = new UnitState(){
|
||||||
public void entered() {
|
public void entered() {
|
||||||
target = null;
|
target = null;
|
||||||
@@ -124,16 +146,15 @@ public class FlyingUnit extends BaseUnit {
|
|||||||
if(!inventory.hasAmmo()) {
|
if(!inventory.hasAmmo()) {
|
||||||
state.set(resupply);
|
state.set(resupply);
|
||||||
}else if (target == null){
|
}else if (target == null){
|
||||||
if(timer.get(timerTarget, 20)) {
|
retarget(() -> {
|
||||||
Unit closest = Units.getClosestEnemy(team, x, y,
|
targetClosest();
|
||||||
inventory.getAmmo().getRange(), other -> distanceTo(other) < 60f);
|
targetClosestEnemyFlag(BlockFlag.target);
|
||||||
if(closest != null){
|
targetClosestEnemyFlag(BlockFlag.producer);
|
||||||
target = closest;
|
|
||||||
}else {
|
if(target == null){
|
||||||
Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, BlockFlag.target));
|
setState(idle);
|
||||||
if (target != null) FlyingUnit.this.target = target.entity;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}else{
|
}else{
|
||||||
attack(150f);
|
attack(150f);
|
||||||
|
|
||||||
@@ -153,7 +174,7 @@ public class FlyingUnit extends BaseUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
if(health >= health){
|
if(health >= getMaxHealth()){
|
||||||
state.set(attack);
|
state.set(attack);
|
||||||
}else if(!targetHasFlag(BlockFlag.repair)){
|
}else if(!targetHasFlag(BlockFlag.repair)){
|
||||||
retarget(() -> {
|
retarget(() -> {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package io.anuke.mindustry.entities.units;
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.entities.Unit;
|
|
||||||
import io.anuke.mindustry.entities.Units;
|
import io.anuke.mindustry.entities.Units;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
import io.anuke.mindustry.type.AmmoType;
|
import io.anuke.mindustry.type.AmmoType;
|
||||||
@@ -12,20 +11,17 @@ import io.anuke.mindustry.world.blocks.types.Floor;
|
|||||||
import io.anuke.ucore.core.Timers;
|
import io.anuke.ucore.core.Timers;
|
||||||
import io.anuke.ucore.graphics.Draw;
|
import io.anuke.ucore.graphics.Draw;
|
||||||
import io.anuke.ucore.graphics.Hue;
|
import io.anuke.ucore.graphics.Hue;
|
||||||
import io.anuke.ucore.util.Angles;
|
import io.anuke.ucore.util.*;
|
||||||
import io.anuke.ucore.util.Geometry;
|
|
||||||
import io.anuke.ucore.util.Mathf;
|
|
||||||
import io.anuke.ucore.util.Translator;
|
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.world;
|
import static io.anuke.mindustry.Vars.world;
|
||||||
|
|
||||||
public abstract class GroundUnitType extends BaseUnit {
|
public abstract class GroundUnit extends BaseUnit {
|
||||||
protected static Translator vec = new Translator();
|
protected static Translator vec = new Translator();
|
||||||
protected static float maxAim = 30f;
|
protected static float maxAim = 30f;
|
||||||
|
|
||||||
protected float walkTime;
|
protected float walkTime;
|
||||||
|
|
||||||
public GroundUnitType(UnitType type, Team team) {
|
public GroundUnit(UnitType type, Team team) {
|
||||||
super(type, team);
|
super(type, team);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +34,8 @@ public abstract class GroundUnitType extends BaseUnit {
|
|||||||
public void update() {
|
public void update() {
|
||||||
super.update();
|
super.update();
|
||||||
|
|
||||||
if(!velocity.isZero(0.0001f) && (target == null
|
if(target == null){
|
||||||
|| (inventory.hasAmmo() && distanceTo(target) > inventory.getAmmo().getRange()))){
|
rotation = Mathf.lerpDelta(rotation, velocity.angle(), 0.2f);
|
||||||
rotation = velocity.angle();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +84,8 @@ public abstract class GroundUnitType extends BaseUnit {
|
|||||||
public void updateTargeting() {
|
public void updateTargeting() {
|
||||||
super.updateTargeting();
|
super.updateTargeting();
|
||||||
|
|
||||||
if(Units.invalidateTarget(target, this)){
|
if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){
|
||||||
|
if(target != null) Log.info("Invalidating target {0}", target);
|
||||||
target = null;
|
target = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,19 +142,11 @@ public abstract class GroundUnitType extends BaseUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
retarget(() -> {
|
retarget(() -> targetClosest());
|
||||||
Unit closest = Units.getClosestEnemy(team, x, y, inventory.getAmmo().getRange(), other -> true);
|
|
||||||
if(closest != null){
|
|
||||||
target = closest;
|
|
||||||
}else {
|
|
||||||
Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, BlockFlag.target));
|
|
||||||
if (target != null) GroundUnitType.this.target = target.entity;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!inventory.hasAmmo()) {
|
if(!inventory.hasAmmo()) {
|
||||||
state.set(resupply);
|
state.set(resupply);
|
||||||
}else{
|
}else if(target != null){
|
||||||
if(distanceTo(target) > inventory.getAmmo().getRange() * 0.7f){
|
if(distanceTo(target) > inventory.getAmmo().getRange() * 0.7f){
|
||||||
moveToCore();
|
moveToCore();
|
||||||
}else{
|
}else{
|
||||||
@@ -173,6 +161,8 @@ public abstract class GroundUnitType extends BaseUnit {
|
|||||||
|
|
||||||
shoot(ammo, Angles.moveToward(rotation, angleTo(target), maxAim), 4f);
|
shoot(ammo, Angles.moveToward(rotation, angleTo(target), maxAim), 4f);
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
moveToCore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2,6 +2,7 @@ package io.anuke.mindustry.entities.units.types;
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.utils.Queue;
|
import com.badlogic.gdx.utils.Queue;
|
||||||
|
import io.anuke.mindustry.content.Items;
|
||||||
import io.anuke.mindustry.entities.BlockBuilder;
|
import io.anuke.mindustry.entities.BlockBuilder;
|
||||||
import io.anuke.mindustry.entities.TileEntity;
|
import io.anuke.mindustry.entities.TileEntity;
|
||||||
import io.anuke.mindustry.entities.Units;
|
import io.anuke.mindustry.entities.Units;
|
||||||
@@ -36,6 +37,8 @@ public class Drone extends FlyingUnit implements BlockBuilder{
|
|||||||
protected Tile mineTile;
|
protected Tile mineTile;
|
||||||
protected Queue<BuildRequest> placeQueue = new Queue<>();
|
protected Queue<BuildRequest> placeQueue = new Queue<>();
|
||||||
|
|
||||||
|
/**Initialize placement event notifier system.
|
||||||
|
* Static initialization is to be avoided, thus, this is done lazily.*/
|
||||||
private static void initEvents(){
|
private static void initEvents(){
|
||||||
Events.on(BlockBuildEvent.class, (team, tile) -> {
|
Events.on(BlockBuildEvent.class, (team, tile) -> {
|
||||||
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
|
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
|
||||||
@@ -70,6 +73,11 @@ public class Drone extends FlyingUnit implements BlockBuilder{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getBuildPower(Tile tile) {
|
||||||
|
return 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Queue<BuildRequest> getPlaceQueue() {
|
public Queue<BuildRequest> getPlaceQueue() {
|
||||||
return placeQueue;
|
return placeQueue;
|
||||||
@@ -176,6 +184,10 @@ public class Drone extends FlyingUnit implements BlockBuilder{
|
|||||||
retarget(() -> {
|
retarget(() -> {
|
||||||
target = Units.findAllyTile(team, x, y, discoverRange,
|
target = Units.findAllyTile(team, x, y, discoverRange,
|
||||||
tile -> tile.entity != null && tile.entity.health + 0.0001f < tile.block().health);
|
tile -> tile.entity != null && tile.entity.health + 0.0001f < tile.block().health);
|
||||||
|
|
||||||
|
if(target == null){
|
||||||
|
setState(mine);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}else if(target.distanceTo(Drone.this) > type.range){
|
}else if(target.distanceTo(Drone.this) > type.range){
|
||||||
circle(type.range);
|
circle(type.range);
|
||||||
@@ -186,6 +198,22 @@ public class Drone extends FlyingUnit implements BlockBuilder{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mine = new UnitState() {
|
||||||
|
public void update() {
|
||||||
|
//if inventory is full, drop it off.
|
||||||
|
if(inventory.isFull()){
|
||||||
|
setState(drop);
|
||||||
|
}else{
|
||||||
|
//only mines iron for now
|
||||||
|
retarget(() -> target = Geometry.findClosest(x, y, world.indexer().getOrePositions(Items.iron)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drop = new UnitState() {
|
||||||
|
public void update() {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
retreat = new UnitState() {
|
retreat = new UnitState() {
|
||||||
public void entered() {
|
public void entered() {
|
||||||
target = null;
|
target = null;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package io.anuke.mindustry.entities.units.types;
|
package io.anuke.mindustry.entities.units.types;
|
||||||
|
|
||||||
import io.anuke.mindustry.entities.units.GroundUnitType;
|
import io.anuke.mindustry.entities.units.GroundUnit;
|
||||||
import io.anuke.mindustry.entities.units.UnitType;
|
import io.anuke.mindustry.entities.units.UnitType;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
|
|
||||||
public class Scout extends GroundUnitType {
|
public class Scout extends GroundUnit {
|
||||||
|
|
||||||
public Scout(UnitType type, Team team) {
|
public Scout(UnitType type, Team team) {
|
||||||
super(type, team);
|
super(type, team);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.anuke.mindustry.entities.units.types;
|
package io.anuke.mindustry.entities.units.types;
|
||||||
|
|
||||||
import io.anuke.mindustry.content.fx.UnitFx;
|
|
||||||
import io.anuke.mindustry.entities.units.FlyingUnit;
|
import io.anuke.mindustry.entities.units.FlyingUnit;
|
||||||
import io.anuke.mindustry.entities.units.UnitType;
|
import io.anuke.mindustry.entities.units.UnitType;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
@@ -54,10 +53,6 @@ public class Vtol extends FlyingUnit {
|
|||||||
if(velocity.len() <= 0.2f){
|
if(velocity.len() <= 0.2f){
|
||||||
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 8f);
|
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 8f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(timer.get(timerBoost, 2)){
|
|
||||||
effectAt(UnitFx.vtolHover, rotation + 180f, 4f, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class DebugFragment implements Fragment {
|
|||||||
row();
|
row();
|
||||||
new button("death", () -> player.damage(99999, false));
|
new button("death", () -> player.damage(99999, false));
|
||||||
row();
|
row();
|
||||||
new button("spawnf", () -> UnitTypes.drone.create(player.team).set(player.x, player.y).add());
|
new button("spawnf", () -> UnitTypes.vtol.create(player.team).set(player.x, player.y).add());
|
||||||
row();
|
row();
|
||||||
new button("spawng", () -> UnitTypes.scout.create(player.team).set(player.x, player.y).add());
|
new button("spawng", () -> UnitTypes.scout.create(player.team).set(player.x, player.y).add());
|
||||||
row();
|
row();
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package io.anuke.mindustry.world;
|
package io.anuke.mindustry.world;
|
||||||
|
|
||||||
public enum BlockFlag {
|
public enum BlockFlag {
|
||||||
|
/**General important target for all types of units.*/
|
||||||
target(0),
|
target(0),
|
||||||
|
/**Point to resupply resources.*/
|
||||||
resupplyPoint(Float.MAX_VALUE),
|
resupplyPoint(Float.MAX_VALUE),
|
||||||
producer(Float.MAX_VALUE),
|
/**Point to drop off resources.*/
|
||||||
|
dropPoint(Float.MAX_VALUE),
|
||||||
|
/**Producer of important goods.*/
|
||||||
|
producer(20),
|
||||||
|
/**Producer or storage unit of volatile materials.*/
|
||||||
|
explosive(10),
|
||||||
|
/**Repair point.*/
|
||||||
repair(Float.MAX_VALUE);
|
repair(Float.MAX_VALUE);
|
||||||
|
|
||||||
public final float cost;
|
public final float cost;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package io.anuke.mindustry.world;
|
package io.anuke.mindustry.world;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.GridPoint2;
|
import com.badlogic.gdx.math.GridPoint2;
|
||||||
|
import com.badlogic.gdx.math.Vector2;
|
||||||
import com.badlogic.gdx.utils.Array;
|
import com.badlogic.gdx.utils.Array;
|
||||||
import com.badlogic.gdx.utils.reflect.ClassReflection;
|
import com.badlogic.gdx.utils.reflect.ClassReflection;
|
||||||
import io.anuke.mindustry.content.blocks.Blocks;
|
import io.anuke.mindustry.content.blocks.Blocks;
|
||||||
|
import io.anuke.mindustry.entities.Targetable;
|
||||||
import io.anuke.mindustry.entities.TileEntity;
|
import io.anuke.mindustry.entities.TileEntity;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
import io.anuke.mindustry.type.Recipe;
|
import io.anuke.mindustry.type.Recipe;
|
||||||
@@ -19,7 +21,7 @@ import static io.anuke.mindustry.Vars.tilesize;
|
|||||||
import static io.anuke.mindustry.Vars.world;
|
import static io.anuke.mindustry.Vars.world;
|
||||||
|
|
||||||
|
|
||||||
public class Tile implements Position{
|
public class Tile implements Position, Targetable{
|
||||||
public static final Object tileSetLock = new Object();
|
public static final Object tileSetLock = new Object();
|
||||||
|
|
||||||
/**Block ID data.*/
|
/**Block ID data.*/
|
||||||
@@ -338,6 +340,16 @@ public class Tile implements Position{
|
|||||||
world.notifyChanged(this);
|
world.notifyChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDead() {
|
||||||
|
return false; //tiles never die
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector2 getVelocity() {
|
||||||
|
return Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getX() {
|
public float getX() {
|
||||||
return drawx();
|
return drawx();
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package io.anuke.mindustry.world.blocks.types.units;
|
||||||
|
|
||||||
|
import io.anuke.mindustry.type.Item;
|
||||||
|
import io.anuke.mindustry.world.Block;
|
||||||
|
import io.anuke.mindustry.world.Tile;
|
||||||
|
|
||||||
|
public class DropPoint extends Block {
|
||||||
|
|
||||||
|
public DropPoint(String name) {
|
||||||
|
super(name);
|
||||||
|
|
||||||
|
hasItems = true;
|
||||||
|
solid = true;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptItem(Item item, Tile tile, Tile source) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Tile tile) {
|
||||||
|
if (tile.entity.items.totalItems() > 0) {
|
||||||
|
tryDump(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||