This commit is contained in:
Anuken
2019-03-10 22:51:04 -04:00
880 changed files with 19481 additions and 12588 deletions
@@ -1,7 +1,6 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.Effects.Effect;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.graphics.Color;
@@ -15,9 +14,10 @@ import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.effect.Fire;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.*;
@@ -32,7 +32,7 @@ public class Damage{
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);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.none, Palette.power, 3,
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.none, Pal.power, 3,
x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
@@ -60,7 +60,7 @@ public class Damage{
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effects.effect(Fx.blockExplosion, x, y);
Effects.effect(Fx.dynamicExplosion, x, y, radius/8f);
}
public static void createIncend(float x, float y, float range, int amount){
@@ -109,7 +109,7 @@ public class Damage{
rect.width += expand * 2;
rect.height += expand * 2;
Consumer<Unit> cons = e -> {
Consumer<io.anuke.mindustry.entities.type.Unit> cons = e -> {
e.hitbox(hitrect);
Rectangle other = hitrect;
other.y -= expand;
@@ -130,8 +130,8 @@ public class Damage{
}
/**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 -> {
public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate<io.anuke.mindustry.entities.type.Unit> predicate, Consumer<io.anuke.mindustry.entities.type.Unit> acceptor){
Consumer<io.anuke.mindustry.entities.type.Unit> cons = entity -> {
if(!predicate.test(entity)) return;
entity.hitbox(hitrect);
@@ -158,14 +158,14 @@ public class 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.dst(x, y) > radius){
if(entity.getTeam() == team || entity.dst(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.velocity().add(tr.setLength((1f - dst / radius) * 2f));
entity.velocity().add(tr.setLength((1f - dst / radius) * 2f / entity.mass()));
};
rect.setSize(radius * 2).setCenter(x, y);
@@ -0,0 +1,166 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Position;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.impl.EffectEntity;
import io.anuke.mindustry.entities.traits.ScaleTrait;
public class Effects{
private static final EffectContainer container = new EffectContainer();
private static Array<Effect> effects = new Array<>();
private static ScreenshakeProvider shakeProvider;
private static float shakeFalloff = 1000f;
private static EffectProvider provider = (effect, color, x, y, rotation, data) -> {
EffectEntity entity = Pools.obtain(EffectEntity.class, EffectEntity::new);
entity.effect = effect;
entity.color = color;
entity.rotation = rotation;
entity.data = data;
entity.set(x, y);
entity.add();
};
public static void setEffectProvider(EffectProvider prov){
provider = prov;
}
public static void setScreenShakeProvider(ScreenshakeProvider provider){
shakeProvider = provider;
}
public static void renderEffect(int id, Effect render, Color color, float life, float rotation, float x, float y, Object data){
container.set(id, color, life, render.lifetime, rotation, x, y, data);
render.draw.render(container);
}
public static Effect getEffect(int id){
if(id >= effects.size || id < 0)
throw new IllegalArgumentException("The effect with ID \"" + id + "\" does not exist!");
return effects.get(id);
}
public static Array<Effect> all(){
return effects;
}
public static void effect(Effect effect, float x, float y, float rotation){
provider.createEffect(effect, Color.WHITE, x, y, rotation, null);
}
public static void effect(Effect effect, float x, float y){
effect(effect, x, y, 0);
}
public static void effect(Effect effect, Color color, float x, float y){
provider.createEffect(effect, color, x, y, 0f, null);
}
public static void effect(Effect effect, Position loc){
provider.createEffect(effect, Color.WHITE, loc.getX(), loc.getY(), 0f, null);
}
public static void effect(Effect effect, Color color, float x, float y, float rotation){
provider.createEffect(effect, color, x, y, rotation, null);
}
public static void effect(Effect effect, Color color, float x, float y, float rotation, Object data){
provider.createEffect(effect, color, x, y, rotation, data);
}
public static void effect(Effect effect, float x, float y, float rotation, Object data){
provider.createEffect(effect, Color.WHITE, x, y, rotation, data);
}
/** Default value is 1000. Higher numbers mean more powerful shake (less falloff). */
public static void setShakeFalloff(float falloff){
shakeFalloff = falloff;
}
private static void shake(float intensity, float duration){
if(shakeProvider == null) throw new RuntimeException("Screenshake provider is null! Set it first.");
shakeProvider.accept(intensity, duration);
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public interface ScreenshakeProvider{
void accept(float intensity, float duration);
}
public static class Effect{
private static int lastid = 0;
public final int id;
public final EffectRenderer draw;
public final float lifetime;
/** Clip size. */
public float size;
public Effect(float life, float clipsize, EffectRenderer draw){
this.id = lastid++;
this.lifetime = life;
this.draw = draw;
this.size = clipsize;
effects.add(this);
}
public Effect(float life, EffectRenderer draw){
this(life, 28f, draw);
}
}
public static class EffectContainer implements ScaleTrait{
public float x, y, time, lifetime, rotation;
public Color color;
public int id;
public Object data;
private EffectContainer innerContainer;
public void set(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
this.x = x;
this.y = y;
this.color = color;
this.time = life;
this.lifetime = lifetime;
this.id = id;
this.rotation = rotation;
this.data = data;
}
public void scaled(float lifetime, Consumer<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.accept(innerContainer);
}
}
@Override
public float fin(){
return time / lifetime;
}
}
public static interface EffectProvider{
void createEffect(Effect effect, Color color, float x, float y, float rotation, Object data);
}
public static interface EffectRenderer{
void render(EffectContainer effect);
}
}
+66
View File
@@ -0,0 +1,66 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntMap;
import io.anuke.mindustry.entities.traits.Entity;
public class Entities{
public static final int maxLeafObjects = 5;
private static final EntityGroup<Entity> defaultGroup;
private static final Array<EntityGroup<?>> groupArray = new Array<>();
private static final IntMap<EntityGroup<?>> groups = new IntMap<>();
static{
defaultGroup = addGroup(Entity.class);
}
public static void clear(){
for(EntityGroup group : groupArray){
group.clear();
}
}
public static Iterable<Entity> all(){
return defaultGroup.all();
}
public static EntityGroup<?> getGroup(int id){
return groups.get(id);
}
public static Iterable<EntityGroup<?>> getAllGroups(){
return groups.values();
}
public static EntityGroup<Entity> defaultGroup(){
return defaultGroup;
}
public static <T extends Entity> EntityGroup<T> addGroup(Class<T> type){
return addGroup(type, true);
}
public static <T extends Entity> EntityGroup<T> addGroup(Class<T> type, boolean useTree){
EntityGroup<T> group = new EntityGroup<>(type, useTree);
groups.put(group.getID(), group);
groupArray.add(group);
return group;
}
public static void update(){
update(defaultGroup());
EntityQuery.collideGroups(defaultGroup(), defaultGroup());
}
public static void update(EntityGroup<?> group){
group.updateEvents();
if(group.useTree()){
EntityQuery.collisions().updatePhysics(group);
}
for(Entity e : group.all()){
e.update();
}
}
}
@@ -0,0 +1,261 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntSet;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.QuadTree;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.mindustry.entities.traits.Entity;
import io.anuke.mindustry.entities.traits.SolidTrait;
public class EntityCollisions{
//range for tile collision scanning
private static final int r = 2;
//move in 1-unit chunks
private static final float seg = 1f;
//tile collisions
private float tilesize;
private Rectangle tmp = new Rectangle();
private TileCollider collider;
private TileHitboxProvider hitboxProvider;
private Vector2 vector = new Vector2();
private Vector2 l1 = new Vector2();
private Rectangle r1 = new Rectangle();
private Rectangle r2 = new Rectangle();
//entity collisions
private IntSet collided = new IntSet();
private Array<SolidTrait> arrOut = new Array<>();
public void setCollider(float tilesize, TileCollider collider, TileHitboxProvider hitbox){
this.tilesize = tilesize;
this.collider = collider;
this.hitboxProvider = hitbox;
}
public void setCollider(float tilesize, TileCollider collider){
setCollider(tilesize, collider, (x, y, out) -> out.setSize(tilesize).setCenter(x * tilesize, y * tilesize));
}
public void move(SolidTrait entity, float deltax, float deltay){
boolean movedx = false;
while(Math.abs(deltax) > 0 || !movedx){
movedx = true;
moveInternal(entity, Math.min(Math.abs(deltax), seg) * Mathf.sign(deltax), 0, true);
if(Math.abs(deltax) >= seg){
deltax -= seg * Mathf.sign(deltax);
}else{
deltax = 0f;
}
}
boolean movedy = false;
while(Math.abs(deltay) > 0 || !movedy){
movedy = true;
moveInternal(entity, 0, Math.min(Math.abs(deltay), seg) * Mathf.sign(deltay), false);
if(Math.abs(deltay) >= seg){
deltay -= seg * Mathf.sign(deltay);
}else{
deltay = 0f;
}
}
}
public void moveInternal(SolidTrait entity, float deltax, float deltay, boolean x){
if(collider == null)
throw new IllegalArgumentException("No tile collider specified! Call setCollider() first.");
Rectangle rect = r1;
entity.hitboxTile(rect);
entity.hitboxTile(r2);
rect.x += deltax;
rect.y += deltay;
int tilex = Math.round((rect.x + rect.width / 2) / tilesize), tiley = Math.round((rect.y + rect.height / 2) / tilesize);
for(int dx = -r; dx <= r; dx++){
for(int dy = -r; dy <= r; dy++){
int wx = dx + tilex, wy = dy + tiley;
if(collider.solid(wx, wy) && entity.collidesGrid(wx, wy)){
hitboxProvider.getHitbox(wx, wy, tmp);
if(tmp.overlaps(rect)){
Vector2 v = Geometry.overlap(rect, tmp, x);
rect.x += v.x;
rect.y += v.y;
}
}
}
}
entity.setX(entity.getX() + rect.x - r2.x);
entity.setY(entity.getY() + rect.y - r2.y);
}
public boolean overlapsTile(Rectangle rect){
if(collider == null)
throw new IllegalArgumentException("No tile collider specified! Call setCollider() first.");
rect.getCenter(vector);
int r = 1;
//assumes tiles are centered
int tilex = Math.round(vector.x / tilesize);
int tiley = Math.round(vector.y / tilesize);
for(int dx = -r; dx <= r; dx++){
for(int dy = -r; dy <= r; dy++){
int wx = dx + tilex, wy = dy + tiley;
if(collider.solid(wx, wy)){
hitboxProvider.getHitbox(wx, wy, r2);
if(r2.overlaps(rect)){
return true;
}
}
}
}
return false;
}
public <T extends Entity> void updatePhysics(EntityGroup<T> group){
collided.clear();
QuadTree tree = group.tree();
tree.clear();
for(Entity entity : group.all()){
if(entity instanceof SolidTrait){
SolidTrait s = (SolidTrait) entity;
s.lastPosition().set(s.getX(), s.getY());
tree.insert(s);
}
}
}
private void checkCollide(Entity entity, Entity other){
SolidTrait a = (SolidTrait) entity;
SolidTrait b = (SolidTrait) other;
a.hitbox(this.r1);
b.hitbox(this.r2);
r1.x += (a.lastPosition().x - a.getX());
r1.y += (a.lastPosition().y - a.getY());
r2.x += (b.lastPosition().x - b.getX());
r2.y += (b.lastPosition().y - b.getY());
float vax = a.getX() - a.lastPosition().x;
float vay = a.getY() - a.lastPosition().y;
float vbx = b.getX() - b.lastPosition().x;
float vby = b.getY() - b.lastPosition().y;
if(a != b && a.collides(b) && b.collides(a)){
l1.set(a.getX(), a.getY());
boolean collide = r1.overlaps(r2) || collide(r1.x, r1.y, r1.width, r1.height, vax, vay,
r2.x, r2.y, r2.width, r2.height, vbx, vby, l1);
if(collide){
a.collision(b, l1.x, l1.y);
b.collision(a, l1.x, l1.y);
}
}
}
private boolean collide(float x1, float y1, float w1, float h1, float vx1, float vy1,
float x2, float y2, float w2, float h2, float vx2, float vy2, Vector2 out){
float px = vx1, py = vy1;
vx1 -= vx2;
vy1 -= vy2;
float xInvEntry, yInvEntry;
float xInvExit, yInvExit;
if(vx1 > 0.0f){
xInvEntry = x2 - (x1 + w1);
xInvExit = (x2 + w2) - x1;
}else{
xInvEntry = (x2 + w2) - x1;
xInvExit = x2 - (x1 + w1);
}
if(vy1 > 0.0f){
yInvEntry = y2 - (y1 + h1);
yInvExit = (y2 + h2) - y1;
}else{
yInvEntry = (y2 + h2) - y1;
yInvExit = y2 - (y1 + h1);
}
float xEntry, yEntry;
float xExit, yExit;
xEntry = xInvEntry / vx1;
xExit = xInvExit / vx1;
yEntry = yInvEntry / vy1;
yExit = yInvExit / vy1;
float entryTime = Math.max(xEntry, yEntry);
float exitTime = Math.min(xExit, yExit);
if(entryTime > exitTime || xExit < 0.0f || yExit < 0.0f || xEntry > 1.0f || yEntry > 1.0f){
return false;
}else{
float dx = x1 + w1 / 2f + px * entryTime;
float dy = y1 + h1 / 2f + py * entryTime;
out.set(dx, dy);
return true;
}
}
public void collideGroups(EntityGroup<?> groupa, EntityGroup<?> groupb){
collided.clear();
for(Entity entity : groupa.all()){
if(!(entity instanceof SolidTrait) || collided.contains(entity.getID()))
continue;
SolidTrait solid = (SolidTrait) entity;
solid.hitbox(r1);
r1.x += (solid.lastPosition().x - solid.getX());
r1.y += (solid.lastPosition().y - solid.getY());
solid.hitbox(r2);
r2.merge(r1);
arrOut.clear();
groupb.tree().getIntersect(arrOut, r2);
for(SolidTrait sc : arrOut){
sc.hitbox(r1);
if(r2.overlaps(r1) && !collided.contains(sc.getID())){
checkCollide(entity, sc);
}
}
collided.add(entity.getID());
}
}
public interface TileCollider{
boolean solid(int x, int y);
}
public interface TileHitboxProvider{
void getHitbox(int x, int y, Rectangle out);
}
}
@@ -0,0 +1,56 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.Core;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.graphics.Camera;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.mindustry.entities.traits.DrawTrait;
public class EntityDraw{
private static final Rectangle viewport = new Rectangle();
private static final Rectangle rect = new Rectangle();
private static boolean clip = true;
private static int count = 0;
public static void setClip(boolean clip){
EntityDraw.clip = clip;
}
public static int countInBounds(EntityGroup<?> group){
count = 0;
drawWith(group, e -> true, e -> count++);
return count;
}
public static void draw(){
draw(Entities.defaultGroup());
}
public static void draw(EntityGroup<?> group){
draw(group, e -> true);
}
public static <T extends DrawTrait> void draw(EntityGroup<?> group, Predicate<T> toDraw){
drawWith(group, toDraw, DrawTrait::draw);
}
@SuppressWarnings("unchecked")
public static <T extends DrawTrait> void drawWith(EntityGroup<?> group, Predicate<T> toDraw, Consumer<T> cons){
if(clip){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
}
group.forEach(e -> {
if(!(e instanceof DrawTrait)) return;
T t = (T) e;
if(!toDraw.test(t) || !e.isAdded()) return;
if(!clip || rect.setSize(((DrawTrait) e).drawSize()).setCenter(e.getX(), e.getY()).overlaps(viewport)){
cons.accept(t);
}
});
}
}
@@ -0,0 +1,194 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.math.geom.QuadTree;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.mindustry.entities.traits.Entity;
public class EntityGroup<T extends Entity>{
private static int lastid;
private final boolean useTree;
private final int id;
private final Class<T> type;
private final Array<T> entityArray = new Array<>(false, 16);
private final Array<T> entitiesToRemove = new Array<>(false, 16);
private final Array<T> entitiesToAdd = new Array<>(false, 16);
private IntMap<T> map;
private QuadTree<T> tree;
private Consumer<T> removeListener;
private Consumer<T> addListener;
public EntityGroup(Class<T> type, boolean useTree){
this.useTree = useTree;
this.id = lastid++;
this.type = type;
}
public boolean useTree(){
return useTree;
}
public void setRemoveListener(Consumer<T> removeListener){
this.removeListener = removeListener;
}
public void setAddListener(Consumer<T> addListener){
this.addListener = addListener;
}
public EntityGroup<T> enableMapping(){
map = new IntMap<>();
return this;
}
public boolean mappingEnabled(){
return map != null;
}
public Class<T> getType(){
return type;
}
public int getID(){
return id;
}
public void updateEvents(){
for(T e : entitiesToAdd){
if(e == null)
continue;
entityArray.add(e);
e.added();
if(map != null){
map.put(e.getID(), e);
}
}
entitiesToAdd.clear();
for(T e : entitiesToRemove){
entityArray.removeValue(e, true);
if(map != null){
map.remove(e.getID());
}
e.removed();
}
entitiesToRemove.clear();
}
public T getByID(int id){
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
return map.get(id);
}
public void removeByID(int id){
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
T t = map.get(id);
if(t != null){ //remove if present in map already
remove(t);
}else{ //maybe it's being queued?
for(T check : entitiesToAdd){
if(check.getID() == id){ //if it is indeed queued, remove it
entitiesToAdd.removeValue(check, true);
if(removeListener != null){
removeListener.accept(check);
}
break;
}
}
}
}
public QuadTree tree(){
return tree;
}
public void setTree(float x, float y, float w, float h){
tree = new QuadTree<>(Entities.maxLeafObjects, new Rectangle(x, y, w, h));
}
public boolean isEmpty(){
return entityArray.size == 0;
}
public int size(){
return entityArray.size;
}
public int count(Predicate<T> pred){
int count = 0;
for(int i = 0; i < entityArray.size; i++){
if(pred.test(entityArray.get(i))) count++;
}
return count;
}
public void add(T type){
if(type == null) throw new RuntimeException("Cannot add a null entity!");
if(type.getGroup() != null) return;
type.setGroup(this);
entitiesToAdd.add(type);
if(mappingEnabled()){
map.put(type.getID(), type);
}
if(addListener != null){
addListener.accept(type);
}
}
public void remove(T type){
if(type == null) throw new RuntimeException("Cannot remove a null entity!");
type.setGroup(null);
entitiesToRemove.add(type);
if(removeListener != null){
removeListener.accept(type);
}
}
public void clear(){
for(T entity : entityArray)
entity.setGroup(null);
for(T entity : entitiesToAdd)
entity.setGroup(null);
for(T entity : entitiesToRemove)
entity.setGroup(null);
entitiesToAdd.clear();
entitiesToRemove.clear();
entityArray.clear();
if(map != null)
map.clear();
}
public T find(Predicate<T> pred){
for(int i = 0; i < entityArray.size; i++){
if(pred.test(entityArray.get(i))) return entityArray.get(i);
}
return null;
}
/**Returns the logic-only array for iteration.*/
public Array<T> all(){
return entityArray;
}
public void forEach(Consumer<T> cons){
for(T t : entityArray){
cons.accept(t);
}
}
}
@@ -0,0 +1,95 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.Array;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.mindustry.entities.traits.Entity;
import io.anuke.mindustry.entities.traits.SolidTrait;
import static io.anuke.mindustry.entities.Entities.defaultGroup;
public class EntityQuery{
private static final EntityCollisions collisions = new EntityCollisions();
private static final Array<SolidTrait> array = new Array<>();
private static final Rectangle r1 = new Rectangle();
public static EntityCollisions collisions(){
return collisions;
}
public static void init(float x, float y, float w, float h){
for(EntityGroup group : Entities.getAllGroups()){
if(group.useTree()){
group.setTree(x, y, w, h);
}
}
}
public static void init(){
init(0, 0, 0, 0);
}
public static void resizeTree(float x, float y, float w, float h){
init(x, y, w, h);
}
public static void getNearby(EntityGroup<?> group, Rectangle rect, Consumer<SolidTrait> out){
if(!group.useTree())
throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
group.tree().getIntersect(out, rect);
}
public static Array<SolidTrait> getNearby(EntityGroup<?> group, Rectangle rect){
array.clear();
if(!group.useTree())
throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
group.tree().getIntersect(array, rect);
return array;
}
public static void getNearby(float x, float y, float size, Consumer<SolidTrait> out){
getNearby(defaultGroup(), r1.setSize(size).setCenter(x, y), out);
}
public static void getNearby(EntityGroup<?> group, float x, float y, float size, Consumer<SolidTrait> out){
getNearby(group, r1.setSize(size).setCenter(x, y), out);
}
public static Array<SolidTrait> getNearby(float x, float y, float size){
return getNearby(defaultGroup(), r1.setSize(size).setCenter(x, y));
}
public static Array<SolidTrait> getNearby(EntityGroup<?> group, float x, float y, float size){
return getNearby(group, r1.setSize(size).setCenter(x, y));
}
public static <T extends Entity> T getClosest(EntityGroup<T> group, float x, float y, float range, Predicate<T> pred){
T closest = null;
float cdist = 0f;
Array<SolidTrait> entities = getNearby(group, x, y, range * 2f);
for(int i = 0; i < entities.size; i++){
T e = (T) entities.get(i);
if(!pred.test(e))
continue;
float dist = Mathf.dst(e.getX(), e.getY(), x, y);
if(dist < range)
if(closest == null || dist < cdist){
closest = e;
cdist = dist;
}
}
return closest;
}
public static void collideGroups(EntityGroup<?> groupa, EntityGroup<?> groupb){
collisions().collideGroups(groupa, groupb);
}
}
@@ -1,88 +0,0 @@
package io.anuke.mindustry.entities;
import io.anuke.mindustry.entities.traits.Saveable;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.content;
public class UnitInventory implements Saveable{
private final Unit unit;
private ItemStack item = new ItemStack(content.item(0), 0);
public UnitInventory(Unit unit){
this.unit = unit;
}
public boolean isFull(){
return item != null && item.amount >= unit.getItemCapacity();
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeByte(item.amount);
stream.writeByte(item.item.id);
}
@Override
public void readSave(DataInput stream) throws IOException{
int iamount = stream.readUnsignedByte();
byte iid = stream.readByte();
item.item = content.item(iid);
item.amount = iamount;
}
public void clear(){
item.amount = 0;
}
public int capacity(){
return unit.getItemCapacity();
}
public boolean isEmpty(){
return item.amount == 0;
}
public int itemCapacityUsed(Item type){
if(canAcceptItem(type)){
return !hasItem() ? unit.getItemCapacity() : (unit.getItemCapacity() - item.amount);
}else{
return unit.getItemCapacity();
}
}
public boolean canAcceptItem(Item type){
return (!hasItem() && 1 <= unit.getItemCapacity()) || (item.item == type && unit.getItemCapacity() - item.amount > 0);
}
public boolean canAcceptItem(Item type, int amount){
return (!hasItem() && amount <= unit.getItemCapacity()) || (item.item == type && item.amount + amount <= unit.getItemCapacity());
}
public void clearItem(){
item.amount = 0;
}
public boolean hasItem(){
return item.amount > 0;
}
public boolean hasItem(Item i, int amount){
return item.item == i && item.amount >= amount;
}
public void addItem(Item item, int amount){
getItem().amount = getItem().item == item ? getItem().amount + amount : amount;
getItem().item = item;
}
public ItemStack getItem(){
return item;
}
}
+20 -11
View File
@@ -1,15 +1,16 @@
package io.anuke.mindustry.entities;
import io.anuke.arc.collection.EnumSet;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.EntityQuery;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
@@ -21,11 +22,10 @@ import static io.anuke.mindustry.Vars.*;
*/
public class Units{
private static Rectangle rect = new Rectangle();
private static Rectangle rectGraphics = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Unit result;
private static float cdist;
private static boolean boolResult, boolResultGraphics;
private static boolean boolResult;
/**
* Validates a target.
@@ -48,7 +48,7 @@ public class Units{
/**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.getWeapon().getAmmo().range());
return invalidateTarget(target, targeter.getTeam(), targeter.x, targeter.y, targeter.getWeapon().bullet.range());
}
/**Returns whether there are any entities on this tile.*/
@@ -113,6 +113,8 @@ public class Units{
/**Returns the neareset enemy tile in a range.*/
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
if(team == Team.none) return null;
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = world.indexer.findTile(enemy, x, y, range, pred);
if(entity != null){
@@ -141,21 +143,28 @@ public class Units{
/**Returns the closest target enemy. First, units are checked, then tile entities.*/
public static TargetTrait getClosestTarget(Team team, float x, float y, float range){
return getClosestTarget(team, x, y, range, u -> !u.isDead() && u.isAdded());
return getClosestTarget(team, x, y, range, Unit::isValid);
}
/**Returns the closest target enemy. First, units are checked, then tile entities.*/
public static TargetTrait getClosestTarget(Team team, float x, float y, float range, Predicate<Unit> unitPred){
return getClosestTarget(team, x, y, range, unitPred, t -> true);
}
/**Returns the closest target enemy. First, units are checked, then tile entities.*/
public static TargetTrait getClosestTarget(Team team, float x, float y, float range, Predicate<Unit> unitPred, Predicate<Tile> tilePred){
Unit unit = getClosestEnemy(team, x, y, range, unitPred);
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, tile -> true);
return findEnemyTile(team, x, y, range, tilePred);
}
}
/**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){
if(team == Team.none) return null;
result = null;
cdist = 0f;
@@ -210,7 +219,7 @@ public class Units{
//now check all players
EntityQuery.getNearby(playerGroup, rect, player -> {
if(((Unit) player).team == team) cons.accept((Unit) player);
if(((Unit) player).getTeam() == team) cons.accept((Unit) player);
});
}
@@ -229,7 +238,7 @@ public class Units{
//now check all players
EntityQuery.getNearby(playerGroup, rect, player -> {
if(((Unit) player).team == team && player.dst(x, y) <= radius){
if(((Unit) player).getTeam() == team && player.dst(x, y) <= radius){
cons.accept((Unit) player);
}
});
@@ -262,7 +271,7 @@ public class Units{
//now check all enemy players
EntityQuery.getNearby(playerGroup, rect, player -> {
if(targets.contains(((Player) player).team)){
if(targets.contains(((Player) player).getTeam())){
cons.accept((Unit) player);
}
});
@@ -1,7 +1,7 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.Effects.Effect;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.mindustry.content.Fx;
@@ -1,22 +1,21 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.Core;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.entities.Damage;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Pal;
/**An extended 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 Color backColor = Pal.bulletYellowBack, frontColor = Pal.bulletYellow;
public float bulletWidth = 5f, bulletHeight = 7f;
public float bulletShrink = 0.5f;
public String bulletSprite;
@@ -72,7 +71,7 @@ public class BasicBulletType extends BulletType{
if(homingPower > 0.0001f){
TargetTrait target = Units.getClosestTarget(b.getTeam(), b.x, b.y, homingRange);
if(target != null){
b.velocity().setAngle(Angles.moveToward(b.velocity().angle(), b.angleTo(target), homingPower * Time.delta()));
b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f));
}
}
}
@@ -108,7 +107,7 @@ public class BasicBulletType extends BulletType{
}
for (int i = 0; i < lightining; i++) {
Lightning.create(b.getTeam(), Palette.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
}
}
}
@@ -2,9 +2,6 @@ package io.anuke.mindustry.entities.bullet;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.SolidEntity;
import io.anuke.arc.entities.trait.*;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
@@ -13,11 +10,11 @@ import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.effect.Lightning;
import io.anuke.mindustry.entities.traits.AbsorbTrait;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.traits.TeamTrait;
import io.anuke.mindustry.entities.impl.SolidEntity;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.world.Tile;
@@ -89,11 +86,21 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
}
/**Internal use only.*/
@Remote(called = Loc.server)
@Remote(called = Loc.server, unreliable = true)
public static void createBullet(BulletType type, float x, float y, float angle){
create(type, null, Team.none, x, y, angle);
}
/**ok*/
@Remote(called = Loc.server, unreliable = true)
public static void createBullet(BulletType type, Team team, float x, float y, float angle){
create(type, null, team, x, y, angle);
}
public Entity getOwner(){
return owner;
}
public boolean collidesTiles(){
return type.collidesTiles;
}
@@ -1,7 +1,7 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.Effects.Effect;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.game.Content;
@@ -66,7 +66,7 @@ public abstract class BulletType extends Content{
/**Returns maximum distance the bullet this bullet type has can travel.*/
public float range(){
return speed * lifetime;
return speed * lifetime * (1f - drag);
}
public boolean collides(Bullet bullet, Tile tile){
@@ -1,6 +1,6 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.entities.Effects;
import io.anuke.mindustry.entities.Effects;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
@@ -20,16 +20,22 @@ public class LiquidBulletType extends BulletType{
Liquid liquid;
public LiquidBulletType(Liquid liquid){
super(2.5f, 0);
super(2.8f, 0);
this.liquid = liquid;
lifetime = 70f;
lifetime = 74f;
status = liquid.effect;
statusDuration = 90f;
despawnEffect = Fx.none;
hitEffect = Fx.hitLiquid;
drag = 0.01f;
knockback = 0.5f;
shootEffect = Fx.none;
drag = 0.009f;
knockback = 0.55f;
}
@Override
public float range(){
return speed * lifetime /2f;
}
@Override
@@ -1,12 +1,12 @@
package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.entities.Effects;
import io.anuke.mindustry.entities.Effects;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
import static io.anuke.mindustry.Vars.content;
@@ -26,10 +26,10 @@ public class MassDriverBolt extends BulletType{
public void draw(Bullet b){
float w = 11f, h = 13f;
Draw.color(Palette.bulletYellowBack);
Draw.color(Pal.bulletYellowBack);
Draw.rect("shell-back", b.x, b.y, w, h, b.rot() + 90);
Draw.color(Palette.bulletYellow);
Draw.color(Pal.bulletYellow);
Draw.rect("shell", b.x, b.y, w, h, b.rot() + 90);
Draw.reset();
@@ -2,21 +2,21 @@ package io.anuke.mindustry.entities.bullet;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.arc.entities.Effects;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.entities.Effects;
import io.anuke.arc.util.Time;
import io.anuke.arc.math.Mathf;
public class MissileBulletType extends BasicBulletType{
protected Color trailColor = Palette.missileYellowBack;
protected Color trailColor = Pal.missileYellowBack;
protected float weaveScale = 0f;
protected float weaveMag = -1f;
public MissileBulletType(float speed, float damage, String bulletSprite){
super(speed, damage, bulletSprite);
backColor = Palette.missileYellowBack;
frontColor = Palette.missileYellow;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;
homingPower = 7f;
}
@@ -2,9 +2,9 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.entities.traits.BelowLiquidTrait;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.TimedEntity;
import io.anuke.arc.entities.trait.DrawTrait;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Mathf;
@@ -3,9 +3,9 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
@@ -17,12 +17,13 @@ import io.anuke.mindustry.content.Bullets;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Damage;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.traits.SaveTrait;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Pos;
import io.anuke.mindustry.world.Tile;
import java.io.DataInput;
@@ -64,10 +65,10 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(x + y * world.width())){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Pos.get(x, y))){
return false;
}
Fire fire = map.get(x + y * world.width());
Fire fire = map.get(Pos.get(x, y));
return fire.isAdded() && fire.fin() < 1f && fire.tile != null && fire.tile.x == x && fire.tile.y == y;
}
@@ -152,7 +153,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
entity.damage(0.4f);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
unit -> !unit.isFlying(),
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.applyEffect(StatusEffects.burning, 60 * 5));
}
}
@@ -1,9 +1,9 @@
package io.anuke.mindustry.entities.effect;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.Effects.Effect;
import io.anuke.arc.entities.Effects.EffectRenderer;
import io.anuke.arc.entities.impl.EffectEntity;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.Effects.EffectRenderer;
import io.anuke.mindustry.entities.impl.EffectEntity;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
@@ -2,9 +2,9 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.TimedEntity;
import io.anuke.arc.entities.trait.DrawTrait;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
import io.anuke.arc.graphics.g2d.Lines;
@@ -14,8 +14,8 @@ import io.anuke.arc.math.geom.Position;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
@@ -43,7 +43,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
@Remote(called = Loc.server, unreliable = true)
public static void transferItemToUnit(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> to.inventory.addItem(item, 1));
create(item, x, y, to, () -> to.addItem(item));
}
@Remote(called = Loc.server)
@@ -105,7 +105,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
@Override
public void draw(){
Lines.stroke(fslope() * 2f, Palette.accent);
Lines.stroke(fslope() * 2f, Pal.accent);
Lines.circle(x, y, fslope() * 2f);
@@ -4,13 +4,14 @@ import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.IntSet;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.TimedEntity;
import io.anuke.arc.entities.trait.DrawTrait;
import io.anuke.arc.entities.trait.TimeTrait;
import io.anuke.arc.graphics.g2d.Fill;
import io.anuke.arc.graphics.g2d.Lines;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.TimedEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.traits.TimeTrait;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Lines;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.RandomXS128;
@@ -20,13 +21,13 @@ import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.Bullets;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Pal;
import java.io.DataInput;
import java.io.DataOutput;
@@ -45,7 +46,7 @@ public class Lightning extends TimedEntity implements DrawTrait, SyncTrait, Time
private static int lastSeed = 0;
private Array<Position> lines = new Array<>();
private Color color = Palette.lancerLaser;
private Color color = Pal.lancerLaser;
/**For pooling use only. Do not call directly!*/
public Lightning(){
@@ -118,7 +119,7 @@ public class Lightning extends TimedEntity implements DrawTrait, SyncTrait, Time
@Override
public void reset(){
super.reset();
color = Palette.lancerLaser;
color = Pal.lancerLaser;
lines.clear();
}
@@ -130,31 +131,22 @@ public class Lightning extends TimedEntity implements DrawTrait, SyncTrait, Time
@Override
public void draw(){
float lx = x, ly = y;
Lines.stroke(3f * fout());
Draw.color(color, Color.WHITE, fin());
//TODO this is really, really bad rendering
/*
for(int i = 0; i < lines.size; i++){
Position v = lines.get(i);
Lines.beginLine();
float f = (float) i / lines.size;
Lines.linePoint(x, y);
for(Position p : lines){
Lines.linePoint(p.getX(), p.getY());
}
Lines.endLine();
Lines.stroke(fout() * 3f * (1.5f - f));
int i = 0;
Lines.stroke(Lines.getStroke() * 4f);
Draw.alpha(0.3f);
Lines.line(lx, ly, v.getX(), v.getY());
Lines.stroke(Lines.getStroke()/4f);
Draw.alpha(1f);
Lines.line(lx, ly, v.getX(), v.getY());
Lines.stroke(3f * fout() * (1f - f));
lx = v.getX();
ly = v.getY();
}*/
Draw.color();
for(Position p : lines){
Fill.square(p.getX(), p.getY(), (5f - (float)i++/lines.size*2f) * fout(), 45);
}
Draw.reset();
}
@Override
@@ -3,10 +3,10 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.collection.IntMap;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.SolidEntity;
import io.anuke.arc.entities.trait.DrawTrait;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.SolidEntity;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
@@ -212,7 +212,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
}
});
if(liquid.temperature > 0.7f && tile.entity != null && Mathf.chance(0.3 * Time.delta())){
if(liquid.temperature > 0.7f && (tile.target().entity != null) && Mathf.chance(0.3 * Time.delta())){
Fire.create(tile);
}
@@ -2,30 +2,43 @@ package io.anuke.mindustry.entities.effect;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
public class RubbleDecal extends Decal{
private int size;
import static io.anuke.mindustry.Vars.headless;
/**
* Creates a rubble effect at a position. Provide a block size to use.
*/
public class RubbleDecal extends Decal{
private static final TextureRegion[][] regions = new TextureRegion[16][0];
private TextureRegion region;
/**Creates a rubble effect at a position. Provide a block size to use.*/
public static void create(float x, float y, int size){
if(headless) return;
if(regions[size].length == 0){
regions[size] = new TextureRegion[2];
for(int j = 0; j < 2; j++){
regions[size][j] = Core.atlas.find("rubble-" + size + "-" + j);
}
}
RubbleDecal decal = new RubbleDecal();
decal.size = size;
decal.region = regions[size][Mathf.clamp(Mathf.randomSeed(decal.id, 0, 1), 0, regions[size].length - 1)];
if(!Core.atlas.isFound(decal.region)){
return;
}
decal.set(x, y);
decal.add();
}
@Override
public void drawDecal(){
String region = "rubble-" + size + "-" + Mathf.randomSeed(id, 0, 1);
if(!Core.atlas.has(region)){
if(!Core.atlas.isFound(region)){
remove();
return;
}
Draw.rect(region, x, y, Mathf.randomSeed(id, 0, 4) * 90);
}
}
@@ -7,6 +7,7 @@ import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.headless;
import static io.anuke.mindustry.Vars.world;
public class ScorchDecal extends Decal{
@@ -14,6 +15,8 @@ public class ScorchDecal extends Decal{
private static final TextureRegion[] regions = new TextureRegion[scorches];
public static void create(float x, float y){
if(headless) return;
if(regions[0] == null){
for(int i = 0; i < regions.length; i++){
regions[i] = Core.atlas.find("scorch" + (i + 1));
+61
View File
@@ -0,0 +1,61 @@
package io.anuke.mindustry.entities.impl;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.Entity;
public abstract class BaseEntity implements Entity{
private static int lastid;
/** Do not modify. Used for network operations and mapping. */
public int id;
public float x, y;
protected transient EntityGroup group;
public BaseEntity(){
id = lastid++;
}
@Override
public int getID(){
return id;
}
@Override
public void resetID(int id){
this.id = id;
}
@Override
public EntityGroup getGroup(){
return group;
}
@Override
public void setGroup(EntityGroup group){
this.group = group;
}
@Override
public float getX(){
return x;
}
@Override
public void setX(float x){
this.x = x;
}
@Override
public float getY(){
return y;
}
@Override
public void setY(float y){
this.y = y;
}
@Override
public String toString(){
return getClass() + " " + id;
}
}
@@ -0,0 +1,45 @@
package io.anuke.mindustry.entities.impl;
import io.anuke.mindustry.entities.traits.DamageTrait;
import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.mindustry.entities.traits.SolidTrait;
public abstract class DestructibleEntity extends SolidEntity implements HealthTrait{
public transient boolean dead;
public float health;
@Override
public boolean collides(SolidTrait other){
return other instanceof DamageTrait;
}
@Override
public void collision(SolidTrait other, float x, float y){
if(other instanceof DamageTrait){
onHit(other);
damage(((DamageTrait) other).damage());
}
}
@Override
public void health(float health){
this.health = health;
}
@Override
public float health(){
return health;
}
@Override
public boolean isDead(){
return dead;
}
@Override
public void setDead(boolean dead){
this.dead = dead;
}
}
@@ -0,0 +1,72 @@
package io.anuke.mindustry.entities.impl;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.traits.DrawTrait;
import io.anuke.mindustry.entities.traits.Entity;
public class EffectEntity extends TimedEntity implements Poolable, DrawTrait{
public Effect effect;
public Color color = new Color(Color.WHITE);
public Object data;
public float rotation = 0f;
public Entity parent;
public float poffsetx, poffsety;
/** For pooling use only! */
public EffectEntity(){
}
public void setParent(Entity parent){
this.parent = parent;
this.poffsetx = x - parent.getX();
this.poffsety = y - parent.getY();
}
@Override
public float lifetime(){
return effect.lifetime;
}
@Override
public float drawSize(){
return effect.size;
}
@Override
public void update(){
if(effect == null){
remove();
return;
}
super.update();
if(parent != null){
x = parent.getX() + poffsetx;
y = parent.getY() + poffsety;
}
}
@Override
public void reset(){
effect = null;
color.set(Color.WHITE);
rotation = time = poffsetx = poffsety = 0f;
parent = null;
data = null;
}
@Override
public void draw(){
Effects.renderEffect(id, effect, color, time, rotation, x, y, data);
}
@Override
public void removed(){
Pools.free(this);
}
}
@@ -0,0 +1,19 @@
package io.anuke.mindustry.entities.impl;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.mindustry.entities.traits.SolidTrait;
public abstract class SolidEntity extends BaseEntity implements SolidTrait{
protected transient Vector2 velocity = new Vector2(0f, 0.0001f);
private transient Vector2 lastPosition = new Vector2();
@Override
public Vector2 lastPosition(){
return lastPosition;
}
@Override
public Vector2 velocity(){
return velocity;
}
}
@@ -0,0 +1,34 @@
package io.anuke.mindustry.entities.impl;
import io.anuke.arc.util.pooling.Pool.Poolable;
import io.anuke.mindustry.entities.traits.ScaleTrait;
import io.anuke.mindustry.entities.traits.TimeTrait;
public abstract class TimedEntity extends BaseEntity implements ScaleTrait, TimeTrait, Poolable{
public float time;
@Override
public void time(float time){
this.time = time;
}
@Override
public float time(){
return time;
}
@Override
public void update(){
updateTime();
}
@Override
public void reset(){
time = 0f;
}
@Override
public float fin(){
return time() / lifetime();
}
}
@@ -1,8 +1,5 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.entities.trait.DamageTrait;
import io.anuke.arc.entities.trait.Entity;
public interface AbsorbTrait extends Entity, TeamTrait, DamageTrait{
void absorb();
@@ -4,8 +4,6 @@ import io.anuke.arc.Core;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.trait.Entity;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
@@ -17,12 +15,13 @@ import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.game.EventType.BuildSelectEvent;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.graphics.Shapes;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item;
@@ -43,7 +42,7 @@ import static io.anuke.mindustry.Vars.*;
/**
* Interface for units that build, break or mine things.
*/
public interface BuilderTrait extends Entity{
public interface BuilderTrait extends Entity, TeamTrait{
//these are not instance variables!
Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()};
float placeDistance = 150f;
@@ -125,22 +124,6 @@ public interface BuilderTrait extends Entity{
return getPlaceQueue().size != 0;
}
/**
* If a place request matching this signature is present, it is removed.
* Otherwise, a new place request is added to the queue.
*/
default void replaceBuilding(int x, int y, int rotation, Block block){
for(BuildRequest request : getPlaceQueue()){
if(request.x == x && request.y == y){
clearBuilding();
addBuildRequest(request);
return;
}
}
addBuildRequest(new BuildRequest(x, y, rotation, block));
}
/**Clears the placement queue.*/
default void clearBuilding(){
getPlaceQueue().clear();
@@ -172,7 +155,8 @@ public interface BuilderTrait extends Entity{
* Update building mechanism for this unit.
* This includes mining.
*/
default void updateBuilding(Unit unit){
default void updateBuilding(){
Unit unit = (Unit)this;
//remove already completed build requests
removal.clear();
for(BuildRequest req : getPlaceQueue()){
@@ -183,8 +167,7 @@ public interface BuilderTrait extends Entity{
for(BuildRequest request : removal){
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
(!request.breaking &&
(world.tile(request.x, request.y).getRotation() == request.rotation || !request.block.rotate)
(!request.breaking && (world.tile(request.x, request.y).getRotation() == request.rotation || !request.block.rotate)
&& world.tile(request.x, request.y).block() == request.block))){
getPlaceQueue().addLast(request);
}
@@ -195,7 +178,7 @@ public interface BuilderTrait extends Entity{
//update mining here
if(current == null){
if(getMineTile() != null){
updateMining(unit);
updateMining();
}
return;
}else{
@@ -204,15 +187,19 @@ public interface BuilderTrait extends Entity{
Tile tile = world.tile(current.x, current.y);
if(unit.dst(tile) > placeDistance){
if(dst(tile) > placeDistance){
if(getPlaceQueue().size > 1){
getPlaceQueue().removeFirst();
getPlaceQueue().addLast(current);
}
return;
}
if(!(tile.block() instanceof BuildBlock)){
if(canCreateBlocks() && !current.breaking && Build.validPlace(unit.getTeam(), current.x, current.y, current.block, current.rotation)){
Build.beginPlace(unit.getTeam(), current.x, current.y, current.block, current.rotation);
}else if(canCreateBlocks() && current.breaking && Build.validBreak(unit.getTeam(), current.x, current.y)){
Build.beginBreak(unit.getTeam(), current.x, current.y);
if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
Build.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
Build.beginBreak(getTeam(), current.x, current.y);
}else{
getPlaceQueue().removeFirst();
return;
@@ -259,12 +246,13 @@ public interface BuilderTrait extends Entity{
}
/**Do not call directly.*/
default void updateMining(Unit unit){
default void updateMining(){
Unit unit = (Unit)this;
Tile tile = getMineTile();
TileEntity core = unit.getClosestCore();
if(core == null || tile.block() != Blocks.air || unit.dst(tile.worldx(), tile.worldy()) > mineDistance
|| tile.floor().itemDrop == null || !unit.inventory.canAcceptItem(tile.floor().itemDrop) || !canMine(tile.floor().itemDrop)){
if(core == null || tile.block() != Blocks.air || dst(tile.worldx(), tile.worldy()) > mineDistance
|| tile.floor().itemDrop == null || !unit.acceptsItem(tile.floor().itemDrop) || !canMine(tile.floor().itemDrop)){
setMineTile(null);
}else{
Item item = tile.floor().itemDrop;
@@ -276,7 +264,7 @@ public interface BuilderTrait extends Entity{
Call.transferItemTo(item, 1,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), core.tile);
}else if(unit.inventory.canAcceptItem(item)){
}else if(unit.acceptsItem(item)){
Call.transferItemToUnit(item,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f),
@@ -293,11 +281,12 @@ public interface BuilderTrait extends Entity{
}
/**Draw placement effects for an entity. This includes mining*/
default void drawBuilding(Unit unit){
default void drawBuilding(){
Unit unit = (Unit)this;
BuildRequest request;
if(!isBuilding()){
if(getMineTile() != null){
drawMining(unit);
drawMining();
}
return;
}
@@ -306,11 +295,11 @@ public interface BuilderTrait extends Entity{
Tile tile = world.tile(request.x, request.y);
if(unit.dst(tile) > placeDistance){
if(dst(tile) > placeDistance){
return;
}
Lines.stroke(1f, Palette.accent);
Lines.stroke(1f, Pal.accent);
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
@@ -340,7 +329,8 @@ public interface BuilderTrait extends Entity{
}
/**Internal use only.*/
default void drawMining(Unit unit){
default void drawMining(){
Unit unit = (Unit)this;
Tile tile = getMineTile();
if(tile == null) return;
@@ -357,10 +347,10 @@ public interface BuilderTrait extends Entity{
Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Shapes.laser("minelaser", "minelaser-end", px, py, ex, ey);
Shapes.laser("minelaser", "minelaser-end", px, py, ex, ey, 0.75f);
if(unit instanceof Player && ((Player) unit).isLocal){
Lines.stroke(1f, Palette.accent);
Lines.stroke(1f, Pal.accent);
Lines.poly(tile.worldx(), tile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time());
}
@@ -1,14 +0,0 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.entities.trait.SolidTrait;
public interface CarriableTrait extends TeamTrait, TargetTrait, SolidTrait{
default boolean isCarried(){
return getCarrier() != null;
}
CarryTrait getCarrier();
void setCarrier(CarryTrait carrier);
}
@@ -1,69 +0,0 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.gen.Call;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.trait.SolidTrait;
public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
@Remote(called = Loc.both, targets = Loc.both, forward = true)
static void dropSelf(Player player){
if(player != null && player.getCarrier() != null){
player.getCarrier().dropCarry();
}
}
@Remote(called = Loc.both, targets = Loc.both, forward = true)
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;
}
if(trait.getCarry() != null){ //already carrying something, drop it
//drop current
Effects.effect(Fx.unitDrop, trait.getCarry());
trait.getCarry().setCarrier(null);
trait.setCarry(null);
if(unit != null){
trait.carry(unit); //now carry this new thing
}
}else if(unit != null){ //not currently carrying anything, make sure it's not null
trait.setCarry(unit);
unit.setCarrier(trait);
Effects.effect(Fx.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){
Call.setCarryOf(this instanceof Player ? (Player) this : null, this, unit);
}
}
@@ -0,0 +1,5 @@
package io.anuke.mindustry.entities.traits;
public interface DamageTrait{
float damage();
}
@@ -0,0 +1,10 @@
package io.anuke.mindustry.entities.traits;
public interface DrawTrait extends Entity{
default float drawSize(){
return 20f;
}
void draw();
}
@@ -0,0 +1,46 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.EntityGroup;
public interface Entity extends MoveTrait{
int getID();
void resetID(int id);
default void update(){
}
default void removed(){
}
default void added(){
}
default EntityGroup targetGroup(){
return Entities.defaultGroup();
}
@SuppressWarnings("unchecked")
default void add(){
targetGroup().add(this);
}
@SuppressWarnings("unchecked")
default void remove(){
if(getGroup() != null){
getGroup().remove(this);
}
setGroup(null);
}
EntityGroup getGroup();
void setGroup(EntityGroup group);
default boolean isAdded(){
return getGroup() != null;
}
}
@@ -0,0 +1,48 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.Mathf;
public interface HealthTrait{
void health(float health);
float health();
float maxHealth();
boolean isDead();
void setDead(boolean dead);
default void onHit(SolidTrait entity){
}
default void onDeath(){
}
default void damage(float amount){
health(health() - amount);
if(health() <= 0 && !isDead()){
onDeath();
setDead(true);
}
}
default void clampHealth(){
health(Mathf.clamp(health(), 0, maxHealth()));
}
default float healthf(){
return health() / maxHealth();
}
default void healBy(float amount){
health(health() + amount);
clampHealth();
}
default void heal(){
health(maxHealth());
setDead(false);
}
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.UnitInventory;
public interface InventoryTrait{
UnitInventory getInventory();
}
@@ -0,0 +1,20 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.geom.Position;
public interface MoveTrait extends Position{
void setX(float x);
void setY(float y);
default void moveBy(float x, float y){
setX(getX() + x);
setY(getY() + y);
}
default void set(float x, float y){
setX(x);
setY(y);
}
}
@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.entities.trait.Entity;
/**
* Marks an entity as serializable.
*/
@@ -0,0 +1,43 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.Interpolation;
public interface ScaleTrait{
/** 0 to 1. */
float fin();
/** 1 to 0 */
default float fout(){
return 1f - fin();
}
/** 1 to 0 */
default float fout(Interpolation i){
return i.apply(fout());
}
/** 1 to 0, ending at the specified margin */
default float fout(float margin){
float f = fin();
if(f >= 1f - margin){
return 1f - (f - (1f - margin)) / margin;
}else{
return 1f;
}
}
/** 0 to 1 **/
default float fin(Interpolation i){
return i.apply(fin());
}
/** 0 to 1 */
default float finpow(){
return Interpolation.pow3Out.apply(fin());
}
/** 0 to 1 to 0 */
default float fslope(){
return (0.5f - Math.abs(fin() - 0.5f)) * 2f;
}
}
@@ -1,10 +1,9 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.entities.trait.VelocityTrait;
import io.anuke.arc.util.Interval;
import io.anuke.mindustry.type.Weapon;
public interface ShooterTrait extends VelocityTrait, TeamTrait, InventoryTrait{
public interface ShooterTrait extends VelocityTrait, TeamTrait{
Interval getTimer();
@@ -0,0 +1,43 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.EntityQuery;
import io.anuke.arc.math.geom.Position;
import io.anuke.arc.math.geom.QuadTree.QuadTreeObject;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
public interface SolidTrait extends QuadTreeObject, MoveTrait, VelocityTrait, Entity, Position{
void hitbox(Rectangle rectangle);
void hitboxTile(Rectangle rectangle);
Vector2 lastPosition();
default boolean collidesGrid(int x, int y){
return true;
}
default float getDeltaX(){
return getX() - lastPosition().x;
}
default float getDeltaY(){
return getY() - lastPosition().y;
}
default boolean movable(){
return false;
}
default boolean collides(SolidTrait other){
return true;
}
default void collision(SolidTrait other, float x, float y){}
default void move(float x, float y){
EntityQuery.collisions().move(this, x, y);
}
}
@@ -1,12 +1,16 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.entities.Unit;
import io.anuke.arc.math.geom.Position;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.world.Tile;
public interface SpawnerTrait{
public interface SpawnerTrait extends TargetTrait, Position{
Tile getTile();
void updateSpawning(Unit unit);
void updateSpawning(Player unit);
float getSpawnProgress();
@Override
default boolean isValid(){
return getTile().entity instanceof SpawnerTrait;
}
}
@@ -3,7 +3,6 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.core.NetClient;
import io.anuke.mindustry.net.Interpolator;
import io.anuke.arc.Core;
import io.anuke.arc.entities.trait.Entity;
import io.anuke.arc.util.Tmp;
import java.io.DataInput;
@@ -63,6 +62,10 @@ public interface SyncTrait extends Entity, TypeTrait{
return true;
}
default float clipSize(){
return (this instanceof DrawTrait ? ((DrawTrait)this).drawSize() : 8f);
}
//Read and write sync data, usually position
void write(DataOutput data) throws IOException;
@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.entities.trait.SolidTrait;
import io.anuke.arc.entities.trait.VelocityTrait;
import io.anuke.arc.math.geom.Position;
import io.anuke.mindustry.game.Team;
@@ -1,7 +1,6 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.game.Team;
import io.anuke.arc.entities.trait.Entity;
public interface TeamTrait extends Entity{
Team getTeam();
@@ -0,0 +1,23 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.util.Time;
public interface TimeTrait extends ScaleTrait, Entity{
float lifetime();
void time(float time);
float time();
default void updateTime(){
time(Mathf.clamp(time() + Time.delta(), 0, lifetime()));
if(time() >= lifetime()){
remove();
}
}
//fin() is not implemented due to compiler issues with iOS/RoboVM
}
@@ -0,0 +1,36 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
public interface VelocityTrait extends MoveTrait{
Vector2 velocity();
default void applyImpulse(float x, float y){
velocity().x += x / mass();
velocity().y += y / mass();
}
default float maxVelocity(){
return Float.MAX_VALUE;
}
default float mass(){
return 1f;
}
default float drag(){
return 0f;
}
default void updateVelocity(){
velocity().scl(1f - drag() * Time.delta());
if(this instanceof SolidTrait){
((SolidTrait) this).move(velocity().x * Time.delta(), velocity().y * Time.delta());
}else{
moveBy(velocity().x * Time.delta(), velocity().y * Time.delta());
}
}
}
@@ -1,10 +1,8 @@
package io.anuke.mindustry.entities.units;
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.Core;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Angles;
@@ -14,22 +12,18 @@ import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.util.Interval;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Damage;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.effect.ScorchDecal;
import io.anuke.mindustry.entities.traits.ShooterTrait;
import io.anuke.mindustry.entities.traits.SpawnerTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.entities.units.StateMachine;
import io.anuke.mindustry.entities.units.UnitDrops;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.Weapon;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
@@ -53,8 +47,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
protected StateMachine state = new StateMachine();
protected TargetTrait target;
protected boolean isWave;
protected Squad squad;
protected int spawner = noSpawner;
/**internal constructor used for deserialization, DO NOT USE*/
@@ -69,16 +61,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
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(Fx.explosion, unit);
Effects.shake(2f, 2f, unit);
//must run afterwards so the unit's group is not null when sending the removal packet
Core.app.post(unit::remove);
}
@@ -104,36 +88,12 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
this.spawner = tile.pos();
}
/**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 != null &&
((TileEntity) target).tile.block().flags.contains(flag);
}
public void updateRespawning(){
if(spawner == noSpawner) return;
Tile tile = world.tile(spawner);
if(tile != null && tile.entity != null){
if(tile.entity instanceof SpawnerTrait){
((SpawnerTrait) tile.entity).updateSpawning(this);
}
}else{
spawner = noSpawner;
}
return target instanceof TileEntity && ((TileEntity) target).tile.block().flags.contains(flag);
}
public void setState(UnitState state){
@@ -169,7 +129,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
public void targetClosest(){
target = Units.getClosestTarget(team, x, y, Math.max(getWeapon().getAmmo().range(), type.range), u -> type.targetAir || !u.isFlying());
target = Units.getClosestTarget(team, x, y, Math.max(getWeapon().bullet.range(), type.range), u -> type.targetAir || !u.isFlying());
}
public TileEntity getClosestEnemyCore(){
@@ -189,15 +149,14 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
protected void drawItems(){
float backTrns = 4f, itemSize = 5f;
if(inventory.hasItem()){
ItemStack stack = inventory.getItem();
int stored = Mathf.clamp(stack.amount / 6, 1, 8);
float backTrns = 4f;
if(item.amount > 0){
int stored = Mathf.clamp(item.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,
Draw.rect(item.item.icon(Item.Icon.large),
x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT),
y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT),
itemSize, itemSize, rotation);
@@ -205,6 +164,15 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
public boolean isBoss(){
return hasEffect(StatusEffects.boss);
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public boolean isValid(){
return super.isValid() && isAdded();
@@ -249,16 +217,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
return type.health;
}
@Override
public float getArmor(){
return type.armor;
}
@Override
public float getSize(){
return 8;
}
@Override
public float mass(){
return type.mass;
@@ -274,7 +232,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
hitTime -= Time.delta();
if(isDead()){
updateRespawning();
return;
}
@@ -290,10 +247,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
damage(health);
}
if(squad != null){
squad.update();
}
updateTargeting();
state.update();
@@ -301,8 +254,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
if(target != null) behavior();
x = Mathf.clamp(x, tilesize, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, tilesize, world.height() * tilesize - tilesize);
if(!isFlying()){
clampPosition();
}
}
@Override
@@ -317,12 +271,22 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
@Override
public void removed(){
Tile tile = world.tile(spawner);
if(tile != null){
tile.block().unitRemoved(tile, this);
}
spawner = noSpawner;
}
@Override
public float drawSize(){
return 14;
return type.hitsize * 10;
}
@Override
public float clipSize(){
return isBoss() ? 10000000000f : super.clipSize();
}
@Override
@@ -356,7 +320,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public void writeSave(DataOutput stream) throws IOException{
super.writeSave(stream);
stream.writeByte(type.id);
stream.writeBoolean(isWave);
stream.writeInt(spawner);
}
@@ -364,7 +327,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public void readSave(DataInput stream) throws IOException{
super.readSave(stream);
byte type = stream.readByte();
this.isWave = stream.readBoolean();
this.spawner = stream.readInt();
this.type = content.getByID(ContentType.unit, type);
@@ -0,0 +1,221 @@
package io.anuke.mindustry.entities.type;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.entities.Predict;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.meta.BlockFlag;
public abstract class FlyingUnit extends BaseUnit{
protected float[] weaponAngles = {0, 0};
protected final UnitState
attack = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(Units.invalidateTarget(target, team, x, y)){
target = null;
}
if(target == null){
retarget(() -> {
targetClosest();
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
if(target == null){
setState(patrol);
}
});
}else{
attack(type.attackLength);
if((Angles.near(angleTo(target), rotation, type.shootCone) || getWeapon().ignoreRotation) //bombers and such don't care about rotation
&& dst(target) < Math.max(getWeapon().bullet.range(), type.range)){
BulletType ammo = getWeapon().bullet;
if(type.rotateWeapon){
for(boolean left : Mathf.booleans){
int wi = Mathf.num(left);
float wx = x + Angles.trnsx(rotation - 90, getWeapon().width * Mathf.sign(left));
float wy = y + Angles.trnsy(rotation - 90, getWeapon().width * Mathf.sign(left));
weaponAngles[wi] = Mathf.slerpDelta(weaponAngles[wi], Angles.angle(wx, wy, target.getX(), target.getY()), 0.1f);
Tmp.v2.trns(weaponAngles[wi], getWeapon().length);
getWeapon().update(FlyingUnit.this, wx + Tmp.v2.x, wy + Tmp.v2.y, weaponAngles[wi], left);
}
}else{
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
}
},
patrol = new UnitState(){
public void update(){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
if(target != null){
setState(attack);
}
target = getClosestCore();
});
if(target != null){
circle(60f + Mathf.absin(Time.time() + id * 23525, 70f, 1200f));
}
}
};
@Override
public void move(float x, float y){
moveBy(x, y);
}
@Override
public void update(){
super.update();
if(!Net.client()){
updateRotation();
wobble();
}
}
@Override
public void drawUnder(){
drawEngine();
}
@Override
public void draw(){
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
Draw.rect(type.region, x, y, rotation - 90);
drawWeapons();
drawItems();
Draw.mixcol();
}
public void drawWeapons(){
}
public void drawEngine(){
Draw.color(Pal.engine);
Fill.circle(x + Angles.trnsx(rotation + 180, type.engineOffset), y + Angles.trnsy(rotation + 180, type.engineOffset),
type.engineSize + Mathf.absin(Time.time(), 2f, type.engineSize/4f));
Draw.color(Color.WHITE);
Fill.circle(x + Angles.trnsx(rotation + 180, type.engineOffset-1f), y + Angles.trnsy(rotation + 180, type.engineOffset-1f),
(type.engineSize + Mathf.absin(Time.time(), 2f, type.engineSize/4f)) / 2f);
Draw.color();
}
@Override
public void behavior(){
if(Units.invalidateTarget(target, this)){
for(boolean left : Mathf.booleans){
int wi = Mathf.num(left);
weaponAngles[wi] = Mathf.slerpDelta(weaponAngles[wi],rotation, 0.1f);
}
}
}
@Override
public UnitState getStartState(){
return attack;
}
protected void wobble(){
if(Net.client()) return;
x += Mathf.sin(Time.time() + id * 999, 25f, 0.05f)*Time.delta();
y += Mathf.cos(Time.time() + id * 999, 25f, 0.05f)*Time.delta();
if(velocity.len() <= 0.05f){
//rotation += Mathf.sin(Time.time() + id * 99, 10f, 2f * type.speed)*Time.delta();
}
}
protected void updateRotation(){
rotation = velocity.angle();
}
protected void circle(float circleLength){
circle(circleLength, type.speed);
}
protected void circle(float circleLength, float speed){
if(target == null) return;
Tmp.v1.set(target.getX() - x, target.getY() - y);
if(Tmp.v1.len() < circleLength){
Tmp.v1.rotate((circleLength - Tmp.v1.len()) / circleLength * 180f);
}
Tmp.v1.setLength(speed * Time.delta());
velocity.add(Tmp.v1);
}
protected void moveTo(float circleLength){
if(target == null) return;
Tmp.v1.set(target.getX() - x, target.getY() - y);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((dst(target) - circleLength) / 100f, -1f, 1f);
Tmp.v1.setLength(type.speed * Time.delta() * length);
if(length < -0.5f){
Tmp.v1.rotate(180f);
}else if(length < 0){
Tmp.v1.setZero();
}
velocity.add(Tmp.v1);
}
protected void attack(float circleLength){
Tmp.v1.set(target.getX() - x, target.getY() - y);
float ang = angleTo(target);
float diff = Angles.angleDist(ang, rotation);
if(diff > 100f && Tmp.v1.len() < circleLength){
Tmp.v1.setAngle(velocity.angle());
}else{
Tmp.v1.setAngle(Mathf.slerpDelta(velocity.angle(), Tmp.v1.angle(), 0.44f));
}
Tmp.v1.setLength(type.speed * Time.delta());
velocity.add(Tmp.v1);
}
}
@@ -1,4 +1,4 @@
package io.anuke.mindustry.entities.units;
package io.anuke.mindustry.entities.type;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
@@ -8,21 +8,14 @@ import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.Predict;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.Weapon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.content;
import static io.anuke.mindustry.Vars.world;
public abstract class GroundUnit extends BaseUnit{
@@ -31,7 +24,6 @@ public abstract class GroundUnit extends BaseUnit{
protected float walkTime;
protected float stuckTime;
protected float baseRotation;
protected Weapon weapon;
public final UnitState
@@ -42,13 +34,19 @@ public abstract class GroundUnit extends BaseUnit{
public void update(){
TileEntity core = getClosestEnemyCore();
float dst = core == null ? 0 : dst(core);
if(core != null && dst < getWeapon().getAmmo().range() / 1.1f){
if(core == null){
setState(patrol);
return;
}
float dst = dst(core);
if(dst < getWeapon().bullet.range() / 1.1f){
target = core;
}
if(dst > getWeapon().getAmmo().range() * 0.5f){
if(dst > getWeapon().bullet.range() * 0.5f){
moveToCore();
}
}
@@ -64,27 +62,8 @@ public abstract class GroundUnit extends BaseUnit{
}
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= maxHealth()){
state.set(attack);
}
moveAwayFromCore();
}
};
@Override
public void init(UnitType type, Team team){
super.init(type, team);
this.weapon = type.weapon;
}
@Override
public void interpolate(){
super.interpolate();
@@ -124,36 +103,32 @@ public abstract class GroundUnit extends BaseUnit{
@Override
public Weapon getWeapon(){
return weapon;
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
return type.weapon;
}
@Override
public void draw(){
Draw.alpha(Draw.getShader() != Shaders.mix ? 1f : hitTime / hitDuration);
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
float ft = Mathf.sin(walkTime * type.speed*5f, 6f, 2f);
float ft = Mathf.sin(walkTime * type.speed*5f, 6f, 2f + type.hitsize/15f);
Floor floor = getFloorOn();
if(floor.isLiquid){
Draw.tint(Color.WHITE, floor.liquidColor, 0.5f);
Draw.color(Color.WHITE, floor.liquidColor, 0.5f);
}
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);
type.legRegion.getWidth() * i * Draw.scl, type.legRegion.getHeight() * Draw.scl - Mathf.clamp(ft * i, 0, 2), baseRotation - 90);
}
if(floor.isLiquid){
Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f);
Draw.color(Color.WHITE, floor.liquidColor, drownTime * 0.4f);
}else{
Draw.tint(Color.WHITE);
Draw.color(Color.WHITE);
}
Draw.rect(type.baseRegion, x, y, baseRotation - 90);
@@ -161,30 +136,27 @@ public abstract class GroundUnit extends BaseUnit{
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;
float w = i > 0 ? -12 : 12;
Draw.rect(weapon.equipRegion,
x + Angles.trnsx(tra, type.weaponOffsetX * i, trY),
y + Angles.trnsy(tra, type.weaponOffsetX * i, trY), w, 12, rotation - 90);
float tra = rotation - 90, trY = -type.weapon.getRecoil(this, i > 0) + type.weaponOffsetY;
float w = - i * type.weapon.region.getWidth() * Draw.scl;
Draw.rect(type.weapon.region,
x + Angles.trnsx(tra, getWeapon().width * i, trY),
y + Angles.trnsy(tra, getWeapon().width * i, trY), w, type.weapon.region.getHeight() * Draw.scl, rotation - 90);
}
drawItems();
Draw.alpha(1f);
Draw.mixcol();
}
@Override
public void behavior(){
if(health <= health * type.retreatPercent){
setState(retreat);
}
if(!Units.invalidateTarget(target, this)){
if(dst(target) < getWeapon().getAmmo().range()){
if(dst(target) < getWeapon().bullet.range()){
rotate(angleTo(target));
if(Angles.near(angleTo(target), rotation, 13f)){
BulletType ammo = getWeapon().getAmmo();
BulletType ammo = getWeapon().bullet;
Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.speed);
@@ -205,32 +177,8 @@ public abstract class GroundUnit extends BaseUnit{
retarget(this::targetClosest);
}
@Override
public void write(DataOutput data) throws IOException{
super.write(data);
data.writeByte(weapon.id);
}
@Override
public void read(DataInput data) throws IOException{
super.read(data);
weapon = content.getByID(ContentType.weapon, data.readByte());
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeByte(weapon.id);
super.writeSave(stream);
}
@Override
public void readSave(DataInput stream) throws IOException{
weapon = content.getByID(ContentType.weapon, stream.readByte());
super.readSave(stream);
}
protected void patrol(){
vec.trns(baseRotation, type.speed * Time.delta());
vec.trns(baseRotation, type.speed * Time.delta() * 2);
velocity.add(vec.x, vec.y);
vec.trns(baseRotation, type.hitsizeTile);
Tile tile = world.tileWorld(x + vec.x, y + vec.y);
@@ -1,12 +1,9 @@
package io.anuke.mindustry.entities;
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.EntityQuery;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.Angles;
@@ -14,19 +11,18 @@ import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Align;
import io.anuke.arc.util.Interval;
import io.anuke.arc.util.Pack;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.Mechs;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.effect.ScorchDecal;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.content.Mechs;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.traits.ShooterTrait;
import io.anuke.mindustry.entities.traits.SpawnerTrait;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.input.Binding;
import io.anuke.mindustry.io.TypeIO;
import io.anuke.mindustry.net.Net;
@@ -36,7 +32,6 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.storage.CoreBlock.CoreEntity;
import java.io.DataInput;
import java.io.DataOutput;
@@ -44,7 +39,7 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTrait{
public class Player extends Unit implements BuilderTrait, ShooterTrait{
public static final int timerSync = 2;
public static final int timerAbility = 3;
private static final int timerShootLeft = 0;
@@ -60,11 +55,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
public String name = "name";
public String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile;
public float boostHeat, shootHeat;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;
public Color color = new Color();
public Mech mech;
public int spawner = noSpawner;
public SpawnerTrait spawner, lastSpawner;
public NetConnection con;
public int playerIndex = 0;
@@ -76,7 +71,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
private float walktime;
private Queue<BuildRequest> placeQueue = new Queue<>();
private Tile mining;
private CarriableTrait carrying;
private Vector2 movement = new Vector2();
private boolean moved;
@@ -90,15 +84,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
player.dead = true;
player.placeQueue.clear();
player.dropCarry();
float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f);
float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize() / 2f, Palette.darkFlame);
ScorchDecal.create(player.x, player.y);
player.onDeath();
player.mech = (player.isMobile ? Mechs.starterMobile : Mechs.starterDesktop);
}
@Override
@@ -129,11 +116,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
@Override
public boolean collidesGrid(int x, int y){
Tile tile = world.tile(x, y);
if(!isFlying()) return true;
if(!mech.flying && tile != null && !tile.block().synthetic() && tile.block().solid){
return true;
}
return false;
return !isFlying() || (!mech.flying && tile != null && !tile.block().synthetic() && tile.block().solid);
}
@Override
@@ -184,21 +167,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
}
@Override
public CarriableTrait getCarry(){
return carrying;
}
@Override
public void setCarry(CarriableTrait unit){
this.carrying = unit;
}
@Override
public float getCarryWeight(){
return mech.carryWeight;
}
@Override
public float getBuildPower(Tile tile){
return mech.buildPower;
@@ -206,7 +174,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
@Override
public float maxHealth(){
return 200;
return mech.health;
}
@Override
@@ -225,8 +193,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
@Override
public float getArmor(){
return mech.armor + mech.getExtraArmor(this);
public float calculateDamage(float amount){
return amount * Mathf.clamp(1f - (status.getArmorMultiplier() + mech.getExtraArmor(this)) / 100f);
}
@Override
@@ -241,12 +209,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
@Override
public boolean isFlying(){
return mech.flying || boostHeat > liftoffBoost || isCarried();
}
@Override
public float getSize(){
return 8;
return mech.flying || boostHeat > liftoffBoost;
}
@Override
@@ -282,16 +245,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
return "Player{" + id + ", mech=" + mech.name + ", local=" + isLocal + ", " + x + ", " + y + "}";
}
@Override
public void removed(){
dropCarryLocal();
TileEntity core = getClosestCore();
if(core != null && ((CoreEntity) core).currentUnit == this){
((CoreEntity) core).currentUnit = null;
}
}
@Override
public EntityGroup targetGroup(){
return playerGroup;
@@ -322,7 +275,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(dead) return;
if(!movement.isZero() && moved && !state.isPaused()){
walktime += movement.len() / 0.7f * getFloorOn().speedMultiplier;
walktime += movement.len() / 1f * getFloorOn().speedMultiplier;
baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f);
}
@@ -331,11 +284,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
Floor floor = getFloorOn();
Draw.color();
Draw.alpha(Draw.getShader() != Shaders.mix ? 1f : hitTime / hitDuration);
Draw.mixcol(Color.WHITE, hitTime / hitDuration);
if(!mech.flying){
if(floor.isLiquid){
Draw.tint(Color.WHITE, floor.liquidColor, 0.5f);
Draw.color(Color.WHITE, floor.liquidColor, 0.5f);
}
float boostTrnsY = -boostHeat * 3f;
@@ -355,9 +308,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
if(floor.isLiquid){
Draw.tint(Color.WHITE, floor.liquidColor, drownTime);
Draw.color(Color.WHITE, floor.liquidColor, drownTime);
}else{
Draw.tint(Color.WHITE);
Draw.color(Color.WHITE);
}
Draw.rect(mech.region, x, y, rotation - 90);
@@ -366,37 +319,36 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
for(int i : Mathf.signs){
float tra = rotation - 90, trY = -mech.weapon.getRecoil(this, i > 0) + mech.weaponOffsetY;
float w = i > 0 ? -mech.weapon.equipRegion.getWidth() : mech.weapon.equipRegion.getWidth();
Draw.rect(mech.weapon.equipRegion,
float w = i > 0 ? -mech.weapon.region.getWidth() : mech.weapon.region.getWidth();
Draw.rect(mech.weapon.region,
x + Angles.trnsx(tra, (mech.weaponOffsetX + mech.spreadX(this)) * i, trY),
y + Angles.trnsy(tra, (mech.weaponOffsetX + mech.spreadX(this)) * i, trY),
w * Draw.scl,
mech.weapon.equipRegion.getHeight() * Draw.scl,
mech.weapon.region.getHeight() * Draw.scl,
rotation - 90);
}
float backTrns = 4f, itemSize = 5f;
if(inventory.hasItem()){
ItemStack stack = inventory.getItem();
float backTrns = 4f;
if(item.amount > 0){
ItemStack stack = item;
int stored = Mathf.clamp(stack.amount / 6, 1, 8);
for(int i = 0; i < stored; i++){
float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 1, 60f);
float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 1f) - 1f;
Draw.rect(stack.item.region,
Draw.rect(stack.item.icon(Item.Icon.large),
x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT),
y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT),
itemSize, itemSize, rotation);
}
}
Draw.alpha(1f);
Draw.reset();
}
@Override
public void drawStats(){
Draw.color(Color.BLACK, team.color, healthf() + Mathf.absin(Time.time(), healthf() * 5f, 1f - healthf()));
Draw.alpha(hitTime / hitDuration);
Draw.rect(getPowerCellRegion(), x + Angles.trnsx(rotation, mech.cellTrnsY, 0f), y + Angles.trnsy(rotation, mech.cellTrnsY, 0f), rotation - 90);
Draw.color();
}
@@ -405,7 +357,22 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
public void drawOver(){
if(dead) return;
drawBuilding(this);
drawBuilding();
}
@Override
public void drawUnder(){
if(dead) return;
float size = mech.engineSize * (mech.flying ? 1f : boostHeat);
Draw.color(mech.engineColor);
Fill.circle(x + Angles.trnsx(rotation + 180, mech.engineOffset), y + Angles.trnsy(rotation + 180, mech.engineOffset),
size + Mathf.absin(Time.time(), 2f, size/4f));
Draw.color(Color.WHITE);
Fill.circle(x + Angles.trnsx(rotation + 180, mech.engineOffset-1f), y + Angles.trnsy(rotation + 180, mech.engineOffset-1f),
(size + Mathf.absin(Time.time(), 2f, size/4f)) / 2f);
Draw.color();
}
public void drawName(){
@@ -434,19 +401,20 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
Draw.reset();
Pools.free(layout);
font.getData().setScale(1f);
font.setColor(Color.WHITE);
font.setUseIntegerPositions(ints);
}
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
public void drawBuildRequests(){
for(BuildRequest request : getPlaceQueue()){
if(getCurrentRequest() == request) continue;
if(getCurrentRequest() == request && request.progress > 0.001f) continue;
if(request.breaking){
Block block = world.tile(request.x, request.y).target().block();
//draw removal request
Lines.stroke(2f, Palette.removeBack);
Lines.stroke(2f, Pal.removeBack);
float rad = Mathf.absin(Time.time(), 7f, 1f) + block.size * tilesize / 2f - 1;
Lines.square(
@@ -454,7 +422,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
request.y * tilesize + block.offset() - 1,
rad);
Draw.color(Palette.remove);
Draw.color(Pal.remove);
Lines.square(
request.x * tilesize + block.offset(),
@@ -464,7 +432,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
float rad = Mathf.absin(Time.time(), 7f, 1f) - 1.5f + request.block.size * tilesize / 2f;
//draw place request
Lines.stroke(1f, Palette.accentBack);
Lines.stroke(1f, Pal.accentBack);
Lines.square(
request.x * tilesize + request.block.offset(),
@@ -475,10 +443,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
Draw.rect(request.block.icon(Icon.full),
request.x * tilesize + request.block.offset(),
request.y * tilesize + request.block.offset(), rad*2, rad*2, request.rotation * 90);
request.y * tilesize + request.block.offset(), rad*2, rad*2, request.block.rotate ? request.rotation * 90 : 0);
Draw.color(Palette.accent);
Draw.color(Pal.accent);
Lines.square(
request.x * tilesize + request.block.offset(),
@@ -496,6 +464,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
@Override
public void update(){
hitTime -= Time.delta();
if(Float.isNaN(x) || Float.isNaN(y)){
@@ -509,13 +478,23 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
setDead(true);
}
if(!isDead() && isOutOfBounds()){
destructTime += Time.delta();
if(destructTime >= boundsCountdown){
kill();
}
}else{
destructTime = 0f;
}
if(isDead()){
isBoosting = false;
boostHeat = 0f;
updateRespawning();
return;
}else{
spawner = noSpawner;
spawner = null;
}
avoidOthers(1f);
@@ -535,7 +514,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(mech.shake > 1f){
Effects.shake(mech.shake, mech.shake, this);
}
Effects.effect(Fx.unitLand, tile.floor().minimapColor, x, y, tile.floor().isLiquid ? 1f : 0.5f);
Effects.effect(Fx.unitLand, tile.floor().liquidColor == null ? tile.floor().color : tile.floor().color, x, y, tile.floor().isLiquid ? 1f : 0.5f);
}
mech.onLand(this);
achievedFlight = false;
@@ -543,15 +522,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(!isLocal){
interpolate();
updateBuilding(this); //building happens even with non-locals
updateBuilding(); //building happens even with non-locals
status.update(this); //status effect updating also happens with non locals for effect purposes
updateVelocityStatus(); //velocity too, for visual purposes
if(getCarrier() != null){
x = getCarrier().getX();
y = getCarrier().getY();
}
if(Net.server()){
updateShooting(); //server simulates player shooting
}
@@ -567,10 +541,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
updateMech();
}
updateBuilding(this);
updateBuilding();
x = Mathf.clamp(x, 0, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize - tilesize);
if(!mech.flying){
clampPosition();
}
}
protected void updateMech(){
@@ -584,10 +559,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
//fraction of speed when at max load
float carrySlowdown = 0.7f;
speed *= ((inventory.hasItem() ? Mathf.lerp(1f, carrySlowdown, (float) inventory.getItem().amount / inventory.capacity()) : 1f));
if(mech.flying){
//prevent strafing backwards, have a penalty for doing so
@@ -595,23 +566,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
speed *= Mathf.lerp(1f, penalty, Angles.angleDist(rotation, velocity.angle()) / 180f);
}
//drop from carrier on key press
if(!ui.chatfrag.chatOpen() && Core.input.keyTap(Binding.drop_unit)){
if(!mech.flying){
if(getCarrier() != null){
Call.dropSelf(this);
}
}else if(getCarry() != null){
dropCarry();
}else{
Unit unit = Units.getClosest(team, x, y, 8f, u -> !u.isFlying() && u.mass() <= mech.carryWeight);
if(unit != null){
carry(unit);
}
}
}
movement.setZero();
float xa = Core.input.axis(Binding.move_x);
@@ -628,18 +582,12 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
movement.limit(speed).scl(Time.delta());
if(getCarrier() == null){
if(!ui.chatfrag.chatOpen()){
velocity.add(movement.x, movement.y);
}
float prex = x, prey = y;
updateVelocityStatus();
moved = dst(prex, prey) > 0.001f;
}else{
velocity.setZero();
x = Mathf.lerpDelta(x, getCarrier().getX(), 0.1f);
y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f);
if(!ui.chatfrag.chatOpen()){
velocity.add(movement.x, movement.y);
}
float prex = x, prey = y;
updateVelocityStatus();
moved = dst(prex, prey) > 0.001f;
if(!ui.chatfrag.chatOpen()){
float baseLerp = mech.getRotationAlpha(this);
@@ -662,7 +610,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
protected void updateFlying(){
if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity) target).damaged() && target.getTeam() == team &&
mech.canHeal && dst(target) < getWeapon().getAmmo().range())){
mech.canHeal && dst(target) < getWeapon().bullet.range())){
target = null;
}
@@ -680,9 +628,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
if(dst(moveTarget) < 2f){
if(moveTarget instanceof CarriableTrait){
carry((CarriableTrait) moveTarget);
}else if(tapping){
if(tapping){
Tile tile = ((TileEntity) moveTarget).tile;
tile.block().tapped(tile, this);
}
@@ -693,12 +639,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
moveTarget = null;
}
if(getCarrier() != null){
velocity.setZero();
x = Mathf.lerpDelta(x, getCarrier().getX(), 0.1f);
y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f);
}
movement.set(targetX - x, targetY - y).limit(isBoosting && !mech.flying ? mech.boostSpeed : mech.speed);
movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f));
@@ -726,7 +666,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
float lx = x, ly = y;
updateVelocityStatus();
moved = dst(lx, ly) > 0.001f && !isCarried();
moved = dst(lx, ly) > 0.001f;
if(mech.flying){
//hovering effect
@@ -742,11 +682,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(target == null){
isShooting = false;
if(Core.settings.getBool("autotarget")){
target = Units.getClosestTarget(team, x, y, getWeapon().getAmmo().range());
target = Units.getClosestTarget(team, x, y, getWeapon().bullet.range(), u -> u.getTeam() != Team.none, u -> u.getTeam() != Team.none);
if(mech.canHeal && target == null){
target = Geometry.findClosest(x, y, world.indexer.getDamaged(Team.blue));
if(target != null && dst(target) > getWeapon().getAmmo().range()){
if(target != null && dst(target) > getWeapon().bullet.range()){
target = null;
}else if(target != null){
target = ((Tile) target).entity;
@@ -758,14 +698,14 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
}
}
}else if(target.isValid() || (target instanceof TileEntity && ((TileEntity) target).damaged() && target.getTeam() == team &&
mech.canHeal && dst(target) < getWeapon().getAmmo().range())){
mech.canHeal && dst(target) < getWeapon().bullet.range())){
//rotate toward and shoot the target
if(mech.turnCursor){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
}
Vector2 intercept =
Predict.intercept(x, y, target.getX(), target.getY(), target.velocity().x - velocity.x, target.velocity().y - velocity.y, getWeapon().getAmmo().speed);
Predict.intercept(x, y, target.getX(), target.getY(), target.velocity().x - velocity.x, target.velocity().y - velocity.y, getWeapon().bullet.speed);
pointerX = intercept.x;
pointerY = intercept.y;
@@ -799,12 +739,12 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
public void resetNoAdd(){
status.clear();
team = Team.blue;
inventory.clear();
item.amount = 0;
placeQueue.clear();
dead = true;
target = null;
moveTarget = null;
carrier = null;
spawner = lastSpawner = null;
health = maxHealth();
boostHeat = drownTime = hitTime = 0f;
mech = (isMobile ? Mechs.starterMobile : Mechs.starterDesktop);
@@ -817,23 +757,27 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
public void updateRespawning(){
if(spawner != noSpawner && world.tile(spawner) != null && world.tile(spawner).entity instanceof SpawnerTrait){
((SpawnerTrait) world.tile(spawner).entity).updateSpawning(this);
}else{
CoreEntity entity = (CoreEntity) getClosestCore();
if(entity != null && !netServer.isWaitingForPlayers()){
this.spawner = entity.tile.pos();
if(spawner != null && spawner.isValid()){
spawner.updateSpawning(this);
}else if(!netServer.isWaitingForPlayers()){
if(lastSpawner != null && lastSpawner.isValid()){
this.spawner = lastSpawner;
}else if(getClosestCore() != null){
this.spawner = (SpawnerTrait)getClosestCore();
}
}else if(getClosestCore() != null){
set(getClosestCore().getX(), getClosestCore().getY());
}
}
public void beginRespawning(SpawnerTrait spawner){
this.spawner = spawner.getTile().pos();
this.spawner = spawner;
this.lastSpawner = spawner;
this.dead = true;
}
public void endRespawning(){
spawner = noSpawner;
spawner = null;
}
//endregion
@@ -853,6 +797,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(isLocal){
stream.writeByte(mech.id);
stream.writeByte(playerIndex);
stream.writeInt(lastSpawner == null ? noSpawner : lastSpawner.getTile().pos());
super.writeSave(stream, false);
}
}
@@ -864,12 +809,17 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(local && !headless){
byte mechid = stream.readByte();
int index = stream.readByte();
int spawner = stream.readInt();
if(world.tile(spawner) != null && world.tile(spawner).entity != null && world.tile(spawner).entity instanceof SpawnerTrait){
lastSpawner = (SpawnerTrait)(world.tile(spawner).entity);
}
players[index].readSaveSuper(stream);
players[index].mech = content.getByID(ContentType.mech, mechid);
players[index].dead = false;
}else if(local){
byte mechid = stream.readByte();
stream.readByte();
stream.readInt();
readSaveSuper(stream);
mech = content.getByID(ContentType.mech, mechid);
dead = false;
@@ -889,8 +839,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2));
buffer.writeInt(Color.rgba8888(color));
buffer.writeByte(mech.id);
buffer.writeInt(mining == null ? -1 : mining.pos());
buffer.writeInt(spawner);
buffer.writeInt(mining == null ? noSpawner : mining.pos());
buffer.writeInt(spawner == null ? noSpawner : spawner.getTile().pos());
buffer.writeShort((short) (baseRotation * 2));
writeBuilding(buffer);
@@ -898,7 +848,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
@Override
public void read(DataInput buffer) throws IOException{
float lastx = x, lasty = y, lastrot = rotation;
float lastx = x, lasty = y, lastrot = rotation, lastvx = velocity.x, lastvy = velocity.y;
super.readSave(buffer);
name = TypeIO.readStringData(buffer);
byte bools = buffer.readByte();
@@ -919,10 +869,17 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
if(isLocal){
x = lastx;
y = lasty;
velocity.x = lastvx;
velocity.y = lastvy;
}else{
mining = world.tile(mine);
isBoosting = boosting;
this.spawner = spawner;
Tile tile = world.tile(spawner);
if(tile != null && tile.entity instanceof SpawnerTrait){
this.spawner = (SpawnerTrait)tile.entity;
}else{
this.spawner = null;
}
}
}
@@ -1,14 +1,14 @@
package io.anuke.mindustry.entities;
package io.anuke.mindustry.entities.type;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.arc.entities.impl.BaseEntity;
import io.anuke.arc.entities.trait.HealthTrait;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.impl.BaseEntity;
import io.anuke.mindustry.entities.traits.HealthTrait;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.math.geom.Vector2;
@@ -59,7 +59,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
private boolean sleeping;
private float sleepTime;
@Remote(called = Loc.server)
@Remote(called = Loc.server, unreliable = true)
public static void onTileDamage(Tile tile, float health){
if(tile.entity != null){
tile.entity.health = health;
@@ -151,7 +151,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
if(health <= 0){
Call.onTileDestroyed(tile);
}else if(preHealth >= maxHealth() - 0.00001f && health < maxHealth()){ //when just damaged
}else if(preHealth >= maxHealth() - 0.00001f && health < maxHealth() && world != null){ //when just damaged
world.indexer.notifyTileDamaged(this);
}
}
@@ -195,7 +195,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
if(other == null) continue;
other = other.target();
if(other.entity == null || other.getTeamID() != tile.getTeamID()) continue;
if(other.entity == null || !(other.interactable(tile.getTeam()))) continue;
other.block().onProximityUpdate(other);
@@ -283,7 +283,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
Block previous = tile.block();
tile.block().update(tile);
if(tile.block() == previous && cons != null){
cons.update(this);
cons.update();
}
}
@@ -291,4 +291,12 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
public EntityGroup targetGroup(){
return tileGroup;
}
@Override
public String toString(){
return "TileEntity{" +
"tile=" + tile +
", health=" + health +
'}';
}
}
@@ -1,28 +1,33 @@
package io.anuke.mindustry.entities;
package io.anuke.mindustry.entities.type;
import io.anuke.arc.Core;
import io.anuke.arc.Events;
import io.anuke.arc.entities.Effects;
import io.anuke.arc.entities.impl.DestructibleEntity;
import io.anuke.arc.entities.trait.DamageTrait;
import io.anuke.arc.entities.trait.DrawTrait;
import io.anuke.arc.entities.trait.SolidTrait;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.Fill;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.entities.Damage;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.effect.ScorchDecal;
import io.anuke.mindustry.entities.impl.DestructibleEntity;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.units.Statuses;
import io.anuke.mindustry.game.EventType.UnitDestroyEvent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.net.Interpolator;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.StatusEffect;
import io.anuke.mindustry.type.Weapon;
import io.anuke.mindustry.world.Pos;
@@ -33,10 +38,9 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait{
public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait{
/**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.*/
@@ -48,42 +52,25 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
private static final Rectangle queryRect = new Rectangle();
private static final Vector2 moveVector = new Vector2();
public final UnitInventory inventory = new UnitInventory(this);
public float rotation;
public float hitTime;
protected final Interpolator interpolator = new Interpolator();
protected final StatusController status = new StatusController();
protected Team team = Team.blue;
protected final Statuses status = new Statuses();
protected final ItemStack item = new ItemStack(content.item(0), 0);
protected CarryTrait carrier;
protected float drownTime;
protected Team team = Team.blue;
protected float drownTime, hitTime;
@Override
public boolean movable(){
return !isDead();
}
@Override
public UnitInventory getInventory(){
return inventory;
}
@Override
public CarryTrait getCarrier(){
return carrier;
}
@Override
public boolean collidesGrid(int x, int y){
return !isFlying();
}
@Override
public void setCarrier(CarryTrait carrier){
this.carrier = carrier;
}
@Override
public Team getTeam(){
return team;
@@ -125,7 +112,15 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
@Override
public void onDeath(){
inventory.clear();
float explosiveness = 2f + item.item.explosiveness * item.amount;
float flammability = item.item.flammability * item.amount;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, getSize() / 2f, Pal.darkFlame);
ScorchDecal.create(x, y);
Effects.effect(Fx.explosion, this);
Effects.shake(2f, 2f, this);
item.amount = 0;
drownTime = 0f;
status.clear();
Events.fire(new UnitDestroyEvent(this));
@@ -145,6 +140,11 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
}
@Override
public boolean isValid(){
return !isDead() && isAdded();
}
@Override
public void writeSave(DataOutput stream) throws IOException{
writeSave(stream, false);
@@ -160,9 +160,12 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
byte yv = stream.readByte();
float rotation = stream.readShort() / 2f;
int health = stream.readShort();
byte itemID = stream.readByte();
short itemAmount = stream.readShort();
this.status.readSave(stream);
this.inventory.readSave(stream);
this.item.amount = itemAmount;
this.item.item = content.item(itemID);
this.dead = dead;
this.team = Team.all[team];
this.health = health;
@@ -181,12 +184,31 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
stream.writeByte((byte) (Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeShort((short) (rotation * 2));
stream.writeShort((short) health);
stream.writeByte(item.item.id);
stream.writeShort((short)item.amount);
status.writeSave(stream);
inventory.writeSave(stream);
}
protected void clampPosition(){
x = Mathf.clamp(x, tilesize, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, tilesize, world.height() * tilesize - tilesize);
}
public void kill(){
health = -1;
damage(1);
}
public boolean isImmune(StatusEffect effect){
return false;
}
public boolean isOutOfBounds(){
return x < -worldBounds || y < -worldBounds || x > world.width() * tilesize + worldBounds || y > world.height() * tilesize + worldBounds;
}
public float calculateDamage(float amount){
return amount * Mathf.clamp(1f - getArmor() / 100f * status.getArmorMultiplier());
return amount * Mathf.clamp(1f - status.getArmorMultiplier() / 100f);
}
public float getDamageMultipler(){
@@ -197,12 +219,13 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
return status.hasEffect(effect);
}
//TODO optimize
public void avoidOthers(float scaling){
hitbox(queryRect);
queryRect.setSize(queryRect.getWidth() * scaling);
Units.getNearby(queryRect, t -> {
if(t == this || t.getCarrier() == this || getCarrier() == t || t.isFlying() != isFlying()) return;
if(t == this || t.isFlying() != isFlying()) return;
float dst = dst(t);
moveVector.set(x, y).sub(t.getX(), t.getY()).setLength(1f * (1f - (dst / queryRect.getWidth())));
applyImpulse(moveVector.x, moveVector.y);
@@ -227,28 +250,22 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public void onRespawn(Tile tile){}
@Override
public boolean isValid(){
return !isDead() && isAdded();
}
/**Updates velocity and status effects.*/
public void updateVelocityStatus(){
Floor floor = getFloorOn();
if(isCarried()){ //carried units do not take into account velocity normally
set(carrier.getX(), carrier.getY());
velocity.set(carrier.velocity());
return;
}
Tile tile = world.tileWorld(x, y);
status.update(this);
velocity.limit(maxVelocity()).scl(1f + (status.getSpeedMultiplier()-1f) * Time.delta());
if(x < -finalWorldBounds || y < -finalWorldBounds || x >= world.width() * tilesize + finalWorldBounds || y >= world.height() * tilesize + finalWorldBounds){
kill();
}
if(isFlying()){
drownTime = 0f;
move(velocity.x * Time.delta(), velocity.y * Time.delta());
}else{
boolean onLiquid = floor.isLiquid;
@@ -296,6 +313,31 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
velocity.scl(Mathf.clamp(1f - drag() * (isFlying() ? 1f : floor.dragMultiplier) * Time.delta()));
}
public boolean acceptsItem(Item item){
return this.item.amount <= 0 || (this.item.item == item && this.item.amount <= getItemCapacity());
}
public void addItem(Item item){
addItem(item, 1);
}
public void addItem(Item item, int amount){
this.item.amount = this.item.item == item ? this.item.amount + amount : amount;
this.item.item = item;
}
public void clearItem(){
item.amount = 0;
}
public ItemStack item(){
return item;
}
public int maxAccepted(Item item){
return this.item.item != item && this.item.amount > 0 ? 0 : getItemCapacity() - this.item.amount;
}
public void applyEffect(StatusEffect effect, float duration){
if(dead || Net.client()) return; //effects are synced and thus not applied through clients
status.handleApply(this, effect, duration);
@@ -323,7 +365,6 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public void drawStats(){
Draw.color(Color.BLACK, team.color, healthf() + Mathf.absin(Time.time(), healthf()*5f, 1f - healthf()));
Draw.alpha(hitTime);
Draw.rect(getPowerCellRegion(), x, y, rotation - 90);
Draw.color();
}
@@ -343,12 +384,9 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
Draw.rect(getIconRegion(), x + offsetX, y + offsetY, rotation - 90);
}
public void drawView(){
Fill.circle(x, y, getViewDistance());
}
public float getViewDistance(){
return 135f;
public float getSize(){
hitbox(Tmp.r1);
return Math.max(Tmp.r1.width, Tmp.r1.height) * 2f;
}
public abstract TextureRegion getIconRegion();
@@ -357,11 +395,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public abstract int getItemCapacity();
public abstract float getArmor();
public abstract float mass();
public abstract boolean isFlying();
public abstract float getSize();
}
@@ -0,0 +1,6 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Crawler extends GroundUnit{
}
@@ -0,0 +1,7 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Dagger extends GroundUnit{
}
@@ -1,18 +1,18 @@
package io.anuke.mindustry.entities.units.types;
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.Events;
import io.anuke.arc.collection.Queue;
import io.anuke.arc.entities.EntityGroup;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.util.Structs;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.entities.units.FlyingUnit;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.entities.type.FlyingUnit;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.game.EventType.BuildSelectEvent;
import io.anuke.mindustry.gen.Call;
@@ -32,8 +32,6 @@ import static io.anuke.mindustry.Vars.unitGroups;
import static io.anuke.mindustry.Vars.world;
public class Drone extends FlyingUnit implements BuilderTrait{
protected static int timerRepairEffect = timerIndex++;
protected Item targetItem;
protected Tile mineTile;
protected Queue<BuildRequest> placeQueue = new Queue<>();
@@ -135,10 +133,10 @@ public class Drone extends FlyingUnit implements BuilderTrait{
}
//if inventory is full, drop it off.
if(inventory.isFull()){
if(item.amount >= getItemCapacity()){
setState(drop);
}else{
if(targetItem != null && !inventory.canAcceptItem(targetItem)){
if(targetItem != null && !acceptsItem(targetItem)){
setState(drop);
return;
}
@@ -177,13 +175,13 @@ public class Drone extends FlyingUnit implements BuilderTrait{
}
public void update(){
if(inventory.isEmpty()){
if(item.amount == 0){
setState(mine);
return;
}
if(inventory.getItem().item.type != ItemType.material){
inventory.clearItem();
if(item.item.type != ItemType.material){
item.amount = 0;
setState(mine);
return;
}
@@ -195,9 +193,9 @@ public class Drone extends FlyingUnit implements BuilderTrait{
TileEntity tile = (TileEntity) target;
if(dst(target) < type.range){
if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){
Call.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile);
inventory.clearItem();
if(tile.tile.block().acceptStack(item.item, item.amount, tile.tile, Drone.this) == item.amount){
Call.transferItemTo(item.item, item.amount, x, y, tile.tile);
item.amount = 0;
}
setState(repair);
@@ -299,7 +297,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{
target = null;
}
updateBuilding(this);
updateBuilding();
}
@Override
@@ -326,7 +324,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{
@Override
public void drawOver(){
drawBuilding(this);
drawBuilding();
}
@Override
@@ -0,0 +1,6 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Eruptor extends GroundUnit{
}
@@ -1,7 +1,7 @@
package io.anuke.mindustry.entities.units.types;
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.units.GroundUnit;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Fortress extends GroundUnit{
@@ -0,0 +1,7 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Ghoul extends FlyingUnit{
}
@@ -0,0 +1,5 @@
package io.anuke.mindustry.entities.type.base;
public class Phantom extends Drone{
}
@@ -0,0 +1,35 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Revenant extends FlyingUnit{
@Override
public void drawWeapons(){
for(int i : Mathf.signs){
float tra = rotation - 90, trY = -getWeapon().getRecoil(this, i > 0) + type.weaponOffsetY;
float w = i > 0 ? -12 : 12;
float wx = x + Angles.trnsx(tra, getWeapon().width * i, trY), wy = y + Angles.trnsy(tra, getWeapon().width * i, trY);
int wi = (i + 1)/2;
Draw.rect(getWeapon().region, wx, wy, w, 12, weaponAngles[wi] - 90);
}
}
@Override
protected void attack(float circleLength){
moveTo(circleLength);
}
@Override
protected void updateRotation(){
if(!Units.invalidateTarget(target, this)){
rotation = Mathf.slerpDelta(rotation, angleTo(target), type.rotatespeed);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), type.baseRotateSpeed);
}
}
}
@@ -0,0 +1,4 @@
package io.anuke.mindustry.entities.type.base;
public class Spirit extends Drone{
}
@@ -0,0 +1,7 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.GroundUnit;
public class Titan extends GroundUnit{
}
@@ -0,0 +1,7 @@
package io.anuke.mindustry.entities.type.base;
import io.anuke.mindustry.entities.type.FlyingUnit;
public class Wraith extends FlyingUnit{
}
@@ -1,249 +0,0 @@
package io.anuke.mindustry.entities.units;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.math.Angles;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.entities.Predict;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.BulletType;
import io.anuke.mindustry.entities.traits.CarriableTrait;
import io.anuke.mindustry.entities.traits.CarryTrait;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
import static io.anuke.mindustry.Vars.world;
public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
protected static Vector2 vec = new Vector2();
protected CarriableTrait carrying;
protected final UnitState
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(target == null){
retarget(() -> {
targetClosest();
if(target == null){
setState(patrol);
return;
}
if(target == null) targetClosestEnemyFlag(BlockFlag.target);
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
if(target == null){
setState(idle);
}
});
}else{
attack(150f);
if((Angles.near(angleTo(target), rotation, 15f) || !getWeapon().getAmmo().keepVelocity) //bombers don't care about rotation
&& dst(target) < Math.max(getWeapon().getAmmo().range(), type.range)){
BulletType ammo = getWeapon().getAmmo();
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
},
patrol = new UnitState(){
public void update(){
retarget(() -> {
targetClosest();
if(target != null){
setState(attack);
}
target = getClosestCore();
});
if(target != null){
circle(60f + Mathf.absin(Time.time() + id * 23525, 70f, 1200f));
}
}
},
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);
}
}
};
@Override
public void move(float x, float y){
moveBy(x, y);
}
@Override
public CarriableTrait getCarry(){
return carrying;
}
@Override
public void setCarry(CarriableTrait unit){
this.carrying = unit;
}
@Override
public float getCarryWeight(){
return type.carryWeight;
}
@Override
public void update(){
super.update();
if(!Net.client()){
updateRotation();
wobble();
}
}
@Override
public void draw(){
Draw.alpha(Draw.getShader() != Shaders.mix ? 1f : hitTime / hitDuration);
Draw.rect(type.name, x, y, rotation - 90);
drawItems();
Draw.alpha(1f);
}
@Override
public void behavior(){
if(health <= health * type.retreatPercent &&
Geometry.findClosest(x, y, world.indexer.getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
if(squad != null){
squad.direction.add(velocity.x / squad.units, velocity.y / squad.units);
velocity.setAngle(Mathf.slerpDelta(velocity.angle(), squad.direction.angle(), 0.3f));
}
}
@Override
public UnitState getStartState(){
return attack;
}
@Override
public float drawSize(){
return 60;
}
protected void wobble(){
if(Net.client()) return;
x += Mathf.sin(Time.time() + id * 999, 25f, 0.08f)*Time.delta();
y += Mathf.cos(Time.time() + id * 999, 25f, 0.08f)*Time.delta();
if(velocity.len() <= 0.05f){
rotation += Mathf.sin(Time.time() + id * 99, 10f, 2.5f)*Time.delta();
}
}
protected void updateRotation(){
rotation = velocity.angle();
}
protected void circle(float circleLength){
circle(circleLength, type.speed);
}
protected void circle(float circleLength, float speed){
if(target == null) return;
vec.set(target.getX() - x, target.getY() - y);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta());
velocity.add(vec);
}
protected void moveTo(float circleLength){
if(target == null) return;
vec.set(target.getX() - x, target.getY() - y);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(type.speed * Time.delta() * length);
if(length < 0) vec.rotate(180f);
velocity.add(vec);
}
protected void attack(float circleLength){
vec.set(target.getX() - x, target.getY() - y);
float ang = angleTo(target);
float diff = Angles.angleDist(ang, rotation);
if(diff > 100f && vec.len() < circleLength){
vec.setAngle(velocity.angle());
}else{
vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f));
}
vec.setLength(type.speed * Time.delta());
velocity.add(vec);
}
}
@@ -1,22 +0,0 @@
package io.anuke.mindustry.entities.units;
import io.anuke.arc.Core;
import io.anuke.arc.math.geom.Vector2;
/**
* 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 Vector2();
public int units;
private long lastUpdated;
protected void update(){
if(Core.graphics.getFrameId() != lastUpdated){
direction.setZero();
lastUpdated = Core.graphics.getFrameId();
}
}
}
@@ -14,6 +14,10 @@ public class StateMachine{
if(next != null) next.entered();
}
public UnitState current(){
return state;
}
public boolean is(UnitState state){
return this.state == state;
}
@@ -1,12 +1,14 @@
package io.anuke.mindustry.entities;
package io.anuke.mindustry.entities.units;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.Bits;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.util.Time;
import io.anuke.arc.util.Tmp;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.entities.traits.Saveable;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.StatusEffect;
@@ -16,18 +18,19 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.content;
/** Class for controlling status effects on an entity.*/
public class StatusController implements Saveable{
public class Statuses implements Saveable{
private static final StatusEntry globalResult = new StatusEntry();
private static final Array<StatusEntry> removals = new Array<>();
private Array<StatusEntry> statuses = new Array<>();
private Bits applied = new Bits(content.getBy(ContentType.status).size);
private float speedMultiplier;
private float damageMultiplier;
private float armorMultiplier;
public void handleApply(Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none) return; //don't apply empty effects
public void handleApply(io.anuke.mindustry.entities.type.Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none || unit.isImmune(effect)) return; //don't apply empty or immune effects
if(statuses.size > 0){
//check for opposite effects
@@ -75,6 +78,7 @@ public class StatusController implements Saveable{
}
public void update(Unit unit){
applied.clear();
speedMultiplier = damageMultiplier = armorMultiplier = 1f;
if(statuses.size == 0) return;
@@ -83,6 +87,7 @@ public class StatusController implements Saveable{
for(StatusEntry entry : statuses){
entry.time = Math.max(entry.time - Time.delta(), 0);
applied.set(entry.effect.id);
if(entry.time <= 0){
Pools.free(entry);
@@ -113,10 +118,7 @@ public class StatusController implements Saveable{
}
public boolean hasEffect(StatusEffect effect){
for(StatusEntry entry : statuses){
if(entry.effect == effect) return true;
}
return false;
return applied.get(effect.id);
}
@Override
@@ -124,7 +126,7 @@ public class StatusController implements Saveable{
stream.writeByte(statuses.size);
for(StatusEntry entry : statuses){
stream.writeByte(entry.effect.id);
stream.writeShort((short) (entry.time * 2));
stream.writeFloat(entry.time);
}
}
@@ -139,7 +141,7 @@ public class StatusController implements Saveable{
byte amount = stream.readByte();
for(int i = 0; i < amount; i++){
byte id = stream.readByte();
float time = stream.readShort() / 2f;
float time = stream.readFloat();
StatusEntry entry = Pools.obtain(StatusEntry.class, StatusEntry::new);
entry.set(content.getByID(ContentType.status, id), time);
statuses.add(entry);
@@ -2,7 +2,8 @@ package io.anuke.mindustry.entities.units;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.type.Item;
import io.anuke.arc.math.Mathf;
@@ -1,104 +0,0 @@
package io.anuke.mindustry.entities.units;
import io.anuke.arc.Core;
import io.anuke.arc.collection.ObjectSet;
import io.anuke.arc.function.Supplier;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Log;
import io.anuke.arc.util.Strings;
import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.content.Weapons;
import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.UnlockableContent;
import io.anuke.mindustry.type.ContentType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Weapon;
import io.anuke.mindustry.ui.ContentDisplay;
public class UnitType extends UnlockableContent{
protected final Supplier<? extends BaseUnit> constructor;
public final String name;
public final String description;
public float health = 60;
public float hitsize = 7f;
public float hitsizeTile = 4f;
public float speed = 0.4f;
public float range = 0;
public float rotatespeed = 0.2f;
public float baseRotateSpeed = 0.1f;
public float mass = 1f;
public boolean isFlying;
public boolean targetAir = true;
public float drag = 0.1f;
public float maxVelocity = 5f;
public float retreatPercent = 0.2f;
public float armor = 0f;
public float carryWeight = 1f;
public int itemCapacity = 30;
public ObjectSet<Item> toMine = ObjectSet.with(Items.lead, Items.copper);
public float buildPower = 0.3f, minePower = 0.7f;
public Weapon weapon = Weapons.blaster;
public float weaponOffsetX, weaponOffsetY;
public Color trailColor = Color.valueOf("ffa665");
public TextureRegion iconRegion, legRegion, baseRegion, region;
public <T extends BaseUnit> UnitType(String name, Class<T> type, Supplier<T> mainConstructor){
this.name = name;
this.constructor = mainConstructor;
this.description = Core.bundle.getOrNull("unit." + name + ".description");
TypeTrait.registerType(type, mainConstructor);
if(!Core.bundle.has("unit." + this.name + ".name")){
Log.err("Warning: unit '" + name + "' is missing a localized name. Add the follow to bundle.properties:");
Log.err("unit." + this.name + ".name=" + Strings.capitalize(name.replace('-', '_')));
}
}
@Override
public void displayInfo(Table table){
ContentDisplay.displayUnit(table, this);
}
@Override
public String localizedName(){
return Core.bundle.get("unit." + name + ".name");
}
@Override
public TextureRegion getContentIcon(){
return iconRegion;
}
@Override
public void load(){
iconRegion = Core.atlas.find("unit-icon-" + name);
region = Core.atlas.find(name);
if(!isFlying){
legRegion = Core.atlas.find(name + "-leg");
baseRegion = Core.atlas.find(name + "-base");
}
}
@Override
public ContentType getContentType(){
return ContentType.unit;
}
@Override
public String getContentName(){
return name;
}
public BaseUnit create(Team team){
BaseUnit unit = constructor.get();
unit.init(this, team);
return unit;
}
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.GroundUnit;
public class Dagger extends GroundUnit{
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.FlyingUnit;
public class Ghoul extends FlyingUnit{
}
@@ -1,5 +0,0 @@
package io.anuke.mindustry.entities.units.types;
public class Phantom extends Drone{
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.FlyingUnit;
public class Revenant extends FlyingUnit{
}
@@ -1,4 +0,0 @@
package io.anuke.mindustry.entities.units.types;
public class Spirit extends Drone{
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.GroundUnit;
public class Titan extends GroundUnit{
}
@@ -1,7 +0,0 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.entities.units.FlyingUnit;
public class Wraith extends FlyingUnit{
}