Implemented new enemy system (largely untested)

This commit is contained in:
Anuken
2018-01-03 13:53:50 -05:00
parent e106a6842b
commit 6034b5ec79
45 changed files with 936 additions and 1042 deletions

View File

@@ -1,6 +1,6 @@
package io.anuke.mindustry.entities;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.ucore.core.Settings;
import io.anuke.ucore.util.Mathf;
@@ -9,7 +9,7 @@ public class EnemySpawn{
private static float[] scalings = {4f, 2.5f, 1.5f};
/**The enemy type spawned*/
public final Class<? extends Enemy> type;
public final EnemyType type;
/**When this spawns should end*/
protected int before = Integer.MAX_VALUE;
/**When this spawns should start*/
@@ -29,7 +29,7 @@ public class EnemySpawn{
/**Amount of enemies spawned initially, with no scaling*/
protected int amount = 1;
public EnemySpawn(Class<? extends Enemy> type){
public EnemySpawn(EnemyType type){
this.type = type;
}
@@ -39,10 +39,10 @@ public class EnemySpawn{
}
float scaling = this.scaling * scalings[(Settings.getInt("difficulty"))];
return Math.min(amount-1 + 1 * Math.max((int)((wave / spacing) / scaling), 1) - (tier(wave, lane)-1) * tierscaleback, max);
return Math.min(amount-1 + Math.max((int)((wave / spacing) / scaling), 1) - (tier(wave, lane)-1) * tierscaleback, max);
}
public int tier(int wave, int lane){
return Mathf.clamp(tier + (wave-after)/tierscale, 1, Enemy.maxtier);
return Mathf.clamp(tier + (wave-after)/tierscale, 1, EnemyType.maxtier);
}
}

View File

@@ -1,22 +1,18 @@
package io.anuke.mindustry.entities;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.reflect.ClassReflection;
import io.anuke.mindustry.entities.enemies.*;
import io.anuke.mindustry.entities.enemies.EnemyTypes;
public class WaveCreator{
public static Array<EnemySpawn> getSpawns(){
//TODO
//Gdx.app.exit();
return Array.with(
new EnemySpawn(Enemy.class){{
new EnemySpawn(EnemyTypes.standard){{
scaling = 1;
before = 3;
}},
new EnemySpawn(FastEnemy.class){{
new EnemySpawn(EnemyTypes.fast){{
scaling = 1;
after = 3;
spacing = 5;
@@ -24,7 +20,7 @@ public class WaveCreator{
tierscaleback = 0;
}},
new EnemySpawn(BlastEnemy.class){{
new EnemySpawn(EnemyTypes.blast){{
after = 4;
amount = 3;
spacing = 5;
@@ -32,56 +28,56 @@ public class WaveCreator{
tierscaleback = 0;
}},
new EnemySpawn(TankEnemy.class){{
new EnemySpawn(EnemyTypes.tank){{
after = 5;
spacing = 5;
scaling = 2;
amount = 2;
}},
new EnemySpawn(RapidEnemy.class){{
new EnemySpawn(EnemyTypes.rapid){{
after = 7;
spacing = 5;
scaling = 2;
amount = 3;
}},
new EnemySpawn(HealerEnemy.class){{
new EnemySpawn(EnemyTypes.healer){{
after = 5;
spacing = 5;
scaling = 1;
amount = 1;
}},
new EnemySpawn(Enemy.class){{
new EnemySpawn(EnemyTypes.standard){{
scaling = 3;
after = 8;
spacing = 4;
tier = 2;
}},
new EnemySpawn(TitanEnemy.class){{
new EnemySpawn(EnemyTypes.titan){{
after = 6;
amount = 2;
spacing = 5;
scaling = 3;
}},
new EnemySpawn(FlamerEnemy.class){{
new EnemySpawn(EnemyTypes.flamer){{
after = 12;
amount = 3;
spacing = 5;
scaling = 3;
}},
new EnemySpawn(EmpEnemy.class){{
new EnemySpawn(EnemyTypes.emp){{
after = 15;
amount = 1;
spacing = 5;
scaling = 2;
}},
new EnemySpawn(BlastEnemy.class){{
new EnemySpawn(EnemyTypes.blast){{
after = 4 + 5 + 5;
amount = 3;
spacing = 5;
@@ -89,14 +85,14 @@ public class WaveCreator{
tierscaleback = 0;
}},
//boss wave
new EnemySpawn(FortressEnemy.class){{
new EnemySpawn(EnemyTypes.fortress){{
after = 16;
amount = 1;
spacing = 5;
scaling = 1;
}},
new EnemySpawn(TitanEnemy.class){{
new EnemySpawn(EnemyTypes.titan){{
after = 16;
amount = 1;
spacing = 5;
@@ -104,7 +100,7 @@ public class WaveCreator{
tierscaleback = 0;
}},
new EnemySpawn(HealerEnemy.class){{
new EnemySpawn(EnemyTypes.healer){{
after = 16;
spacing = 5;
scaling = 2;
@@ -113,14 +109,14 @@ public class WaveCreator{
//end boss wave
//enchanced boss wave
new EnemySpawn(MortarEnemy.class){{
new EnemySpawn(EnemyTypes.mortar){{
after = 16 + 5;
amount = 1;
spacing = 5;
scaling = 3;
}},
new EnemySpawn(EmpEnemy.class){{
new EnemySpawn(EnemyTypes.emp){{
after = 16 + 5;
amount = 1;
spacing = 5;
@@ -129,71 +125,7 @@ public class WaveCreator{
//end enchanced boss wave
);
}
public static Array<EnemySpawn> getSpawnsOld(){
return Array.with(
new EnemySpawn(Enemy.class){{
scaling = 2;
before = 4;
}},
new EnemySpawn(Enemy.class){{
scaling = 3;
tierscaleback = 3;
spacing = 2;
after = 4;
}},
new EnemySpawn(TitanEnemy.class){{
after = 5;
spacing = 2;
scaling = 5;
}},
new EnemySpawn(FortressEnemy.class){{
after = 12;
spacing = 5;
scaling = 7;
}},
new EnemySpawn(HealerEnemy.class){{
scaling = 3;
spacing = 2;
after = 8;
}},
new EnemySpawn(FastEnemy.class){{
after = 2;
scaling = 3;
}},
new EnemySpawn(FlamerEnemy.class){{
after = 14;
spacing = 6;
scaling = 3;
}},
new EnemySpawn(BlastEnemy.class){{
after = 12;
spacing = 2;
scaling = 3;
}},
new EnemySpawn(RapidEnemy.class){{
after = 7;
spacing = 3;
scaling = 3;
}},
new EnemySpawn(EmpEnemy.class){{
after = 19;
spacing = 3;
scaling = 5;
}},
new EnemySpawn(TankEnemy.class){{
after = 4;
spacing = 2;
scaling = 3;
}},
new EnemySpawn(MortarEnemy.class){{
after = 20;
spacing = 3;
scaling = 5;
}}
);
}
public static void testWaves(int from, int to){
Array<EnemySpawn> spawns = getSpawns();
for(int i = from; i <= to; i ++){
@@ -205,7 +137,7 @@ public class WaveCreator{
total += a;
if(a > 0){
System.out.print(a+"x" + ClassReflection.getSimpleName(spawn.type) + "-" + t + " ");
System.out.print(a+"x" + spawn.type.name + "-" + t + " ");
}
}
System.out.print(" (" + total + ")");

View File

@@ -1,52 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.ucore.util.Tmp;
public class BlastEnemy extends Enemy{
public BlastEnemy() {
maxhealth = 30;
speed = 0.7f;
bullet = null;
turretrotatespeed = 0f;
mass = 0.8f;
stopNearCore = false;
heal();
}
void move(){
super.move();
float range = 10f;
Vector2 offset = Tmp.v3.setZero();
if(target instanceof TileEntity){
TileEntity e = (TileEntity)target;
range = (e.tile.block().width * Vars.tilesize) /2f + 8f;
offset.set(e.tile.block().getPlaceOffset());
}
if(target != null && target.distanceTo(this.x - offset.x, this.y - offset.y) < range){
explode();
}
}
@Override
public void onDeath(){
super.onDeath();
explode();
}
void explode(){
Bullet b = new Bullet(BulletType.blast, this, x, y, 0).add();
b.damage = BulletType.blast.damage + (tier-1) * 40;
damage(999);
remove();
}
}

View File

@@ -1,19 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.entities.BulletType;
public class EmpEnemy extends Enemy{
public EmpEnemy() {
speed = 0.3f;
reload = 70;
maxhealth = 210;
range = 80f;
bullet = BulletType.emp;
turretrotatespeed = 0.1f;
heal();
}
}

View File

@@ -1,209 +1,57 @@
package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.reflect.ClassReflection;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Syncable;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.ucore.core.Draw;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.DestructibleEntity;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.Entity;
import io.anuke.ucore.entities.SolidEntity;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Timer;
import io.anuke.ucore.util.Tmp;
import static io.anuke.mindustry.Vars.world;
public class Enemy extends DestructibleEntity implements Syncable{
public final static Color[] tierColors = { Color.valueOf("ffe451"), Color.valueOf("f48e20"), Color.valueOf("ff6757"), Color.valueOf("ff2d86") };
public final static int maxtier = 4;
public final static float maxIdle = 60*1.5f;
public final static float maxIdleLife = 60f*13f; //13 seconds idle = death
protected int timeid;
protected Timer timer = new Timer(5);
protected float speed = 0.4f;
protected float reload = 32;
protected float range = 60;
protected float length = 4;
protected float rotatespeed = 0.1f;
protected float turretrotatespeed = 0.2f;
protected boolean alwaysRotate = false;
protected BulletType bullet = BulletType.small;
protected String shootsound = "enemyshoot";
protected int damage;
protected Enemy spawner;
protected int spawned = 0;
protected boolean targetCore = false;
protected boolean stopNearCore = true;
protected float mass = 1f;
protected String className;
protected Interpolator<Enemy> inter = new Interpolator<>(SyncType.enemy);
protected Interpolator<Enemy> inter = new Interpolator(SyncType.enemy);
public final EnemyType type;
public Timer timer = new Timer(5);
public float idletime = 0f;
public int lane;
public int node = -1;
public Tile[] path;
public Enemy spawner;
public int spawned;
public float angle;
public float xvelocity, yvelocity;
public Vector2 velocity = new Vector2();
public Entity target;
public int tier = 1;
protected final int timerTarget = timeid ++;
protected final int timerReload = timeid ++;
public Enemy() {
hitbox.setSize(5f);
hitboxTile.setSize(4f);
maxhealth = 60;
heal();
className = ClassReflection.getSimpleName(getClass()).toLowerCase();
}
public Interpolator<Enemy> getInterpolator() {
return inter;
}
public float drawSize(){
return 12;
}
void move(){
if(Net.client() && Net.active()){
inter.update(this);
return;
}
Tile core = Vars.control.getCore();
if(idletime > maxIdleLife){
onDeath();
return;
}
boolean nearCore = distanceTo(core.worldx(), core.worldy()) <= range - 18f && stopNearCore;
Vector2 vec;
if(nearCore){
vec = Tmp.v1.setZero();
if(targetCore) target = core.entity;
}else{
vec = Vars.world.pathfinder().find(this);
vec.sub(x, y).limit(speed);
}
Vector2 shift = Tmp.v3.setZero();
float shiftRange = hitbox.width + 2f;
float avoidRange = shiftRange + 4f;
float attractRange = avoidRange + 7f;
float avoidSpeed = this.speed/2.7f;
Entities.getNearby(Vars.control.enemyGroup, x, y, range, other -> {
Enemy enemy = (Enemy)other;
if(other == this) return;
float dst = other.distanceTo(this);
if(dst < shiftRange){
float scl = Mathf.clamp(1.4f - dst / shiftRange) * enemy.mass * 1f/mass;
shift.add((x - other.x) * scl, (y - other.y) * scl);
}else if(dst < avoidRange){
Tmp.v2.set((x - other.x), (y - other.y)).setLength(avoidSpeed);
shift.add(Tmp.v2.scl(1.1f));
}else if(dst < attractRange && !nearCore){
Tmp.v2.set((x - other.x), (y - other.y)).setLength(avoidSpeed);
shift.add(Tmp.v2.scl(-1));
}
});
shift.limit(1f);
vec.add(shift.scl(0.5f));
move(vec.x * Timers.delta(), vec.y * Timers.delta());
updateTargeting(nearCore);
}
void updateTargeting(boolean nearCore){
if(target != null && target instanceof TileEntity && ((TileEntity)target).dead){
target = null;
}
if(timer.get(timerTarget, 15) && !nearCore){
target = Vars.world.findTileTarget(x, y, null, range, false);
//no tile found
if(target == null){
target = Entities.getClosest(Vars.control.playerGroup, x, y, range, e -> true);
}
}else if(nearCore){
target = Vars.control.getCore().entity;
}
if(target != null && bullet != null){
updateShooting();
}
}
void updateShooting(){
if(timer.get(timerReload, reload * Vars.multiplier)){
shoot(bullet);
if(shootsound != null) Effects.sound(shootsound, this);
}
}
void shoot(BulletType bullet){
shoot(bullet, 0);
}
void shoot(BulletType bullet, float rotation){
if(!(Net.active() && Net.client())) {
Angles.translation(angle + rotation, length);
Bullet out = new Bullet(bullet, this, x + Angles.x(), y + Angles.y(), this.angle + rotation).add();
out.damage = (int) (damage * Vars.multiplier);
onShoot(bullet, rotation);
if(Net.active() && Net.server()){
Vars.netServer.handleBullet(bullet, this, x + Angles.x(), y + Angles.y(), this.angle + rotation, (short) (damage * Vars.multiplier));
}
}
}
void onShoot(BulletType type, float rotation){
public Enemy(EnemyType type){
this.type = type;
}
@Override
public void added(){
if(bullet != null){
damage = (int) (bullet.damage * (1 + (tier - 1) * 1f));
}
public void update(){
type.update(this);
}
maxhealth *= tier;
speed += 0.04f * tier /*+ Mathf.range(0.1f)*/;
reload /= Math.max(tier / 1.5f, 1f);
range += tier * 5;
speed = Math.max(speed, 0.07f);
@Override
public void draw(){
type.draw(this);
}
heal();
@Override
public void drawOver(){
type.drawOver(this);
}
@Override
public float drawSize(){
return 14;
}
@Override
@@ -213,90 +61,48 @@ public class Enemy extends DestructibleEntity implements Syncable{
@Override
public void onDeath(){
Effects.effect(Fx.explosion, this);
Effects.shake(3f, 4f, this);
Effects.sound("bang2", this);
remove();
dead = true;
if(Net.active() && Net.server()){
Vars.netServer.handleEnemyDeath(this);
}
type.onDeath(this);
}
@Override
public void removed(){
if(!dead){
if(spawner != null){
spawner.spawned --;
}else{
Vars.control.enemyDeath();
}
}
type.removed(this);
}
@Override
public void update(){
float lastx = x, lasty = y;
public void added(){
hitbox.setSize(type.hitsize);
hitboxTile.setSize(type.hitsizeTile);
maxhealth = type.health * tier;
move();
xvelocity = (x - lastx) / Timers.delta();
yvelocity = (y - lasty) / Timers.delta();
float minv = 0.07f;
if(Vector2.dst(xvelocity, yvelocity, 0, 0) < minv && node > 0 && target == null){
idletime += Timers.delta();
}else{
idletime = 0;
}
Tile tile = world.tileWorld(x, y);
if(tile != null && tile.floor().liquid && tile.block() == Blocks.air){
damage(health+1); //drown
}
if(Float.isNaN(angle)){
angle = 0;
}
if(target == null || alwaysRotate){
angle = Mathf.slerp(angle, 180f+Mathf.atan2(xvelocity, yvelocity), rotatespeed * Timers.delta());
}else{
angle = Mathf.slerp(angle, angleTo(target), turretrotatespeed * Timers.delta());
}
}
@Override
public void draw(){
String region = className + "-t" + Mathf.clamp(tier, 1, 3);
Shaders.outline.color.set(tierColors[tier - 1]);
Shaders.outline.region = Draw.region(region);
Shaders.outline.apply();
Draw.rect(region, x, y, this.angle - 90);
if(Vars.showPaths){
Draw.color(Color.PURPLE);
Draw.line(x, y, x + xvelocity*10f, y + yvelocity*10f);
Draw.color(Color.BLACK, Color.WHITE, idletime / maxIdleLife);
Draw.square(x, y, 7f);
Draw.color();
}
Draw.color();
Graphics.flush();
heal();
}
@Override
public Enemy add(){
return add(Vars.control.enemyGroup);
}
@Override
public Interpolator<Enemy> getInterpolator() {
return inter;
}
public void shoot(BulletType bullet){
shoot(bullet, 0);
}
public void shoot(BulletType bullet, float rotation){
if(!(Net.active() && Net.client())) {
Angles.translation(angle + rotation, type.length);
Bullet out = new Bullet(bullet, this, x + Angles.x(), y + Angles.y(), this.angle + rotation).add();
out.damage = (int) ((bullet.damage * (1 + (tier - 1) * 1f)) * Vars.multiplier);
type.onShoot(this, bullet, rotation);
if(Net.active() && Net.server()){
Vars.netServer.handleBullet(bullet, this, x + Angles.x(), y + Angles.y(), this.angle + rotation, (short)out.damage);
}
}
}
}

View File

@@ -0,0 +1,230 @@
package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.ucore.core.Draw;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Tmp;
import static io.anuke.mindustry.Vars.world;
public class EnemyType {
//TODO documentation, comments
private static byte lastid = 0;
private static Array<EnemyType> types = new Array<>();
public final static Color[] tierColors = { Color.valueOf("ffe451"), Color.valueOf("f48e20"), Color.valueOf("ff6757"), Color.valueOf("ff2d86") };
public final static int maxtier = 4;
public final static float maxIdle = 60*1.5f;
public final static float maxIdleLife = 60f*13f; //13 seconds idle = death
public final String name;
public final byte id;
protected int timeid;
protected int health = 60;
protected float hitsize = 5f;
protected float hitsizeTile = 4f;
protected float speed = 0.4f;
protected float reload = 32;
protected float range = 60;
protected float length = 4;
protected float rotatespeed = 0.1f;
protected float turretrotatespeed = 0.2f;
protected boolean alwaysRotate = false;
protected BulletType bullet = BulletType.small;
protected String shootsound = "enemyshoot";
protected boolean targetCore = false;
protected boolean stopNearCore = true;
protected float mass = 1f;
protected final int timerTarget = timeid ++;
protected final int timerReload = timeid ++;
public EnemyType(String name){
this.id = lastid++;
this.name = name;
types.add(this);
}
public void draw(Enemy enemy){
String region = name + "-t" + Mathf.clamp(enemy.tier, 1, 3);
Shaders.outline.color.set(tierColors[enemy.tier - 1]);
Shaders.outline.region = Draw.region(region);
Shaders.outline.apply();
Draw.rect(region, enemy.x, enemy.y, enemy.angle - 90);
Draw.color();
Graphics.flush();
}
public void drawOver(Enemy enemy){ }
public void update(Enemy enemy){
float lastx = enemy.x, lasty = enemy.y;
move(enemy);
enemy.velocity.set(enemy.x - lastx, enemy.y - lasty).scl(1f / Timers.delta());
float minv = 0.07f;
if(enemy.velocity.len() < minv && enemy.node > 0 && enemy.target == null){
enemy.idletime += Timers.delta();
}else{
enemy.idletime = 0;
}
Tile tile = world.tileWorld(enemy.x, enemy.y);
if(tile != null && tile.floor().liquid && tile.block() == Blocks.air){
enemy.damage(enemy.health+1); //drown
}
if(Float.isNaN(enemy.angle)){
enemy.angle = 0;
}
if(enemy.target == null || alwaysRotate){
enemy.angle = Mathf.slerp(enemy.angle, 180f + enemy.velocity.angle(), rotatespeed * Timers.delta());
}else{
enemy.angle = Mathf.slerp(enemy.angle, enemy.angleTo(enemy.target), turretrotatespeed * Timers.delta());
}
}
public void move(Enemy enemy){
float speed = this.speed + 0.04f * enemy.tier;
float range = this.range + enemy.tier * 5;
if(Net.client() && Net.active()){
enemy.inter.update(enemy); //TODO? better structure for interpolation
return;
}
Tile core = Vars.control.getCore();
if(enemy.idletime > maxIdleLife){
enemy.onDeath();
return;
}
boolean nearCore = enemy.distanceTo(core.worldx(), core.worldy()) <= range - 18f && stopNearCore;
Vector2 vec;
if(nearCore){
vec = Tmp.v1.setZero();
if(targetCore) enemy.target = core.entity;
}else{
vec = Vars.world.pathfinder().find(enemy);
vec.sub(enemy.x, enemy.y).limit(speed);
}
Vector2 shift = Tmp.v3.setZero();
float shiftRange = enemy.hitbox.width + 2f;
float avoidRange = shiftRange + 4f;
float attractRange = avoidRange + 7f;
float avoidSpeed = this.speed/2.7f;
Entities.getNearby(Vars.control.enemyGroup, enemy.x, enemy.y, range, en -> {
Enemy other = (Enemy)en;
if(other == enemy) return;
float dst = other.distanceTo(enemy);
if(dst < shiftRange){
float scl = Mathf.clamp(1.4f - dst / shiftRange) * mass * 1f/mass;
shift.add((enemy.x - other.x) * scl, (enemy.y - other.y) * scl);
}else if(dst < avoidRange){
Tmp.v2.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(Tmp.v2.scl(1.1f));
}else if(dst < attractRange && !nearCore){
Tmp.v2.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed);
shift.add(Tmp.v2.scl(-1));
}
});
shift.limit(1f);
vec.add(shift.scl(0.5f));
enemy.move(vec.x * Timers.delta(), vec.y * Timers.delta());
updateTargeting(enemy, nearCore);
}
public void updateTargeting(Enemy enemy, boolean nearCore){
if(enemy.target != null && enemy.target instanceof TileEntity && ((TileEntity)enemy.target).dead){
enemy.target = null;
}
if(enemy.timer.get(timerTarget, 15) && !nearCore){
enemy.target = Vars.world.findTileTarget(enemy.x, enemy.y, null, range, false);
//no tile found
if(enemy.target == null){
enemy.target = Entities.getClosest(Vars.control.playerGroup, enemy.x, enemy.y, range, e -> true);
}
}else if(nearCore){
enemy.target = Vars.control.getCore().entity;
}
if(enemy.target != null && bullet != null){
updateShooting(enemy);
}
}
public void updateShooting(Enemy enemy){
float reload = this.reload / Math.max(enemy.tier / 1.5f, 1f);
if(enemy.timer.get(timerReload, reload * Vars.multiplier)){
shoot(enemy);
}
}
public void shoot(Enemy enemy){
enemy.shoot(bullet);
if(shootsound != null) Effects.sound(shootsound, enemy);
}
public void onShoot(Enemy enemy, BulletType type, float rotation){}
public void onDeath(Enemy enemy){
Effects.effect(Fx.explosion, enemy);
Effects.shake(3f, 4f, enemy);
Effects.sound("bang2", enemy);
enemy.remove();
enemy.dead = true;
if(Net.active() && Net.server()){
Vars.netServer.handleEnemyDeath(enemy);
}
}
public void removed(Enemy enemy){
if(!enemy.dead){
if(enemy.spawner != null){
enemy.spawner.spawned --;
}else{
Vars.control.enemyDeath();
}
}
}
public static EnemyType getByID(byte id){
return types.get(id);
}
}

View File

@@ -0,0 +1,32 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.entities.enemies.types.BlastEnemy;
import io.anuke.mindustry.entities.enemies.types.EmpEnemy;
import io.anuke.mindustry.entities.enemies.types.FastEnemy;
import io.anuke.mindustry.entities.enemies.types.FlamerEnemy;
import io.anuke.mindustry.entities.enemies.types.FortressEnemy;
import io.anuke.mindustry.entities.enemies.types.HealerEnemy;
import io.anuke.mindustry.entities.enemies.types.MortarEnemy;
import io.anuke.mindustry.entities.enemies.types.RapidEnemy;
import io.anuke.mindustry.entities.enemies.types.*;
import io.anuke.mindustry.entities.enemies.types.TankEnemy;
import io.anuke.mindustry.entities.enemies.types.TargetEnemy;
import io.anuke.mindustry.entities.enemies.types.TitanEnemy;
public class EnemyTypes {
public static final EnemyType
standard = new StandardEnemy(),
fast = new FastEnemy(),
rapid = new RapidEnemy(),
flamer = new FlamerEnemy(),
tank = new TankEnemy(),
blast = new BlastEnemy(),
mortar = new MortarEnemy(),
healer = new HealerEnemy(),
titan = new TitanEnemy(),
emp = new EmpEnemy(),
fortress = new FortressEnemy(),
target = new TargetEnemy();
}

View File

@@ -1,15 +0,0 @@
package io.anuke.mindustry.entities.enemies;
public class FastEnemy extends Enemy{
public FastEnemy() {
speed = 0.73f;
reload = 25;
mass = 0.2f;
maxhealth = 40;
heal();
}
}

View File

@@ -1,62 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Angles;
public class FortressEnemy extends Enemy{
static int maxSpawn = 6;
float spawnTime = 190;
boolean deployed;
public FortressEnemy() {
speed = 0.25f;
reload = 90;
maxhealth = 700;
range = 70f;
bullet = BulletType.yellowshell;
hitbox.setSize(10f);
turretrotatespeed = rotatespeed = 0.08f;
length = 7f;
mass = 7f;
heal();
}
@Override
public void move(){
super.move();
if(deployed){
if(Timers.get(this, "spawn", spawnTime) && spawned < maxSpawn){
Angles.translation(angle, 20f);
FastEnemy enemy = new FastEnemy();
enemy.lane = lane;
enemy.tier = this.tier;
enemy.spawner = this;
enemy.set(x + Angles.x(), y + Angles.y());
Effects.effect(Fx.spawn, enemy);
enemy.add();
spawned ++;
}
}else if(distanceTo(Vars.control.getCore().worldx(),
Vars.control.getCore().worldy()) <= 90f){
deployed = true;
speed = 0.001f;
}
}
void onShoot(BulletType type, float rotation){
Effects.effect(Fx.largeCannonShot, x + Angles.x(), y + Angles.y(), angle);
Effects.shake(3f, 3f, this);
}
}

View File

@@ -1,91 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.math.MathUtils;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.ucore.core.*;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.graphics.Hue;
import io.anuke.ucore.util.Angles;
public class HealerEnemy extends Enemy{
public HealerEnemy() {
speed = 0.25f;
reload = 10;
maxhealth = 200;
bullet = BulletType.shot;
range = 40f;
alwaysRotate = false;
targetCore = false;
stopNearCore = true;
mass = 1.1f;
heal();
}
@Override
void move(){
super.move();
if(idletime > 60f*3){ //explode after 3 seconds of stillness
explode();
Effects.effect(Fx.shellexplosion, this);
Effects.effect(Fx.shellsmoke, this);
}
}
@Override
void updateTargeting(boolean nearCore){
if(timer.get(timerTarget, 15)){
target = Entities.getClosest(Vars.control.enemyGroup,
x, y, range, e -> e instanceof Enemy && e != this && ((Enemy)e).healthfrac() < 1f);
}
if(target != null){
updateShooting();
}
}
@Override
void updateShooting(){
Enemy enemy = (Enemy)target;
if(enemy.health < enemy.maxhealth && Timers.get(this, "heal", reload)){
enemy.health ++;
idletime = 0;
}
}
@Override
public void drawOver(){
super.drawOver();
Enemy enemy = (Enemy)target;
if(enemy == null) return;
Angles.translation(this.angleTo(enemy), 5f);
Graphics.shader();
if(enemy != null && enemy.health < enemy.maxhealth){
Draw.color(Hue.rgb(138, 244, 138, (MathUtils.sin(Timers.time()) + 1f) / 13f));
Draw.alpha(0.9f);
Draw.laser("laser", "laserend", x + Angles.x(), y + Angles.y(), enemy.x - Angles.x()/1.5f, enemy.y - Angles.y()/1.5f);
Draw.color();
}
Graphics.shader(Shaders.outline);
}
void explode(){
Bullet b = new Bullet(BulletType.blast, this, x, y, 0).add();
b.damage = BulletType.blast.damage + (tier-1) * 30;
damage(999);
remove();
}
}

View File

@@ -1,21 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.entities.BulletType;
public class RapidEnemy extends Enemy{
public RapidEnemy() {
reload = 8;
bullet = BulletType.purple;
rotatespeed = 0.08f;
maxhealth = 260;
speed = 0.33f;
heal();
hitbox.setSize(8f);
mass = 3f;
range = 70;
}
}

View File

@@ -1,32 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.ucore.util.Angles;
public class TankEnemy extends Enemy{
public TankEnemy() {
maxhealth = 350;
speed = 0.24f;
reload = 90f;
rotatespeed = 0.06f;
bullet = BulletType.small;
length = 3f;
mass = 1.4f;
}
void shoot(){
Angles.translation(angle, 8f);
Angles.shotgun(3, 8f, angle, f->{
Bullet out = new Bullet(bullet, this, x+vector.x, y+vector.y, f).add();
out.damage = (int)(damage*Vars.multiplier);
});
}
}

View File

@@ -1,55 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.graphics.Color;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.ucore.core.Draw;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Mathf;
public class TargetEnemy extends Enemy{
public TargetEnemy(){
speed = 0f;
maxhealth = 25;
shootsound = null;
}
@Override
void move(){
speed = 0f;
super.move();
}
@Override
void shoot(BulletType bullet){
//do nothing
}
@Override
public void removed(){
//don't call enemy death since this is only a target
}
@Override
public void draw(){
super.draw();
Draw.color(Color.YELLOW);
if(Vars.control.getTutorial().showTarget()){
Draw.spikes(x, y, 11f + Mathf.sin(Timers.time(), 7f, 1f), 4f, 8, Timers.time());
}
Draw.color();
}
@Override
public void onDeath(){
super.onDeath();
Timers.run(100f, ()->{
new TargetEnemy().set(x, y).add();
});
}
}

View File

@@ -1,20 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.ucore.core.Timers;
public class TestEnemy extends Enemy{
boolean dir = false;
public TestEnemy() {
maxhealth = 99999;
heal();
}
void move(){
if(Timers.get(this, "switch", 300)){
dir = !dir;
}
move(dir ? -0.3f * Timers.delta() : 0.3f * Timers.delta(), 0);
}
}

View File

@@ -1,50 +0,0 @@
package io.anuke.mindustry.entities.enemies;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
public class TitanEnemy extends Enemy{
public TitanEnemy() {
speed = 0.26f;
reload = 30;
maxhealth = 430;
range = 60f;
bullet = BulletType.small;
hitbox.setSize(7f);
mass = 4f;
heal();
Timers.reset(this, "salvo", 0);
Timers.reset(this, "shotgun", 0);
Timers.reset(this, "circle", 0);
}
@Override
void updateShooting(){
Timers.get(this, "salvo", 240);
if(Timers.getTime(this, "salvo") < 60){
if(Timers.get(this, "salvoShoot", 6)){
shoot(BulletType.flame, Mathf.range(20f));
}
}
if(Timers.get(this, "shotgun", 80)){
Angles.shotgun(5, 10f, 0f, f->{
shoot(BulletType.smallSlow, f);
});
}
if(Timers.get(this, "circle", 200)){
Angles.circle(8, f->{
shoot(BulletType.smallSlow, f);
});
}
}
}

View File

@@ -1,11 +0,0 @@
package io.anuke.mindustry.entities.enemies.flying;
import io.anuke.mindustry.entities.enemies.Enemy;
public class FlyingEnemy extends Enemy{
public FlyingEnemy() {
}
}

View File

@@ -0,0 +1,56 @@
package io.anuke.mindustry.entities.enemies.types;
import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.ucore.util.Tmp;
public class BlastEnemy extends EnemyType {
public BlastEnemy() {
super("blastenemy");
health = 30;
speed = 0.7f;
bullet = null;
turretrotatespeed = 0f;
mass = 0.8f;
stopNearCore = false;
}
@Override
public void move(Enemy enemy){
super.move(enemy);
float range = 10f;
Vector2 offset = Tmp.v3.setZero();
if(enemy.target instanceof TileEntity){
TileEntity e = (TileEntity)enemy.target;
range = (e.tile.block().width * Vars.tilesize) /2f + 8f;
offset.set(e.tile.block().getPlaceOffset());
}
if(enemy.target != null && enemy.target.distanceTo(enemy.x - offset.x, enemy.y - offset.y) < range){
explode(enemy);
}
}
@Override
public void onDeath(Enemy enemy){
super.onDeath(enemy);
explode(enemy);
}
void explode(Enemy enemy){
Bullet b = new Bullet(BulletType.blast, enemy, enemy.x, enemy.y, 0).add();
b.damage = BulletType.blast.damage + (enemy.tier-1) * 40;
enemy.damage(999);
enemy.remove();
}
}

View File

@@ -0,0 +1,19 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class EmpEnemy extends EnemyType {
public EmpEnemy() {
super("empenemy");
speed = 0.3f;
reload = 70;
health = 210;
range = 80f;
bullet = BulletType.emp;
turretrotatespeed = 0.1f;
}
}

View File

@@ -0,0 +1,17 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class FastEnemy extends EnemyType {
public FastEnemy() {
super("fastenemy");
speed = 0.73f;
reload = 25;
mass = 0.2f;
health = 40;
}
}

View File

@@ -1,22 +1,20 @@
package io.anuke.mindustry.entities.enemies;
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class FlamerEnemy extends Enemy{
public class FlamerEnemy extends EnemyType {
public FlamerEnemy() {
super("flamerenemy");
speed = 0.35f;
maxhealth = 150;
health = 150;
reload = 6;
bullet = BulletType.flameshot;
shootsound = "flame";
mass = 1.5f;
range = 40;
heal();
}
}

View File

@@ -0,0 +1,61 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.mindustry.entities.enemies.EnemyTypes;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Angles;
public class FortressEnemy extends EnemyType {
final int maxSpawn = 6;
final float spawnTime = 190;
public FortressEnemy() {
super("fortressenemy");
speed = 0.25f;
reload = 90;
health = 700;
range = 70f;
bullet = BulletType.yellowshell;
hitsize = 10f;
turretrotatespeed = rotatespeed = 0.08f;
length = 7f;
mass = 7f;
}
@Override
public void move(Enemy enemy){
if(enemy.distanceTo(Vars.control.getCore().worldx(),
Vars.control.getCore().worldy()) <= 90f){
if(Timers.get(this, "spawn", spawnTime) && enemy.spawned < maxSpawn){
Angles.translation(enemy.angle, 20f);
Enemy s = new Enemy(EnemyTypes.fast); //TODO assign type!
s.lane = enemy.lane;
s.tier = enemy.tier;
s.spawner = enemy;
s.set(enemy.x + Angles.x(), enemy.y + Angles.y());
s.add();
Effects.effect(Fx.spawn, enemy);
enemy.spawned ++;
}
}else {
super.move(enemy);
}
}
public void onShoot(Enemy enemy, BulletType type, float rotation){
Effects.effect(Fx.largeCannonShot, enemy.x + Angles.x(), enemy.y + Angles.y(), enemy.angle);
Effects.shake(3f, 3f, enemy);
}
}

View File

@@ -0,0 +1,91 @@
package io.anuke.mindustry.entities.enemies.types;
import com.badlogic.gdx.math.MathUtils;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.ucore.core.*;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.graphics.Hue;
import io.anuke.ucore.util.Angles;
public class HealerEnemy extends EnemyType {
public HealerEnemy() {
super("healerenemy");
speed = 0.25f;
reload = 10;
health = 200;
bullet = BulletType.shot;
range = 40f;
alwaysRotate = false;
targetCore = false;
stopNearCore = true;
mass = 1.1f;
}
@Override
public void move(Enemy enemy){
super.move(enemy);
if(enemy.idletime > 60f*3){ //explode after 3 seconds of stillness
explode(enemy);
Effects.effect(Fx.shellexplosion, enemy);
Effects.effect(Fx.shellsmoke, enemy);
}
}
@Override
public void updateTargeting(Enemy enemy, boolean nearCore){
if(enemy.timer.get(timerTarget, 15)){
enemy.target = Entities.getClosest(Vars.control.enemyGroup,
enemy.x, enemy.y, range, e -> e instanceof Enemy && e != enemy && ((Enemy)e).healthfrac() < 1f);
}
if(enemy.target != null){
updateShooting(enemy);
}
}
@Override
public void updateShooting(Enemy enemy){
Enemy target = (Enemy)enemy.target;
if(target.health < target.maxhealth && enemy.timer.get(timerReload, reload)){
target.health ++;
enemy.idletime = 0;
}
}
@Override
public void drawOver(Enemy enemy){
Enemy target = (Enemy)enemy.target;
if(target == null) return;
Angles.translation(enemy.angleTo(target), 5f);
Graphics.shader();
if(target.health < target.maxhealth){
Draw.color(Hue.rgb(138, 244, 138, (MathUtils.sin(Timers.time()) + 1f) / 13f));
Draw.alpha(0.9f);
Draw.laser("laser", "laserend", enemy.x + Angles.x(), enemy.y + Angles.y(), target.x - Angles.x()/1.5f, target.y - Angles.y()/1.5f);
Draw.color();
}
Graphics.shader(Shaders.outline);
}
void explode(Enemy enemy){
Bullet b = new Bullet(BulletType.blast, enemy, enemy.x, enemy.y, 0).add();
b.damage = BulletType.blast.damage + (enemy.tier-1) * 30;
enemy.damage(999);
enemy.remove();
}
}

View File

@@ -1,12 +1,14 @@
package io.anuke.mindustry.entities.enemies;
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class MortarEnemy extends Enemy{
public class MortarEnemy extends EnemyType {
public MortarEnemy() {
super("mortarenemy");
maxhealth = 200;
health = 200;
speed = 0.25f;
reload = 100f;
bullet = BulletType.shell;
@@ -14,8 +16,6 @@ public class MortarEnemy extends Enemy{
rotatespeed = 0.05f;
range = 120f;
mass = 1.2f;
heal();
}
}

View File

@@ -0,0 +1,21 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class RapidEnemy extends EnemyType {
public RapidEnemy() {
super("rapidenemy");
reload = 8;
bullet = BulletType.purple;
rotatespeed = 0.08f;
health = 260;
speed = 0.33f;
hitsize = 8f;
mass = 3f;
range = 70;
}
}

View File

@@ -0,0 +1,10 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.enemies.EnemyType;
public class StandardEnemy extends EnemyType {
public StandardEnemy(){
super("standardenemy");
}
}

View File

@@ -0,0 +1,33 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.ucore.util.Angles;
public class TankEnemy extends EnemyType {
public TankEnemy() {
super("tankenemy");
health = 350;
speed = 0.24f;
reload = 90f;
rotatespeed = 0.06f;
bullet = BulletType.small;
length = 3f;
mass = 1.4f;
}
@Override
public void shoot(Enemy enemy){
super.shoot(enemy);
Angles.translation(enemy.angle, 8f);
Angles.shotgun(3, 8f, enemy.angle, f -> {
enemy.shoot(bullet, f);
});
}
}

View File

@@ -0,0 +1,58 @@
package io.anuke.mindustry.entities.enemies.types;
import com.badlogic.gdx.graphics.Color;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.mindustry.entities.enemies.EnemyTypes;
import io.anuke.ucore.core.Draw;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Mathf;
public class TargetEnemy extends EnemyType {
public TargetEnemy(){
super("targetenemy");
speed = 0f;
health = 25;
shootsound = null;
}
@Override
public void move(Enemy enemy){
super.move(enemy);
}
@Override
public void shoot(Enemy enemy){
//do nothing
}
@Override
public void removed(Enemy enemy){
//don't call enemy death since this is only a target
}
@Override
public void draw(Enemy enemy){
super.draw(enemy);
Draw.color(Color.YELLOW);
if(Vars.control.getTutorial().showTarget()){
Draw.spikes(enemy.x, enemy.y, 11f + Mathf.sin(Timers.time(), 7f, 1f), 4f, 8, Timers.time());
}
Draw.color();
}
@Override
public void onDeath(Enemy enemy){
super.onDeath(enemy);
Timers.run(100f, ()->{
new Enemy(EnemyTypes.target).set(enemy.x, enemy.y).add();
});
}
}

View File

@@ -0,0 +1,47 @@
package io.anuke.mindustry.entities.enemies.types;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
public class TitanEnemy extends EnemyType {
public TitanEnemy() {
super("titanenemy");
speed = 0.26f;
reload = 30;
health = 430;
range = 60f;
bullet = BulletType.small;
hitsize = 7f;
mass = 4f;
}
@Override
public void updateShooting(Enemy enemy){
Timers.get(enemy, "salvo", 240);
if(Timers.getTime(enemy, "salvo") < 60){
if(Timers.get(enemy, "salvoShoot", 6)){
enemy.shoot(BulletType.flame, Mathf.range(20f));
}
}
if(Timers.get(enemy, "shotgun", 80)){
Angles.shotgun(5, 10f, 0f, f->{
enemy.shoot(BulletType.smallSlow, f);
});
}
if(Timers.get(enemy, "circle", 200)){
Angles.circle(8, f->{
enemy.shoot(BulletType.smallSlow, f);
});
}
}
}