Merge remote-tracking branch 'origin/4.0' into 4.0

# Conflicts:
#	core/src/io/anuke/mindustry/Vars.java
This commit is contained in:
Anuken
2018-07-13 19:54:20 -04:00
433 changed files with 25227 additions and 23221 deletions

View File

@@ -24,84 +24,90 @@ import io.anuke.ucore.util.Translator;
import static io.anuke.mindustry.Vars.*;
/**Utility class for damaging in an area.*/
public class Damage {
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Translator tr = new Translator();
/**
* Utility class for damaging in an area.
*/
public class Damage{
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Translator tr = new Translator();
/**Creates a dynamic explosion based on specified parameters.*/
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i ++){
int branches = 5 + Mathf.clamp((int)(power/30), 1, 20);
Timers.run(i*2f + Mathf.random(4f), () -> Lightning.create(Team.none, Fx.none, Palette.power, 3,
x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
/**
* Creates a dynamic explosion based on specified parameters.
*/
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int) (power / 30), 1, 20);
Timers.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.none, Fx.none, Palette.power, 3,
x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i ++){
Timers.run(i/2, () -> CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Timers.run(i / 2, () -> CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f)));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
int waves = Mathf.clamp((int) (explosiveness / 4), 0, 30);
for(int i = 0; i < waves; i ++){
int f = i;
Timers.run(i*2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f)/waves), explosiveness/2f);
Effects.effect(ExplosionFx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius));
});
}
for(int i = 0; i < waves; i++){
int f = i;
Timers.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Effects.effect(ExplosionFx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius));
});
}
if(explosiveness > 15f){
Effects.effect(ExplosionFx.shockwave, x, y);
}
if(explosiveness > 15f){
Effects.effect(ExplosionFx.shockwave, x, y);
}
if(explosiveness > 30f){
Effects.effect(ExplosionFx.bigShockwave, x, y);
}
if(explosiveness > 30f){
Effects.effect(ExplosionFx.bigShockwave, x, y);
}
float shake = Math.min(explosiveness/4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effects.effect(ExplosionFx.blockExplosion, x, y);
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effects.effect(ExplosionFx.blockExplosion, x, y);
}
public static void createIncend(float x, float y, float range, int amount){
for (int i = 0; i < amount; i++) {
float cx = x + Mathf.range(range);
float cy = y + Mathf.range(range);
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Fire.create(tile);
}
}
}
public static void createIncend(float x, float y, float range, int amount){
for(int i = 0; i < amount; i++){
float cx = x + Mathf.range(range);
float cy = y + Mathf.range(range);
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Fire.create(tile);
}
}
}
/**Damages entities in a line.
* Only enemies of the specified team are damaged.*/
public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){
tr.trns(angle, length);
rect.setPosition(x, y).setSize(tr.x, tr.y);
float x2 = tr.x + x, y2 = tr.y + y;
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){
tr.trns(angle, length);
rect.setPosition(x, y).setSize(tr.x, tr.y);
float x2 = tr.x + x, y2 = tr.y + y;
if(rect.width < 0){
rect.x += rect.width;
rect.width *= -1;
}
if(rect.width < 0){
rect.x += rect.width;
rect.width *= -1;
}
if(rect.height < 0){
rect.y += rect.height;
rect.height *= -1;
}
if(rect.height < 0){
rect.y += rect.height;
rect.height *= -1;
}
float expand = 3f;
float expand = 3f;
rect.y -= expand;
rect.x -= expand;
rect.width += expand*2;
rect.height += expand*2;
rect.y -= expand;
rect.x -= expand;
rect.width += expand * 2;
rect.height += expand * 2;
Consumer<Unit> cons = e -> {
e.getHitbox(hitrect);
e.getHitbox(hitrect);
Rectangle other = hitrect;
other.y -= expand;
other.x -= expand;
@@ -110,79 +116,85 @@ public class Damage {
Vector2 vec = Physics.raycastRect(x, y, x2, y2, other);
if (vec != null) {
if(vec != null){
Effects.effect(effect, vec.x, vec.y);
e.collision(hitter, vec.x, vec.y);
hitter.collision(e, vec.x, vec.y);
}
};
Units.getNearbyEnemies(team, rect, cons);
}
Units.getNearbyEnemies(team, rect, cons);
}
/**Damages all entities and blocks in a radius that are enemies of the team.*/
public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate<Unit> predicate, Consumer<Unit> acceptor) {
Consumer<Unit> cons = entity -> {
if(!predicate.test(entity)) return;
/**
* Damages all entities and blocks in a radius that are enemies of the team.
*/
public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate<Unit> predicate, Consumer<Unit> acceptor){
Consumer<Unit> cons = entity -> {
if(!predicate.test(entity)) return;
entity.getHitbox(hitrect);
if (!hitrect.overlaps(rect)) {
return;
}
entity.damage(damage);
acceptor.accept(entity);
};
entity.getHitbox(hitrect);
if(!hitrect.overlaps(rect)){
return;
}
entity.damage(damage);
acceptor.accept(entity);
};
rect.setSize(size * 2).setCenter(x, y);
if (team != null) {
Units.getNearbyEnemies(team, rect, cons);
} else {
Units.getNearby(rect, cons);
}
}
rect.setSize(size * 2).setCenter(x, y);
if(team != null){
Units.getNearbyEnemies(team, rect, cons);
}else{
Units.getNearby(rect, cons);
}
}
/**Damages everything in a radius.*/
public static void damage(float x, float y, float radius, float damage){
damage(null, x, y, radius, damage);
}
/**
* Damages everything in a radius.
*/
public static void damage(float x, float y, float radius, float damage){
damage(null, x, y, radius, damage);
}
/**Damages all entities and blocks in a radius that are enemies of the team.*/
public static void damage(Team team, float x, float y, float radius, float damage){
Consumer<Unit> cons = entity -> {
if(entity.team == team || entity.distanceTo(x, y) > radius){
return;
}
float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage);
entity.damage(amount);
//TODO better velocity displacement
float dst = tr.set(entity.x - x, entity.y - y).len();
entity.getVelocity().add(tr.setLength((1f-dst/radius) * 2f));
};
/**
* Damages all entities and blocks in a radius that are enemies of the team.
*/
public static void damage(Team team, float x, float y, float radius, float damage){
Consumer<Unit> cons = entity -> {
if(entity.team == team || entity.distanceTo(x, y) > radius){
return;
}
float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage);
entity.damage(amount);
//TODO better velocity displacement
float dst = tr.set(entity.x - x, entity.y - y).len();
entity.getVelocity().add(tr.setLength((1f - dst / radius) * 2f));
};
rect.setSize(radius *2).setCenter(x, y);
if(team != null) {
Units.getNearbyEnemies(team, rect, cons);
}else{
Units.getNearby(rect, cons);
}
rect.setSize(radius * 2).setCenter(x, y);
if(team != null){
Units.getNearbyEnemies(team, rect, cons);
}else{
Units.getNearby(rect, cons);
}
int trad = (int)(radius / tilesize);
for(int dx = -trad; dx <= trad; dx ++){
for(int dy= -trad; dy <= trad; dy ++){
Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy);
if(tile != null && tile.entity != null && (team == null || state.teams.areEnemies(team, tile.getTeam())) && Vector2.dst(dx, dy, 0, 0) <= trad){
float amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage);
tile.entity.damage(amount);
}
}
}
int trad = (int) (radius / tilesize);
for(int dx = -trad; dx <= trad; dx++){
for(int dy = -trad; dy <= trad; dy++){
Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy);
if(tile != null && tile.entity != null && (team == null || state.teams.areEnemies(team, tile.getTeam())) && Vector2.dst(dx, dy, 0, 0) <= trad){
float amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage);
tile.entity.damage(amount);
}
}
}
}
private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){
float dist = Vector2.dst(x, y, tx, ty);
float falloff = 0.4f;
float scaled = Mathf.lerp(1f - dist/radius, 1f, falloff);
return damage * scaled;
}
}
private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){
float dist = Vector2.dst(x, y, tx, ty);
float falloff = 0.4f;
float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff);
return damage * scaled;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,16 @@ import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.ucore.util.Mathf;
/**Class for predicting shoot angles based on velocities of targets.*/
public class Predict {
/**
* Class for predicting shoot angles based on velocities of targets.
*/
public class Predict{
private static Vector2 vec = new Vector2();
private static Vector2 vresult = new Vector2();
/**Calculates of intercept of a stationary and moving target. Do not 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
@@ -17,52 +21,55 @@ public class Predict {
* @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) {
* @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;
// Get quadratic equation components
float a = dstvx * dstvx + dstvy * dstvy - v*v;
float a = dstvx * dstvx + dstvy * dstvy - v * v;
float b = 2 * (dstvx * tx + dstvy * ty);
float c = tx*tx + ty*ty;
float c = tx * tx + ty * ty;
// Solve quadratic
Vector2 ts = quad(a, b, c);
// Find smallest positive solution
Vector2 sol = vresult.set(0, 0);
if (ts != null) {
if(ts != null){
float t0 = ts.x, t1 = ts.y;
float t = Math.min(t0, t1);
if (t < 0) t = Math.max(t0, t1);
if (t > 0) {
sol.set(dstx + dstvx*t, dsty + dstvy*t);
if(t < 0) t = Math.max(t0, t1);
if(t > 0){
sol.set(dstx + dstvx * t, dsty + dstvy * t);
}
}
return sol;
}
/**See {@link #intercept(float, float, float, float, float, float, float)}.*/
public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v) {
/**
* See {@link #intercept(float, float, float, float, float, float, float)}.
*/
public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getVelocity().x - src.getVelocity().x, dst.getVelocity().x - src.getVelocity().y, v);
}
private static Vector2 quad(float a, float b, float c) {
private static Vector2 quad(float a, float b, float c){
Vector2 sol = null;
if (Math.abs(a) < 1e-6) {
if (Math.abs(b) < 1e-6) {
sol = Math.abs(c) < 1e-6 ? vec.set(0,0) : null;
} else {
vec.set(-c/b, -c/b);
if(Math.abs(a) < 1e-6){
if(Math.abs(b) < 1e-6){
sol = Math.abs(c) < 1e-6 ? vec.set(0, 0) : null;
}else{
vec.set(-c / b, -c / b);
}
} else {
float disc = b*b - 4*a*c;
if (disc >= 0) {
}else{
float disc = b * b - 4 * a * c;
if(disc >= 0){
disc = Mathf.sqrt(disc);
a = 2*a;
sol = vec.set((-b-disc)/a, (-b+disc)/a);
a = 2 * a;
sol = vec.set((-b - disc) / a, (-b + disc) / a);
}
}
return sol;

View File

@@ -12,7 +12,9 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**Class for controlling status effects on an entity.*/
/**
* Class for controlling status effects on an entity.
*/
public class StatusController implements Saveable{
private static final StatusEntry globalResult = new StatusEntry();
private static final Array<StatusEntry> removals = new ThreadArray<>();
@@ -26,20 +28,20 @@ public class StatusController implements Saveable{
public void handleApply(Unit unit, StatusEffect effect, float intensity){
if(effect == StatusEffects.none) return; //don't apply empty effects
float newTime = effect.baseDuration*intensity;
float newTime = effect.baseDuration * intensity;
if(statuses.size > 0){
//check for opposite effects
for(StatusEntry entry : statuses){
//extend effect
if(entry.effect == effect) {
if(entry.effect == effect){
entry.time = Math.max(entry.time, newTime);
return;
}else if(entry.effect.isOpposite(effect)){ //find opposite
entry.effect.getTransition(unit, effect, entry.time, newTime, globalResult);
entry.time = globalResult.time;
if (globalResult.effect != entry.effect) {
if(globalResult.effect != entry.effect){
entry.effect.onTransition(unit, globalResult.effect);
entry.effect = globalResult.effect;
}
@@ -94,7 +96,7 @@ public class StatusController implements Saveable{
return damageMultiplier;
}
public float getArmorMultiplier() {
public float getArmorMultiplier(){
return armorMultiplier;
}
@@ -106,24 +108,24 @@ public class StatusController implements Saveable{
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
stream.writeByte(statuses.size);
for(StatusEntry entry : statuses){
stream.writeByte(entry.effect.id);
stream.writeShort((short)(entry.time * 2));
stream.writeShort((short) (entry.time * 2));
}
}
@Override
public void readSave(DataInput stream) throws IOException {
for (StatusEntry effect : statuses){
public void readSave(DataInput stream) throws IOException{
for(StatusEntry effect : statuses){
Pooling.free(effect);
}
statuses.clear();
byte amount = stream.readByte();
for (int i = 0; i < amount; i++) {
for(int i = 0; i < amount; i++){
byte id = stream.readByte();
float time = stream.readShort() / 2f;
StatusEntry entry = Pooling.obtain(StatusEntry.class);
@@ -132,7 +134,7 @@ public class StatusController implements Saveable{
}
}
public static class StatusEntry {
public static class StatusEntry{
public StatusEffect effect;
public float time;

View File

@@ -1,6 +1,9 @@
package io.anuke.mindustry.entities;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.mindustry.content.fx.Fx;
@@ -10,11 +13,14 @@ import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.CallBlocks;
import io.anuke.mindustry.net.In;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Edges;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Wall;
import io.anuke.mindustry.world.blocks.modules.InventoryModule;
import io.anuke.mindustry.world.blocks.modules.LiquidModule;
import io.anuke.mindustry.world.blocks.modules.PowerModule;
import io.anuke.mindustry.world.consumers.Consume;
import io.anuke.mindustry.world.modules.ConsumeModule;
import io.anuke.mindustry.world.modules.InventoryModule;
import io.anuke.mindustry.world.modules.LiquidModule;
import io.anuke.mindustry.world.modules.PowerModule;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.EntityGroup;
@@ -29,153 +35,219 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.tileGroup;
import static io.anuke.mindustry.Vars.world;
public class TileEntity extends BaseEntity implements TargetTrait {
public static final float timeToSleep = 60f*4; //4 seconds to fall asleep
/**This value is only used for debugging.*/
public static int sleepingEntities = 0;
public class TileEntity extends BaseEntity implements TargetTrait{
public static final float timeToSleep = 60f * 4; //4 seconds to fall asleep
private static final ObjectSet<Tile> tmpTiles = new ObjectSet<>();
/**
* This value is only used for debugging.
*/
public static int sleepingEntities = 0;
public Tile tile;
public Timer timer;
public float health;
public Tile tile;
public Timer timer;
public float health;
public PowerModule power;
public InventoryModule items;
public LiquidModule liquids;
public ConsumeModule cons;
public PowerModule power;
public InventoryModule items;
public LiquidModule liquids;
/**List of (cached) tiles with entities in proximity, used for outputting to*/
private Array<Tile> proximity = new Array<>(8);
private boolean dead = false;
private boolean sleeping;
private float sleepTime;
private boolean dead = false;
private boolean sleeping;
private float sleepTime;
/**Sets this tile entity data to this tile, and adds it if necessary.*/
public TileEntity init(Tile tile, boolean added){
this.tile = tile;
x = tile.drawx();
y = tile.drawy();
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDamage(Tile tile, float health){
if(tile.entity != null){
tile.entity.health = health;
}
}
health = tile.block().health;
timer = new Timer(tile.block().timers);
if(added){
//if(!tile.block().autoSleep) { //TODO only autosleep when creating a fresh block!
add();
/*}else{
sleeping = true;
sleepingEntities ++;
}*/
}
return this;
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDestroyed(Tile tile){
if(tile.entity == null) return;
tile.entity.onDeath();
}
/**Call when nothing is happening to the entity.
* This increments the internal sleep timer.*/
public void sleep(){
sleepTime += Timers.delta();
if(!sleeping && sleepTime >= timeToSleep){
remove();
sleeping = true;
sleepingEntities ++;
}
}
/**Sets this tile entity data to this tile, and adds it if necessary.*/
public TileEntity init(Tile tile, boolean added){
this.tile = tile;
x = tile.drawx();
y = tile.drawy();
/**Call when something just happened to the entity.
* If the entity was sleeping, this enables it. This also resets the sleep timer.*/
public void wakeUp(){
sleepTime = 0f;
if(sleeping){
add();
sleeping = false;
sleepingEntities --;
}
}
health = tile.block().health;
public boolean isSleeping(){
return sleeping;
}
timer = new Timer(tile.block().timers);
public boolean isDead() {
return dead;
}
if(added){
add();
}
public void write(DataOutputStream stream) throws IOException{}
public void read(DataInputStream stream) throws IOException{}
return this;
}
private void onDeath(){
if(!dead) {
dead = true;
Block block = tile.block();
/**
* Call when nothing is happening to the entity.
* This increments the internal sleep timer.
*/
public void sleep(){
sleepTime += Timers.delta();
if(!sleeping && sleepTime >= timeToSleep){
remove();
sleeping = true;
sleepingEntities++;
}
}
block.onDestroyed(tile);
world.removeBlock(tile);
block.afterDestroyed(tile, this);
remove();
}
}
/**
* Call when something just happened to the entity.
* If the entity was sleeping, this enables it. This also resets the sleep timer.
*/
public void wakeUp(){
sleepTime = 0f;
if(sleeping){
add();
sleeping = false;
sleepingEntities--;
}
}
public boolean collide(Bullet other){
return true;
}
public void collision(Bullet other){
tile.block().handleBulletHit(this, other);
}
public void damage(float damage){
if(dead) return;
public boolean isSleeping(){
return sleeping;
}
CallBlocks.onTileDamage(tile, health - tile.block().handleDamage(tile, damage));
public boolean isDead(){
return dead;
}
if(health <= 0){
CallBlocks.onTileDestroyed(tile);
}
}
public void write(DataOutputStream stream) throws IOException{
}
public Tile getTile(){
return tile;
}
public void read(DataInputStream stream) throws IOException{
}
@Override
public Team getTeam() {
return tile.getTeam();
}
private void onDeath(){
if(!dead){
dead = true;
Block block = tile.block();
@Override
public Vector2 getVelocity() {
return Vector2.Zero;
}
block.onDestroyed(tile);
world.removeBlock(tile);
block.afterDestroyed(tile, this);
remove();
}
}
@Override
public void update(){
synchronized (Tile.tileSetLock) {
//TODO better smoke effect, this one is awful
if (health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) &&
Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))) {
public boolean collide(Bullet other){
return true;
}
Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4));
}
public void collision(Bullet other){
tile.block().handleBulletHit(this, other);
}
if (health <= 0) {
onDeath();
}
public void damage(float damage){
if(dead) return;
tile.block().update(tile);
}
}
CallBlocks.onTileDamage(tile, health - tile.block().handleDamage(tile, damage));
@Override
public EntityGroup targetGroup() {
return tileGroup;
}
if(health <= 0){
CallBlocks.onTileDestroyed(tile);
}
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDamage(Tile tile, float health){
tile.entity.health = health;
}
public Tile getTile(){
return tile;
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDestroyed(Tile tile){
if(tile.entity == null) return;
tile.entity.onDeath();
}
public boolean consumed(Class<? extends Consume> type){
return tile.block().consumes.get(type).valid(tile.block(), this);
}
public void removeFromProximity(){
GridPoint2[] nearby = Edges.getEdges(tile.block().size);
for(GridPoint2 point : nearby){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
//remove this tile from all nearby tile's proximities
if(other != null){
other = other.target();
other.block().onProximityUpdate(other);
}
if(other != null && other.entity != null){
other.entity.proximity.removeValue(tile, true);
}
}
}
public void updateProximity(){
tmpTiles.clear();
proximity.clear();
GridPoint2[] nearby = Edges.getEdges(tile.block().size);
for(GridPoint2 point : nearby){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null){
other.block().onProximityUpdate(other);
other = other.target();
}
if(other != null && other.entity != null){
tmpTiles.add(other);
//add this tile to proximity of nearby tiles
if(!other.entity.proximity.contains(tile, true)){
other.entity.proximity.add(tile);
}
}
}
//using a set to prevent duplicates
for(Tile tile : tmpTiles){
proximity.add(tile);
}
tile.block().onProximityUpdate(tile);
}
public Array<Tile> proximity(){
return proximity;
}
@Override
public Team getTeam(){
return tile.getTeam();
}
@Override
public Vector2 getVelocity(){
return Vector2.Zero;
}
@Override
public void update(){
synchronized(Tile.tileSetLock){
//TODO better smoke effect, this one is awful
if(health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) &&
Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))){
Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4));
}
if(health <= 0){
onDeath();
}
tile.block().update(tile);
if(cons != null){
cons.update(this);
}
}
}
@Override
public EntityGroup targetGroup(){
return tileGroup;
}
}

View File

@@ -31,13 +31,20 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait {
/**total duration of hit flash effect*/
public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait{
/**
* total duration of hit flash effect
*/
public static final float hitDuration = 9f;
/**Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks.*/
/**
* Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks.
*/
public static final float velocityPercision = 8f;
/**Maximum absolute value of a velocity vector component.*/
public static final float maxAbsVelocity = 127f/velocityPercision;
/**
* Maximum absolute value of a velocity vector component.
*/
public static final float maxAbsVelocity = 127f / velocityPercision;
public static final float elevationScale = 4f;
private static final Vector2 moveVector = new Vector2();
@@ -52,30 +59,31 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
protected Vector2 velocity = new Translator(0f, 0.0001f);
protected float hitTime;
protected float drownTime;
protected float elevation;
@Override
public UnitInventory getInventory() {
public UnitInventory getInventory(){
return inventory;
}
@Override
public float getRotation() {
public float getRotation(){
return rotation;
}
@Override
public void setRotation(float rotation) {
public void setRotation(float rotation){
this.rotation = rotation;
}
@Override
public void setCarrier(CarryTrait carrier) {
this.carrier = carrier;
public CarryTrait getCarrier(){
return carrier;
}
@Override
public CarryTrait getCarrier() {
return carrier;
public void setCarrier(CarryTrait carrier){
this.carrier = carrier;
}
@Override
@@ -84,7 +92,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public void interpolate() {
public void interpolate(){
interpolator.update();
x = interpolator.pos.x;
@@ -96,7 +104,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public Interpolator getInterpolator() {
public Interpolator getInterpolator(){
return interpolator;
}
@@ -113,31 +121,31 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public void onDeath() {
public void onDeath(){
inventory.clear();
drownTime = 0f;
status.clear();
}
@Override
public Vector2 getVelocity() {
public Vector2 getVelocity(){
return velocity;
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
writeSave(stream, false);
}
@Override
public void readSave(DataInput stream) throws IOException {
public void readSave(DataInput stream) throws IOException{
byte team = stream.readByte();
boolean dead = stream.readBoolean();
float x = stream.readFloat();
float y = stream.readFloat();
byte xv = stream.readByte();
byte yv = stream.readByte();
float rotation = stream.readShort()/2f;
float rotation = stream.readShort() / 2f;
int health = stream.readShort();
this.status.readSave(stream);
@@ -151,21 +159,21 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
this.rotation = rotation;
}
public void writeSave(DataOutput stream, boolean net) throws IOException {
public void writeSave(DataOutput stream, boolean net) throws IOException{
stream.writeByte(team.ordinal());
stream.writeBoolean(isDead());
stream.writeFloat(net ? interpolator.target.x : x);
stream.writeFloat(net ? interpolator.target.y : y);
stream.writeByte((byte)(Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeByte((byte)(Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeShort((short)(rotation*2));
stream.writeShort((short)health);
stream.writeByte((byte) (Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeByte((byte) (Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeShort((short) (rotation * 2));
stream.writeShort((short) health);
status.writeSave(stream);
inventory.writeSave(stream);
}
public float calculateDamage(float amount){
return amount * Mathf.clamp(1f-getArmor()/100f*status.getArmorMultiplier());
return amount * Mathf.clamp(1f - getArmor() / 100f * status.getArmorMultiplier());
}
public float getDamageMultipler(){
@@ -180,7 +188,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
if(state.teams.has(team)){
TeamData data = state.teams.get(team);
Tile tile = Geometry.findClosest(x, y, data.cores);
Tile tile = Geometry.findClosest(x, y, data.cores);
if(tile == null){
return null;
}else{
@@ -198,15 +206,18 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public void avoidOthers(float avoidRange){
EntityPhysics.getNearby(getGroup(), x, y, avoidRange*2f, t -> {
if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) return;
EntityPhysics.getNearby(getGroup(), x, y, avoidRange * 2f, t -> {
if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t))
return;
float dst = distanceTo(t);
if(dst > avoidRange) return;
velocity.add(moveVector.set(x, y).sub(t.getX(), t.getY()).setLength(1f * (1f - (dst / avoidRange))));
});
}
/**Updates velocity and status effects.*/
/**
* Updates velocity and status effects.
*/
public void updateVelocityStatus(float drag, float maxVelocity){
if(isCarried()){ //carried units do not take into account velocity normally
set(carrier.getX(), carrier.getY());
@@ -221,9 +232,13 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
velocity.limit(maxVelocity).scl(status.getSpeedMultiplier());
if(isFlying()) {
if(isFlying()){
x += velocity.x / getMass() * Timers.delta();
y += velocity.y / getMass() * Timers.delta();
if(tile != null){
elevation = Mathf.lerpDelta(elevation, tile.elevation, 0.04f);
}
}else{
boolean onLiquid = floor.isLiquid;
@@ -239,7 +254,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
}
if(onLiquid && velocity.len() > 0.4f && Timers.get(this, "flooreffect", 14 - (velocity.len() * floor.speedMultiplier)*2f)){
if(onLiquid && velocity.len() > 0.4f && Timers.get(this, "flooreffect", 14 - (velocity.len() * floor.speedMultiplier) * 2f)){
Effects.effect(floor.walkEffect, floor.liquidColor, x, y);
}
@@ -250,7 +265,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
if(onLiquid && floor.drownTime > 0){
drownTime += Timers.delta() * 1f/floor.drownTime;
drownTime += Timers.delta() * 1f / floor.drownTime;
if(Timers.get(this, "drowneffect", 15)){
Effects.effect(floor.drownUpdateEffect, floor.liquidColor, x, y);
}
@@ -270,7 +285,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
if(Math.abs(py - y) <= 0.0001f) velocity.y = 0f;
}
velocity.scl(Mathf.clamp(1f-drag* floor.dragMultiplier* Timers.delta()));
velocity.scl(Mathf.clamp(1f - drag * floor.dragMultiplier * Timers.delta()));
}
public void applyEffect(StatusEffect effect, float intensity){
@@ -293,11 +308,17 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public float getAmmoFraction(){
return inventory.totalAmmo() / (float)inventory.ammoCapacity();
return inventory.totalAmmo() / (float) inventory.ammoCapacity();
}
public void drawUnder(){}
public void drawOver(){}
public void drawUnder(){
}
public void drawOver(){
}
public void drawShadow(){
}
public void drawView(){
Fill.circle(x, y, getViewDistance());
@@ -312,12 +333,20 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public abstract TextureRegion getIconRegion();
public abstract int getItemCapacity();
public abstract int getAmmoCapacity();
public abstract float getArmor();
public abstract boolean acceptsAmmo(Item item);
public abstract void addAmmo(Item item);
public abstract float getMass();
public abstract boolean isFlying();
public abstract float getSize();
}

View File

@@ -8,16 +8,17 @@ import io.anuke.mindustry.type.AmmoType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import java.io.*;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class UnitInventory implements Saveable{
private final Unit unit;
private Array<AmmoEntry> ammos = new Array<>();
private int totalAmmo;
private ItemStack item = new ItemStack(Items.stone, 0);
private final Unit unit;
public UnitInventory(Unit unit) {
public UnitInventory(Unit unit){
this.unit = unit;
}
@@ -26,24 +27,24 @@ public class UnitInventory implements Saveable{
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
stream.writeShort(item.amount);
stream.writeByte(item.item.id);
stream.writeShort(totalAmmo);
stream.writeByte(ammos.size);
for(int i = 0; i < ammos.size; i ++){
for(int i = 0; i < ammos.size; i++){
stream.writeByte(ammos.get(i).type.id);
stream.writeShort(ammos.get(i).amount);
}
}
@Override
public void readSave(DataInput stream) throws IOException {
public void readSave(DataInput stream) throws IOException{
short iamount = stream.readShort();
byte iid = stream.readByte();
this.totalAmmo = stream.readShort();
byte ammoa = stream.readByte();
for(int i = 0; i < ammoa; i ++){
for(int i = 0; i < ammoa; i++){
byte aid = stream.readByte();
int am = stream.readShort();
ammos.add(new AmmoEntry(AmmoType.getByID(aid), am));
@@ -53,12 +54,14 @@ public class UnitInventory implements Saveable{
item.amount = iamount;
}
/**Returns ammo range, or MAX_VALUE if this inventory has no ammo.*/
/**
* 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() {
public AmmoType getAmmo(){
return ammos.size == 0 ? null : ammos.peek().type;
}
@@ -69,9 +72,9 @@ public class UnitInventory implements Saveable{
public void useAmmo(){
if(unit.isInfiniteAmmo()) return;
AmmoEntry entry = ammos.peek();
entry.amount --;
entry.amount--;
if(entry.amount == 0) ammos.pop();
totalAmmo --;
totalAmmo--;
}
public int totalAmmo(){
@@ -87,22 +90,23 @@ public class UnitInventory implements Saveable{
}
public void addAmmo(AmmoType type){
if(type == null) return;
totalAmmo += type.quantityMultiplier;
//find ammo entry by type
for(int i = ammos.size - 1; i >= 0; i --){
for(int i = ammos.size - 1; i >= 0; i--){
AmmoEntry entry = ammos.get(i);
//if found, put it to the right
if(entry.type == type){
entry.amount += type.quantityMultiplier;
ammos.swap(i, ammos.size-1);
ammos.swap(i, ammos.size - 1);
return;
}
}
//must not be found
AmmoEntry entry = new AmmoEntry(type, (int)type.quantityMultiplier);
AmmoEntry entry = new AmmoEntry(type, (int) type.quantityMultiplier);
ammos.add(entry);
}

View File

@@ -15,12 +15,19 @@ import io.anuke.ucore.function.Predicate;
import static io.anuke.mindustry.Vars.*;
/**Utility class for unit and team interactions.*/
public class Units {
/**
* Utility class for unit and team interactions.
*/
public class Units{
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Unit result;
private static float cdist;
private static boolean boolResult;
/**Validates a target.
/**
* 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
@@ -28,44 +35,52 @@ public class Units {
* @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(TargetTrait target, Team team, float x, float y, float range) {
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && target.distanceTo(x, y) > range) || target.getTeam() == team || !target.isValid();
}
/**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/
/**
* See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}
*/
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){
return invalidateTarget(target, team, x, y, Float.MAX_VALUE);
}
/**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/
/**
* See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}
*/
public static boolean invalidateTarget(TargetTrait target, Unit targeter){
return invalidateTarget(target, targeter.team, targeter.x, targeter.y, targeter.inventory.getAmmoRange());
}
/**Returns whether there are any entities on this tile.*/
/**
* Returns whether there are any entities on this tile.
*/
public static boolean anyEntities(Tile tile){
Block type = tile.block();
rect.setSize(type.size * tilesize, type.size * tilesize);
rect.setCenter(tile.drawx(), tile.drawy());
boolean[] value = new boolean[1];
boolResult = false;
Units.getNearby(rect, unit -> {
if (value[0]) return;
if (!unit.isFlying()) {
if(boolResult) return;
if(!unit.isFlying()){
unit.getHitbox(hitrect);
if (hitrect.overlaps(rect)) {
value[0] = true;
if(hitrect.overlaps(rect)){
boolResult = true;
}
}
});
return value[0];
return boolResult;
}
/**Returns whether there are any entities on this tile, with the hitbox expanded.*/
/**
* Returns whether there are any entities on this tile, with the hitbox expanded.
*/
public static boolean anyEntities(Tile tile, float expansion, Predicate<Unit> pred){
Block type = tile.block();
rect.setSize(type.size * tilesize + expansion, type.size * tilesize + expansion);
@@ -78,7 +93,7 @@ public class Units {
if(!unit.isFlying()){
unit.getHitbox(hitrect);
if(hitrect.overlaps(rect)) {
if(hitrect.overlaps(rect)){
value[0] = true;
}
}
@@ -87,7 +102,9 @@ public class Units {
return value[0];
}
/**Returns the neareset ally tile in a range.*/
/**
* Returns the neareset ally tile in a range.
*/
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
for(Team enemy : state.teams.alliesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
@@ -98,7 +115,9 @@ public class Units {
return null;
}
/**Returns the neareset enemy tile in a range.*/
/**
* Returns the neareset enemy tile in a range.
*/
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
@@ -109,7 +128,9 @@ public class Units {
return null;
}
/**Iterates over all units on all teams, including players.*/
/**
* Iterates over all units on all teams, including players.
*/
public static void allUnits(Consumer<Unit> cons){
//check all unit groups first
for(EntityGroup<BaseUnit> group : unitGroups){
@@ -126,7 +147,9 @@ public class Units {
}
}
/**Returns the closest target enemy. First, units are checked, then tile entities.*/
/**
* Returns the closest target enemy. First, units are checked, then tile entities.
*/
public static TargetTrait getClosestTarget(Team team, float x, float y, float range){
Unit unit = getClosestEnemy(team, x, y, range, u -> true);
if(unit != null){
@@ -136,74 +159,82 @@ public class Units {
}
}
/**Returns the closest enemy of this team. Filter by predicate.*/
/**
* Returns the closest enemy of this team. Filter by predicate.
*/
public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate<Unit> predicate){
Unit[] result = {null};
float[] cdist = {0};
result = null;
cdist = 0f;
rect.setSize(range*2f).setCenter(x, y);
rect.setSize(range * 2f).setCenter(x, y);
getNearbyEnemies(team, rect, e -> {
if (e.isDead() || !predicate.test(e))
if(e.isDead() || !predicate.test(e))
return;
float dist = Vector2.dst(e.x, e.y, x, y);
if (dist < range) {
if (result[0] == null || dist < cdist[0]) {
result[0] = e;
cdist[0] = dist;
if(dist < range){
if(result == null || dist < cdist){
result = e;
cdist = dist;
}
}
});
return result[0];
return result;
}
/**Returns the closest ally of this team. Filter by predicate.*/
/**
* Returns the closest ally of this team. Filter by predicate.
*/
public static Unit getClosest(Team team, float x, float y, float range, Predicate<Unit> predicate){
Unit[] result = {null};
float[] cdist = {0};
result = null;
cdist = 0f;
rect.setSize(range*2f).setCenter(x, y);
rect.setSize(range * 2f).setCenter(x, y);
getNearby(team, rect, e -> {
if (!predicate.test(e))
if(!predicate.test(e))
return;
float dist = Vector2.dst(e.x, e.y, x, y);
if (dist < range) {
if (result[0] == null || dist < cdist[0]) {
result[0] = e;
cdist[0] = dist;
if(dist < range){
if(result == null || dist < cdist){
result = e;
cdist = dist;
}
}
});
return result[0];
return result;
}
/**Iterates over all units in a rectangle.*/
/**
* Iterates over all units in a rectangle.
*/
public static void getNearby(Team team, Rectangle rect, Consumer<Unit> cons){
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!group.isEmpty()){
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity));
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity));
}
//now check all players
EntityPhysics.getNearby(playerGroup, rect, player -> {
if(((Unit)player).team == team) cons.accept((Unit)player);
if(((Unit) player).team == team) cons.accept((Unit) player);
});
}
/**Iterates over all units in a circle around this position.*/
/**
* Iterates over all units in a circle around this position.
*/
public static void getNearby(Team team, float x, float y, float radius, Consumer<Unit> cons){
rect.setSize(radius * 2).setCenter(x, y);
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!group.isEmpty()){
EntityPhysics.getNearby(group, rect, entity -> {
if(entity.distanceTo(x, y) <= radius) {
if(entity.distanceTo(x, y) <= radius){
cons.accept((Unit) entity);
}
});
@@ -211,46 +242,52 @@ public class Units {
//now check all players
EntityPhysics.getNearby(playerGroup, rect, player -> {
if(((Unit)player).team == team && player.distanceTo(x, y) <= radius){
cons.accept((Unit)player);
if(((Unit) player).team == team && player.distanceTo(x, y) <= radius){
cons.accept((Unit) player);
}
});
}
/**Iterates over all units in a rectangle.*/
/**
* Iterates over all units in a rectangle.
*/
public static void getNearby(Rectangle rect, Consumer<Unit> cons){
for(Team team : Team.all){
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!group.isEmpty()){
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity));
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity));
}
}
//now check all enemy players
EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit)player));
EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit) player));
}
/**Iterates over all units that are enemies of this team.*/
/**
* Iterates over all units that are enemies of this team.
*/
public static void getNearbyEnemies(Team team, Rectangle rect, Consumer<Unit> cons){
ObjectSet<Team> targets = state.teams.enemiesOf(team);
for(Team other : targets){
EntityGroup<BaseUnit> group = unitGroups[other.ordinal()];
if(!group.isEmpty()){
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit)entity));
EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity));
}
}
//now check all enemy players
EntityPhysics.getNearby(playerGroup, rect, player -> {
if(targets.contains(((Player)player).team)){
cons.accept((Unit)player);
if(targets.contains(((Player) player).team)){
cons.accept((Unit) player);
}
});
}
/**Iterates over all units.*/
/**
* Iterates over all units.
*/
public static void getAllUnits(Consumer<Unit> cons){
for(Team team : Team.all){

View File

@@ -6,10 +6,10 @@ import io.anuke.ucore.core.Effects.Effect;
import io.anuke.ucore.graphics.Draw;
//TODO scale velocity depending on fslope()
public class ArtilleryBulletType extends BasicBulletType {
public class ArtilleryBulletType extends BasicBulletType{
protected Effect trailEffect = BulletFx.artilleryTrail;
public ArtilleryBulletType(float speed, float damage, String bulletSprite) {
public ArtilleryBulletType(float speed, float damage, String bulletSprite){
super(speed, damage, bulletSprite);
collidesTiles = false;
collides = false;
@@ -17,18 +17,18 @@ public class ArtilleryBulletType extends BasicBulletType {
}
@Override
public void update(Bullet b) {
public void update(Bullet b){
super.update(b);
if(b.timer.get(0, 3 + b.fslope()*2f)){
if(b.timer.get(0, 3 + b.fslope() * 2f)){
Effects.effect(trailEffect, backColor, b.x, b.y, b.fslope() * 4f);
}
}
@Override
public void draw(Bullet b) {
public void draw(Bullet b){
float baseScale = 0.7f;
float scale = (baseScale + b.fslope()*(1f-baseScale));
float scale = (baseScale + b.fslope() * (1f - baseScale));
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());

View File

@@ -12,8 +12,10 @@ import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
/**A BulletType for most ammo-based bullets shot from turrets and units.*/
public class BasicBulletType extends BulletType {
/**
* A BulletType for most ammo-based bullets shot from turrets and units.
*/
public class BasicBulletType extends BulletType{
public Color backColor = Palette.bulletYellowBack, frontColor = Palette.bulletYellow;
public float bulletWidth = 5f, bulletHeight = 7f;
public float bulletShrink = 0.5f;
@@ -23,7 +25,9 @@ public class BasicBulletType extends BulletType {
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public BulletType fragBullet = null;
/**Use a negative value to disable splash damage.*/
/**
* Use a negative value to disable splash damage.
*/
public float splashDamageRadius = -1f;
public float splashDamage = 6f;
@@ -39,19 +43,19 @@ public class BasicBulletType extends BulletType {
public float hitShake = 0f;
public BasicBulletType(float speed, float damage, String bulletSprite) {
public BasicBulletType(float speed, float damage, String bulletSprite){
super(speed, damage);
this.bulletSprite = bulletSprite;
}
@Override
public void load() {
public void load(){
backRegion = Draw.region(bulletSprite + "-back");
frontRegion = Draw.region(bulletSprite);
}
@Override
public void draw(Bullet b) {
public void draw(Bullet b){
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());
Draw.color(backColor);
@@ -62,7 +66,7 @@ public class BasicBulletType extends BulletType {
}
@Override
public void update(Bullet b) {
public void update(Bullet b){
super.update(b);
if(homingPower > 0.0001f){
@@ -74,20 +78,20 @@ public class BasicBulletType extends BulletType {
}
@Override
public void hit(Bullet b, float x, float y) {
public void hit(Bullet b, float x, float y){
super.hit(b, x, y);
Effects.shake(hitShake, hitShake, b);
if(fragBullet != null) {
for (int i = 0; i < fragBullets; i++) {
if(fragBullet != null){
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = Mathf.random(360f);
Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
}
}
if(Mathf.chance(incendChance)) {
if(Mathf.chance(incendChance)){
Damage.createIncend(x, y, incendSpread, incendAmount);
}
@@ -97,7 +101,7 @@ public class BasicBulletType extends BulletType {
}
@Override
public void despawned(Bullet b) {
public void despawned(Bullet b){
if(fragBullet != null || splashDamageRadius > 0){
hit(b);
}

View File

@@ -1,8 +1,8 @@
package io.anuke.mindustry.entities.bullet;
public class BombBulletType extends BasicBulletType {
public class BombBulletType extends BasicBulletType{
public BombBulletType(float damage, float radius, String sprite) {
public BombBulletType(float damage, float radius, String sprite){
super(0.7f, 0, sprite);
splashDamageRadius = radius;
splashDamage = damage;

View File

@@ -27,198 +27,199 @@ import static io.anuke.mindustry.Vars.bulletGroup;
import static io.anuke.mindustry.Vars.world;
public class Bullet extends BulletEntity<BulletType> implements TeamTrait, SyncTrait{
private static Vector2 vector = new Vector2();
private static Vector2 vector = new Vector2();
public Timer timer = new Timer(3);
private Team team;
private Object data;
private boolean supressCollision;
private Team team;
private Object data;
private boolean supressCollision;
/**
* Internal use only!
*/
public Bullet(){
}
public Timer timer = new Timer(3);
public static void create(BulletType type, TeamTrait owner, float x, float y, float angle){
create(type, owner, owner.getTeam(), x, y, angle);
}
public static void create (BulletType type, TeamTrait owner, float x, float y, float angle){
create(type, owner, owner.getTeam(), x, y, angle);
}
public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle){
create(type, owner, team, x, y, angle, 1f);
}
public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle){
create(type, owner, team, x, y, angle, 1f);
}
public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){
create(type, owner, team, x, y, angle, velocityScl, null);
}
public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){
create(type, owner, team, x, y, angle, velocityScl, null);
}
public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, Object data){
Bullet bullet = Pooling.obtain(Bullet.class);
bullet.type = type;
bullet.owner = owner;
bullet.data = data;
public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, Object data){
Bullet bullet = Pooling.obtain(Bullet.class);
bullet.type = type;
bullet.owner = owner;
bullet.data = data;
bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl);
if(type.keepVelocity){
bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait) owner).getVelocity() : Vector2.Zero);
}
bullet.hitbox.setSize(type.hitsize);
bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl);
if(type.keepVelocity){
bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait)owner).getVelocity() : Vector2.Zero);
}
bullet.hitbox.setSize(type.hitsize);
bullet.team = team;
bullet.type = type;
bullet.team = team;
bullet.type = type;
//translate bullets backwards, purely for visual reasons
float backDelta = Timers.delta();
//translate bullets backwards, purely for visual reasons
float backDelta = Timers.delta();
bullet.lastPosition().set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta, bullet.angle());
bullet.setLastUpdated(TimeUtils.millis());
bullet.setUpdateSpacing((long) ((Timers.delta() / 60f) * 1000));
bullet.set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta);
bullet.lastPosition().set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta, bullet.angle());
bullet.setLastUpdated(TimeUtils.millis());
bullet.setUpdateSpacing((long)((Timers.delta() / 60f) * 1000));
bullet.set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta);
bullet.add();
}
bullet.add();
}
public static void create(BulletType type, Bullet parent, float x, float y, float angle){
create(type, parent.owner, parent.team, x, y, angle);
}
public static void create(BulletType type, Bullet parent, float x, float y, float angle){
create(type, parent.owner, parent.team, x, y, angle);
}
public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){
create(type, parent.owner, parent.team, x, y, angle, velocityScl);
}
public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){
create(type, parent.owner, parent.team, x, y, angle, velocityScl);
}
@Remote(called = Loc.server, in = In.entities)
public static void createBullet(BulletType type, float x, float y, float angle){
create(type, null, Team.none, x, y, angle);
}
@Remote(called = Loc.server, in = In.entities)
public static void createBullet(BulletType type, float x, float y, float angle){
create(type, null, Team.none, x, y, angle);
}
public boolean collidesTiles(){
return type.collidesTiles;
}
/**Internal use only!*/
public Bullet(){}
public void supressCollision(){
supressCollision = true;
}
public boolean collidesTiles(){
return type.collidesTiles;
}
public void resetOwner(Entity entity, Team team){
this.owner = entity;
this.team = team;
}
public void supressCollision(){
supressCollision = true;
}
public void scaleTime(float add){
time += add;
}
public void resetOwner(Entity entity, Team team){
this.owner = entity;
this.team = team;
}
public Object getData(){
return data;
}
public void scaleTime(float add){
time += add;
}
@Override
public float getDamage(){
if(owner instanceof Unit){
return super.getDamage() * ((Unit) owner).getDamageMultipler();
}
public Object getData() {
return data;
}
return super.getDamage();
}
@Override
public float getDamage(){
if(owner instanceof Unit){
return super.getDamage() * ((Unit) owner).getDamageMultipler();
}
@Override
public boolean isSyncing(){
return type.syncable;
}
return super.getDamage();
}
@Override
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeFloat(velocity.x);
data.writeFloat(velocity.y);
data.writeByte(team.ordinal());
data.writeByte(type.id);
}
@Override
public boolean isSyncing(){
return type.syncable;
}
@Override
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
velocity.x = data.readFloat();
velocity.y = data.readFloat();
team = Team.all[data.readByte()];
type = BulletType.getByID(data.readByte());
}
@Override
public void write(DataOutput data) throws IOException {
data.writeFloat(x);
data.writeFloat(y);
data.writeFloat(velocity.x);
data.writeFloat(velocity.y);
data.writeByte(team.ordinal());
data.writeByte(type.id);
}
@Override
public Team getTeam(){
return team;
}
@Override
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
velocity.x = data.readFloat();
velocity.y = data.readFloat();
team = Team.all[data.readByte()];
type = BulletType.getByID(data.readByte());
}
@Override
public void draw(){
type.draw(this);
}
@Override
public Team getTeam() {
return team;
}
@Override
public float drawSize(){
return 8;
}
@Override
public void draw(){
type.draw(this);
}
@Override
public boolean collides(SolidTrait other){
return type.collides && super.collides(other);
}
@Override
public float drawSize(){
return 8;
}
@Override
public void collision(SolidTrait other, float x, float y){
super.collision(other, x, y);
@Override
public boolean collides(SolidTrait other){
return type.collides && super.collides(other);
}
if(other instanceof Unit){
Unit unit = (Unit) other;
unit.getVelocity().add(vector.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.getMass()));
unit.applyEffect(type.status, type.statusIntensity);
}
}
@Override
public void collision(SolidTrait other, float x, float y){
super.collision(other, x, y);
@Override
public void update(){
super.update();
if(other instanceof Unit){
Unit unit = (Unit)other;
unit.getVelocity().add(vector.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.getMass()));
unit.applyEffect(type.status, type.statusIntensity);
}
}
if(type.hitTiles && collidesTiles() && !supressCollision){
world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> {
@Override
public void update(){
super.update();
Tile tile = world.tile(x, y);
if(tile == null) return false;
tile = tile.target();
if (type.hitTiles && collidesTiles() && !supressCollision) {
world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> {
if(tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team){
tile.entity.collision(this);
Tile tile = world.tile(x, y);
if (tile == null) return false;
tile = tile.target();
if(!supressCollision){
type.hit(this);
remove();
}
if (tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team) {
tile.entity.collision(this);
return true;
}
if(!supressCollision){
type.hit(this);
remove();
}
return false;
});
}
return true;
}
supressCollision = false;
}
return false;
});
}
@Override
public void reset(){
super.reset();
timer.clear();
team = null;
data = null;
}
supressCollision = false;
}
@Override
public void removed(){
Pooling.free(this);
}
@Override
public void reset() {
super.reset();
timer.clear();
team = null;
data = null;
}
@Override
public void removed() {
Pooling.free(this);
}
@Override
public EntityGroup targetGroup() {
return bulletGroup;
}
@Override
public EntityGroup targetGroup(){
return bulletGroup;
}
}

View File

@@ -9,65 +9,83 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.entities.impl.BaseBulletType;
public abstract class BulletType extends BaseBulletType<Bullet> implements Content{
private static int lastid = 0;
private static Array<BulletType> types = new Array<>();
private static int lastid = 0;
private static Array<BulletType> types = new Array<>();
public final int id;
/**Knockback in velocity.*/
public float knockback;
/**Whether this bullet hits tiles.*/
public boolean hitTiles = true;
/**Status effect applied on hit.*/
public StatusEffect status = StatusEffects.none;
/**Intensity of applied status effect in terms of duration.*/
public float statusIntensity = 0.5f;
/**What fraction of armor is pierced, 0-1*/
public float armorPierce = 0f;
/**Whether to sync this bullet to clients.*/
public boolean syncable;
/**Whether this bullet type collides with tiles.*/
public boolean collidesTiles = true;
/**Whether this bullet types collides with anything at all.*/
public boolean collides = true;
/**Whether velocity is inherited from the shooter.*/
public boolean keepVelocity = true;
public final int id;
/**
* Knockback in velocity.
*/
public float knockback;
/**
* Whether this bullet hits tiles.
*/
public boolean hitTiles = true;
/**
* Status effect applied on hit.
*/
public StatusEffect status = StatusEffects.none;
/**
* Intensity of applied status effect in terms of duration.
*/
public float statusIntensity = 0.5f;
/**
* What fraction of armor is pierced, 0-1
*/
public float armorPierce = 0f;
/**
* Whether to sync this bullet to clients.
*/
public boolean syncable;
/**
* Whether this bullet type collides with tiles.
*/
public boolean collidesTiles = true;
/**
* Whether this bullet types collides with anything at all.
*/
public boolean collides = true;
/**
* Whether velocity is inherited from the shooter.
*/
public boolean keepVelocity = true;
public BulletType(float speed, float damage){
this.id = lastid ++;
this.speed = speed;
this.damage = damage;
lifetime = 40f;
hiteffect = BulletFx.hitBulletSmall;
despawneffect = BulletFx.despawn;
public BulletType(float speed, float damage){
this.id = lastid++;
this.speed = speed;
this.damage = damage;
lifetime = 40f;
hiteffect = BulletFx.hitBulletSmall;
despawneffect = BulletFx.despawn;
types.add(this);
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hiteffect, hitx, hity, b.angle());
}
types.add(this);
}
@Override
public void despawned(Bullet b){
Effects.effect(despawneffect, b.x, b.y, b.angle());
}
public static BulletType getByID(int id){
return types.get(id);
}
@Override
public String getContentTypeName() {
return "bullettype";
}
public static Array<BulletType> all(){
return types;
}
@Override
public Array<? extends Content> getAll() {
return types;
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hiteffect, hitx, hity, b.angle());
}
public static BulletType getByID(int id){
return types.get(id);
}
@Override
public void despawned(Bullet b){
Effects.effect(despawneffect, b.x, b.y, b.angle());
}
public static Array<BulletType> all(){
return types;
}
@Override
public String getContentTypeName(){
return "bullettype";
}
@Override
public Array<? extends Content> getAll(){
return types;
}
}

View File

@@ -16,10 +16,10 @@ import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.tilesize;
import static io.anuke.mindustry.Vars.world;
public class LiquidBulletType extends BulletType {
public class LiquidBulletType extends BulletType{
Liquid liquid;
public LiquidBulletType(Liquid liquid) {
public LiquidBulletType(Liquid liquid){
super(2.5f, 0);
this.liquid = liquid;
@@ -31,14 +31,14 @@ public class LiquidBulletType extends BulletType {
}
@Override
public void draw(Bullet b) {
public void draw(Bullet b){
Draw.color(liquid.color, Color.WHITE, b.fout() / 100f + Mathf.randomSeedRange(b.id, 0.1f));
Fill.circle(b.x, b.y, 0.5f + b.fout()*2.5f);
Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f);
}
@Override
public void hit(Bullet b, float hitx, float hity) {
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hiteffect, liquid.color, hitx, hity);
Puddle.deposit(world.tileWorld(hitx, hity), liquid, 5f);
@@ -46,7 +46,7 @@ public class LiquidBulletType extends BulletType {
float intensity = 400f;
Fire.extinguish(world.tileWorld(hitx, hity), intensity);
for(GridPoint2 p : Geometry.d4){
Fire.extinguish(world.tileWorld(hitx + p.x*tilesize, hity + p.y*tilesize), intensity);
Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
}
}
}

View File

@@ -4,9 +4,9 @@ import io.anuke.mindustry.content.fx.BulletFx;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.ucore.core.Effects;
public class MissileBulletType extends BasicBulletType {
public class MissileBulletType extends BasicBulletType{
public MissileBulletType(float speed, float damage, String bulletSprite) {
public MissileBulletType(float speed, float damage, String bulletSprite){
super(speed, damage, bulletSprite);
backColor = Palette.missileYellowBack;
frontColor = Palette.missileYellow;
@@ -14,7 +14,7 @@ public class MissileBulletType extends BasicBulletType {
}
@Override
public void update(Bullet b) {
public void update(Bullet b){
super.update(b);
if(b.timer.get(0, 4f)){

View File

@@ -10,24 +10,26 @@ import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.groundEffectGroup;
/**Class for creating block rubble on the ground.*/
public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait {
/**
* Class for creating block rubble on the ground.
*/
public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{
private static final Color color = Color.valueOf("52504e");
@Override
public float lifetime() {
public float lifetime(){
return 8200f;
}
@Override
public void draw(){
Draw.color(color.r, color.g, color.b, 1f-Mathf.curve(fin(), 0.98f));
Draw.color(color.r, color.g, color.b, 1f - Mathf.curve(fin(), 0.98f));
drawDecal();
Draw.color();
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return groundEffectGroup;
}

View File

@@ -31,7 +31,7 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable {
public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
private static final IntMap<Fire> map = new IntMap<>();
private static final float baseLifetime = 1000f;
@@ -41,7 +41,15 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
private float baseFlammability = -1, puddleFlammability;
private float lifetime;
/**Start a fire on the tile. If there already is a file there, refreshes its lifetime.*/
/**
* Deserialization use only!
*/
public Fire(){
}
/**
* Start a fire on the tile. If there already is a file there, refreshes its lifetime.
*/
public static void create(Tile tile){
if(Net.client() || tile == null) return; //not clientside.
@@ -60,24 +68,28 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
}
/**Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.*/
public static void extinguish(Tile tile, float intensity) {
if (tile != null && map.containsKey(tile.packedPosition())) {
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.packedPosition())){
map.get(tile.packedPosition()).time += intensity * Timers.delta();
}
}
/**Deserialization use only!*/
public Fire(){}
@Remote(called = Loc.server, in = In.entities)
public static void onFireRemoved(int fireid){
fireGroup.removeByID(fireid);
}
@Override
public float lifetime() {
public float lifetime(){
return lifetime;
}
@Override
public void update() {
if(Mathf.chance(0.1 * Timers.delta())) {
public void update(){
if(Mathf.chance(0.1 * Timers.delta())){
Effects.effect(EnvironmentFx.fire, x + Mathf.range(4f), y + Mathf.range(4f));
}
@@ -91,9 +103,10 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
time = Mathf.clamp(time + Timers.delta(), 0, lifetime());
if(time >= lifetime()){
if(time >= lifetime() || tile == null){
CallEntity.onFireRemoved(getID());
remove();
return;
}
TileEntity entity = tile.target().entity;
@@ -102,19 +115,19 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Timers.delta()*8;
time += Timers.delta() * 8;
}
if (baseFlammability < 0 || block != tile.block()){
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.block().getFlammability(tile);
block = tile.block();
}
if(damage) {
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Timers.delta();
}
if (flammability > 1f && Mathf.chance(0.03 * Timers.delta() * Mathf.clamp(flammability/5f, 0.3f, 2f))) {
if(flammability > 1f && Mathf.chance(0.03 * Timers.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
GridPoint2 p = Mathf.select(Geometry.d4);
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
create(other);
@@ -127,7 +140,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
if(Mathf.chance(0.1 * Timers.delta())){
Puddle p = Puddle.getPuddle(tile);
if(p != null){
puddleFlammability = p.getFlammability()/3f;
puddleFlammability = p.getFlammability() / 3f;
}else{
puddleFlammability = 0;
}
@@ -140,14 +153,14 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.packedPosition());
stream.writeFloat(lifetime);
stream.writeFloat(time);
}
@Override
public void readSave(DataInput stream) throws IOException {
public void readSave(DataInput stream) throws IOException{
this.loadedPosition = stream.readInt();
this.lifetime = stream.readFloat();
this.time = stream.readFloat();
@@ -155,19 +168,19 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
@Override
public void write(DataOutput data) throws IOException {
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
}
@Override
public void read(DataInput data, long time) throws IOException {
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
}
@Override
public void reset() {
public void reset(){
loadedPosition = -1;
tile = null;
baseFlammability = -1;
@@ -175,7 +188,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
@Override
public void added() {
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
@@ -184,7 +197,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
@Override
public void removed() {
public void removed(){
if(tile != null){
map.remove(tile.packedPosition());
}
@@ -192,12 +205,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return fireGroup;
}
@Remote(called = Loc.server, in = In.entities)
public static void onFireRemoved(int fireid){
fireGroup.removeByID(fireid);
}
}

View File

@@ -9,27 +9,29 @@ import io.anuke.ucore.entities.impl.EffectEntity;
import io.anuke.ucore.function.EffectRenderer;
import io.anuke.ucore.util.Mathf;
/**A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.*/
public class GroundEffectEntity extends EffectEntity {
/**
* A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.
*/
public class GroundEffectEntity extends EffectEntity{
private boolean once;
@Override
public void update(){
GroundEffect effect = (GroundEffect)this.effect;
GroundEffect effect = (GroundEffect) this.effect;
if(effect.isStatic) {
if(effect.isStatic){
time += Timers.delta();
time = Mathf.clamp(time, 0, effect.staticLife);
if (!once && time >= lifetime()) {
if(!once && time >= lifetime()){
once = true;
time = 0f;
Tile tile = Vars.world.tileWorld(x, y);
if(tile != null && tile.floor().isLiquid){
remove();
}
} else if (once && time >= effect.staticLife) {
}else if(once && time >= effect.staticLife){
remove();
}
}else{
@@ -39,7 +41,7 @@ public class GroundEffectEntity extends EffectEntity {
@Override
public void draw(){
GroundEffect effect = (GroundEffect)this.effect;
GroundEffect effect = (GroundEffect) this.effect;
if(once && effect.isStatic)
Effects.renderEffect(id, effect, color, lifetime(), rotation, x, y, data);
@@ -48,32 +50,38 @@ public class GroundEffectEntity extends EffectEntity {
}
@Override
public void reset() {
public void reset(){
super.reset();
once = false;
}
/**An effect that is rendered on the ground layer as opposed to the top layer.*/
/**
* An effect that is rendered on the ground layer as opposed to the top layer.
*/
public static class GroundEffect extends Effect{
/**How long this effect stays on the ground when static.*/
/**
* How long this effect stays on the ground when static.
*/
public final float staticLife;
/**If true, this effect will stop and lie on the ground for a specific duration,
* after its initial lifetime is over.*/
/**
* If true, this effect will stop and lie on the ground for a specific duration,
* after its initial lifetime is over.
*/
public final boolean isStatic;
public GroundEffect(float life, float staticLife, EffectRenderer draw) {
public GroundEffect(float life, float staticLife, EffectRenderer draw){
super(life, draw);
this.staticLife = staticLife;
this.isStatic = true;
}
public GroundEffect(boolean isStatic, float life, EffectRenderer draw) {
public GroundEffect(boolean isStatic, float life, EffectRenderer draw){
super(life, draw);
this.staticLife = 0f;
this.isStatic = isStatic;
}
public GroundEffect(float life, EffectRenderer draw) {
public GroundEffect(float life, EffectRenderer draw){
super(life, draw);
this.staticLife = 0f;
this.isStatic = false;

View File

@@ -35,7 +35,7 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawTrait, VelocityTrait, TimeTrait, TargetTrait, Poolable {
public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawTrait, VelocityTrait, TimeTrait, TargetTrait, Poolable{
private static final float sinkLifetime = 80f;
private Interpolator interpolator = new Interpolator();
@@ -46,6 +46,14 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
private float time;
private float sinktime;
/**
* Internal use only!
*/
public ItemDrop(){
hitbox.setSize(5f);
hitboxTile.setSize(5f);
}
public static ItemDrop create(Item item, int amount, float x, float y, float angle){
ItemDrop drop = new ItemDrop();
drop.item = item;
@@ -73,13 +81,7 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
}
/**Internal use only!*/
public ItemDrop(){
hitbox.setSize(5f);
hitboxTile.setSize(5f);
}
public Item getItem() {
public Item getItem(){
return item;
}
@@ -88,66 +90,66 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
@Override
public boolean isDead() {
public boolean isDead(){
return !isAdded();
}
@Override
public Team getTeam() {
public Team getTeam(){
return Team.none;
}
@Override
public float lifetime() {
return 60*60;
public float lifetime(){
return 60 * 60;
}
@Override
public void time(float time) {
public void time(float time){
this.time = time;
}
@Override
public float time() {
public float time(){
return time;
}
@Override
public Vector2 getVelocity() {
public Vector2 getVelocity(){
return velocity;
}
@Override
public boolean collides(SolidTrait other) {
public boolean collides(SolidTrait other){
return other instanceof Player && time > 20f;
}
@Override
public void collision(SolidTrait other, float x, float y) {
Unit player = (Unit)other;
public void collision(SolidTrait other, float x, float y){
Unit player = (Unit) other;
if(player.inventory.canAcceptItem(item, 1)){
int used = Math.min(amount, player.inventory.capacity() - player.inventory.getItem().amount);
player.inventory.addItem(item, used);
amount -= used;
if(amount <= 0) {
if(amount <= 0){
CallEntity.onPickup(getID());
}
}
}
@Override
public void draw() {
float size = itemSize * (1f - sinktime/sinkLifetime) * (1f-Mathf.curve(fin(), 0.98f));
public void draw(){
float size = itemSize * (1f - sinktime / sinkLifetime) * (1f - Mathf.curve(fin(), 0.98f));
Tile tile = world.tileWorld(x, y);
Draw.color(Color.WHITE, tile == null || !tile.floor().isLiquid ? Color.WHITE : tile.floor().liquidColor, sinktime/sinkLifetime);
Draw.color(Color.WHITE, tile == null || !tile.floor().isLiquid ? Color.WHITE : tile.floor().liquidColor, sinktime / sinkLifetime);
Draw.rect(item.region, x, y, size, size);
int stored = Mathf.clamp(amount / 6, 1, 8);
for(int i = 0; i < stored; i ++) {
for(int i = 0; i < stored; i++){
float px = stored == 1 ? 0 : Mathf.randomSeedRange(i + 1, 4f);
float py = stored == 1 ? 0 : Mathf.randomSeedRange(i + 2, 4f);
Draw.rect(item.region, x + px, y + py, size, size);
@@ -157,8 +159,8 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
@Override
public void update() {
if(Net.client()) {
public void update(){
if(Net.client()){
interpolate();
}else{
updateVelocity(0.2f);
@@ -190,28 +192,28 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
@Override
public void reset() {
public void reset(){
time = 0f;
interpolator.reset();
}
@Override
public Interpolator getInterpolator() {
public Interpolator getInterpolator(){
return interpolator;
}
@Override
public float drawSize() {
public float drawSize(){
return 10;
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return itemGroup;
}
@Override
public void writeSave(DataOutput data) throws IOException {
public void writeSave(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(item.id);
@@ -219,7 +221,7 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
@Override
public void readSave(DataInput data) throws IOException {
public void readSave(DataInput data) throws IOException{
x = data.readFloat();
y = data.readFloat();
item = Item.getByID(data.readByte());

View File

@@ -32,17 +32,22 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
private PosTrait to;
private Runnable done;
public ItemTransfer(){
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
public static void transferAmmo(Item item, float x, float y, Unit to){
if(to == null) return;
to.addAmmo(item);
create(item, x, y, to, () -> {});
create(item, x, y, to, () -> {
});
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
public static void transferItemEffect(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> {});
create(item, x, y, to, () -> {
});
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
@@ -54,10 +59,11 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
@Remote(in = In.entities, called = Loc.server)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null) return;
for (int i = 0; i < Mathf.clamp(amount/3, 1, 8); i++) {
Timers.run(i*3, () -> create(item, x, y, tile, () -> {}));
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Timers.run(i * 3, () -> create(item, x, y, tile, () -> {
}));
}
tile.entity.items.addItem(item, amount);
tile.entity.items.add(item, amount);
}
public static void create(Item item, float fromx, float fromy, PosTrait to, Runnable done){
@@ -70,15 +76,13 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
tr.add();
}
public ItemTransfer(){}
@Override
public float lifetime() {
public float lifetime(){
return 60;
}
@Override
public void reset() {
public void reset(){
super.reset();
item = null;
to = null;
@@ -89,7 +93,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
}
@Override
public void removed() {
public void removed(){
if(done != null){
threads.run(done);
}
@@ -97,7 +101,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
}
@Override
public void update() {
public void update(){
if(to == null){
remove();
return;
@@ -110,24 +114,24 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
}
@Override
public void draw() {
float length = fslope()*6f;
public void draw(){
float length = fslope() * 6f;
float angle = current.set(x, y).sub(from).angle();
Draw.color(Palette.accent);
Lines.stroke(fslope()*2f);
Lines.stroke(fslope() * 2f);
Lines.circle(x, y, fslope()*2f);
Lines.circle(x, y, fslope() * 2f);
Lines.lineAngleCenter(x, y, angle, length);
Lines.lineAngle(x, y, angle, fout()*6f);
Lines.lineAngle(x, y, angle, fout() * 6f);
Draw.color(item.color);
Fill.circle(x, y, fslope()*1.5f);
Fill.circle(x, y, fslope() * 1.5f);
Draw.reset();
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return effectGroup;
}

View File

@@ -46,7 +46,15 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
private Color color = Palette.lancerLaser;
private SeedRandom random = new SeedRandom();
/**Create a lighting branch at a location. Use Team.none to damage everyone.*/
/**
* For pooling use only. Do not call directly!
*/
public Lightning(){
}
/**
* Create a lighting branch at a location. Use Team.none to damage everyone.
*/
public static void create(Team team, Effect effect, Color color, float damage, float x, float y, float targetAngle, int length){
CallEntity.createLighting(lastSeed++, team, effect, color, damage, x, y, targetAngle, length);
}
@@ -69,7 +77,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
Units.getNearbyEnemies(team, rect, entities::add);
for(int i = 0; i < length; i ++){
for(int i = 0; i < length; i++){
l.lines.add(new Vector2(x, y));
float fx = x, fy = y;
@@ -82,15 +90,15 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
Units.getNearbyEnemies(team, rect, entity -> {
float dst = entity.distanceTo(x2, y2);
if(dst < attractRange) {
if(dst < attractRange){
angle = Mathf.slerp(angle, Angles.angle(x2, y2, entity.x, entity.y), (attractRange - dst) / attractRange / 4f);
}
entity.getHitbox(hitrect);
hitrect.x -= range/2f;
hitrect.y -= range/2f;
hitrect.width += range/2f;
hitrect.height += range/2f;
hitrect.x -= range / 2f;
hitrect.y -= range / 2f;
hitrect.width += range / 2f;
hitrect.height += range / 2f;
if(hitrect.contains(x2, y2) || hitrect.contains(fx, fy)){
float result = damage;
@@ -104,7 +112,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
});
if(l.random.chance(0.1f)){
createLighting(l.random.nextInt(), team, effect, color, damage, x2, y2, angle + l.random.range(100f), length/3);
createLighting(l.random.nextInt(), team, effect, color, damage, x2, y2, angle + l.random.range(100f), length / 3);
}
x = x2;
@@ -115,47 +123,44 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
l.add();
}
/**For pooling use only. Do not call directly!*/
public Lightning(){}
@Override
public boolean isSyncing() {
public boolean isSyncing(){
return false;
}
@Override
public void write(DataOutput data) throws IOException {
public void write(DataOutput data) throws IOException{
}
@Override
public void read(DataInput data, long time) throws IOException {
public void read(DataInput data, long time) throws IOException{
}
@Override
public float lifetime() {
public float lifetime(){
return 10;
}
@Override
public void reset() {
public void reset(){
color = Palette.lancerLaser;
lines.clear();
}
@Override
public void removed() {
public void removed(){
Pooling.free(this);
}
@Override
public void draw() {
public void draw(){
float lx = x, ly = y;
Draw.color(color, Color.WHITE, fin());
for(int i = 0; i < lines.size; i ++){
for(int i = 0; i < lines.size; i++){
Vector2 v = lines.get(i);
Lines.stroke(fout() * 3f + 1f-(float)i/lines.size);
Lines.stroke(fout() * 3f + 1f - (float) i / lines.size);
Lines.line(lx, ly, v.x, v.y);
lx = v.x;
ly = v.y;
@@ -164,7 +169,7 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return bulletGroup;
}
}

View File

@@ -40,7 +40,7 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.puddleGroup;
import static io.anuke.mindustry.Vars.world;
public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait {
public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait{
private static final IntMap<Puddle> map = new IntMap<>();
private static final float maxLiquid = 70f;
private static final int maxGeneration = 2;
@@ -58,17 +58,29 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
private float accepting;
private byte generation;
/**Deposists a puddle between tile and source.*/
/**
* Deserialization use only!
*/
public Puddle(){
}
/**
* Deposists a puddle between tile and source.
*/
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
}
/**Deposists a puddle at a tile.*/
/**
* Deposists a puddle at a tile.
*/
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/**Returns the puddle on the specified tile. May return null.*/
/**
* Returns the puddle on the specified tile. May return null.
*/
public static Puddle getPuddle(Tile tile){
return map.get(tile.packedPosition());
}
@@ -76,11 +88,11 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
private static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
reactPuddle(tile.floor().liquidDrop, liquid, amount, tile,
(tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f);
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
if(generation == 0 && Timers.get(tile, "ripple", 50)){
Effects.effect(BlockFx.ripple, tile.floor().liquidDrop.color,
(tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f);
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
}
return;
}
@@ -93,28 +105,32 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
puddle.tile = tile;
puddle.liquid = liquid;
puddle.amount = amount;
puddle.generation = (byte)generation;
puddle.set((tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f);
puddle.generation = (byte) generation;
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.packedPosition(), puddle);
}else if(p.liquid == liquid){
p.accepting = Math.max(amount, p.accepting);
if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid/2f){
Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f);
if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid / 2f){
Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
}
}else{
p.amount -= reactPuddle(p.liquid, liquid, amount, p.tile, p.x, p.y);
}
}
/**Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.*/
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
/**Reacts two liquids together at a location.*/
/**
* Reacts two liquids together at a location.
*/
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
@@ -126,25 +142,27 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
if(Mathf.chance(0.5f * amount)){
Effects.effect(EnvironmentFx.steam, x, y);
}
return - 0.1f * amount;
return -0.1f * amount;
}else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold puddle
if(Mathf.chance(0.8f * amount)){
Effects.effect(EnvironmentFx.steam, x, y);
}
return - 0.4f * amount;
return -0.4f * amount;
}
return 0f;
}
/**Deserialization use only!*/
public Puddle(){}
@Remote(called = Loc.server, in = In.entities)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
public float getFlammability(){
return liquid.flammability * amount;
}
@Override
public void update() {
public void update(){
//no updating happens clientside
if(Net.client()){
@@ -158,11 +176,11 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
amount += accepting;
accepting = 0f;
if (amount >= maxLiquid / 1.5f && generation < maxGeneration) {
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Timers.delta();
for (GridPoint2 point : Geometry.d4) {
for(GridPoint2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if (other.block() == Blocks.air && other.cliffs == 0) {
if(other.block() == Blocks.air && other.cliffs == 0){
deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down puddle propagation
}
@@ -171,14 +189,14 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
amount = Mathf.clamp(amount, 0, maxLiquid);
if (amount <= 0f) {
if(amount <= 0f){
CallEntity.onPuddleRemoved(getID());
}
}
//effects-only code
if(amount >= maxLiquid/2f && updateTime <= 0f){
Units.getNearby(rect.setSize(Mathf.clamp(amount/(maxLiquid/1.5f))*10f).setCenter(x, y), unit -> {
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.getNearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isFlying()) return;
unit.getHitbox(rect2);
@@ -186,7 +204,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
unit.applyEffect(liquid.effect, 0.5f);
if(unit.getVelocity().len() > 0.1) {
if(unit.getVelocity().len() > 0.1){
Effects.effect(BlockFx.ripple, liquid.color, unit.x, unit.y);
}
});
@@ -202,30 +220,30 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void draw() {
public void draw(){
seeds = id;
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount/(maxLiquid/1.5f));
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 20f;
Draw.color(Hue.shift(tmp.set(liquid.color), 2, -0.05f));
Fill.circle(x + Mathf.sin(Timers.time() + seeds*532, sscl, smag), y + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 8f);
Fill.circle(x + Mathf.sin(Timers.time() + seeds * 532, sscl, smag), y + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds*532, sscl, smag),
y + ey + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 5f);
seeds ++;
Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds * 532, sscl, smag),
y + ey + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 5f);
seeds++;
});
Draw.color();
}
@Override
public float drawSize() {
public float drawSize(){
return 20;
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.packedPosition());
stream.writeFloat(x);
stream.writeFloat(y);
@@ -235,7 +253,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void readSave(DataInput stream) throws IOException {
public void readSave(DataInput stream) throws IOException{
this.loadedPosition = stream.readInt();
this.x = stream.readFloat();
this.y = stream.readFloat();
@@ -246,7 +264,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void reset() {
public void reset(){
loadedPosition = -1;
tile = null;
liquid = null;
@@ -256,7 +274,7 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void added() {
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
@@ -264,38 +282,33 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
@Override
public void removed() {
public void removed(){
map.remove(tile.packedPosition());
reset();
}
@Override
public void write(DataOutput data) throws IOException {
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(liquid.id);
data.writeShort((short)(amount * 4));
data.writeShort((short) (amount * 4));
data.writeInt(tile.packedPosition());
}
@Override
public void read(DataInput data, long time) throws IOException {
public void read(DataInput data, long time) throws IOException{
x = data.readFloat();
y = data.readFloat();
liquid = Liquid.getByID(data.readByte());
targetAmount = data.readShort()/4f;
targetAmount = data.readShort() / 4f;
tile = world.tile(data.readInt());
map.put(tile.packedPosition(), this);
}
@Override
public EntityGroup targetGroup() {
public EntityGroup targetGroup(){
return puddleGroup;
}
@Remote(called = Loc.server, in = In.entities)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
}

View File

@@ -3,10 +3,12 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.Mathf;
public class RubbleDecal extends Decal {
public class RubbleDecal extends Decal{
private int size;
/**Creates a rubble effect at a position. Provide a block size to use.*/
/**
* Creates a rubble effect at a position. Provide a block size to use.
*/
public static void create(float x, float y, int size){
RubbleDecal decal = new RubbleDecal();
decal.size = size;

View File

@@ -8,21 +8,21 @@ import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.world;
public class ScorchDecal extends Decal {
public class ScorchDecal extends Decal{
private static final int scorches = 5;
private static final TextureRegion[] regions = new TextureRegion[scorches];
public static void create(float x, float y){
if(regions[0] == null){
for (int i = 0; i < regions.length; i++) {
regions[i] = Draw.region("scorch" + (i+1));
for(int i = 0; i < regions.length; i++){
regions[i] = Draw.region("scorch" + (i + 1));
}
}
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().liquidDrop != null) return;
ScorchDecal decal = new ScorchDecal();
decal.set(x, y);
decal.add();
@@ -31,10 +31,10 @@ public class ScorchDecal extends Decal {
@Override
public void drawDecal(){
for (int i = 0; i < 5; i++) {
TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches-1)];
for(int i = 0; i < 5; i++){
TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches - 1)];
float rotation = Mathf.randomSeed(id + i, 0, 360);
float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20)/10f;
float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20) / 10f;
Draw.grect(region, x + Angles.trnsx(rotation, space), y + Angles.trnsy(rotation, space), rotation - 90);
}
}

View File

@@ -13,44 +13,43 @@ import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.shieldGroup;
//todo re-implement
public class Shield extends BaseEntity implements DrawTrait {
public boolean active;
public boolean hitPlayers = false;
public float radius = 0f;
private float uptime = 0f;
private final Tile tile;
public Shield(Tile tile){
this.tile = tile;
this.x = tile.worldx();
this.y = tile.worldy();
}
public float drawSize(){
return 150;
}
@Override
public void update(){
float alpha = 0.1f;
Interpolation interp = Interpolation.fade;
if(active){
uptime = interp.apply(uptime, 1f, alpha * Timers.delta());
}else{
uptime = interp.apply(uptime, 0f, alpha * Timers.delta());
if(uptime <= 0.05f)
remove();
}
uptime = Mathf.clamp(uptime);
if(!(tile.block() instanceof ShieldBlock)){
remove();
return;
}
ShieldBlock block = (ShieldBlock)tile.block();
public class Shield extends BaseEntity implements DrawTrait{
private final Tile tile;
public boolean active;
public boolean hitPlayers = false;
public float radius = 0f;
private float uptime = 0f;
public Shield(Tile tile){
this.tile = tile;
this.x = tile.worldx();
this.y = tile.worldy();
}
public float drawSize(){
return 150;
}
@Override
public void update(){
float alpha = 0.1f;
Interpolation interp = Interpolation.fade;
if(active){
uptime = interp.apply(uptime, 1f, alpha * Timers.delta());
}else{
uptime = interp.apply(uptime, 0f, alpha * Timers.delta());
if(uptime <= 0.05f)
remove();
}
uptime = Mathf.clamp(uptime);
if(!(tile.block() instanceof ShieldBlock)){
remove();
return;
}
ShieldBlock block = (ShieldBlock) tile.block();
/*
Entities.getNearby(bulletGroup, x, y, block.shieldRadius * 2*uptime + 10, entity->{
@@ -64,39 +63,39 @@ public class Shield extends BaseEntity implements DrawTrait {
}
}
});*/
}
@Override
public void draw(){
if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){
return;
}
}
Fill.circle(x, y, drawRadius());
}
float drawRadius(){
return (radius + Mathf.sin(Timers.time(), 25f, 1f));
}
public void removeDelay(){
active = false;
}
@Override
public void draw(){
if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){
return;
}
@Override
public EntityGroup targetGroup() {
return shieldGroup;
}
Fill.circle(x, y, drawRadius());
}
float drawRadius(){
return (radius + Mathf.sin(Timers.time(), 25f, 1f));
}
public void removeDelay(){
active = false;
}
@Override
public EntityGroup targetGroup(){
return shieldGroup;
}
@Override
public void added(){
active = true;
}
@Override
public void removed(){
active = false;
uptime = 0f;
}
@Override
public void added(){
active = true;
}
@Override
public void removed(){
active = false;
uptime = 0f;
}
}

View File

@@ -1,5 +1,7 @@
package io.anuke.mindustry.entities.traits;
/**A flag interface for marking an effect as appearing below liquids.*/
public interface BelowLiquidTrait {
/**
* A flag interface for marking an effect as appearing below liquids.
*/
public interface BelowLiquidTrait{
}

View File

@@ -37,27 +37,46 @@ import static io.anuke.mindustry.Vars.tilesize;
import static io.anuke.mindustry.Vars.tmptr;
import static io.anuke.mindustry.Vars.world;
/**Interface for units that build, break or mine things.*/
/**
* Interface for units that build, break or mine things.
*/
public interface BuilderTrait extends Entity{
//these are not instance variables!
float placeDistance = 140f;
float mineDistance = 70f;
/**Returns the queue for storing build requests.*/
/**
* Returns the queue for storing build requests.
*/
Queue<BuildRequest> getPlaceQueue();
/**Returns the tile this builder is currently mining.*/
/**
* Returns the tile this builder is currently mining.
*/
Tile getMineTile();
/**Sets the tile this builder is currently mining.*/
/**
* Sets the tile this builder is currently mining.
*/
void setMineTile(Tile tile);
/**Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc.*/
/**
* Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc.
*/
float getMinePower();
/**Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.*/
/**
* Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.
*/
float getBuildPower(Tile tile);
/**
* Whether this type of builder can begin creating new blocks.
*/
default boolean canCreateBlocks(){
return true;
}
default void writeBuilding(DataOutput output) throws IOException{
BuildRequest request = getCurrentRequest();
@@ -78,17 +97,17 @@ public interface BuilderTrait extends Entity{
}
default void readBuilding(DataInput input, boolean applyChanges) throws IOException{
synchronized (getPlaceQueue()) {
synchronized(getPlaceQueue()){
if(applyChanges) getPlaceQueue().clear();
byte type = input.readByte();
if (type != -1) {
if(type != -1){
int position = input.readInt();
BuildRequest request;
if (type == 1) { //remove
if(type == 1){ //remove
request = new BuildRequest(position % world.width(), position / world.width());
} else { //place
}else{ //place
byte recipe = input.readByte();
byte rotation = input.readByte();
request = new BuildRequest(position % world.width(), position / world.width(), rotation, Recipe.getByID(recipe));
@@ -101,17 +120,21 @@ public interface BuilderTrait extends Entity{
}
}
/**Return whether this builder's place queue contains items.*/
/**
* 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.*/
/**
* 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){
synchronized (getPlaceQueue()) {
for (BuildRequest request : getPlaceQueue()) {
if (request.x == x && request.y == y) {
synchronized(getPlaceQueue()){
for(BuildRequest request : getPlaceQueue()){
if(request.x == x && request.y == y){
clearBuilding();
addBuildRequest(request);
return;
@@ -122,16 +145,20 @@ public interface BuilderTrait extends Entity{
addBuildRequest(new BuildRequest(x, y, rotation, recipe));
}
/**Clears the placement queue.*/
/**
* Clears the placement queue.
*/
default void clearBuilding(){
getPlaceQueue().clear();
}
/**Add another build requests to the tail of the queue, if it doesn't exist there yet.*/
/**
* Add another build requests to the tail of the queue, if it doesn't exist there yet.
*/
default void addBuildRequest(BuildRequest place){
synchronized (getPlaceQueue()) {
for (BuildRequest request : getPlaceQueue()) {
if (request.x == place.x && request.y == place.y) {
synchronized(getPlaceQueue()){
for(BuildRequest request : getPlaceQueue()){
if(request.x == place.x && request.y == place.y){
return;
}
}
@@ -139,16 +166,20 @@ public interface BuilderTrait extends Entity{
}
}
/**Return the build requests currently active, or the one at the top of the queue.
* May return null.*/
/**
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default BuildRequest getCurrentRequest(){
synchronized (getPlaceQueue()) {
synchronized(getPlaceQueue()){
return getPlaceQueue().size == 0 ? null : getPlaceQueue().first();
}
}
/**Update building mechanism for this unit.
* This includes mining.*/
/**
* Update building mechanism for this unit.
* This includes mining.
*/
default void updateBuilding(Unit unit){
BuildRequest current = getCurrentRequest();
@@ -171,10 +202,10 @@ public interface BuilderTrait extends Entity{
Tile tile = world.tile(current.x, current.y);
if (!(tile.block() instanceof BuildBlock)) {
if(!current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)) {
if(!(tile.block() instanceof BuildBlock)){
if(canCreateBlocks() && !current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)){
Build.beginPlace(unit.getTeam(), current.x, current.y, current.recipe, current.rotation);
}else if(current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){
}else if(canCreateBlocks() && current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){
Build.beginBreak(unit.getTeam(), current.x, current.y);
}else{
getPlaceQueue().removeFirst();
@@ -198,7 +229,9 @@ public interface BuilderTrait extends Entity{
current.progress = entity.progress();
}
/**Do not call directly.*/
/**
* Do not call directly.
*/
default void updateMining(Unit unit){
Tile tile = getMineTile();
@@ -211,26 +244,28 @@ public interface BuilderTrait extends Entity{
if(unit.inventory.canAcceptItem(item) &&
Mathf.chance(Timers.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){
CallEntity.transferItemToUnit(item,
tile.worldx() + Mathf.range(tilesize/2f),
tile.worldy() + Mathf.range(tilesize/2f),
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f),
unit);
}
if(Mathf.chance(0.06 * Timers.delta())){
Effects.effect(BlockFx.pulverizeSmall,
tile.worldx() + Mathf.range(tilesize/2f),
tile.worldy() + Mathf.range(tilesize/2f), 0f, item.color);
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
}
}
/**Draw placement effects for an entity. This includes mining*/
/**
* Draw placement effects for an entity. This includes mining
*/
default void drawBuilding(Unit unit){
BuildRequest request;
synchronized (getPlaceQueue()) {
if (!isBuilding()) {
if (getMineTile() != null) {
synchronized(getPlaceQueue()){
if(!isBuilding()){
if(getMineTile() != null){
drawMining(unit);
}
return;
@@ -250,7 +285,7 @@ public interface BuilderTrait extends Entity{
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
float sz = Vars.tilesize*tile.block().size/2f;
float sz = Vars.tilesize * tile.block().size / 2f;
float ang = unit.angleTo(tile);
tmptr[0].set(tile.drawx() - sz, tile.drawy() - sz);
@@ -281,14 +316,16 @@ public interface BuilderTrait extends Entity{
Draw.color();
}
/**Internal use only.*/
/**
* Internal use only.
*/
default void drawMining(Unit unit){
Tile tile = getMineTile();
if(tile == null) return;
float focusLen = 4f + Mathf.absin(Timers.time(), 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize/8f;
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
@@ -297,10 +334,10 @@ public interface BuilderTrait extends Entity{
float ex = tile.worldx() + Mathf.sin(Timers.time() + 48, swingScl, swingMag);
float ey = tile.worldy() + Mathf.sin(Timers.time() + 48, swingScl + 2f, swingMag);
Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f-flashScl + Mathf.absin(Timers.time(), 0.5f, flashScl));
Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f - flashScl + Mathf.absin(Timers.time(), 0.5f, flashScl));
Shapes.laser("minelaser", "minelaser-end", px, py, ex, ey);
if(unit instanceof Player && ((Player) unit).isLocal) {
if(unit instanceof Player && ((Player) unit).isLocal){
Draw.color(Palette.accent);
Lines.poly(tile.worldx(), tile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Timers.time());
}
@@ -308,16 +345,20 @@ public interface BuilderTrait extends Entity{
Draw.color();
}
/**Class for storing build requests. Can be either a place or remove request.*/
class BuildRequest {
/**
* Class for storing build requests. Can be either a place or remove request.
*/
class BuildRequest{
public final int x, y, rotation;
public final Recipe recipe;
public final boolean remove;
public float progress;
/**This creates a build request.*/
public BuildRequest(int x, int y, int rotation, Recipe recipe) {
/**
* This creates a build request.
*/
public BuildRequest(int x, int y, int rotation, Recipe recipe){
this.x = x;
this.y = y;
this.rotation = rotation;
@@ -325,8 +366,10 @@ public interface BuilderTrait extends Entity{
this.remove = false;
}
/**This creates a remove request.*/
public BuildRequest(int x, int y) {
/**
* This creates a remove request.
*/
public BuildRequest(int x, int y){
this.x = x;
this.y = y;
this.rotation = -1;

View File

@@ -8,6 +8,7 @@ public interface CarriableTrait extends TeamTrait, TargetTrait, SolidTrait{
return getCarrier() != null;
}
void setCarrier(CarryTrait carrier);
CarryTrait getCarrier();
void setCarrier(CarryTrait carrier);
}

View File

@@ -10,28 +10,6 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.entities.trait.SolidTrait;
public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
/**Returns the thing this carrier is carrying.*/
CarriableTrait getCarry();
/**Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state.*/
void setCarry(CarriableTrait unit);
/**Returns maximum mass this carrier can carry.*/
float getCarryWeight();
/**Drops the unit that is being carried, if applicable.*/
default void dropCarry(){
carry(null);
}
default void dropCarryLocal(){
setCarryOf(null, this, null);
}
/**Do not override unless absolutely necessary.
* Carries a unit. To drop a unit, call with {@code null}.*/
default void carry(CarriableTrait unit){
CallEntity.setCarryOf(this instanceof Player ? (Player)this : null, this, unit);
}
@Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities)
static void dropSelf(Player player){
if(player.getCarrier() != null){
@@ -41,6 +19,7 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
@Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities)
static void setCarryOf(Player player, CarryTrait trait, CarriableTrait unit){
if(trait == null) return;
if(player != null){ //when a server recieves this called from a player, set the carrier to the player.
trait = player;
}
@@ -61,4 +40,38 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
Effects.effect(UnitFx.unitPickup, trait);
}
}
/**
* Returns the thing this carrier is carrying.
*/
CarriableTrait getCarry();
/**
* Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state.
*/
void setCarry(CarriableTrait unit);
/**
* Returns maximum mass this carrier can carry.
*/
float getCarryWeight();
/**
* Drops the unit that is being carried, if applicable.
*/
default void dropCarry(){
carry(null);
}
default void dropCarryLocal(){
setCarryOf(null, this, null);
}
/**
* Do not override unless absolutely necessary.
* Carries a unit. To drop a unit, call with {@code null}.
*/
default void carry(CarriableTrait unit){
CallEntity.setCarryOf(this instanceof Player ? (Player) this : null, this, unit);
}
}

View File

@@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.UnitInventory;
public interface InventoryTrait {
public interface InventoryTrait{
UnitInventory getInventory();
}

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.ucore.entities.trait.HealthTrait;
//TODO implement
public interface RepairTrait extends TeamTrait {
public interface RepairTrait extends TeamTrait{
HealthTrait getRepairing();

View File

@@ -2,6 +2,8 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.ucore.entities.trait.Entity;
/**Marks an entity as serializable.*/
/**
* Marks an entity as serializable.
*/
public interface SaveTrait extends Entity, TypeTrait, Saveable{
}

View File

@@ -4,7 +4,8 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public interface Saveable {
public interface Saveable{
void writeSave(DataOutput stream) throws IOException;
void readSave(DataInput stream) throws IOException;
}

View File

@@ -7,6 +7,8 @@ import io.anuke.ucore.util.Timer;
public interface ShooterTrait extends VelocityTrait, TeamTrait, InventoryTrait{
Timer getTimer();
int getShootTimer(boolean left);
Weapon getWeapon();
}

View File

@@ -3,8 +3,10 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.world.Tile;
public interface SpawnerTrait {
public interface SpawnerTrait{
Tile getTile();
void updateSpawning(Unit unit);
float getSpawnProgress();
}

View File

@@ -10,18 +10,22 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.threads;
public interface SyncTrait extends Entity, TypeTrait {
public interface SyncTrait extends Entity, TypeTrait{
/**Whether smoothing of entities is enabled when using multithreading; not yet implemented.*/
/**
* Whether smoothing of entities is enabled when using multithreading; not yet implemented.
*/
static boolean isSmoothing(){
return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f;
}
/**Sets the position of this entity and updated the interpolator.*/
/**
* Sets the position of this entity and updated the interpolator.
*/
default void setNet(float x, float y){
set(x, y);
if(getInterpolator() != null) {
if(getInterpolator() != null){
getInterpolator().target.set(x, y);
getInterpolator().last.set(x, y);
getInterpolator().pos.set(0, 0);
@@ -30,9 +34,12 @@ public interface SyncTrait extends Entity, TypeTrait {
}
}
/**Interpolate entity position only. Override if you need to interpolate rotations or other values.*/
/**
* Interpolate entity position only. Override if you need to interpolate rotations or other values.
*/
default void interpolate(){
if(getInterpolator() == null) throw new RuntimeException("This entity must have an interpolator to interpolate()!");
if(getInterpolator() == null)
throw new RuntimeException("This entity must have an interpolator to interpolate()!");
getInterpolator().update();
@@ -40,17 +47,22 @@ public interface SyncTrait extends Entity, TypeTrait {
setY(getInterpolator().pos.y);
}
/**Return the interpolator used for smoothing the position. Optional.*/
/**
* Return the interpolator used for smoothing the position. Optional.
*/
default Interpolator getInterpolator(){
return null;
}
/**Whether syncing is enabled for this entity; true by default.*/
/**
* Whether syncing is enabled for this entity; true by default.
*/
default boolean isSyncing(){
return true;
}
//Read and write sync data, usually position
void write(DataOutput data) throws IOException;
void read(DataInput data, long time) throws IOException;
}

View File

@@ -1,16 +1,21 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.game.Team;
import io.anuke.ucore.entities.trait.VelocityTrait;
import io.anuke.ucore.entities.trait.PosTrait;
import io.anuke.ucore.entities.trait.VelocityTrait;
/**Base interface for targetable entities.*/
public interface TargetTrait extends PosTrait, VelocityTrait {
/**
* Base interface for targetable entities.
*/
public interface TargetTrait extends PosTrait, VelocityTrait{
boolean isDead();
Team getTeam();
/**Whether this entity is a valid target.*/
/**
* Whether this entity is a valid target.
*/
default boolean isValid(){
return !isDead();
}

View File

@@ -3,6 +3,6 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.game.Team;
import io.anuke.ucore.entities.trait.Entity;
public interface TeamTrait extends Entity {
public interface TeamTrait extends Entity{
Team getTeam();
}

View File

@@ -4,12 +4,14 @@ import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectIntMap;
import io.anuke.ucore.function.Supplier;
public interface TypeTrait {
public interface TypeTrait{
int[] lastRegisteredID = {0};
Array<Supplier<? extends TypeTrait>> registeredTypes = new Array<>();
ObjectIntMap<Class<? extends TypeTrait>> typeToID = new ObjectIntMap<>();
/**Register and return a type ID. The supplier should return a fresh instace of that type.*/
/**
* Register and return a type ID. The supplier should return a fresh instace of that type.
*/
static <T extends TypeTrait> void registerType(Class<T> type, Supplier<T> supplier){
if(typeToID.get(type, -1) != -1){
throw new RuntimeException("Type is already registered: '" + type + "'!");
@@ -18,10 +20,12 @@ public interface TypeTrait {
registeredTypes.add(supplier);
int result = lastRegisteredID[0];
typeToID.put(type, result);
lastRegisteredID[0] ++;
lastRegisteredID[0]++;
}
/**Registers a syncable type by ID.*/
/**
* Registers a syncable type by ID.
*/
static Supplier<? extends TypeTrait> getTypeByID(int id){
if(id == -1){
throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?");
@@ -29,11 +33,14 @@ public interface TypeTrait {
return registeredTypes.get(id);
}
/**Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX.
* Do not override!*/
/**
* Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX.
* Do not override!
*/
default int getTypeID(){
int id = typeToID.get(getClass(), -1);
if(id == -1) throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?");
if(id == -1)
throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?");
return id;
}
}

View File

@@ -30,7 +30,10 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.*;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Timer;
import java.io.DataInput;
import java.io.DataOutput;
@@ -39,376 +42,392 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public abstract class BaseUnit extends Unit implements ShooterTrait{
protected static int timerIndex = 0;
protected static int timerIndex = 0;
protected static final int timerTarget = timerIndex++;
protected static final int timerTarget = timerIndex++;
protected static final int timerShootLeft = timerIndex++;
protected static final int timerShootRight = timerIndex++;
protected static final int timerShootLeft = timerIndex++;
protected static final int timerShootRight = timerIndex++;
protected UnitType type;
protected Timer timer = new Timer(5);
protected StateMachine state = new StateMachine();
protected TargetTrait target;
protected UnitType type;
protected Timer timer = new Timer(5);
protected StateMachine state = new StateMachine();
protected TargetTrait target;
protected boolean isWave;
protected Squad squad;
protected int spawner;
protected boolean isWave;
protected Squad squad;
protected int spawner;
/**Initialize the type and team of this unit. Only call once!*/
public void init(UnitType type, Team team){
if(this.type != null) throw new RuntimeException("This unit is already initialized!");
/**
* internal constructor used for deserialization, DO NOT USE
*/
public BaseUnit(){
}
this.type = type;
this.team = team;
}
@Remote(called = Loc.server, in = In.entities)
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
public void setSpawner(Tile tile) {
this.spawner = tile.packedPosition();
}
if(Net.server() || !Net.active()){
UnitDrops.dropItems(unit);
}
public UnitType getType() {
return type;
}
float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f);
float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize() / 2f, Palette.darkFlame);
/**internal constructor used for deserialization, DO NOT USE*/
public BaseUnit(){}
unit.onSuperDeath();
/**Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo.*/
public void setWave(){
isWave = true;
}
ScorchDecal.create(unit.x, unit.y);
Effects.effect(ExplosionFx.explosion, unit);
Effects.shake(2f, 2f, unit);
public void setSquad(Squad squad) {
this.squad = squad;
squad.units ++;
}
//must run afterwards so the unit's group is not null
threads.runDelay(unit::remove);
}
public void rotate(float angle){
rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed);
}
/**
* Initialize the type and team of this unit. Only call once!
*/
public void init(UnitType type, Team team){
if(this.type != null) throw new RuntimeException("This unit is already initialized!");
public boolean targetHasFlag(BlockFlag flag){
return target instanceof TileEntity &&
((TileEntity)target).tile.block().flags.contains(flag);
}
this.type = type;
this.team = team;
}
public void updateRespawning(){
if(spawner == -1) return;
public UnitType getType(){
return type;
}
Tile tile = world.tile(spawner);
if(tile != null && tile.entity != null){
if(tile.entity instanceof SpawnerTrait){
((SpawnerTrait) tile.entity).updateSpawning(this);
public Tile getSpawner(){
return world.tile(spawner);
}
public void setSpawner(Tile tile){
this.spawner = tile.packedPosition();
}
/**
* Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo.
*/
public void setWave(){
isWave = true;
}
public void setSquad(Squad squad){
this.squad = squad;
squad.units++;
}
public void rotate(float angle){
rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed);
}
public boolean targetHasFlag(BlockFlag flag){
return target instanceof TileEntity &&
((TileEntity) target).tile.block().flags.contains(flag);
}
public void updateRespawning(){
if(spawner == -1) return;
Tile tile = world.tile(spawner);
if(tile != null && tile.entity != null){
if(tile.entity instanceof SpawnerTrait){
((SpawnerTrait) tile.entity).updateSpawning(this);
}
}else{
spawner = -1;
}
}
public void setState(UnitState state){
this.state.set(state);
}
public void retarget(Runnable run){
if(timer.get(timerTarget, 20)){
run.run();
}
}
/**Only runs when the unit has a target.*/
public void behavior(){
}
public void updateTargeting(){
if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team))
|| (target instanceof TileEntity && ((TileEntity) target).tile.entity == null)){
target = null;
}
}
public void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag));
if (target != null) this.target = target.entity;
}
public void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag));
if (target != null) this.target = target.entity;
}
public void targetClosest(){
target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange());
}
public TileEntity getClosestEnemyCore(){
if(Vars.state.teams.has(team)){
ObjectSet<TeamData> datas = Vars.state.teams.enemyDataOf(team);
for(TeamData data : datas){
Tile tile = Geometry.findClosest(x, y, data.cores);
if(tile != null){
return tile.entity;
}
}
}
return null;
}
public UnitState getStartState(){
return null;
}
protected void drawItems(){
float backTrns = 4f, itemSize = 5f;
if(inventory.hasItem()){
ItemStack stack = inventory.getItem();
int stored = Mathf.clamp(stack.amount / 6, 1, 8);
for(int i = 0; i < stored; i ++) {
float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 60f);
float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 3, 1f) - 1f;
Draw.rect(stack.item.region,
x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT),
y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT),
itemSize, itemSize, rotation);
}
}
}
@Override
public boolean isValid() {
return super.isValid() && isAdded();
}
@Override
public Timer getTimer() {
return timer;
}
@Override
public int getShootTimer(boolean left) {
return left ? timerShootLeft : timerShootRight;
}
@Override
public Weapon getWeapon() {
return type.weapon;
}
@Override
public TextureRegion getIconRegion() {
return type.iconRegion;
}
@Override
public int getItemCapacity() {
return type.itemCapacity;
}
@Override
public int getAmmoCapacity() {
return type.ammoCapacity;
}
@Override
public boolean isInfiniteAmmo() {
return isWave;
}
@Override
public void interpolate() {
super.interpolate();
if(interpolator.values.length > 0){
rotation = interpolator.values[0];
}
}
@Override
public float maxHealth() {
return type.health;
}
@Override
public float getArmor() {
return type.armor;
}
@Override
public boolean acceptsAmmo(Item item) {
return getWeapon().getAmmoType(item) != null && inventory.canAcceptAmmo(getWeapon().getAmmoType(item));
}
@Override
public void addAmmo(Item item) {
inventory.addAmmo(getWeapon().getAmmoType(item));
}
@Override
public float getSize() {
return 8;
}
@Override
public float getMass() {
return type.mass;
}
@Override
public boolean isFlying() {
return type.isFlying;
}
@Override
public void update(){
if(hitTime > 0){
hitTime -= Timers.delta();
}
if(hitTime < 0) hitTime = 0;
if(isDead()){
updateRespawning();
return;
}
if(Net.client()){
interpolate();
status.update(this);
return;
}
avoidOthers(8f);
if(squad != null){
squad.update();
}
updateTargeting();
state.update();
updateVelocityStatus(type.drag, type.maxVelocity);
if(target != null) behavior();
if(!isWave) {
x = Mathf.clamp(x, 0, world.width() * tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize);
}
}
@Override
public void draw(){
}
@Override
public void drawUnder(){
}
@Override
public void drawOver(){
}
@Override
public void removed() {
Tile tile = world.tile(spawner);
if(tile != null && tile.entity instanceof UnitFactoryEntity){
UnitFactoryEntity factory = (UnitFactoryEntity)tile.entity;
factory.hasSpawned = false;
}
spawner = -1;
}
@Override
public float drawSize(){
return 14;
}
@Override
public void onDeath(){
CallEntity.onUnitDeath(this);
}
@Override
public void added(){
hitbox.setSize(type.hitsize);
hitboxTile.setSize(type.hitsizeTile);
state.set(getStartState());
health(maxHealth());
}
@Override
public EntityGroup targetGroup() {
return unitGroups[team.ordinal()];
}
@Override
public void writeSave(DataOutput stream) throws IOException {
super.writeSave(stream);
stream.writeByte(type.id);
stream.writeBoolean(isWave);
stream.writeInt(spawner);
}
@Override
public void readSave(DataInput stream) throws IOException {
super.readSave(stream);
byte type = stream.readByte();
this.isWave = stream.readBoolean();
this.spawner = stream.readInt();
this.type = UnitType.getByID(type);
add();
}
@Override
public void write(DataOutput data) throws IOException{
super.writeSave(data);
data.writeByte(type.id);
data.writeInt(spawner);
}
@Override
public void read(DataInput data, long time) throws IOException{
float lastx = x, lasty = y, lastrot = rotation;
super.readSave(data);
this.type = UnitType.getByID(data.readByte());
this.spawner = data.readInt();
interpolator.read(lastx, lasty, x, y, time, rotation);
rotation = lastrot;
}
public void onSuperDeath(){
super.onDeath();
}
@Remote(called = Loc.server, in = In.entities)
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
if(Net.server() || !Net.active()){
UnitDrops.dropItems(unit);
}
float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f);
float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize()/2f, Palette.darkFlame);
unit.onSuperDeath();
ScorchDecal.create(unit.x, unit.y);
Effects.effect(ExplosionFx.explosion, unit);
Effects.shake(2f, 2f, unit);
//must run afterwards so the unit's group is not null
threads.runDelay(unit::remove);
}
}else{
spawner = -1;
}
}
public void setState(UnitState state){
this.state.set(state);
}
public void retarget(Runnable run){
if(timer.get(timerTarget, 20)){
run.run();
}
}
/**
* Only runs when the unit has a target.
*/
public void behavior(){
}
public void updateTargeting(){
if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team))
|| (target instanceof TileEntity && ((TileEntity) target).tile.entity == null)){
target = null;
}
}
public void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag));
if(target != null) this.target = target.entity;
}
public void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag));
if(target != null) this.target = target.entity;
}
public void targetClosest(){
target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange());
}
public TileEntity getClosestEnemyCore(){
if(Vars.state.teams.has(team)){
ObjectSet<TeamData> datas = Vars.state.teams.enemyDataOf(team);
for(TeamData data : datas){
Tile tile = Geometry.findClosest(x, y, data.cores);
if(tile != null){
return tile.entity;
}
}
}
return null;
}
public UnitState getStartState(){
return null;
}
protected void drawItems(){
float backTrns = 4f, itemSize = 5f;
if(inventory.hasItem()){
ItemStack stack = inventory.getItem();
int stored = Mathf.clamp(stack.amount / 6, 1, 8);
for(int i = 0; i < stored; i++){
float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 60f);
float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 3, 1f) - 1f;
Draw.rect(stack.item.region,
x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT),
y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT),
itemSize, itemSize, rotation);
}
}
}
@Override
public boolean isValid(){
return super.isValid() && isAdded();
}
@Override
public Timer getTimer(){
return timer;
}
@Override
public int getShootTimer(boolean left){
return left ? timerShootLeft : timerShootRight;
}
@Override
public Weapon getWeapon(){
return type.weapon;
}
@Override
public TextureRegion getIconRegion(){
return type.iconRegion;
}
@Override
public int getItemCapacity(){
return type.itemCapacity;
}
@Override
public int getAmmoCapacity(){
return type.ammoCapacity;
}
@Override
public boolean isInfiniteAmmo(){
return isWave;
}
@Override
public void interpolate(){
super.interpolate();
if(interpolator.values.length > 0){
rotation = interpolator.values[0];
}
}
@Override
public float maxHealth(){
return type.health;
}
@Override
public float getArmor(){
return type.armor;
}
@Override
public boolean acceptsAmmo(Item item){
return getWeapon().getAmmoType(item) != null && inventory.canAcceptAmmo(getWeapon().getAmmoType(item));
}
@Override
public void addAmmo(Item item){
inventory.addAmmo(getWeapon().getAmmoType(item));
}
@Override
public float getSize(){
return 8;
}
@Override
public float getMass(){
return type.mass;
}
@Override
public boolean isFlying(){
return type.isFlying;
}
@Override
public void update(){
if(hitTime > 0){
hitTime -= Timers.delta();
}
if(hitTime < 0) hitTime = 0;
if(isDead()){
updateRespawning();
return;
}
if(Net.client()){
interpolate();
status.update(this);
return;
}
if(!Net.client()){
avoidOthers(8f);
}
if(squad != null){
squad.update();
}
updateTargeting();
state.update();
updateVelocityStatus(type.drag, type.maxVelocity);
if(target != null) behavior();
if(!isWave){
x = Mathf.clamp(x, 0, world.width() * tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize);
}
}
@Override
public void draw(){
}
@Override
public void drawUnder(){
}
@Override
public void drawOver(){
}
@Override
public void removed(){
Tile tile = world.tile(spawner);
if(tile != null && tile.entity instanceof UnitFactoryEntity){
UnitFactoryEntity factory = (UnitFactoryEntity) tile.entity;
factory.hasSpawned = false;
}
spawner = -1;
}
@Override
public float drawSize(){
return 14;
}
@Override
public void onDeath(){
CallEntity.onUnitDeath(this);
}
@Override
public void added(){
hitbox.setSize(type.hitsize);
hitboxTile.setSize(type.hitsizeTile);
state.set(getStartState());
health(maxHealth());
}
@Override
public EntityGroup targetGroup(){
return unitGroups[team.ordinal()];
}
@Override
public void writeSave(DataOutput stream) throws IOException{
super.writeSave(stream);
stream.writeByte(type.id);
stream.writeBoolean(isWave);
stream.writeInt(spawner);
}
@Override
public void readSave(DataInput stream) throws IOException{
super.readSave(stream);
byte type = stream.readByte();
this.isWave = stream.readBoolean();
this.spawner = stream.readInt();
this.type = UnitType.getByID(type);
add();
}
@Override
public void write(DataOutput data) throws IOException{
super.writeSave(data);
data.writeByte(type.id);
data.writeInt(spawner);
}
@Override
public void read(DataInput data, long time) throws IOException{
float lastx = x, lasty = y, lastrot = rotation;
super.readSave(data);
this.type = UnitType.getByID(data.readByte());
this.spawner = data.readInt();
interpolator.read(lastx, lasty, x, y, time, rotation);
rotation = lastrot;
}
public void onSuperDeath(){
super.onDeath();
}
}

View File

@@ -27,28 +27,124 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
protected Trail trail = new Trail(8);
protected CarriableTrait carrying;
protected final UnitState
resupply = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.resupplyPoint)){
retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint));
}else{
circle(20f);
}
}
},
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(){
public void entered(){
target = null;
}
public void update(){
if(Units.invalidateTarget(target, team, x, y)){
target = null;
}
if(!inventory.hasAmmo()){
state.set(resupply);
}else if(target == null){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
targetClosestEnemyFlag(BlockFlag.producer);
if(target == null){
setState(idle);
}
});
}else{
attack(150f);
if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation
&& distanceTo(target) < inventory.getAmmo().getRange()){
AmmoType ammo = inventory.getAmmo();
inventory.useAmmo();
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= maxHealth()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.repair)){
retarget(() -> {
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair));
if(target != null) FlyingUnit.this.target = target.entity;
});
}else{
circle(20f);
}
}
};
//instantiation only
public FlyingUnit(){
}
@Override
public CarriableTrait getCarry() {
public void drawShadow(){
Draw.rect(type.region, x + elevation * elevationScale, y - elevation * elevationScale, rotation - 90);
}
@Override
public CarriableTrait getCarry(){
return carrying;
}
@Override
public void setCarry(CarriableTrait unit) {
public void setCarry(CarriableTrait unit){
this.carrying = unit;
}
@Override
public float getCarryWeight() {
public float getCarryWeight(){
return type.carryWeight;
}
@Override
public void update() {
public void update(){
super.update();
updateRotation();
@@ -59,7 +155,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
}
@Override
public void draw() {
public void draw(){
Draw.alpha(hitTime / hitDuration);
Draw.rect(type.name, x, y, rotation - 90);
@@ -70,12 +166,12 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
}
@Override
public void drawOver() {
trail.draw(Palette.lightTrail, Palette.lightTrail, 5f);
public void drawOver(){
trail.draw(Palette.lightTrail, 5f);
}
@Override
public void behavior() {
public void behavior(){
if(health <= health * type.retreatPercent && !isWave &&
Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
@@ -93,7 +189,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
}
@Override
public float drawSize() {
public float drawSize(){
return 60;
}
@@ -103,7 +199,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f);
y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f);
if (velocity.len() <= 0.2f) {
if(velocity.len() <= 0.2f){
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 8f);
}
}
@@ -122,7 +218,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
vec.set(target.getX() - x, target.getY() - y);
if(vec.len() < circleLength){
vec.rotate((circleLength-vec.len())/circleLength * 180f);
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Timers.delta());
@@ -135,7 +231,7 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
vec.set(target.getX() - x, target.getY() - y);
float length = Mathf.clamp((distanceTo(target) - circleLength)/100f, -1f, 1f);
float length = Mathf.clamp((distanceTo(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(type.speed * Timers.delta() * length);
if(length < 0) vec.rotate(180f);
@@ -152,102 +248,11 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
if(diff > 100f && vec.len() < circleLength){
vec.setAngle(velocity.angle());
}else{
vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f));
vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f));
}
vec.setLength(type.speed*Timers.delta());
vec.setLength(type.speed * Timers.delta());
velocity.add(vec);
}
public final UnitState
resupply = new UnitState(){
public void entered() {
target = null;
}
public void update() {
if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.resupplyPoint)){
retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint));
}else{
circle(20f);
}
}
},
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(){
public void entered() {
target = null;
}
public void update() {
if(Units.invalidateTarget(target, team, x, y)){
target = null;
}
if(!inventory.hasAmmo()) {
state.set(resupply);
}else if (target == null){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
targetClosestEnemyFlag(BlockFlag.producer);
if(target == null){
setState(idle);
}
});
}else{
attack(150f);
if ((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation
&& distanceTo(target) < inventory.getAmmo().getRange()) {
AmmoType ammo = inventory.getAmmo();
inventory.useAmmo();
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
},
retreat = new UnitState() {
public void entered() {
target = null;
}
public void update() {
if(health >= maxHealth()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.repair)){
retarget(() -> {
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair));
if (target != null) FlyingUnit.this.target = target.entity;
});
}else{
circle(20f);
}
}
};
}

View File

@@ -26,24 +26,101 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.world;
public abstract class GroundUnit extends BaseUnit {
public abstract class GroundUnit extends BaseUnit{
protected static Translator vec = new Translator();
protected float walkTime;
protected float baseRotation;
public final UnitState
resupply = new UnitState(){
public void entered(){
target = null;
}
public void update(){
Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint));
if(tile != null && distanceTo(tile) > 40){
moveAwayFromCore();
}
//TODO move toward resupply point
if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}
}
},
attack = new UnitState(){
public void entered(){
target = null;
}
public void update(){
TileEntity core = getClosestEnemyCore();
float dst = core == null ? 0 : distanceTo(core);
if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange() / 1.1f){
target = core;
}else{
retarget(() -> targetClosest());
}
if(!inventory.hasAmmo()){
state.set(resupply);
}else if(target != null){
if(core != null){
if(dst > inventory.getAmmo().getRange() * 0.5f){
moveToCore();
}
}else{
moveToCore();
}
if(distanceTo(target) < inventory.getAmmo().getRange()){
rotate(angleTo(target));
if(Mathf.angNear(angleTo(target), rotation, 13f)){
AmmoType ammo = inventory.getAmmo();
Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed);
getWeapon().update(GroundUnit.this, to.x, to.y);
}
}
}else{
moveToCore();
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= health){
state.set(attack);
}
moveAwayFromCore();
}
};
protected Weapon weapon;
@Override
public void init(UnitType type, Team team) {
public void init(UnitType type, Team team){
super.init(type, team);
this.weapon = type.weapon;
}
@Override
public void interpolate() {
public void interpolate(){
super.interpolate();
if(interpolator.values.length > 1) {
if(interpolator.values.length > 1){
baseRotation = interpolator.values[1];
}
}
@@ -57,12 +134,12 @@ public abstract class GroundUnit extends BaseUnit {
}
@Override
public UnitState getStartState() {
public UnitState getStartState(){
return resupply;
}
@Override
public void update() {
public void update(){
super.update();
if(!velocity.isZero(0.0001f) && (target == null || !inventory.hasAmmo() || (inventory.hasAmmo() && distanceTo(target) > inventory.getAmmoRange()))){
@@ -71,12 +148,16 @@ public abstract class GroundUnit extends BaseUnit {
}
@Override
public Weapon getWeapon() {
public Weapon getWeapon(){
return weapon;
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
@Override
public void draw() {
public void draw(){
Draw.alpha(hitTime / hitDuration);
float walktime = walkTime;
@@ -89,25 +170,25 @@ public abstract class GroundUnit extends BaseUnit {
Draw.tint(Color.WHITE, floor.liquidColor, 0.5f);
}
for (int i : Mathf.signs) {
for(int i : Mathf.signs){
Draw.rect(type.legRegion,
x + Angles.trnsx(baseRotation, ft * i),
y + Angles.trnsy(baseRotation, ft * i),
12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90);
}
if(floor.isLiquid) {
if(floor.isLiquid){
Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f);
}else {
}else{
Draw.tint(Color.WHITE);
}
Draw.rect(type.baseRegion, x, y, baseRotation- 90);
Draw.rect(type.baseRegion, x, y, baseRotation - 90);
Draw.rect(type.region, x, y, rotation -90);
Draw.rect(type.region, x, y, rotation - 90);
for (int i : Mathf.signs) {
float tra = rotation - 90, trY = - weapon.getRecoil(this, i > 0) + type.weaponOffsetY;
for(int i : Mathf.signs){
float tra = rotation - 90, trY = -weapon.getRecoil(this, i > 0) + type.weaponOffsetY;
float w = i > 0 ? -12 : 12;
Draw.rect(weapon.equipRegion,
x + Angles.trnsx(tra, type.weaponOffsetX * i, trY),
@@ -120,35 +201,35 @@ public abstract class GroundUnit extends BaseUnit {
}
@Override
public void behavior() {
public void behavior(){
if(health <= health * type.retreatPercent && !isWave){
setState(retreat);
}
}
@Override
public void updateTargeting() {
public void updateTargeting(){
super.updateTargeting();
if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){
if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){
target = null;
}
}
@Override
public void write(DataOutput data) throws IOException {
public void write(DataOutput data) throws IOException{
super.write(data);
data.writeByte(weapon.id);
}
@Override
public void read(DataInput data, long time) throws IOException {
public void read(DataInput data, long time) throws IOException{
super.read(data, time);
weapon = Upgrade.getByID(data.readByte());
}
@Override
public void writeSave(DataOutput stream) throws IOException {
public void writeSave(DataOutput stream) throws IOException{
stream.writeByte(weapon.id);
super.writeSave(stream);
}
@@ -159,10 +240,6 @@ public abstract class GroundUnit extends BaseUnit {
super.readSave(stream);
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
protected void moveToCore(){
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
@@ -189,82 +266,4 @@ public abstract class GroundUnit extends BaseUnit {
walkTime += Timers.delta();
velocity.add(vec);
}
public final UnitState
resupply = new UnitState(){
public void entered() {
target = null;
}
public void update() {
Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint));
if (tile != null && distanceTo(tile) > 40) {
moveAwayFromCore();
}
//TODO move toward resupply point
if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}
}
},
attack = new UnitState(){
public void entered() {
target = null;
}
public void update() {
TileEntity core = getClosestEnemyCore();
float dst = core == null ? 0 :distanceTo(core);
if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange()/1.1f){
target = core;
}else {
retarget(() -> targetClosest());
}
if(!inventory.hasAmmo()) {
state.set(resupply);
}else if(target != null){
if(core != null){
if(dst > inventory.getAmmo().getRange() * 0.5f){
moveToCore();
}
}else{
moveToCore();
}
if(distanceTo(target) < inventory.getAmmo().getRange()){
rotate(angleTo(target));
if (Mathf.angNear(angleTo(target), rotation, 13f)) {
AmmoType ammo = inventory.getAmmo();
Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed);
getWeapon().update(GroundUnit.this, to.x, to.y);
}
}
}else{
moveToCore();
}
}
},
retreat = new UnitState() {
public void entered() {
target = null;
}
public void update() {
if(health >= health){
state.set(attack);
}
moveAwayFromCore();
}
};
}

View File

@@ -5,9 +5,11 @@ import io.anuke.ucore.util.Translator;
import static io.anuke.mindustry.Vars.threads;
/**Used to group entities together, for formations and such.
* Usually, squads are used by units spawned in the same wave.*/
public class Squad {
/**
* Used to group entities together, for formations and such.
* Usually, squads are used by units spawned in the same wave.
*/
public class Squad{
public Vector2 direction = new Translator();
public int units;

View File

@@ -1,13 +1,13 @@
package io.anuke.mindustry.entities.units;
public class StateMachine {
public class StateMachine{
private UnitState state;
public void update(){
if(state != null) state.update();
}
public void set( UnitState next){
public void set(UnitState next){
if(next == state) return;
if(state != null) state.exited();
this.state = next;

View File

@@ -6,7 +6,7 @@ import io.anuke.mindustry.entities.effect.ItemDrop;
import io.anuke.mindustry.type.Item;
import io.anuke.ucore.util.Mathf;
public class UnitDrops {
public class UnitDrops{
private static final int maxItems = 200;
private static Item[] dropTable;
@@ -19,7 +19,7 @@ public class UnitDrops {
dropTable = new Item[]{Items.tungsten, Items.lead, Items.carbide};
}
for (int i = 0; i < 3; i++) {
for(int i = 0; i < 3; i++){
for(Item item : dropTable){
if(Mathf.chance(0.03)){
int amount = Mathf.random(20, 40);

View File

@@ -1,7 +1,12 @@
package io.anuke.mindustry.entities.units;
public interface UnitState {
default void entered(){}
default void exited(){}
default void update(){}
public interface UnitState{
default void entered(){
}
default void exited(){
}
default void update(){
}
}

View File

@@ -18,12 +18,9 @@ import io.anuke.ucore.util.Bundles;
public class UnitType implements UnlockableContent{
private static byte lastid = 0;
private static Array<UnitType> types = new Array<>();
protected final Supplier<? extends BaseUnit> constructor;
public final String name;
public final byte id;
protected final Supplier<? extends BaseUnit> constructor;
public float health = 60;
public float hitsize = 5f;
public float hitsizeTile = 4f;
@@ -57,44 +54,52 @@ public class UnitType implements UnlockableContent{
TypeTrait.registerType(type, mainConstructor);
}
public static UnitType getByID(byte id){
return types.get(id);
}
public static Array<UnitType> all(){
return types;
}
@Override
public void displayInfo(Table table) {
public void displayInfo(Table table){
ContentDisplay.displayUnit(table, this);
}
@Override
public String localizedName() {
public String localizedName(){
return Bundles.get("unit." + name + ".name");
}
@Override
public TextureRegion getContentIcon() {
public TextureRegion getContentIcon(){
return iconRegion;
}
@Override
public void load() {
public void load(){
iconRegion = Draw.region("unit-icon-" + name);
region = Draw.region(name);
if(!isFlying) {
if(!isFlying){
legRegion = Draw.region(name + "-leg");
baseRegion = Draw.region(name + "-base");
}
}
@Override
public String getContentTypeName() {
public String getContentTypeName(){
return "unit-type";
}
@Override
public String getContentName() {
public String getContentName(){
return name;
}
@Override
public Array<? extends Content> getAll() {
public Array<? extends Content> getAll(){
return types;
}
@@ -103,12 +108,4 @@ public class UnitType implements UnlockableContent{
unit.init(this, team);
return unit;
}
public static UnitType getByID(byte id){
return types.get(id);
}
public static Array<UnitType> all(){
return types;
}
}

View File

@@ -16,6 +16,7 @@ import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.EventType.BlockBuildEvent;
import io.anuke.mindustry.gen.CallEntity;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.world.Tile;
@@ -39,7 +40,7 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public class Drone extends FlyingUnit implements BuilderTrait {
public class Drone extends FlyingUnit implements BuilderTrait{
protected static ObjectSet<Item> toMine;
protected static float discoverRange = 120f;
protected static boolean initialized;
@@ -48,189 +49,18 @@ public class Drone extends FlyingUnit implements BuilderTrait {
protected Tile mineTile;
protected Queue<BuildRequest> placeQueue = new ThreadQueue<>();
/**Initialize placement event notifier system.
* Static initialization is to be avoided, thus, this is done lazily.*/
private static void initEvents(){
if(initialized) return;
toMine = ObjectSet.with(Items.lead, Items.tungsten);
Events.on(BlockBuildEvent.class, (team, tile) -> {
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!(tile.entity instanceof BuildEntity)) return;
BuildEntity entity = tile.entity();
for(BaseUnit unit : group.all()){
if(unit instanceof Drone){
((Drone) unit).notifyPlaced(entity);
}
}
});
initialized = true;
}
{
initEvents();
}
private void notifyPlaced(BuildEntity entity){
float timeToBuild = entity.recipe.cost;
float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0);
if(dist / type.maxVelocity < timeToBuild * 0.9f){
//CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe);
target = entity;
setState(build);
}
}
@Override
public float getBuildPower(Tile tile) {
return type.buildPower;
}
@Override
public float getMinePower() {
return type.minePower;
}
@Override
public Queue<BuildRequest> getPlaceQueue() {
return placeQueue;
}
@Override
public Tile getMineTile() {
return mineTile;
}
@Override
public void setMineTile(Tile tile) {
mineTile = tile;
}
@Override
public void update() {
super.update();
x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f);
y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f);
updateBuilding(this);
}
@Override
protected void updateRotation() {
if(target != null && (state.is(repair) || state.is(mine))){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f);
}
if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f);
}
}
@Override
public void behavior() {
if(health <= health * type.retreatPercent &&
Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}
@Override
public UnitState getStartState() {
return repair;
}
@Override
public void drawOver() {
trail.draw(Palette.lightTrail, Palette.lightTrail, 3f);
TargetTrait entity = target;
if(entity instanceof TileEntity && state.is(repair)){
float len = 5f;
Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f));
Shapes.laser("beam", "beam-end",
x + Angles.trnsx(rotation, len),
y + Angles.trnsy(rotation, len),
entity.getX(), entity.getY());
Draw.color();
}
drawBuilding(this);
}
@Override
public float drawSize() {
return isBuilding() ? placeDistance*2f : 30f;
}
@Override
public float getAmmoFraction() {
return inventory.getItem().amount / (float)type.itemCapacity;
}
protected void findItem(){
TileEntity entity = getClosestCore();
if(entity == null){
return;
}
targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.getItem(a), entity.items.getItem(b)));
}
protected boolean findItemDrop(){
TileEntity core = getClosestCore();
if(core == null) return false;
//find nearby dropped items to pick up if applicable
ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f,
item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() &&
inventory.canAcceptItem(item.getItem(), 1));
if(drop != null){
setState(pickup);
target = drop;
return true;
}
return false;
}
@Override
public void write(DataOutput data) throws IOException {
super.write(data);
data.writeInt(mineTile == null ? -1 : mineTile.packedPosition());
writeBuilding(data);
}
@Override
public void read(DataInput data, long time) throws IOException {
super.read(data, time);
int mined = data.readInt();
readBuilding(data);
if(mined != -1){
mineTile = world.tile(mined);
}
}
public final UnitState
build = new UnitState(){
public void entered() {
public void entered(){
if(!(target instanceof BuildEntity)){
target = null;
}
}
public void update() {
BuildEntity entity = (BuildEntity)target;
public void update(){
BuildEntity entity = (BuildEntity) target;
TileEntity core = getClosestCore();
if(entity == null){
@@ -247,7 +77,7 @@ public class Drone extends FlyingUnit implements BuilderTrait {
//if it's missing requirements, try and mine them
for(ItemStack stack : entity.recipe.requirements){
if(!core.items.hasItem(stack.item, stack.amount) && toMine.contains(stack.item)){
if(!core.items.has(stack.item, stack.amount) && toMine.contains(stack.item)){
targetItem = stack.item;
getPlaceQueue().clear();
setState(mine);
@@ -263,17 +93,18 @@ public class Drone extends FlyingUnit implements BuilderTrait {
},
repair = new UnitState(){
public void entered() {
public void entered(){
target = null;
}
public void update() {
if(target != null && (((TileEntity)target).health >= ((TileEntity)target).tile.block().health
public void update(){
if(target != null && (((TileEntity) target).health >= ((TileEntity) target).tile.block().health
|| target.distanceTo(Drone.this) > discoverRange)){
target = null;
}
if (target == null) {
if(target == null){
retarget(() -> {
target = Units.findAllyTile(team, x, y, discoverRange,
tile -> tile.entity != null && tile.entity.health + 0.0001f < tile.block().health);
@@ -291,17 +122,18 @@ public class Drone extends FlyingUnit implements BuilderTrait {
}
}
},
mine = new UnitState() {
public void entered() {
mine = new UnitState(){
public void entered(){
target = null;
}
public void update() {
public void update(){
TileEntity entity = getClosestCore();
if(entity == null) return;
if(targetItem == null) {
if(targetItem == null){
findItem();
}
@@ -334,31 +166,31 @@ public class Drone extends FlyingUnit implements BuilderTrait {
target = world.indexer().findClosestOre(x, y, targetItem);
});
if(target instanceof Tile) {
moveTo(type.range/1.5f);
if(target instanceof Tile){
moveTo(type.range / 1.5f);
if (distanceTo(target) < type.range && mineTile != target) {
setMineTile((Tile)target);
if(distanceTo(target) < type.range && mineTile != target){
setMineTile((Tile) target);
}
if(((Tile)target).block() != Blocks.air){
if(((Tile) target).block() != Blocks.air){
setState(drop);
}
}
}
}
public void exited() {
public void exited(){
setMineTile(null);
}
},
pickup = new UnitState() {
public void entered() {
pickup = new UnitState(){
public void entered(){
target = null;
}
public void update() {
ItemDrop item = (ItemDrop)target;
public void update(){
ItemDrop item = (ItemDrop) target;
if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){
setState(drop);
@@ -379,12 +211,12 @@ public class Drone extends FlyingUnit implements BuilderTrait {
moveTo(0f);
}
},
drop = new UnitState() {
public void entered() {
drop = new UnitState(){
public void entered(){
target = null;
}
public void update() {
public void update(){
if(inventory.isEmpty()){
setState(mine);
return;
@@ -394,10 +226,10 @@ public class Drone extends FlyingUnit implements BuilderTrait {
if(target == null) return;
TileEntity tile = (TileEntity)target;
TileEntity tile = (TileEntity) target;
if(distanceTo(target) < type.range){
if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount) {
if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){
CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile);
inventory.clearItem();
}
@@ -405,21 +237,21 @@ public class Drone extends FlyingUnit implements BuilderTrait {
setState(repair);
}
circle(type.range/1.8f);
circle(type.range / 1.8f);
}
},
retreat = new UnitState() {
public void entered() {
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update() {
public void update(){
if(health >= health){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.repair)){
if(timer.get(timerTarget, 20)) {
if(timer.get(timerTarget, 20)){
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair));
if (target != null) Drone.this.target = target.entity;
if(target != null) Drone.this.target = target.entity;
}
}else{
circle(40f);
@@ -427,4 +259,199 @@ public class Drone extends FlyingUnit implements BuilderTrait {
}
};
{
initEvents();
}
/**
* Initialize placement event notifier system.
* Static initialization is to be avoided, thus, this is done lazily.
*/
private static void initEvents(){
if(initialized) return;
toMine = ObjectSet.with(Items.lead, Items.tungsten);
Events.on(BlockBuildEvent.class, (team, tile) -> {
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!(tile.entity instanceof BuildEntity)) return;
BuildEntity entity = tile.entity();
for(BaseUnit unit : group.all()){
if(unit instanceof Drone){
((Drone) unit).notifyPlaced(entity);
}
}
});
initialized = true;
}
private void notifyPlaced(BuildEntity entity){
float timeToBuild = entity.recipe.cost;
float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0);
if(dist / type.maxVelocity < timeToBuild * 0.9f){
//CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe);
target = entity;
setState(build);
}
}
@Override
public float getBuildPower(Tile tile){
return type.buildPower;
}
@Override
public float getMinePower(){
return type.minePower;
}
@Override
public Queue<BuildRequest> getPlaceQueue(){
return placeQueue;
}
@Override
public Tile getMineTile(){
return mineTile;
}
@Override
public void setMineTile(Tile tile){
mineTile = tile;
}
@Override
public void update(){
super.update();
if(Net.client() && state.is(repair) && target instanceof TileEntity){
TileEntity entity = (TileEntity) target;
entity.health += type.healSpeed * Timers.delta();
entity.health = Mathf.clamp(entity.health, 0, entity.tile.block().health);
}
x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f);
y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f);
updateBuilding(this);
}
@Override
protected void updateRotation(){
if(target != null && (state.is(repair) || state.is(mine))){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f);
}
if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f);
}
}
@Override
public void behavior(){
if(health <= health * type.retreatPercent &&
Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}
@Override
public UnitState getStartState(){
return repair;
}
@Override
public void drawOver(){
trail.draw(Palette.lightTrail, 3f);
TargetTrait entity = target;
if(entity instanceof TileEntity && state.is(repair)){
float len = 5f;
Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f));
Shapes.laser("beam", "beam-end",
x + Angles.trnsx(rotation, len),
y + Angles.trnsy(rotation, len),
entity.getX(), entity.getY());
Draw.color();
}
drawBuilding(this);
}
@Override
public float drawSize(){
return isBuilding() ? placeDistance * 2f : 30f;
}
@Override
public float getAmmoFraction(){
return inventory.getItem().amount / (float) type.itemCapacity;
}
protected void findItem(){
TileEntity entity = getClosestCore();
if(entity == null){
return;
}
targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b)));
}
protected boolean findItemDrop(){
TileEntity core = getClosestCore();
if(core == null) return false;
//find nearby dropped items to pick up if applicable
ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f,
item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() &&
inventory.canAcceptItem(item.getItem(), 1));
if(drop != null){
setState(pickup);
target = drop;
return true;
}
return false;
}
@Override
public boolean canCreateBlocks(){
return false;
}
@Override
public void write(DataOutput data) throws IOException{
super.write(data);
data.writeInt(mineTile == null ? -1 : mineTile.packedPosition());
data.writeInt(state.is(repair) && target instanceof TileEntity ? ((TileEntity)target).tile.packedPosition() : -1);
writeBuilding(data);
}
@Override
public void read(DataInput data, long time) throws IOException{
super.read(data, time);
int mined = data.readInt();
int repairing = data.readInt();
readBuilding(data);
if(mined != -1){
mineTile = world.tile(mined);
}
if(repairing != -1){
Tile tile = world.tile(repairing);
target = tile.entity;
state.set(repair);
}else{
state.set(retreat);
}
}
}

View File

@@ -1,5 +1,5 @@
package io.anuke.mindustry.entities.units.types;
public class Fabricator extends Drone {
public class Fabricator extends Drone{
}

View File

@@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.FlyingUnit;
public class Monsoon extends FlyingUnit {
public class Monsoon extends FlyingUnit{
}

View File

@@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.GroundUnit;
public class Scout extends GroundUnit {
public class Scout extends GroundUnit{
}

View File

@@ -2,6 +2,6 @@ package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.FlyingUnit;
public class Vtol extends FlyingUnit {
public class Vtol extends FlyingUnit{
}