Merge branch '6.0' into crater

# Conflicts:
#	core/assets/icons/icons.properties
#	core/assets/sprites/block_colors.png
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
#	core/assets/sprites/sprites3.png
#	core/assets/sprites/sprites5.png
#	core/src/mindustry/content/Blocks.java
#	core/src/mindustry/ui/fragments/PlayerListFragment.java
#	core/src/mindustry/world/BlockStorage.java
This commit is contained in:
Patrick 'Quezler' Mounier
2020-04-16 12:39:52 +02:00
610 changed files with 27167 additions and 25697 deletions

View File

@@ -0,0 +1,37 @@
package mindustry.entities;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
class AllDefs{
@GroupDef(Entityc.class)
class all{
}
@GroupDef(value = Playerc.class, mapping = true)
class player{
}
@GroupDef(value = Bulletc.class, spatial = true, collide = {unit.class})
class bullet{
}
@GroupDef(value = Unitc.class, spatial = true, collide = {unit.class}, mapping = true)
class unit{
}
@GroupDef(Tilec.class)
class tile{
}
@GroupDef(value = Syncc.class, mapping = true)
class sync{
}
}

View File

@@ -1,17 +1,14 @@
package mindustry.entities;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.Effects.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -33,12 +30,11 @@ 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.derelict, Pal.power, 3,
x, y, Mathf.random(360f), branches + Mathf.range(2)));
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1, 1));
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, -1f, Mathf.random(360f), 1, 1));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
@@ -47,21 +43,21 @@ public class Damage{
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Effects.effect(Fx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius));
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
}
if(explosiveness > 15f){
Effects.effect(Fx.shockwave, x, y);
Fx.shockwave.at(x, y);
}
if(explosiveness > 30f){
Effects.effect(Fx.bigShockwave, x, y);
Fx.bigShockwave.at(x, y);
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effects.effect(Fx.dynamicExplosion, x, y, radius / 8f);
Fx.dynamicExplosion.at(x, y, radius / 8f);
}
public static void createIncend(float x, float y, float range, int amount){
@@ -70,12 +66,12 @@ public class Damage{
float cy = y + Mathf.range(range);
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Fire.create(tile);
Fires.create(tile);
}
}
}
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
public static void collideLine(Bulletc hitter, Team team, Effect effect, float x, float y, float angle, float length){
collideLine(hitter, team, effect, x, y, angle, length, false);
}
@@ -83,15 +79,15 @@ public class Damage{
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
public static void collideLine(Bulletc hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
collidedBlocks.clear();
tr.trns(angle, length);
Intc2 collider = (cx, cy) -> {
Tile tile = world.ltile(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.entity != null && tile.getTeamID() != team.id && tile.entity.collide(hitter)){
tile.entity.collision(hitter);
Tilec tile = world.ent(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
tile.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy());
hitter.type().hit(hitter, tile.x(), tile.y());
}
};
@@ -125,7 +121,7 @@ public class Damage{
rect.width += expand * 2;
rect.height += expand * 2;
Cons<Unit> cons = e -> {
Cons<Unitc> cons = e -> {
e.hitbox(hitrect);
Rect other = hitrect;
other.y -= expand;
@@ -136,7 +132,7 @@ public class Damage{
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, other);
if(vec != null){
Effects.effect(effect, vec.x, vec.y);
effect.at(vec.x, vec.y);
e.collision(hitter, vec.x, vec.y);
hitter.collision(e, vec.x, vec.y);
}
@@ -146,8 +142,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, Boolf<Unit> predicate, Cons<Unit> acceptor){
Cons<Unit> cons = entity -> {
public static void damageUnits(Team team, float x, float y, float size, float damage, Boolf<Unitc> predicate, Cons<Unitc> acceptor){
Cons<Unitc> cons = entity -> {
if(!predicate.get(entity)) return;
entity.hitbox(hitrect);
@@ -178,15 +174,15 @@ 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, boolean complete){
Cons<Unit> cons = entity -> {
if(entity.getTeam() == team || entity.dst(x, y) > radius){
Cons<Unitc> cons = entity -> {
if(entity.team() == team || entity.dst(x, y) > radius){
return;
}
float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage);
float amount = calculateDamage(x, y, entity.getX(), entity.getY(), 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.mass()));
float dst = tr.set(entity.getX() - x, entity.getY() - y).len();
entity.vel().add(tr.setLength((1f - dst / radius) * 2f / entity.mass()));
if(complete && damage >= 9999999f && entity == player){
Events.fire(Trigger.exclusionDeath);
@@ -231,15 +227,15 @@ public class Damage{
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
bits.set(bitOffset + x, bitOffset + y);
Tile tile = world.ltile(startx + x, starty + y);
Tilec tile = world.ent(startx + x, starty + y);
if(scaledDamage <= 0 || tile == null) continue;
//apply damage to entity if needed
if(tile.entity != null && tile.getTeam() != team){
int health = (int)tile.entity.health;
if(tile.entity.health > 0){
tile.entity.damage(scaledDamage);
if(tile.team() != team){
int health = (int)tile.health();
if(tile.health() > 0){
tile.damage(scaledDamage);
scaledDamage -= health;
if(scaledDamage <= 0) continue;
@@ -259,7 +255,7 @@ public class Damage{
for(int dx = -trad; dx <= trad; dx++){
for(int dy = -trad; dy <= trad; dy++){
Tile tile = world.tile(Math.round(x / tilesize) + dx, Math.round(y / tilesize) + dy);
if(tile != null && tile.entity != null && (team == null ||team.isEnemy(tile.getTeam())) && Mathf.dst(dx, dy) <= trad){
if(tile != null && tile.entity != null && (team == null ||team.isEnemy(tile.team())) && Mathf.dst(dx, dy) <= trad){
tile.entity.damage(damage);
}
}

View File

@@ -0,0 +1,118 @@
package mindustry.entities;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
public class Effect{
private static final EffectContainer container = new EffectContainer();
private static int lastid = 0;
public final int id;
public final Cons<EffectContainer> renderer;
public final float lifetime;
/** Clip size. */
public float size;
public boolean ground;
public float groundDuration;
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
this.id = lastid++;
this.lifetime = life;
this.renderer = renderer;
this.size = clipsize;
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
}
public Effect ground(){
ground = true;
return this;
}
public Effect ground(float duration){
ground = true;
this.groundDuration = duration;
return this;
}
public void at(Position pos){
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
}
public void at(Position pos, float rotation){
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
}
public void at(float x, float y){
Effects.create(this, x, y, 0, Color.white, null);
}
public void at(float x, float y, float rotation){
Effects.create(this, x, y, rotation, Color.white, null);
}
public void at(float x, float y, float rotation, Color color){
Effects.create(this, x, y, rotation, color, null);
}
public void at(float x, float y, Color color){
Effects.create(this, x, y, 0, color, null);
}
public void at(float x, float y, float rotation, Color color, Object data){
Effects.create(this, x, y, rotation, color, data);
}
public void at(float x, float y, float rotation, Object data){
Effects.create(this, x, y, rotation, Color.white, data);
}
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
container.set(id, color, life, lifetime, rotation, x, y, data);
renderer.get(container);
Draw.reset();
}
public static class EffectContainer implements Scaled{
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 <T> T data(){
return (T)data;
}
public void scaled(float lifetime, Cons<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.get(innerContainer);
}
}
@Override
public float fin(){
return time / lifetime;
}
}
}

View File

@@ -1,90 +1,25 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.pooling.*;
import mindustry.entities.type.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
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 = 10000f;
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);
Draw.reset();
}
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 final float shakeFalloff = 10000f;
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);
if(!headless){
renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
@@ -100,68 +35,58 @@ public class Effects{
shake(intensity, duration, loc.getX(), loc.getY());
}
public interface ScreenshakeProvider{
void accept(float intensity, float duration);
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
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 Scaled{
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, Cons<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.get(innerContainer);
if(view.overlaps(pos)){
Effectc entity = effect.ground ? GroundEffectEntity.create() : StandardEffectEntity.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
entity.set(x, y);
entity.color().set(color);
if(data instanceof Posc) entity.parent((Posc)data);
entity.add();
}
}
@Override
public float fin(){
return time / lifetime;
}
}
public interface EffectProvider{
void createEffect(Effect effect, Color color, float x, float y, float rotation, Object data);
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Decalc decal = DecalEntity.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public interface EffectRenderer{
void render(EffectContainer effect);
public static void scorch(float x, float y, int size){
if(headless) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
}

View File

@@ -1,33 +0,0 @@
package mindustry.entities;
import arc.struct.*;
import mindustry.entities.traits.*;
/** Simple container for managing entity groups.*/
public class Entities{
private final Array<EntityGroup<?>> groupArray = new Array<>();
public void clear(){
for(EntityGroup group : groupArray){
group.clear();
}
}
public EntityGroup<?> get(int id){
return groupArray.get(id);
}
public Array<EntityGroup<?>> all(){
return groupArray;
}
public <T extends Entity> EntityGroup<T> add(Class<T> type){
return add(type, true);
}
public <T extends Entity> EntityGroup<T> add(Class<T> type, boolean useTree){
EntityGroup<T> group = new EntityGroup<>(groupArray.size, type, useTree);
groupArray.add(group);
return group;
}
}

View File

@@ -1,14 +1,12 @@
package mindustry.entities;
import arc.struct.Array;
import arc.math.Mathf;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.Entity;
import mindustry.entities.traits.SolidTrait;
import mindustry.world.Tile;
import arc.struct.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.world;
import static mindustry.Vars.*;
public class EntityCollisions{
//range for tile collision scanning
@@ -24,15 +22,19 @@ public class EntityCollisions{
private Rect r2 = new Rect();
//entity collisions
private Array<SolidTrait> arrOut = new Array<>();
private Array<Hitboxc> arrOut = new Array<>();
public void move(SolidTrait entity, float deltax, float deltay){
public void move(Hitboxc entity, float deltax, float deltay){
move(entity, deltax, deltay, EntityCollisions::solid);
}
public void move(Hitboxc entity, float deltax, float deltay, SolidPred solidCheck){
boolean movedx = false;
while(Math.abs(deltax) > 0 || !movedx){
movedx = true;
moveDelta(entity, Math.min(Math.abs(deltax), seg) * Mathf.sign(deltax), 0, true);
moveDelta(entity, Math.min(Math.abs(deltax), seg) * Mathf.sign(deltax), 0, true, solidCheck);
if(Math.abs(deltax) >= seg){
deltax -= seg * Mathf.sign(deltax);
@@ -45,7 +47,7 @@ public class EntityCollisions{
while(Math.abs(deltay) > 0 || !movedy){
movedy = true;
moveDelta(entity, 0, Math.min(Math.abs(deltay), seg) * Mathf.sign(deltay), false);
moveDelta(entity, 0, Math.min(Math.abs(deltay), seg) * Mathf.sign(deltay), false, solidCheck);
if(Math.abs(deltay) >= seg){
deltay -= seg * Mathf.sign(deltay);
@@ -55,33 +57,30 @@ public class EntityCollisions{
}
}
public void moveDelta(SolidTrait entity, float deltax, float deltay, boolean x){
Rect rect = r1;
entity.hitboxTile(rect);
public void moveDelta(Hitboxc entity, float deltax, float deltay, boolean x, SolidPred solidCheck){
entity.hitboxTile(r1);
entity.hitboxTile(r2);
rect.x += deltax;
rect.y += deltay;
r1.x += deltax;
r1.y += deltay;
int tilex = Math.round((rect.x + rect.width / 2) / tilesize), tiley = Math.round((rect.y + rect.height / 2) / tilesize);
int tilex = Math.round((r1.x + r1.width / 2) / tilesize), tiley = Math.round((r1.y + r1.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(solid(wx, wy) && entity.collidesGrid(wx, wy)){
if(solidCheck.solid(wx, wy)){
tmp.setSize(tilesize).setCenter(wx * tilesize, wy * tilesize);
if(tmp.overlaps(rect)){
Vec2 v = Geometry.overlap(rect, tmp, x);
rect.x += v.x;
rect.y += v.y;
if(tmp.overlaps(r1)){
Vec2 v = Geometry.overlap(r1, tmp, x);
if(x) r1.x += v.x;
if(!x) r1.y += v.y;
}
}
}
}
entity.setX(entity.getX() + rect.x - r2.x);
entity.setY(entity.getY() + rect.y - r2.y);
entity.trns(r1.x - r2.x, r1.y - r2.y);
}
public boolean overlapsTile(Rect rect){
@@ -108,44 +107,43 @@ public class EntityCollisions{
}
@SuppressWarnings("unchecked")
public <T extends Entity> void updatePhysics(EntityGroup<T> group){
public <T extends Hitboxc> void updatePhysics(EntityGroup<T> group){
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);
}
}
group.each(s -> {
s.updateLastPosition();
tree.insert(s);
});
}
private static boolean solid(int x, int y){
public static boolean waterSolid(int x, int y){
Tile tile = world.tile(x, y);
return tile != null && (tile.solid() || !tile.floor().isLiquid);
}
public static boolean solid(int x, int y){
Tile tile = world.tile(x, y);
return tile != null && tile.solid();
}
private void checkCollide(Entity entity, Entity other){
SolidTrait a = (SolidTrait)entity;
SolidTrait b = (SolidTrait)other;
private void checkCollide(Hitboxc a, Hitboxc b){
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());
r1.x += (a.lastX() - a.getX());
r1.y += (a.lastY() - a.getY());
r2.x += (b.lastX() - b.getX());
r2.y += (b.lastY() - 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;
float vax = a.getX() - a.lastX();
float vay = a.getY() - a.lastY();
float vbx = b.getX() - b.lastX();
float vby = b.getY() - b.lastY();
if(a != b && a.collides(b) && b.collides(a)){
if(a != b && a.collides(b)){
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);
@@ -207,17 +205,12 @@ public class EntityCollisions{
}
@SuppressWarnings("unchecked")
public void collideGroups(EntityGroup<?> groupa, EntityGroup<?> groupb){
for(Entity entity : groupa.all()){
if(!(entity instanceof SolidTrait))
continue;
SolidTrait solid = (SolidTrait)entity;
public void collideGroups(EntityGroup<? extends Hitboxc> groupa, EntityGroup<? extends Hitboxc> groupb){
groupa.each(solid -> {
solid.hitbox(r1);
r1.x += (solid.lastPosition().x - solid.getX());
r1.y += (solid.lastPosition().y - solid.getY());
r1.x += (solid.lastX() - solid.getX());
r1.y += (solid.lastY() - solid.getY());
solid.hitbox(r2);
r2.merge(r1);
@@ -225,12 +218,18 @@ public class EntityCollisions{
arrOut.clear();
groupb.tree().getIntersect(arrOut, r2);
for(SolidTrait sc : arrOut){
for(Hitboxc sc : arrOut){
sc.hitbox(r1);
if(r2.overlaps(r1)){
checkCollide(entity, sc);
checkCollide(solid, sc);
//break out of loop when this object hits something
if(!solid.isAdded()) return;
}
}
}
});
}
public interface SolidPred{
boolean solid(int x, int y);
}
}

View File

@@ -1,11 +1,10 @@
package mindustry.entities;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import arc.struct.*;
import mindustry.gen.*;
import java.util.*;
@@ -13,128 +12,82 @@ import static mindustry.Vars.collisions;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
public class EntityGroup<T extends Entity> implements Iterable<T>{
private final boolean useTree;
private final int id;
private final Class<T> type;
private final Array<T> entityArray = new Array<>(false, 32);
private final Array<T> entitiesToRemove = new Array<>(false, 32);
private final Array<T> entitiesToAdd = new Array<>(false, 32);
public class EntityGroup<T extends Entityc> implements Iterable<T>{
private static int lastId = 0;
private final Array<T> array;
private final Array<T> intersectArray = new Array<>();
private final Rect viewport = new Rect();
private final Rect intersectRect = new Rect();
private IntMap<T> map;
private QuadTree tree;
private Cons<T> removeListener;
private Cons<T> addListener;
private boolean clearing;
private final Rect viewport = new Rect();
private int count = 0;
private int index;
public EntityGroup(int id, Class<T> type, boolean useTree){
this.useTree = useTree;
this.id = id;
this.type = type;
public static int nextId(){
return lastId++;
}
if(useTree){
public EntityGroup(Class<T> type, boolean spatial, boolean mapping){
array = new Array<>(false, 32, type);
if(spatial){
tree = new QuadTree<>(new Rect(0, 0, 0, 0));
}
if(mapping){
map = new IntMap<>();
}
}
public void sort(Comparator<? super T> comp){
array.sort(comp);
}
public void collide(EntityGroup<? extends Hitboxc> other){
collisions.collideGroups((EntityGroup<? extends Hitboxc>)this, other);
}
public void updatePhysics(){
collisions.updatePhysics((EntityGroup<? extends Hitboxc>)this);
}
public void update(){
updateEvents();
each(Entityc::update);
}
if(useTree()){
collisions.updatePhysics(this);
}
for(Entity e : all()){
e.update();
public void each(Cons<T> cons){
for(index = 0; index < array.size; index++){
cons.get(array.items[index]);
}
}
public int countInBounds(){
count = 0;
draw(e -> true, e -> count++);
return count;
public void each(Boolf<T> filter, Cons<T> cons){
for(index = 0; index < array.size; index++){
if(filter.get(array.items[index])) cons.get(array.items[index]);
}
}
public void draw(){
draw(e -> true);
}
public void draw(Cons<T> cons){
Core.camera.bounds(viewport);
public void draw(Boolf<T> toDraw){
draw(toDraw, t -> ((DrawTrait)t).draw());
}
public void draw(Boolf<T> toDraw, Cons<T> cons){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
for(Entity e : all()){
if(!(e instanceof DrawTrait) || !toDraw.get((T)e) || !e.isAdded()) continue;
DrawTrait draw = (DrawTrait)e;
if(viewport.overlaps(draw.getX() - draw.drawSize()/2f, draw.getY() - draw.drawSize()/2f, draw.drawSize(), draw.drawSize())){
cons.get((T)e);
each(e -> {
Drawc draw = (Drawc)e;
if(viewport.overlaps(draw.x() - draw.clipSize()/2f, draw.y() - draw.clipSize()/2f, draw.clipSize(), draw.clipSize())){
cons.get(e);
}
}
});
}
public boolean useTree(){
return useTree;
}
public void setRemoveListener(Cons<T> removeListener){
this.removeListener = removeListener;
}
public void setAddListener(Cons<T> addListener){
this.addListener = addListener;
}
public EntityGroup<T> enableMapping(){
map = new IntMap<>();
return this;
return map != null;
}
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.remove(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);
@@ -145,123 +98,95 @@ public class EntityGroup<T extends Entity> implements Iterable<T>{
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.remove(check, true);
if(removeListener != null){
removeListener.get(check);
}
break;
}
}
}
}
@SuppressWarnings("unchecked")
public void intersect(float x, float y, float width, float height, Cons<? super T> out){
//don't waste time for empty groups
if(isEmpty()) return;
tree().getIntersect(out, x, y, width, height);
tree.getIntersect(out, x, y, width, height);
}
@SuppressWarnings("unchecked")
public Array<T> intersect(float x, float y, float width, float height){
intersectArray.clear();
//don't waste time for empty groups
if(isEmpty()) return intersectArray;
tree().getIntersect(intersectArray, intersectRect.set(x, y, width, height));
tree.getIntersect(intersectArray, intersectRect.set(x, y, width, height));
return intersectArray;
}
public QuadTree tree(){
if(!useTree) throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
if(tree == null) throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
return tree;
}
/** Resizes the internal quadtree, if it is enabled.*/
public void resize(float x, float y, float w, float h){
if(useTree){
if(tree != null){
tree = new QuadTree<>(new Rect(x, y, w, h));
}
}
public boolean isEmpty(){
return entityArray.size == 0;
return array.size == 0;
}
public T index(int i){
return array.get(i);
}
public int size(){
return entityArray.size;
return array.size;
}
public boolean contains(Boolf<T> pred){
return array.contains(pred);
}
public int count(Boolf<T> pred){
int count = 0;
for(int i = 0; i < entityArray.size; i++){
if(pred.get(entityArray.get(i))) count++;
}
return count;
return array.count(pred);
}
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);
array.add(type);
if(mappingEnabled()){
map.put(type.getID(), type);
}
if(addListener != null){
addListener.get(type);
map.put(type.id(), type);
}
}
public void remove(T type){
if(clearing) return;
if(type == null) throw new RuntimeException("Cannot remove a null entity!");
type.setGroup(null);
entitiesToRemove.add(type);
int idx = array.indexOf(type, true);
if(idx != -1){
array.remove(idx);
if(removeListener != null){
removeListener.get(type);
//fix iteration index when removing
if(index >= idx){
index --;
}
}
}
public void clear(){
for(T entity : entityArray){
entity.removed();
entity.setGroup(null);
}
clearing = true;
for(T entity : entitiesToAdd)
entity.setGroup(null);
for(T entity : entitiesToRemove)
entity.setGroup(null);
entitiesToAdd.clear();
entitiesToRemove.clear();
entityArray.clear();
array.each(Entityc::remove);
array.clear();
if(map != null)
map.clear();
clearing = false;
}
public T find(Boolf<T> pred){
for(int i = 0; i < entityArray.size; i++){
if(pred.get(entityArray.get(i))) return entityArray.get(i);
}
return null;
}
/** Returns the array for iteration. */
public Array<T> all(){
return entityArray;
return array.find(pred);
}
@Override
public Iterator<T> iterator(){
return entityArray.iterator();
return array.iterator();
}
}

View File

@@ -0,0 +1,70 @@
package mindustry.entities;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Fires{
private static final float baseLifetime = 1000f;
private static final IntMap<Firec> map = new IntMap<>();
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
Firec fire = map.get(tile.pos());
if(fire == null){
fire = FireEntity.create();
fire.tile(tile);
fire.lifetime(baseLifetime);
fire.set(tile.worldx(), tile.worldy());
fire.add();
map.put(tile.pos(), fire);
}else{
fire.lifetime(baseLifetime);
fire.time(0f);
}
}
public static Firec get(int x, int y){
return map.get(Point2.pack(x, y));
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Point2.pack(x, y))){
return false;
}
Firec fire = map.get(Point2.pack(x, y));
return fire.isAdded() && fire.fin() < 1f && fire.tile() != null && fire.tile().x == x && fire.tile().y == y;
}
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
Firec fire = map.get(tile.pos());
fire.time(fire.time() + intensity * Time.delta());
Fx.steam.at(fire);
if(fire.time() >= fire.lifetime()){
Events.fire(Trigger.fireExtinguish);
}
}
}
public static void remove(Tile tile){
map.remove(tile.pos());
}
public static void register(Firec fire){
map.put(fire.tile().pos(), fire);
}
}

View File

@@ -0,0 +1,86 @@
package mindustry.entities;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
//TODO move into a different class
public class Lightning{
private static final Rand random = new Rand();
private static final Rect rect = new Rect();
private static final Array<Unitc> entities = new Array<>();
private static final IntSet hit = new IntSet();
private static final int maxChain = 8;
private static final float hitRange = 30f;
private static boolean bhit = false;
private static int lastSeed = 0;
/** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(lastSeed++, team, color, damage, x, y, targetAngle, length);
}
//TODO remote method
//@Remote(called = Loc.server, unreliable = true)
private static void createLightingInternal(int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
random.setSeed(seed);
hit.clear();
Array<Vec2> lines = new Array<>();
bhit = false;
for(int i = 0; i < length / 2; i++){
Bullets.damageLightning.create(null, team, x, y, 0f, damage, 1f, 1f, null);
lines.add(new Vec2(x + Mathf.range(3f), y + Mathf.range(3f)));
if(lines.size > 1){
bhit = false;
Vec2 from = lines.get(lines.size - 2);
Vec2 to = lines.get(lines.size - 1);
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.tile(wx, wy);
if(tile != null && tile.block().insulated){
bhit = true;
//snap it instead of removing
lines.get(lines.size -1).set(wx * tilesize, wy * tilesize);
return true;
}
return false;
});
if(bhit) break;
}
rect.setSize(hitRange).setCenter(x, y);
entities.clear();
if(hit.size < maxChain){
Units.nearbyEnemies(team, rect, u -> {
if(!hit.contains(u.id())){
entities.add(u);
}
});
}
Unitc furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
hit.add(furthest.id());
x = furthest.x();
y = furthest.y();
}else{
rotation += random.range(20f);
x += Angles.trnsx(rotation, hitRange / 2f);
y += Angles.trnsy(rotation, hitRange / 2f);
}
}
Fx.lightning.at(x, y, rotation, color, lines);
}
}

View File

@@ -3,7 +3,7 @@ package mindustry.entities;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.traits.*;
import mindustry.gen.*;
/**
* Class for predicting shoot angles based on velocities of targets.
@@ -51,11 +51,24 @@ public class Predict{
return sol;
}
public static Vec2 intercept(Position src, Hitboxc dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.deltaX(), dst.deltaY(), v);
}
public static Vec2 intercept(Position src, Position dst, float v){
float ddx = 0, ddy = 0;
if(dst instanceof Hitboxc){
ddx = ((Hitboxc)dst).deltaX();
ddy = ((Hitboxc)dst).deltaY();
}
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, v);
}
/**
* See {@link #intercept(float, float, float, float, float, float, float)}.
*/
public static Vec2 intercept(TargetTrait src, TargetTrait dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getTargetVelocityX() - src.getTargetVelocityX()/(2f*Time.delta()), dst.getTargetVelocityY() - src.getTargetVelocityY()/(2f*Time.delta()), v);
public static Vec2 intercept(Hitboxc src, Hitboxc dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.deltaX() - src.deltaX()/(2f*Time.delta()), dst.deltaY() - src.deltaX()/(2f*Time.delta()), v);
}
private static Vec2 quad(float a, float b, float c){

View File

@@ -0,0 +1,109 @@
package mindustry.entities;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
public class Puddles{
private static final IntMap<Puddlec> map = new IntMap<>();
public static final float maxLiquid = 70f;
/** Deposists a Puddlec between tile and source. */
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
}
/** Deposists a Puddlec at a tile. */
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/** Returns the Puddlec on the specified tile. May return null. */
public static Puddlec get(Tile tile){
return map.get(tile.pos());
}
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
if(tile == null) return;
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
reactPuddle(tile.floor().liquidDrop, liquid, amount, tile,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
Puddlec p = map.get(tile.pos());
if(generation == 0 && p != null && p.lastRipple() <= Time.time() - 40f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, tile.floor().liquidDrop.color);
p.lastRipple(Time.time());
}
return;
}
Puddlec p = map.get(tile.pos());
if(p == null){
Puddlec puddle = PuddleEntity.create();
puddle.tile(tile);
puddle.liquid(liquid);
puddle.amount(amount);
puddle.generation(generation);
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.pos(), puddle);
}else if(p.liquid() == liquid){
p.accepting(Math.max(amount, p.accepting()));
if(generation == 0 && p.lastRipple() <= Time.time() - 40f && p.amount() >= maxLiquid / 2f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, p.liquid().color);
p.lastRipple(Time.time());
}
}else{
p.amount(p.amount() + reactPuddle(p.liquid(), liquid, amount, p.tile(), p.x(), p.y()));
}
}
public static void remove(Tile tile){
if(tile == null) return;
map.remove(tile.pos());
}
public static void register(Puddlec puddle){
map.put(puddle.tile().pos(), puddle);
}
/** Reacts two liquids together at a location. */
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fires.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddlec
if(Mathf.chance(0.5f * amount)){
Fx.steam.at(x, y);
}
return -0.1f * amount;
}else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold Puddlec
if(Mathf.chance(0.8f * amount)){
Fx.steam.at(x, y);
}
return -0.4f * amount;
}
return 0f;
}
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
}

View File

@@ -1,11 +1,9 @@
package mindustry.entities;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -13,13 +11,13 @@ import static mindustry.Vars.*;
/** Utility class for unit and team interactions.*/
public class Units{
private static Rect hitrect = new Rect();
private static Unit result;
private static Unitc result;
private static float cdist;
private static boolean boolResult;
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
public static boolean canInteract(Player player, Tile tile){
return player == null || tile == null || tile.interactable(player.getTeam());
public static boolean canInteract(Playerc player, Tilec tile){
return player == null || tile == null || tile.interactable(player.team());
}
/**
@@ -31,18 +29,18 @@ public class Units{
* @param range The maximum distance from the target X/Y the targeter can be for it to be valid
* @return whether the target is invalid
*/
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || target.getTeam() == team || !target.isValid();
public static boolean invalidateTarget(Posc target, Team team, float x, float y, float range){
return target == null || !target.isAdded() || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || (target instanceof Teamc && ((Teamc)target).team() == team) || (target instanceof Healthc && !((Healthc)target).isValid());
}
/** See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} */
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){
/** See {@link #invalidateTarget(Posc, Team, float, float, float)} */
public static boolean invalidateTarget(Posc target, Team team, float x, float y){
return invalidateTarget(target, team, x, y, Float.MAX_VALUE);
}
/** See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} */
public static boolean invalidateTarget(TargetTrait target, Unit targeter){
return invalidateTarget(target, targeter.getTeam(), targeter.x, targeter.y, targeter.getWeapon().bullet.range());
/** See {@link #invalidateTarget(Posc, Team, float, float, float)} */
public static boolean invalidateTarget(Teamc target, Unitc targeter, float range){
return invalidateTarget(target, targeter.team(), targeter.x(), targeter.y(), range);
}
/** Returns whether there are any entities on this tile. */
@@ -56,7 +54,7 @@ public class Units{
nearby(x, y, width, height, unit -> {
if(boolResult) return;
if(!unit.isFlying()){
if(unit.isGrounded()){
unit.hitbox(hitrect);
if(hitrect.overlaps(x, y, width, height)){
@@ -69,38 +67,38 @@ public class Units{
}
/** Returns the neareset damaged tile. */
public static TileEntity findDamagedTile(Team team, float x, float y){
public static Tilec findDamagedTile(Team team, float x, float y){
Tile tile = Geometry.findClosest(x, y, indexer.getDamaged(team));
return tile == null ? null : tile.entity;
}
/** Returns the neareset ally tile in a range. */
public static TileEntity findAllyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public static Tilec findAllyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public static Tilec findEnemyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
if(team == Team.derelict) return null;
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unit::isValid);
public static Teamc closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unitc::isValid);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred){
public static Teamc closestTarget(Team team, float x, float y, float range, Boolf<Unitc> unitPred){
return closestTarget(team, x, y, range, unitPred, t -> true);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Tile> tilePred){
public static Teamc closestTarget(Team team, float x, float y, float range, Boolf<Unitc> unitPred, Boolf<Tilec> tilePred){
if(team == Team.derelict) return null;
Unit unit = closestEnemy(team, x, y, range, unitPred);
Unitc unit = closestEnemy(team, x, y, range, unitPred);
if(unit != null){
return unit;
}else{
@@ -109,16 +107,16 @@ public class Units{
}
/** Returns the closest enemy of this team. Filter by predicate. */
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
public static Unitc closestEnemy(Team team, float x, float y, float range, Boolf<Unitc> predicate){
if(team == Team.derelict) return null;
result = null;
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.isDead() || !predicate.get(e)) return;
if(e.dead() || !predicate.get(e)) return;
float dst2 = Mathf.dst2(e.x, e.y, x, y);
float dst2 = e.dst2(x, y);
if(dst2 < range*range && (result == null || dst2 < cdist)){
result = e;
cdist = dst2;
@@ -129,14 +127,14 @@ public class Units{
}
/** Returns the closest ally of this team. Filter by predicate. */
public static Unit closest(Team team, float x, float y, float range, Boolf<Unit> predicate){
public static Unitc closest(Team team, float x, float y, float range, Boolf<Unitc> predicate){
result = null;
cdist = 0f;
nearby(team, x, y, range, e -> {
if(!predicate.get(e)) return;
float dist = Mathf.dst2(e.x, e.y, x, y);
float dist = e.dst2(x, y);
if(result == null || dist < cdist){
result = e;
cdist = dist;
@@ -147,73 +145,45 @@ public class Units{
}
/** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, u -> {
if(u.getTeam() == team){
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, u -> {
if(u.team() == team){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(player.getTeam() == team){
cons.get(player);
}
});
}
/** Iterates over all units in a circle around this position. */
public static void nearby(Team team, float x, float y, float radius, Cons<Unit> cons){
unitGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
cons.get(unit);
}
});
playerGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
public static void nearby(Team team, float x, float y, float radius, Cons<Unitc> cons){
Groups.unit.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.team() == team && unit.withinDst(x, y, radius)){
cons.get(unit);
}
});
}
/** Iterates over all units in a rectangle. */
public static void nearby(float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, cons);
playerGroup.intersect(x, y, width, height, cons);
public static void nearby(float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, cons);
}
/** Iterates over all units in a rectangle. */
public static void nearby(Rect rect, Cons<Unit> cons){
public static void nearby(Rect rect, Cons<Unitc> cons){
nearby(rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, u -> {
if(team.isEnemy(u.getTeam())){
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, u -> {
if(team.isEnemy(u.team())){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(team.isEnemy(player.getTeam())){
cons.get(player);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rect rect, Cons<Unit> cons){
public static void nearbyEnemies(Team team, Rect rect, Cons<Unitc> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units. */
public static void all(Cons<Unit> cons){
unitGroup.all().each(cons);
playerGroup.all().each(cons);
}
public static void each(Team team, Cons<BaseUnit> cons){
unitGroup.all().each(t -> t.getTeam() == team, cons);
}
}

View File

@@ -3,8 +3,6 @@ package mindustry.entities.bullet;
import arc.graphics.g2d.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
//TODO scale velocity depending on fslope()
@@ -25,25 +23,25 @@ public class ArtilleryBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(b.timer.get(0, 3 + b.fslope() * 2f)){
Effects.effect(trailEffect, backColor, b.x, b.y, b.fslope() * 4f);
if(b.timer(0, 3 + b.fslope() * 2f)){
trailEffect.at(b.x(), b.y(), b.fslope() * 4f, backColor);
}
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
float baseScale = 0.7f;
float scale = (baseScale + b.fslope() * (1f - baseScale));
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, bulletWidth * scale, height * scale, b.rot() - 90);
Draw.rect(backRegion, b.x(), b.y(), bulletWidth * scale, height * scale, b.rotation() - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, bulletWidth * scale, height * scale, b.rot() - 90);
Draw.rect(frontRegion, b.x(), b.y(), bulletWidth * scale, height * scale, b.rotation() - 90);
Draw.color();
}
}

View File

@@ -4,7 +4,7 @@ import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
/** An extended BulletType for most ammo-based bullets shot from turrets and units. */
@@ -34,13 +34,13 @@ public class BasicBulletType extends BulletType{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, bulletWidth, height, b.rot() - 90);
Draw.rect(backRegion, b.x(), b.y(), bulletWidth, height, b.rotation() - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, bulletWidth, height, b.rot() - 90);
Draw.rect(frontRegion, b.x(), b.y(), bulletWidth, height, b.rotation() - 90);
Draw.color();
}
}

View File

@@ -2,17 +2,16 @@ package mindustry.entities.bullet;
import arc.audio.*;
import arc.math.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
public abstract class BulletType extends Content{
public float lifetime;
@@ -56,8 +55,8 @@ public abstract class BulletType extends Content{
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */
public boolean collidesTeam = false;
/** Whether this bullet type collides with air units. */
public boolean collidesAir = true;
/** Whether this bullet type collides with air/ground units. */
public boolean collidesAir = true, collidesGround = true;
/** Whether this bullet types collides with anything at all. */
public boolean collides = true;
/** Whether velocity is inherited from the shooter. */
@@ -97,20 +96,20 @@ public abstract class BulletType extends Content{
return speed * lifetime * (1f - drag);
}
public boolean collides(Bullet bullet, Tile tile){
public boolean collides(Bulletc bullet, Tilec tile){
return true;
}
public void hitTile(Bullet b, Tile tile){
public void hitTile(Bulletc b, Tilec tile){
hit(b);
}
public void hit(Bullet b){
hit(b, b.x, b.y);
public void hit(Bulletc b){
hit(b, b.getX(), b.getY());
}
public void hit(Bullet b, float x, float y){
Effects.effect(hitEffect, x, y, b.rot());
public void hit(Bulletc b, float x, float y){
hitEffect.at(x, y, b.rotation());
hitSound.at(b);
Effects.shake(hitShake, hitShake, b);
@@ -119,7 +118,7 @@ public abstract class BulletType extends Content{
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = Mathf.random(360f);
Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
}
}
@@ -128,12 +127,12 @@ public abstract class BulletType extends Content{
}
if(splashDamageRadius > 0){
Damage.damage(b.getTeam(), x, y, splashDamageRadius, splashDamage * b.damageMultiplier());
Damage.damage(b.team(), x, y, splashDamageRadius, splashDamage * b.damageMultiplier());
}
}
public void despawned(Bullet b){
Effects.effect(despawnEffect, b.x, b.y, b.rot());
public void despawned(Bulletc b){
despawnEffect.at(b.getX(), b.getY(), b.rotation());
hitSound.at(b);
if(fragBullet != null || splashDamageRadius > 0){
@@ -141,16 +140,16 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightining; i++){
Lightning.createLighting(Lightning.nextSeed(), b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b.team(), Pal.surge, damage, b.getX(), b.getY(), Mathf.random(360f), lightningLength);
}
}
public void draw(Bullet b){
public void draw(Bulletc b){
}
public void init(Bullet b){
if(killShooter && b.getOwner() instanceof HealthTrait){
((HealthTrait)b.getOwner()).kill();
public void init(Bulletc b){
if(killShooter && b.owner() instanceof Healthc){
((Healthc)b.owner()).kill();
}
if(instantDisappear){
@@ -158,12 +157,11 @@ public abstract class BulletType extends Content{
}
}
public void update(Bullet b){
public void update(Bulletc b){
if(homingPower > 0.0001f){
TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange, e -> !e.isFlying() || collidesAir);
Teamc target = Units.closestTarget(b.team(), b.getX(), b.getY(), homingRange, e -> e.isGrounded() || collidesAir);
if(target != null){
b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f));
b.vel().setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), 0.08f));
}
}
}
@@ -172,4 +170,58 @@ public abstract class BulletType extends Content{
public ContentType getContentType(){
return ContentType.bullet;
}
//TODO change 'create' to 'at'
public Bulletc create(Teamc owner, float x, float y, float angle){
return create(owner, owner.team(), x, y, angle);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle){
return create(owner, team, x, y, angle, 1f);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle, float velocityScl){
return create(owner, team, x, y, angle, -1, velocityScl, 1f, null);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle, float velocityScl, float lifetimeScl){
return create(owner, team, x, y, angle, -1, velocityScl, lifetimeScl, null);
}
public Bulletc create(Bulletc parent, float x, float y, float angle){
return create(parent.owner(), parent.team(), x, y, angle);
}
public Bulletc create(Bulletc parent, float x, float y, float angle, float velocityScl){
return create(parent.owner(), parent.team(), x, y, angle, velocityScl);
}
public Bulletc create(@Nullable Entityc owner, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl, Object data){
Bulletc bullet = BulletEntity.create();
bullet.type(this);
bullet.owner(owner);
bullet.team(team);
bullet.vel().trns(angle, speed * velocityScl);
bullet.set(x - bullet.vel().x * Time.delta(), y - bullet.vel().y * Time.delta());
bullet.lifetime(lifetime * lifetimeScl);
bullet.data(data);
bullet.drag(drag);
bullet.hitSize(hitSize);
bullet.damage(damage < 0 ? this.damage : damage);
bullet.add();
if(keepVelocity && owner instanceof Velc) bullet.vel().add(((Velc)owner).vel());
return bullet;
}
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
}
@Remote(called = Loc.server, unreliable = true)
public static void createBullet(BulletType type, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
type.create(null, team, x, y, angle, damage, velocityScl, lifetimeScl, null);
}
}

View File

@@ -1,14 +1,12 @@
package mindustry.entities.bullet;
import arc.math.geom.Rect;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class FlakBulletType extends BasicBulletType{
protected static Rect rect = new Rect();
protected float explodeRange = 30f;
public float explodeRange = 30f;
public FlakBulletType(float speed, float damage){
super(speed, damage, "shell");
@@ -17,6 +15,7 @@ public class FlakBulletType extends BasicBulletType{
hitEffect = Fx.flakExplosionBig;
bulletWidth = 8f;
bulletHeight = 10f;
collidesGround = false;
}
public FlakBulletType(){
@@ -24,18 +23,18 @@ public class FlakBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(b.getData() instanceof Integer) return;
if(b.data() instanceof Integer) return;
if(b.timer.get(2, 6)){
Units.nearbyEnemies(b.getTeam(), rect.setSize(explodeRange * 2f).setCenter(b.x, b.y), unit -> {
if(b.getData() instanceof Float) return;
if(b.timer(2, 6)){
Units.nearbyEnemies(b.team(), Tmp.r1.setSize(explodeRange * 2f).setCenter(b.x(), b.y()), unit -> {
if(b.data() instanceof Float || (unit.isFlying() && !collidesAir) || (unit.isGrounded() && !collidesGround)) return;
if(unit.dst(b) < explodeRange){
b.setData(0);
b.data(0);
Time.run(5f, () -> {
if(b.getData() instanceof Integer){
if(b.data() instanceof Integer){
b.time(b.lifetime());
}
});

View File

@@ -3,14 +3,14 @@ package mindustry.entities.bullet;
import arc.graphics.*;
import arc.graphics.g2d.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
public class HealBulletType extends BulletType{
protected float healPercent = 3f;
protected float bulletHeight = 7f, bulletWidth = 2f;
protected Color backColor = Pal.heal, frontColor = Color.white;
public HealBulletType(float speed, float damage){
super(speed, damage);
@@ -27,28 +27,27 @@ public class HealBulletType extends BulletType{
}
@Override
public boolean collides(Bullet b, Tile tile){
return tile.getTeam() != b.getTeam() || tile.entity.healthf() < 1f;
public boolean collides(Bulletc b, Tilec tile){
return tile.team() != b.team() || tile.healthf() < 1f;
}
@Override
public void draw(Bullet b){
Draw.color(Pal.heal);
Lines.stroke(2f);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 7f);
Draw.color(Color.white);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 3f);
public void draw(Bulletc b){
Draw.color(backColor);
Lines.stroke(bulletWidth);
Lines.lineAngleCenter(b.x(), b.y(), b.rotation(), bulletHeight);
Draw.color(frontColor);
Lines.lineAngleCenter(b.x(), b.y(), b.rotation(), bulletHeight / 2f);
Draw.reset();
}
@Override
public void hitTile(Bullet b, Tile tile){
public void hitTile(Bulletc b, Tilec tile){
super.hit(b);
tile = tile.link();
if(tile.entity != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
if(tile.team() == b.team() && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x(), tile.y(), tile.block().size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}
}
}

View File

@@ -0,0 +1,73 @@
package mindustry.entities.bullet;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class LaserBulletType extends BulletType{
protected Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
protected float length = 160f;
protected float width = 15f;
protected float lengthFalloff = 0.5f;
protected float sideLength = 29f, sideWidth = 0.7f;
protected float sideAngle = 90f;
public LaserBulletType(float damage){
super(0.01f, damage);
keepVelocity = false;
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
shootEffect = Fx.hitLancer;
smokeEffect = Fx.lancerLaserShootSmoke;
hitSize = 4;
lifetime = 16f;
pierce = true;
}
public LaserBulletType(){
this(1f);
}
@Override
public float range(){
return length;
}
@Override
public void init(Bulletc b){
Damage.collideLine(b, b.team(), hitEffect, b.x(), b.y(), b.rotation(), length);
}
@Override
public void draw(Bulletc b){
float f = Mathf.curve(b.fin(), 0f, 0.2f);
float baseLen = length * f;
float cwidth = width;
float compound = 1f;
Lines.lineAngle(b.x(), b.y(), b.rotation(), baseLen);
Lines.precise(true);
for(Color color : colors){
Draw.color(color);
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
Lines.lineAngle(b.x(), b.y(), b.rotation(), baseLen, CapStyle.none);
Tmp.v1.trns(b.rotation(), baseLen);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());
Fill.circle(b.x(), b.y(), 1f * cwidth * b.fout());
for(int i : Mathf.signs){
Drawf.tri(b.x(), b.y(), sideWidth * b.fout() * cwidth, sideLength * compound, b.rotation() + sideAngle * i);
}
compound *= lengthFalloff;
}
Lines.precise(false);
Draw.reset();
}
}

View File

@@ -0,0 +1,30 @@
package mindustry.entities.bullet;
import arc.graphics.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class LightningBulletType extends BulletType{
protected Color lightningColor = Pal.lancerLaser;
protected int lightningLength = 25;
public LightningBulletType(){
super(0.0001f, 1f);
lifetime = 1;
despawnEffect = Fx.none;
hitEffect = Fx.hitLancer;
keepVelocity = false;
}
@Override
public void draw(Bulletc b){
}
@Override
public void init(Bulletc b){
Lightning.create(b.team(), lightningColor, damage, b.x(), b.y(), b.rotation(), lightningLength);
}
}

View File

@@ -6,8 +6,7 @@ import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -45,13 +44,13 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(liquid.canExtinguish()){
Tile tile = world.tileWorld(b.x, b.y);
if(tile != null && Fire.has(tile.x, tile.y)){
Fire.extinguish(tile, 100f);
Tile tile = world.tileWorld(b.x(), b.y());
if(tile != null && Fires.has(tile.x, tile.y)){
Fires.extinguish(tile, 100f);
b.remove();
hit(b);
}
@@ -59,22 +58,22 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f);
Fill.circle(b.x(), b.y(), 0.5f + b.fout() * 2.5f);
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hitEffect, liquid.color, hitx, hity);
Puddle.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
public void hit(Bulletc b, float hitx, float hity){
hitEffect.at(hitx, hity, liquid.color);
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
float intensity = 400f;
Fire.extinguish(world.tileWorld(hitx, hity), intensity);
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
for(Point2 p : Geometry.d4){
Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
}
}
}

View File

@@ -5,8 +5,7 @@ import arc.graphics.g2d.Draw;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
@@ -24,32 +23,32 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void draw(mindustry.entities.type.Bullet b){
public void draw(Bulletc b){
float w = 11f, h = 13f;
Draw.color(Pal.bulletYellowBack);
Draw.rect("shell-back", b.x, b.y, w, h, b.rot() + 90);
Draw.rect("shell-back", b.x(), b.y(), w, h, b.rotation() + 90);
Draw.color(Pal.bulletYellow);
Draw.rect("shell", b.x, b.y, w, h, b.rot() + 90);
Draw.rect("shell", b.x(), b.y(), w, h, b.rotation() + 90);
Draw.reset();
}
@Override
public void update(mindustry.entities.type.Bullet b){
public void update(Bulletc b){
//data MUST be an instance of DriverBulletData
if(!(b.getData() instanceof DriverBulletData)){
if(!(b.data() instanceof DriverBulletData)){
hit(b);
return;
}
float hitDst = 7f;
DriverBulletData data = (DriverBulletData)b.getData();
DriverBulletData data = (DriverBulletData)b.data();
//if the target is dead, just keep flying until the bullet explodes
if(data.to.isDead()){
if(data.to.dead()){
return;
}
@@ -68,7 +67,7 @@ public class MassDriverBolt extends BulletType{
if(Angles.near(angleTo, baseAngle, 2f)){
intersect = true;
//snap bullet position back; this is used for low-FPS situations
b.set(data.to.x + Angles.trnsx(baseAngle, hitDst), data.to.y + Angles.trnsy(baseAngle, hitDst));
b.set(data.to.x() + Angles.trnsx(baseAngle, hitDst), data.to.y() + Angles.trnsy(baseAngle, hitDst));
}
}
@@ -83,24 +82,24 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void despawned(mindustry.entities.type.Bullet b){
public void despawned(Bulletc b){
super.despawned(b);
if(!(b.getData() instanceof DriverBulletData)) return;
if(!(b.data() instanceof DriverBulletData)) return;
DriverBulletData data = (DriverBulletData)b.getData();
DriverBulletData data = (DriverBulletData)b.data();
for(int i = 0; i < data.items.length; i++){
int amountDropped = Mathf.random(0, data.items[i]);
if(amountDropped > 0){
float angle = b.rot() + Mathf.range(100f);
Effects.effect(Fx.dropItem, Color.white, b.x, b.y, angle, content.item(i));
float angle = b.rotation() + Mathf.range(100f);
Fx.dropItem.at(b.x(), b.y(), angle, Color.white, content.item(i));
}
}
}
@Override
public void hit(Bullet b, float hitx, float hity){
public void hit(Bulletc b, float hitx, float hity){
super.hit(b, hitx, hity);
despawned(b);
}

View File

@@ -4,8 +4,6 @@ import arc.graphics.Color;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
@@ -28,15 +26,15 @@ public class MissileBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(Mathf.chance(Time.delta() * 0.2)){
Effects.effect(Fx.missileTrail, trailColor, b.x, b.y, 2f);
Fx.missileTrail.at(b.x(), b.y(), 2f, trailColor);
}
if(weaveMag > 0){
b.velocity().rotate(Mathf.sin(Time.time() + b.id * 4422, weaveScale, weaveMag) * Time.delta());
b.vel().rotate(Mathf.sin(Time.time() + b.id() * 442, weaveScale, weaveMag) * Time.delta());
}
}
}

View File

@@ -0,0 +1,36 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 180f;
@Import float x, y;
@Import Vec2 vel;
@Override
public void update(){
//repel unit out of bounds
if(x < 0) vel.x += (-x/warpDst);
if(y < 0) vel.y += (-y/warpDst);
if(x > world.unitWidth()) vel.x -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) vel.y -= (y - world.unitHeight())/warpDst;
//clamp position if not flying
if(isGrounded()){
x = Mathf.clamp(x, 0, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize - tilesize);
}
//kill when out of bounds
if(x < -finalWorldBounds || y < -finalWorldBounds || x >= world.width() * tilesize + finalWorldBounds || y >= world.height() * tilesize + finalWorldBounds){
kill();
}
}
}

View File

@@ -0,0 +1,239 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.Queue;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import java.util.*;
import static mindustry.Vars.*;
@Component
abstract class BuilderComp implements Unitc, DrawLayerFlyingc{
static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
@Import float x, y, rotation;
Queue<BuildRequest> requests = new Queue<>();
transient float buildSpeed = 1f;
//boolean building;
@Override
public void update(){
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange;
Iterator<BuildRequest> it = requests.iterator();
while(it.hasNext()){
BuildRequest req = it.next();
Tile tile = world.tile(req.x, req.y);
if(tile == null || (req.breaking && tile.block() == Blocks.air) || (!req.breaking && (tile.rotation() == req.rotation || !req.block.rotate) && tile.block() == req.block)){
it.remove();
}
}
Tilec core = closestCore();
//nothing to build.
if(buildRequest() == null) return;
//find the next build request
if(requests.size > 1){
int total = 0;
BuildRequest req;
while((dst((req = buildRequest()).tile()) > finalPlaceDst || shouldSkip(req, core)) && total < requests.size){
requests.removeFirst();
requests.addLast(req);
total++;
}
}
BuildRequest current = buildRequest();
if(dst(current.tile()) > finalPlaceDst) return;
Tile tile = world.tile(current.x, current.y);
if(dst(tile) <= finalPlaceDst){
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
}
if(!(tile.block() instanceof BuildBlock)){
if(!current.initialized && !current.breaking && Build.validPlace(team(), current.x, current.y, current.block, current.rotation)){
boolean hasAll = !Structs.contains(current.block.requirements, i -> !core.items().has(i.item));
if(hasAll){
Build.beginPlace(team(), current.x, current.y, current.block, current.rotation);
}else{
current.stuck = true;
}
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
Build.beginBreak(team(), current.x, current.y);
}else{
requests.removeFirst();
return;
}
}else if(tile.team() != team()){
requests.removeFirst();
return;
}
if(tile.entity instanceof BuildEntity && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team(), (Builderc)this, current.breaking)));
current.initialized = true;
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){
return;
}
//otherwise, update it.
BuildEntity entity = tile.ent();
if(current.breaking){
entity.deconstruct(this, core, 1f / entity.buildCost * Time.delta() * buildSpeed * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(this, core, 1f / entity.buildCost * Time.delta() * buildSpeed * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.onTileConfig(null, tile.entity, current.config);
}
}
}
current.stuck = Mathf.equal(current.progress, entity.progress);
current.progress = entity.progress;
}
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
void drawBuildRequests(){
for(BuildRequest request : requests){
if(request.progress > 0.01f || (buildRequest() == request && request.initialized && (dst(request.x * tilesize, request.y * tilesize) <= buildingRange || state.isEditor()))) continue;
request.animScale = 1f;
if(request.breaking){
control.input.drawBreaking(request);
}else{
request.block.drawRequest(request, control.input.allRequests(),
Build.validPlace(team(), request.x, request.y, request.block, request.rotation) || control.input.requestMatches(request));
}
}
Draw.reset();
}
/** @return whether this request should be skipped, in favor of the next one. */
boolean shouldSkip(BuildRequest request, @Nullable Tilec core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || request.breaking || core == null) return false;
//TODO these are bad criteria
return (request.stuck && !core.items().has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items().has(i.item)) && !request.initialized);
}
void removeBuild(int x, int y, boolean breaking){
//remove matching request
int idx = requests.indexOf(req -> req.breaking == breaking && req.x == x && req.y == y);
if(idx != -1){
requests.removeIndex(idx);
}
}
/** Return whether this builder's place queue contains items. */
boolean isBuilding(){
return requests.size != 0;
}
/** Clears the placement queue. */
void clearBuilding(){
requests.clear();
}
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
void addBuild(BuildRequest place){
addBuild(place, true);
}
/** Add another build requests to the queue, if it doesn't exist there yet. */
void addBuild(BuildRequest place, boolean tail){
BuildRequest replace = null;
for(BuildRequest request : requests){
if(request.x == place.x && request.y == place.y){
replace = request;
break;
}
}
if(replace != null){
requests.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.entity instanceof BuildEntity){
place.progress = tile.<BuildEntity>ent().progress;
}
if(tail){
requests.addLast(place);
}else{
requests.addFirst(place);
}
}
/** Return the build requests currently active, or the one at the top of the queue.*/
@Nullable BuildRequest buildRequest(){
return requests.size == 0 ? null : requests.first();
}
@Override
public void drawFlying(){
if(!isBuilding()) return;
BuildRequest request = buildRequest();
Tile tile = world.tile(request.x, request.y);
if(dst(tile) > buildingRange && !state.isEditor()){
return;
}
int size = request.breaking ? tile.block().size : request.block.size;
float tx = request.drawx(), ty = request.drawy();
Lines.stroke(1f, Pal.accent);
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float sz = Vars.tilesize * size / 2f;
float ang = angleTo(tx, ty);
vecs[0].set(tx - sz, ty - sz);
vecs[1].set(tx + sz, ty - sz);
vecs[2].set(tx - sz, ty + sz);
vecs[3].set(tx + sz, ty + sz);
Arrays.sort(vecs, Structs.comparingFloat(vec -> -Angles.angleDist(angleTo(vec), ang)));
float x1 = vecs[0].x, y1 = vecs[0].y,
x3 = vecs[1].x, y3 = vecs[1].y;
Draw.alpha(1f);
Lines.line(px, py, x1, y1);
Lines.line(px, py, x3, y3);
Fill.circle(px, py, 1.6f + Mathf.absin(Time.time(), 0.8f, 1.5f));
Draw.color();
}
}

View File

@@ -0,0 +1,130 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import static mindustry.Vars.*;
@EntityDef(value = {Bulletc.class}, pooled = true)
@Component
abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Drawc, Shielderc, Ownerc, Velc, Bulletc, Timerc, DrawLayerBulletsc{
Object data;
BulletType type;
float damage;
@Override
public void drawBullets(){
type.draw(this);
}
@Override
public void add(){
type.init(this);
}
@Override
public void remove(){
type.despawned(this);
}
@Override
public float damageMultiplier(){
if(owner() instanceof Unitc){
return ((Unitc)owner()).damageMultiplier();
}
return 1f;
}
@Override
public void absorb(){
//TODO
remove();
}
@Override
public float clipSize(){
return type.drawSize;
}
@Override
public float damage(){
return damage * damageMultiplier();
}
@Replace
@Override
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc && ((Teamc)other).team() != team())
&& !(other instanceof Flyingc && ((((Flyingc)other).isFlying() && !type.collidesAir) || (((Flyingc)other).isGrounded() && !type.collidesGround)));
}
@MethodPriority(100)
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(this, x, y);
if(other instanceof Healthc){
Healthc h = (Healthc)other;
h.damage(damage);
}
if(other instanceof Unitc){
Unitc unit = (Unitc)other;
unit.vel().add(Tmp.v3.set(other.x(), other.y()).sub(x, y).setLength(type.knockback / unit.mass()));
unit.apply(type.status, type.statusDuration);
}
//must be last.
if(!type.pierce) remove();
}
@Override
public void update(){
type.update(this);
if(type.hitTiles){
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Tilec tile = world.ent(x, y);
if(tile == null) return false;
if(tile.collide(this) && type.collides(this, tile) && !tile.dead() && (type.collidesTeam || tile.team() != team())){
if(tile.team() != team()){
tile.collision(this);
}
type.hitTile(this, tile);
remove();
return true;
}
return false;
});
}
}
@Override
public void draw(){
type.draw(this);
//TODO refactor
renderer.lights.add(x(), y(), 16f, Pal.powerLight, 0.3f);
}
/** Sets the bullet's rotation in degrees. */
@Override
public void rotation(float angle){
vel().setAngle(angle);
}
/** @return the bullet's rotation. */
@Override
public float rotation(){
float angle = Mathf.atan2(vel().x, vel().y) * Mathf.radiansToDegrees;
if(angle < 0) angle += 360;
return angle;
}
}

View File

@@ -0,0 +1,29 @@
package mindustry.entities.def;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class ChildComp implements Posc{
@Import float x, y;
@Nullable Posc parent;
float offsetX, offsetY;
@Override
public void add(){
if(parent != null){
offsetX = x - parent.getX();
offsetY = y - parent.getY();
}
}
@Override
public void update(){
if(parent != null){
x = parent.getX() + offsetX;
y = parent.getY() + offsetY;
}
}
}

View File

@@ -0,0 +1,8 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
@Component
abstract class DamageComp{
abstract float damage();
}

View File

@@ -0,0 +1,30 @@
package mindustry.entities.def;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {Decalc.class}, pooled = true)
@Component
abstract class DecalComp implements Drawc, Timedc, Rotc, Posc, DrawLayerFloorc{
@Import float x, y, rotation;
Color color = new Color(1, 1, 1, 1);
TextureRegion region;
@Override
public void drawFloor(){
Draw.color(color);
Draw.alpha(1f - Mathf.curve(fin(), 0.98f));
Draw.rect(region, x, y, rotation);
Draw.color();
}
@Override
public float clipSize(){
return region.getWidth()*2;
}
}

View File

@@ -0,0 +1,9 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class DrawComp implements Posc{
abstract float clipSize();
}

View File

@@ -0,0 +1,22 @@
package mindustry.entities.def;
import arc.graphics.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
@Component
abstract class EffectComp implements Posc, Drawc, Timedc, Rotc, Childc{
Color color = new Color(Color.white);
Effect effect;
Object data;
void draw(){
effect.render(id(), color, time(), rotation(), x(), y(), data);
}
@Override
public float clipSize(){
return effect.size;
}
}

View File

@@ -0,0 +1,22 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import static mindustry.Vars.collisions;
@Component
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
@Import float x, y;
@Replace
@Override
public void move(float cx, float cy){
if(isFlying()){
x += cx;
y += cy;
}else{
collisions.move(this, cx, cy);
}
}
}

View File

@@ -0,0 +1,66 @@
package mindustry.entities.def;
import arc.func.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
import static mindustry.Vars.player;
@Component
@BaseComponent
abstract class EntityComp{
private transient boolean added;
transient int id = EntityGroup.nextId();
boolean isAdded(){
return added;
}
void update(){}
void remove(){
added = false;
}
void add(){
added = true;
}
boolean isLocal(){
return ((Object)this) == player || ((Object)this) instanceof Unitc && ((Unitc)((Object)this)).controller() == player;
}
boolean isNull(){
return false;
}
<T> T as(Class<T> type){
return (T)this;
}
<T> T with(Cons<T> cons){
cons.get((T)this);
return (T)this;
}
@InternalImpl
abstract int classId();
@InternalImpl
abstract boolean serialize();
@MethodPriority(1)
void read(Reads read){
afterRead();
}
void write(Writes write){
}
void afterRead(){
}
}

View File

@@ -0,0 +1,102 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component
abstract class FireComp implements Timedc, Posc, Firec{
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
@Import float time, lifetime, x, y;
Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
@Override
public void update(){
if(Mathf.chance(0.1 * Time.delta())){
Fx.fire.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.05 * Time.delta())){
Fx.fireSmoke.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.001 * Time.delta())){
Sounds.fire.at(this);
}
time = Mathf.clamp(time + Time.delta(), 0, lifetime());
if(Vars.net.client()){
return;
}
if(time >= lifetime() || tile == null){
remove();
return;
}
Tilec entity = tile.entity;
boolean damage = entity != null;
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta() * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.entity == null ? 0 : tile.entity.getFlammability();
block = tile.block();
}
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Time.delta();
}
if(flammability > 1f && Mathf.chance(spreadChance * Time.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
Point2 p = Geometry.d4[Mathf.random(3)];
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
Fires.create(other);
if(Mathf.chance(fireballChance * Time.delta() * Mathf.clamp(flammability / 10f))){
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1, 1);
}
}
if(Mathf.chance(0.1 * Time.delta())){
Puddlec p = Puddles.get(tile);
puddleFlammability = p != null ? p.getFlammability() / 3f : 0;
if(damage){
entity.damage(0.4f);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.apply(StatusEffects.burning, 60 * 5));
}
}
@Override
public void remove(){
Fires.remove(tile);
}
@Override
public void afterRead(){
Fires.register(this);
}
}

View File

@@ -0,0 +1,79 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.net;
@Component
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
@Import float x, y, drag;
@Import Vec2 vel;
float elevation;
float drownTime;
transient float splashTimer;
boolean isGrounded(){
return elevation < 0.001f;
}
boolean isFlying(){
return elevation >= 0.001f;
}
boolean canDrown(){
return isGrounded();
}
void wobble(){
x += Mathf.sin(Time.time() + id() * 99, 25f, 0.05f) * Time.delta() * elevation;
y += Mathf.cos(Time.time() + id() * 99, 25f, 0.05f) * Time.delta() * elevation;
}
void moveAt(Vec2 vector, float acceleration){
Vec2 t = tmp1.set(vector).scl(floorSpeedMultiplier()); //target vector
tmp2.set(t).sub(vel).limit(acceleration * vector.len()); //delta vector
vel.add(tmp2);
}
float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier;
}
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded() && floor.isLiquid){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= 7f){
floor.walkEffect.at(x, y, 0, floor.mapColor);
splashTimer = 0f;
}
}
if(canDrown() && floor.isLiquid && floor.drownTime > 0){
drownTime += Time.delta() * 1f / floor.drownTime;
drownTime = Mathf.clamp(drownTime);
if(Mathf.chance(Time.delta() * 0.05f)){
floor.drownUpdateEffect.at(x, y, 0f, floor.mapColor);
}
//TODO is the netClient check necessary?
if(drownTime >= 0.999f && !net.client()){
kill();
//TODO drown event!
}
}else{
drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f);
}
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {GroundEffectc.class, Childc.class}, pooled = true)
@Component
abstract class GroundEffectComp implements Effectc, DrawLayerFloorOverc{
@Override
public void drawFloorOver(){
draw();
}
}

View File

@@ -0,0 +1,82 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HealthComp implements Entityc{
static final float hitDuration = 9f;
float health;
transient float hitTime;
transient float maxHealth = 1f;
transient boolean dead;
boolean isValid(){
return !dead && isAdded();
}
float healthf(){
return health / maxHealth;
}
@Override
public void update(){
hitTime -= Time.delta() / hitDuration;
}
void killed(){
//implement by other components
}
void kill(){
if(dead) return;
health = 0;
dead = true;
killed();
remove();
}
void heal(){
dead = false;
health = maxHealth;
}
boolean damaged(){
return health < maxHealth - 0.001f;
}
void damage(float amount){
health -= amount;
hitTime = 1f;
if(health <= 0 && !dead){
kill();
}
}
void damage(float amount, boolean withEffect){
float pre = hitTime;
damage(amount);
if(!withEffect){
hitTime = pre;
}
}
void damageContinuous(float amount){
damage(amount * Time.delta(), hitTime <= -20 + hitDuration);
}
void clampHealth(){
health = Mathf.clamp(health, 0, maxHealth);
}
void heal(float amount){
health += amount;
clampHealth();
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.math.geom.QuadTree.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HitboxComp implements Posc, QuadTreeObject{
@Import float x, y;
transient float lastX, lastY, hitSize;
@Override
public void update(){
}
@Override
public void add(){
updateLastPosition();
}
@Override
public void afterRead(){
updateLastPosition();
}
void updateLastPosition(){
lastX = x;
lastY = y;
}
void collision(Hitboxc other, float x, float y){
}
float deltaX(){
return x - lastX;
}
float deltaY(){
return y - lastY;
}
boolean collides(Hitboxc other){
return true;
}
@Override
public void hitbox(Rect rect){
rect.setCentered(x, y, hitSize, hitSize);
}
public void hitboxTile(Rect rect){
float scale = 0.66f;
rect.setCentered(x, y, hitSize * scale, hitSize * scale);
}
}

View File

@@ -0,0 +1,50 @@
package mindustry.entities.def;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.type.*;
@Component
abstract class ItemsComp implements Posc{
@ReadOnly ItemStack stack = new ItemStack();
transient float itemTime;
abstract int itemCapacity();
@Override
public void update(){
stack.amount = Mathf.clamp(stack.amount, 0, itemCapacity());
itemTime = Mathf.lerpDelta(itemTime, Mathf.num(hasItem()), 0.05f);
}
Item item(){
return stack.item;
}
void clearItem(){
stack.amount = 0;
}
boolean acceptsItem(Item item){
return !hasItem() || item == stack.item && stack.amount + 1 <= itemCapacity();
}
boolean hasItem(){
return stack.amount > 0;
}
void addItem(Item item){
addItem(item, 1);
}
void addItem(Item item, int amount){
stack.amount = stack.item == item ? stack.amount + amount : amount;
stack.item = item;
stack.amount = Mathf.clamp(stack.amount, 0, itemCapacity());
}
int maxAccepted(Item item){
return stack.item != item && stack.amount > 0 ? 0 : itemCapacity() - stack.amount;
}
}

View File

@@ -0,0 +1,26 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class LegsComp implements Posc, Flyingc, Hitboxc, DrawLayerGroundUnderc, Unitc, Legsc, ElevationMovec{
@Import float x, y;
float baseRotation;
transient float walkTime;
@Override
public void update(){
float len = vel().len();
baseRotation = Angles.moveToward(baseRotation, vel().angle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed));
walkTime += Time.delta()*len/1f;
}
@Override
public void drawGroundUnder(){
type().drawLegs(this);
}
}

View File

@@ -0,0 +1,13 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class MassComp implements Velc{
transient float mass = 1f;
public void impulse(float x, float y){
vel().add(x / mass, y / mass);
}
}

View File

@@ -0,0 +1,107 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@Component
abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, DrawLayerGroundc{
@Import float x, y, rotation;
transient float mineTimer;
@Nullable Tile mineTile;
abstract boolean canMine(Item item);
abstract float miningSpeed();
abstract boolean offloadImmediately();
boolean mining(){
return mineTile != null;
}
@Override
public void update(){
Tilec core = closestCore();
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && dst(core) < mineTransferRange){
int accepted = core.acceptStack(item(), stack().amount, this);
if(accepted > 0){
Call.transferItemTo(item(), accepted,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
clearItem();
}
}
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).isBuilding())
|| mineTile.drop() == null || !acceptsItem(mineTile.drop()) || !canMine(mineTile.drop())){
mineTile = null;
mineTimer = 0f;
}else{
Item item = mineTile.drop();
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
mineTimer += Time.delta()*miningSpeed();
if(mineTimer >= 50f + item.hardness*10f){
mineTimer = 0;
if(dst(core) < mineTransferRange && core.acceptStack(item, 1, this) == 1 && offloadImmediately()){
Call.transferItemTo(item, 1,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
}else if(acceptsItem(item)){
//this is clientside, since items are synced anyway
InputHandler.transferItemToUnit(item,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f),
this);
}
}
if(Mathf.chance(0.06 * Time.delta())){
Fx.pulverizeSmall.at(mineTile.worldx() + Mathf.range(tilesize / 2f), mineTile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
}
}
@Override
public void drawGround(){
if(!mining()) return;
float focusLen = 4f + Mathf.absin(Time.time(), 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float ex = mineTile.worldx() + Mathf.sin(Time.time() + 48, swingScl, swingMag);
float ey = mineTile.worldy() + Mathf.sin(Time.time() + 48, swingScl + 2f, swingMag);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);
//TODO hack?
if(isLocal()){
Lines.stroke(1f, Pal.accent);
Lines.poly(mineTile.worldx(), mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time());
}
Draw.color();
}
}

View File

@@ -0,0 +1,9 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
class OwnerComp{
Entityc owner;
}

View File

@@ -0,0 +1,211 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.pooling.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@EntityDef(value = {Playerc.class}, serialize = false)
@Component
abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc{
@NonNull @ReadOnly Unitc unit = Nulls.unit;
@ReadOnly Team team = Team.sharded;
String name = "noname";
@Nullable NetConnection con;
boolean admin, typing;
Color color = new Color();
float mouseX, mouseY;
String lastText = "";
float textFadeTime;
public boolean isBuilder(){
return unit instanceof Builderc;
}
public boolean isMiner(){
return unit instanceof Minerc;
}
public @Nullable CoreEntity closestCore(){
return state.teams.closestCore(x(), y(), team);
}
public void reset(){
team = state.rules.defaultTeam;
admin = typing = false;
textFadeTime = 0f;
if(!dead()){
unit.controller(unit.type().createController());
unit = Nulls.unit;
}
}
public void update(){
if(unit.dead()){
clearUnit();
}
CoreEntity core = closestCore();
if(!dead()){
x(unit.x());
y(unit.y());
unit.team(team);
}else if(core != null){
core.requestSpawn((Playerc)this);
}
textFadeTime -= Time.delta() / (60 * 5);
}
public void team(Team team){
this.team = team;
unit.team(team);
}
public void clearUnit(){
unit(Nulls.unit);
}
public Unitc unit(){
return unit;
}
public Minerc miner(){
return !(unit instanceof Minerc) ? Nulls.miner : (Minerc)unit;
}
public Builderc builder(){
return !(unit instanceof Builderc) ? Nulls.builder : (Builderc)unit;
}
public void unit(Unitc unit){
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit != Nulls.unit){
//un-control the old unit
this.unit.controller(this.unit.type().createController());
}
this.unit = unit;
if(unit != Nulls.unit){
unit.team(team);
unit.controller(this);
}
}
boolean dead(){
return unit.isNull();
}
String uuid(){
return con == null ? "[LOCAL]" : con.uuid;
}
String usid(){
return con == null ? "[LOCAL]" : con.usid;
}
void kick(KickReason reason){
con.kick(reason);
}
void kick(String reason){
con.kick(reason);
}
void drawName(){
BitmapFont font = Fonts.def;
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
final float nameHeight = 11;
final float textHeight = 15;
boolean ints = font.usesIntegerPositions();
font.setUseIntegerPositions(false);
font.getData().setScale(0.25f / Scl.scl(1f));
layout.setText(font, name);
if(!isLocal()){
Draw.color(0f, 0f, 0f, 0.3f);
Fill.rect(unit.x(), unit.y() + nameHeight - layout.height / 2, layout.width + 2, layout.height + 3);
Draw.color();
font.setColor(color);
font.draw(name, unit.x(), unit.y() + nameHeight, 0, Align.center, false);
if(admin){
float s = 3f;
Draw.color(color.r * 0.5f, color.g * 0.5f, color.b * 0.5f, 1f);
Draw.rect(Icon.adminSmall.getRegion(), unit.x() + layout.width / 2f + 2 + 1, unit.y() + nameHeight - 1.5f, s, s);
Draw.color(color);
Draw.rect(Icon.adminSmall.getRegion(), unit.x() + layout.width / 2f + 2 + 1, unit.y() + nameHeight - 1f, s, s);
}
}
if(Core.settings.getBool("playerchat") && ((textFadeTime > 0 && lastText != null) || typing)){
String text = textFadeTime <= 0 || lastText == null ? "[LIGHT_GRAY]" + Strings.animated(Time.time(), 4, 15f, ".") : lastText;
float width = 100f;
float visualFadeTime = 1f - Mathf.curve(1f - textFadeTime, 0.9f);
font.setColor(1f, 1f, 1f, textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime);
layout.setText(font, text, Color.white, width, Align.bottom, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(unit.x(), unit.y() + textHeight + layout.height - layout.height/2f, layout.width + 2, layout.height + 3);
font.draw(text, unit.x() - width/2f, unit.y() + textHeight + layout.height, width, Align.center, true);
}
Draw.reset();
Pools.free(layout);
font.getData().setScale(1f);
font.setColor(Color.white);
font.setUseIntegerPositions(ints);
}
void sendMessage(String text){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, null);
}
}else{
Call.sendMessage(con, text, null, null);
}
}
void sendMessage(String text, Playerc from){
sendMessage(text, from, NetClient.colorizeName(from.id(), from.name()));
}
void sendMessage(String text, Playerc from, String fromName){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, fromName);
}
}else{
Call.sendMessage(con, text, fromName, from);
}
}
PlayerInfo getInfo(){
if(isLocal()){
throw new IllegalArgumentException("Local players cannot be traced and do not have info.");
}else{
return netServer.admins.getInfo(uuid());
}
}
}

View File

@@ -0,0 +1,58 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.world;
@Component
abstract class PosComp implements Position{
float x, y;
void set(float x, float y){
this.x = x;
this.y = y;
}
void set(Position pos){
set(pos.getX(), pos.getY());
}
void trns(float x, float y){
set(this.x + x, this.y + y);
}
int tileX(){
return Vars.world.toTile(x);
}
int tileY(){
return Vars.world.toTile(y);
}
/** Returns air if this unit is on a non-air top block. */
public Floor floorOn(){
Tile tile = tileOn();
return tile == null || tile.block() != Blocks.air ? (Floor)Blocks.air : tile.floor();
}
public @Nullable
Tile tileOn(){
return world.tileWorld(x, y);
}
@Override
public float getX(){
return x;
}
@Override
public float getY(){
return y;
}
}

View File

@@ -0,0 +1,129 @@
package mindustry.entities.def;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.world;
import static mindustry.entities.Puddles.maxLiquid;
@EntityDef(value = {Puddlec.class}, pooled = true)
@Component
abstract class PuddleComp implements Posc, DrawLayerFloorOverc, Puddlec{
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect();
private static final Rect rect2 = new Rect();
private static int seeds;
@Import float x, y;
float amount, lastRipple, accepting, updateTime;
int generation;
Tile tile;
Liquid liquid;
public float getFlammability(){
return liquid.flammability * amount;
}
@Override
public void update(){
//update code
float addSpeed = accepting > 0 ? 3f : 0f;
amount -= Time.delta() * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
accepting = 0f;
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Time.delta();
for(Point2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && other.block() == Blocks.air){
Puddles.deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down Puddlec propagation
}
}
}
amount = Mathf.clamp(amount, 0, maxLiquid);
if(amount <= 0f){
remove();
}
//effects-only code
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isGrounded()){
unit.hitbox(rect2);
if(rect.overlaps(rect2)){
unit.apply(liquid.effect, 60 * 2);
if(unit.vel().len() > 0.1){
Fx.ripple.at(unit.x(), unit.y(), liquid.color);
}
}
}
});
if(liquid.temperature > 0.7f && (tile.entity != null) && Mathf.chance(0.3 * Time.delta())){
Fires.create(tile);
}
updateTime = 20f;
}
updateTime -= Time.delta();
}
@Override
public void drawFloorOver(){
seeds = id();
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 20f;
Draw.color(tmp.set(liquid.color).shiftValue(-0.05f));
Fill.circle(x + Mathf.sin(Time.time() + seeds * 532, sscl, smag), y + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id(), 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Time.time() + seeds * 532, sscl, smag),
y + ey + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 5f);
seeds++;
});
Draw.color();
if(liquid.lightColor.a > 0.001f && f > 0){
Color color = liquid.lightColor;
float opacity = color.a * f;
Vars.renderer.lights.add(tile.drawx(), tile.drawy(), 30f * f, color, opacity * 0.8f);
}
}
@Override
public float clipSize(){
return 20;
}
@Override
public void remove(){
Puddles.remove(tile);
}
@Override
public void afterRead(){
Puddles.register(this);
}
}

View File

@@ -0,0 +1,17 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class RotComp implements Entityc{
float rotation;
void interpolate(){
Syncc sync = as(Syncc.class);
if(sync.interpolator().values.length > 0){
rotation = sync.interpolator().values[0];
}
}
}

View File

@@ -0,0 +1,12 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class ShielderComp implements Damagec, Teamc, Posc{
void absorb(){
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {StandardEffectc.class, Childc.class}, pooled = true)
@Component
abstract class StandardEffectComp implements Effectc, DrawLayerEffectsc{
@Override
public void drawEffects(){
draw();
}
}

View File

@@ -1,49 +1,52 @@
package mindustry.entities.units;
package mindustry.entities.def;
import arc.struct.Bits;
import arc.struct.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.ContentType;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.environment.*;
import java.io.*;
import static mindustry.Vars.content;
/** Class for controlling status effects on an entity. */
public class Statuses implements Saveable{
private static final StatusEntry globalResult = new StatusEntry();
private static final Array<StatusEntry> removals = new Array<>();
@Component
abstract class StatusComp implements Posc, Flyingc{
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;
@ReadOnly transient float speedMultiplier, damageMultiplier, armorMultiplier;
public void handleApply(Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || unit.isImmune(effect)) return; //don't apply empty or immune effects
/** @return damage taken based on status armor multipliers */
float getShieldDamage(float amount){
return amount * Mathf.clamp(1f - armorMultiplier / 100f);
}
void apply(StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || isImmune(effect)) return; //don't apply empty or immune effects
if(statuses.size > 0){
//check for opposite effects
for(StatusEntry entry : statuses){
for(int i = 0; i < statuses.size; i ++){
StatusEntry entry = statuses.get(i);
//extend effect
if(entry.effect == effect){
entry.time = Math.max(entry.time, duration);
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
globalResult.effect = entry.effect;
entry.effect.getTransition(unit, effect, entry.time, duration, globalResult);
entry.time = globalResult.time;
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition((Unitc)this, effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(globalResult.effect != entry.effect){
entry.effect = globalResult.effect;
if(StatusEntry.tmp.effect != entry.effect){
entry.effect = StatusEntry.tmp.effect;
}
//stop looking when one is found
@@ -58,70 +61,71 @@ public class Statuses implements Saveable{
statuses.add(entry);
}
public Color getStatusColor(){
boolean isBoss(){
return hasEffect(StatusEffects.boss);
}
abstract boolean isImmune(StatusEffect effect);
Color statusColor(){
if(statuses.size == 0){
return Tmp.c1.set(Color.white);
}
float r = 0f, g = 0f, b = 0f;
float r = 1f, g = 1f, b = 1f, total = 0f;
for(StatusEntry entry : statuses){
r += entry.effect.color.r;
g += entry.effect.color.g;
b += entry.effect.color.b;
float intensity = entry.time < 10f ? entry.time/10f : 1f;
r += entry.effect.color.r * intensity;
g += entry.effect.color.g * intensity;
b += entry.effect.color.b * intensity;
total += intensity;
}
return Tmp.c1.set(r / statuses.size, g / statuses.size, b / statuses.size, 1f);
float count = statuses.size + total;
return Tmp.c1.set(r / count, g / count, b / count, 1f);
}
public void clear(){
statuses.clear();
}
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded() && floor.status != null){
//apply effect
apply(floor.status, floor.statusDuration);
}
public void update(Unit unit){
applied.clear();
speedMultiplier = damageMultiplier = armorMultiplier = 1f;
if(statuses.size == 0) return;
if(statuses.isEmpty()) return;
removals.clear();
int index = 0;
while(index < statuses.size){
StatusEntry entry = statuses.get(index++);
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);
removals.add(entry);
index --;
statuses.remove(index);
}else{
speedMultiplier *= entry.effect.speedMultiplier;
armorMultiplier *= entry.effect.armorMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
entry.effect.update(unit, entry.time);
//TODO ugly casting
entry.effect.update((Unitc)this, entry.time);
}
}
if(removals.size > 0){
statuses.removeAll(removals, true);
}
}
public float getSpeedMultiplier(){
return speedMultiplier;
}
public float getDamageMultiplier(){
return damageMultiplier;
}
public float getArmorMultiplier(){
return armorMultiplier;
}
public boolean hasEffect(StatusEffect effect){
boolean hasEffect(StatusEffect effect){
return applied.get(effect.id);
}
@Override
public void writeSave(DataOutput stream) throws IOException{
//TODO autogen io code
void writeSave(DataOutput stream) throws IOException{
stream.writeByte(statuses.size);
for(StatusEntry entry : statuses){
stream.writeByte(entry.effect.id);
@@ -129,8 +133,7 @@ public class Statuses implements Saveable{
}
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
void readSave(DataInput stream, byte version) throws IOException{
for(StatusEntry effect : statuses){
Pools.free(effect);
}
@@ -146,5 +149,4 @@ public class Statuses implements Saveable{
statuses.add(entry);
}
}
}

View File

@@ -0,0 +1,37 @@
package mindustry.entities.def;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.net.*;
@Component
abstract class SyncComp implements Posc{
@Import float x, y;
Interpolator interpolator = new Interpolator();
void setNet(float x, float y){
set(x, y);
//TODO change interpolator API
interpolator.target.set(x, y);
interpolator.last.set(x, y);
interpolator.pos.set(0, 0);
interpolator.updateSpacing = 16;
interpolator.lastUpdated = 0;
}
@Override
public void update(){
if(Vars.net.client() && !isLocal()){
interpolate();
}
}
void interpolate(){
interpolator.update();
x = interpolator.pos.x;
y = interpolator.pos.y;
}
}

View File

@@ -0,0 +1,23 @@
package mindustry.entities.def;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.state;
@Component
abstract class TeamComp implements Posc{
@Import float x, y;
Team team = Team.derelict;
public @Nullable Tilec closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable Tilec closestEnemyCore(){
return state.teams.closestEnemyCore(x, y, team);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class TimedComp implements Entityc, Scaled{
float time, lifetime;
//called last so pooling and removal happens then.
@MethodPriority(100)
@Override
public void update(){
time = Math.min(time + Time.delta(), lifetime);
if(time >= lifetime){
remove();
}
}
@Override
public float fin(){
return time / lifetime;
}
}

View File

@@ -0,0 +1,13 @@
package mindustry.entities.def;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@Component
abstract class TimerComp{
Interval timer = new Interval(6);
public boolean timer(int index, float time){
return timer.get(index, time);
}
}

View File

@@ -0,0 +1,240 @@
package mindustry.entities.def;
import arc.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class UnitComp implements Healthc, Velc, Statusc, Teamc, Itemsc, Hitboxc, Rotc, Massc, Unitc, Weaponsc, Drawc, Boundedc,
DrawLayerGroundc, DrawLayerFlyingc, DrawLayerGroundShadowsc, DrawLayerFlyingShadowsc, Syncc{
@Import float x, y, rotation, elevation;
private UnitController controller;
private UnitType type;
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
}
public void aimLook(Position pos){
aim(pos);
lookAt(pos);
}
public void aimLook(float x, float y){
aim(x, y);
lookAt(x, y);
}
@Override
public float clipSize(){
return type.region.getWidth() * 2f;
}
@Override
public int itemCapacity(){
return type.itemCapacity;
}
@Override
public float bounds(){
return hitSize() * 2f;
}
@Override
public void controller(UnitController controller){
this.controller = controller;
if(controller.unit() != this) controller.unit(this);
}
@Override
public UnitController controller(){
return controller;
}
@Override
public void set(UnitType def, UnitController controller){
type(type);
controller(controller);
}
@Override
public void collision(Hitboxc other, float x, float y){
if(!(other instanceof Unitc)) return;
Unitc unit = (Unitc)other;
if(isGrounded() != unit.isGrounded()) return;
float scale = 2f;
hitbox(Tmp.r1);
other.hitbox(Tmp.r2);
Vec2 v = Geometry.overlap(Tmp.r1, Tmp.r2, true);
float tm = mass() + unit.mass();
float s1 = mass() / tm, s2 = unit.mass() / tm;
move(v.x*s2/scale, v.y*s2/scale);
unit.move(-v.x*s1/scale, -v.y*s1/scale);
}
@Override
public void type(UnitType type){
this.type = type;
maxHealth(type.health);
heal();
drag(type.drag);
hitSize(type.hitsize);
controller(type.createController());
setupWeapons(type);
elevation = type.flying ? 1f : 0f;
}
@Override
public UnitType type(){
return type;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta());
}
public void lookAt(Position pos){
lookAt(angleTo(pos));
}
public void lookAt(float x, float y){
lookAt(angleTo(x, y));
}
public boolean isAI(){
return controller instanceof AIController;
}
@Override
public void afterRead(){
//set up type info after reading
type(this.type);
}
@Override
public void update(){
drag(type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f));
//apply knockback based on spawns
if(team() != state.rules.waveTeam){
float relativeSize = state.rules.dropZoneRadius + bounds()/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(withinDst(spawn.worldx(), spawn.worldy(), relativeSize)){
vel().add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta()));
}
}
}
Tile tile = tileOn();
Floor floor = floorOn();
if(tile != null && isGrounded()){
//unit block update
if(tile.entity != null){
tile.entity.unitOn(this);
}
//kill when stuck in wall
if(tile.solid()){
kill();
}
//apply damage
if(floor.damageTaken > 0f){
damageContinuous(floor.damageTaken);
}
}
controller.update();
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public void draw(){
type.drawEngine(this);
type.drawBody(this);
type.drawWeapons(this);
if(type.drawCell) type.drawCell(this);
if(type.drawItems) type.drawItems(this);
type.drawLight(this);
}
@Override
public void drawFlyingShadows(){
if(isFlying()) type.drawShadow(this);
}
@Override
public void drawGroundShadows(){
type.drawOcclusion(this);
}
@Override
public void drawFlying(){
if(isFlying()) draw();
}
@Override
public void drawGround(){
if(isGrounded()) draw();
}
@Override
public boolean isPlayer(){
return controller instanceof Playerc;
}
@Override
public void killed(){
float explosiveness = 2f + item().explosiveness * stack().amount;
float flammability = item().flammability * stack().amount;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame);
Effects.scorch(x, y, (int)(hitSize() / 5));
Fx.explosion.at(this);
Effects.shake(2f, 2f, this);
type.deathSound.at(this);
Events.fire(new UnitDestroyEvent(this));
if(explosiveness > 7f && isLocal()){
Events.fire(Trigger.suicideBomb);
}
}
@Override
public boolean canMine(Item item){
return type.drillTier >= item.hardness;
}
@Override
public float miningSpeed(){
return type.mineSpeed;
}
@Override
public boolean offloadImmediately(){
return false;
}
}

View File

@@ -0,0 +1,27 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class VelComp implements Posc{
@Import float x, y;
final Vec2 vel = new Vec2();
transient float drag = 0f;
//velocity needs to be called first, as it affects delta and lastPosition
@MethodPriority(-1)
@Override
public void update(){
move(vel.x * Time.delta(), vel.y * Time.delta());
vel.scl(1f - drag * Time.delta());
}
void move(float cx, float cy){
x += cx;
y += cy;
}
}

View File

@@ -0,0 +1,41 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.collisions;
//just a proof of concept
@Component
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc{
@Import float x, y;
@Replace
@Override
public void move(float cx, float cy){
if(isGrounded()){
if(!EntityCollisions.waterSolid(tileX(), tileY())){
collisions.move(this, cx, cy, EntityCollisions::waterSolid);
}
}else{
x += cx;
y += cy;
}
}
@Replace
@Override
public boolean canDrown(){
return false;
}
@Replace
public float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.isDeep() ? 1.3f : 1f;
}
}

View File

@@ -0,0 +1,153 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc{
@Import float x, y, rotation;
/** minimum cursor distance from player, fixes 'cross-eyed' shooting */
static final float minAimDst = 20f;
/** temporary weapon sequence number */
static int sequenceNum = 0;
/** weapon mount array, never null */
@ReadOnly WeaponMount[] mounts = {};
@ReadOnly transient float range, aimX, aimY;
@ReadOnly transient boolean isRotate, isShooting;
boolean inRange(Position other){
return within(other, range);
}
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
range = 0f;
for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i));
range = Math.max(range, def.weapons.get(i).bullet.range());
}
}
void controlWeapons(boolean rotate, boolean shoot){
for(WeaponMount mount : mounts){
mount.rotate = rotate;
mount.shoot = shoot;
}
isRotate = rotate;
isShooting = shoot;
}
void aim(Position pos){
aim(pos.getX(), pos.getY());
}
/** Aim at something. This will make all mounts point at it. */
void aim(float x, float y){
Tmp.v1.set(x, y).sub(this.x, this.y);
if(Tmp.v1.len() < minAimDst) Tmp.v1.setLength(minAimDst);
x = Tmp.v1.x + this.x;
y = Tmp.v1.y + this.y;
for(WeaponMount mount : mounts){
mount.aimX = x;
mount.aimY = y;
}
aimX = x;
aimY = y;
}
/** Update shooting and rotation for this unit. */
@Override
public void update(){
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta(), 0);
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot)){
float axisXOffset = weapon.mirror ? 0f : weapon.x;
float axisX = this.x + Angles.trnsx(rotation, axisXOffset, weapon.y),
axisY = this.y + Angles.trnsy(rotation, axisXOffset, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation();
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta());
}else{
mount.rotation = this.rotation;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
if(mount.shoot){
float rotation = this.rotation - 90;
//shoot if applicable
if(mount.reload <= 0.0001f && Angles.within(mount.rotation, mount.targetRotation, 1.5f)){
for(int i : (weapon.mirror && !weapon.alternate ? Mathf.signs : Mathf.one)){
i *= Mathf.sign(weapon.flipped) * Mathf.sign(mount.side);
//m a t h
float weaponRotation = rotation + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(rotation, weapon.x * i, weapon.y),
mountY = this.y + Angles.trnsy(rotation, weapon.x * i, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX * i, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX * i, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
shoot(weapon, shootX, shootY, shootAngle, -i);
}
mount.side = !mount.side;
mount.reload = weapon.reload;
}
}
}
}
private void shoot(Weapon weapon, float x, float y, float rotation, int side){
float baseX = this.x, baseY = this.y;
weapon.shootSound.at(x, y, Mathf.random(0.8f, 1.0f));
sequenceNum = 0;
if(weapon.shotDelay > 0.01f){
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
Time.run(sequenceNum * weapon.shotDelay, () -> bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy)));
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy)));
}
BulletType ammo = weapon.bullet;
Tmp.v1.trns(rotation + 180f, ammo.recoil);
if(this instanceof Velc){
//TODO apply force?
((Velc)this).vel().add(Tmp.v1);
}
Tmp.v1.trns(rotation, 3f);
boolean parentize = ammo.keepVelocity;
Effects.shake(weapon.shake, weapon.shake, x, y);
weapon.ejectEffect.at(x, y, rotation * side);
ammo.shootEffect.at(x + Tmp.v1.x, y + Tmp.v1.y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x + Tmp.v1.x, y + Tmp.v1.y, rotation, parentize ? this : null);
}
private void bullet(Weapon weapon, float x, float y, float angle){
Tmp.v1.trns(angle, 3f);
weapon.bullet.create(this, team(), x + Tmp.v1.x, y + Tmp.v1.y, angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd));
}
}

View File

@@ -1,36 +0,0 @@
package mindustry.entities.effect;
import arc.graphics.g2d.Draw;
import arc.math.Mathf;
import mindustry.entities.EntityGroup;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.BelowLiquidTrait;
import mindustry.entities.traits.DrawTrait;
import mindustry.graphics.Pal;
import static mindustry.Vars.groundEffectGroup;
/**
* Class for creating block rubble on the ground.
*/
public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{
@Override
public float lifetime(){
return 3600;
}
@Override
public void draw(){
Draw.color(Pal.rubble.r, Pal.rubble.g, Pal.rubble.b, 1f - Mathf.curve(fin(), 0.98f));
drawDecal();
Draw.color();
}
@Override
public EntityGroup targetGroup(){
return groundEffectGroup;
}
abstract void drawDecal();
}

View File

@@ -1,229 +0,0 @@
package mindustry.entities.effect;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class Fire extends TimedEntity implements SaveTrait, SyncTrait{
private static final IntMap<Fire> map = new IntMap<>();
private static final float baseLifetime = 1000f, spreadChance = 0.05f, fireballChance = 0.07f;
private int loadedPosition = -1;
private Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
private float lifetime;
/** Deserialization use only! */
public Fire(){
}
@Remote
public static void onRemoveFire(int fid){
fireGroup.removeByID(fid);
}
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
Fire fire = map.get(tile.pos());
if(fire == null){
fire = new Fire();
fire.tile = tile;
fire.lifetime = baseLifetime;
fire.set(tile.worldx(), tile.worldy());
fire.add();
map.put(tile.pos(), fire);
}else{
fire.lifetime = baseLifetime;
fire.time = 0f;
}
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Pos.get(x, y))){
return false;
}
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;
}
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
Fire fire = map.get(tile.pos());
fire.time += intensity * Time.delta();
if(fire.time >= fire.lifetime()){
Events.fire(Trigger.fireExtinguish);
}
}
}
@Override
public TypeID getTypeID(){
return TypeIDs.fire;
}
@Override
public byte version(){
return 0;
}
@Override
public float lifetime(){
return lifetime;
}
@Override
public void update(){
if(Mathf.chance(0.1 * Time.delta())){
Effects.effect(Fx.fire, x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.05 * Time.delta())){
Effects.effect(Fx.fireSmoke, x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.001 * Time.delta())){
Sounds.fire.at(this);
}
time = Mathf.clamp(time + Time.delta(), 0, lifetime());
map.put(tile.pos(), this);
if(net.client()){
return;
}
if(time >= lifetime() || tile == null){
remove();
return;
}
TileEntity entity = tile.link().entity;
boolean damage = entity != null;
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta() * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.block().getFlammability(tile);
block = tile.block();
}
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Time.delta();
}
if(flammability > 1f && Mathf.chance(spreadChance * Time.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
Point2 p = Geometry.d4[Mathf.random(3)];
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
create(other);
if(Mathf.chance(fireballChance * Time.delta() * Mathf.clamp(flammability / 10f))){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1, 1);
}
}
if(Mathf.chance(0.1 * Time.delta())){
Puddle p = Puddle.getPuddle(tile);
if(p != null){
puddleFlammability = p.getFlammability() / 3f;
}else{
puddleFlammability = 0;
}
if(damage){
entity.damage(0.4f);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.applyEffect(StatusEffects.burning, 60 * 5));
}
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.pos());
stream.writeFloat(lifetime);
stream.writeFloat(time);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
this.loadedPosition = stream.readInt();
this.lifetime = stream.readFloat();
this.time = stream.readFloat();
add();
}
@Override
public void write(DataOutput data) throws IOException{
data.writeInt(tile.pos());
data.writeFloat(lifetime);
}
@Override
public void read(DataInput data) throws IOException{
int pos = data.readInt();
this.lifetime = data.readFloat();
x = Pos.x(pos) * tilesize;
y = Pos.y(pos) * tilesize;
tile = world.tile(pos);
}
@Override
public void reset(){
loadedPosition = -1;
tile = null;
baseFlammability = -1;
puddleFlammability = 0f;
incrementID();
}
@Override
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
set(tile.worldx(), tile.worldy());
}
}
@Override
public void removed(){
if(tile != null){
Call.onRemoveFire(id);
map.remove(tile.pos());
}
}
@Override
public EntityGroup targetGroup(){
return fireGroup;
}
}

View File

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

View File

@@ -1,118 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.Loc;
import mindustry.annotations.Annotations.Remote;
import arc.graphics.g2d.*;
import arc.math.Interpolation;
import arc.math.Mathf;
import arc.math.geom.Position;
import arc.math.geom.Vec2;
import arc.util.Time;
import arc.util.pooling.Pools;
import mindustry.entities.*;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.DrawTrait;
import mindustry.entities.type.Unit;
import mindustry.graphics.Pal;
import mindustry.type.Item;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class ItemTransfer extends TimedEntity implements DrawTrait{
private Vec2 from = new Vec2();
private Vec2 current = new Vec2();
private Vec2 tovec = new Vec2();
private Item item;
private float seed;
private Position to;
private Runnable done;
public ItemTransfer(){}
@Remote(called = Loc.server, unreliable = true)
public static void transferItemEffect(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> {
});
}
@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.addItem(item));
}
@Remote(called = Loc.server, unreliable = true)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null || tile.entity == null || tile.entity.items == null) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Time.run(i * 3, () -> create(item, x, y, tile, () -> {}));
}
tile.entity.items.add(item, amount);
}
public static void create(Item item, float fromx, float fromy, Position to, Runnable done){
ItemTransfer tr = Pools.obtain(ItemTransfer.class, ItemTransfer::new);
tr.item = item;
tr.from.set(fromx, fromy);
tr.to = to;
tr.done = done;
tr.seed = Mathf.range(1f);
tr.add();
}
@Override
public float lifetime(){
return 60;
}
@Override
public void reset(){
super.reset();
item = null;
to = null;
done = null;
from.setZero();
current.setZero();
tovec.setZero();
}
@Override
public void removed(){
if(done != null){
done.run();
}
Pools.free(this);
}
@Override
public void update(){
if(to == null){
remove();
return;
}
super.update();
current.set(from).interpolate(tovec.set(to.getX(), to.getY()), fin(), Interpolation.pow3);
current.add(tovec.set(to.getX(), to.getY()).sub(from).nor().rotate90(1).scl(seed * fslope() * 10f));
set(current.x, current.y);
}
@Override
public void draw(){
Lines.stroke(fslope() * 2f, Pal.accent);
Lines.circle(x, y, fslope() * 2f);
Draw.color(item.color);
Fill.circle(x, y, fslope() * 1.5f);
Draw.reset();
}
@Override
public EntityGroup targetGroup(){
return effectGroup;
}
}

View File

@@ -1,160 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.Loc;
import mindustry.annotations.Annotations.Remote;
import arc.struct.Array;
import arc.struct.IntSet;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.pooling.Pools;
import mindustry.content.Bullets;
import mindustry.entities.EntityGroup;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.DrawTrait;
import mindustry.entities.traits.TimeTrait;
import mindustry.entities.type.Unit;
import mindustry.game.Team;
import mindustry.gen.Call;
import mindustry.graphics.Pal;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
public static final float lifetime = 10f;
private static final Rand random = new Rand();
private static final Rect rect = new Rect();
private static final Array<Unit> entities = new Array<>();
private static final IntSet hit = new IntSet();
private static final int maxChain = 8;
private static final float hitRange = 30f;
private static int lastSeed = 0;
private Array<Vec2> lines = new Array<>();
private Color color = Pal.lancerLaser;
/** For pooling use only. Do not call directly! */
public Lightning(){
}
/** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
Call.createLighting(nextSeed(), team, color, damage, x, y, targetAngle, length);
}
public static int nextSeed(){
return lastSeed++;
}
/** Do not invoke! */
@Remote(called = Loc.server, unreliable = true)
public static void createLighting(int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
Lightning l = Pools.obtain(Lightning.class, Lightning::new);
Float dmg = damage;
l.x = x;
l.y = y;
l.color = color;
l.add();
random.setSeed(seed);
hit.clear();
boolean[] bhit = {false};
for(int i = 0; i < length / 2; i++){
Bullet.create(Bullets.damageLightning, l, team, x, y, 0f, 1f, 1f, dmg);
l.lines.add(new Vec2(x + Mathf.range(3f), y + Mathf.range(3f)));
if(l.lines.size > 1){
bhit[0] = false;
Position from = l.lines.get(l.lines.size - 2);
Position to = l.lines.get(l.lines.size - 1);
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.ltile(wx, wy);
if(tile != null && tile.block().insulated){
bhit[0] = true;
//snap it instead of removing
l.lines.get(l.lines.size -1).set(wx * tilesize, wy * tilesize);
return true;
}
return false;
});
if(bhit[0]) break;
}
rect.setSize(hitRange).setCenter(x, y);
entities.clear();
if(hit.size < maxChain){
Units.nearbyEnemies(team, rect, u -> {
if(!hit.contains(u.getID())){
entities.add(u);
}
});
}
Unit furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
hit.add(furthest.getID());
x = furthest.x;
y = furthest.y;
}else{
rotation += random.range(20f);
x += Angles.trnsx(rotation, hitRange / 2f);
y += Angles.trnsy(rotation, hitRange / 2f);
}
}
}
@Override
public float lifetime(){
return lifetime;
}
@Override
public void reset(){
super.reset();
color = Pal.lancerLaser;
lines.clear();
}
@Override
public void removed(){
super.removed();
Pools.free(this);
}
@Override
public void draw(){
Lines.stroke(3f * fout());
Draw.color(color, Color.white, fin());
Lines.beginLine();
Lines.linePoint(x, y);
for(Position p : lines){
Lines.linePoint(p.getX(), p.getY());
}
Lines.endLine();
int i = 0;
for(Position p : lines){
Fill.square(p.getX(), p.getY(), (5f - (float)i++ / lines.size * 2f) * fout(), 45);
}
Draw.reset();
}
@Override
public EntityGroup targetGroup(){
return bulletGroup;
}
}

View File

@@ -1,322 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.pooling.Pool.*;
import arc.util.pooling.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait{
private static final IntMap<Puddle> map = new IntMap<>();
private static final float maxLiquid = 70f;
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect();
private static final Rect rect2 = new Rect();
private static int seeds;
private int loadedPosition = -1;
private float updateTime;
private float lastRipple;
private Tile tile;
private Liquid liquid;
private float amount, targetAmount;
private float accepting;
private byte generation;
/** Deserialization use only! */
public Puddle(){
}
/** Deposists a puddle between tile and source. */
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
}
/** Deposists a puddle at a tile. */
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/** Returns the puddle on the specified tile. May return null. */
public static Puddle getPuddle(Tile tile){
return map.get(tile.pos());
}
private static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
if(tile == null) return;
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
reactPuddle(tile.floor().liquidDrop, liquid, amount, tile,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
Puddle p = map.get(tile.pos());
if(generation == 0 && p != null && p.lastRipple <= Time.time() - 40f){
Effects.effect(Fx.ripple, tile.floor().liquidDrop.color,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
p.lastRipple = Time.time();
}
return;
}
Puddle p = map.get(tile.pos());
if(p == null){
if(net.client()) return; //not clientside.
Puddle puddle = Pools.obtain(Puddle.class, Puddle::new);
puddle.tile = tile;
puddle.liquid = liquid;
puddle.amount = amount;
puddle.generation = (byte)generation;
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.pos(), puddle);
}else if(p.liquid == liquid){
p.accepting = Math.max(amount, p.accepting);
if(generation == 0 && p.lastRipple <= Time.time() - 40f && p.amount >= maxLiquid / 2f){
Effects.effect(Fx.ripple, p.liquid.color, (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
p.lastRipple = Time.time();
}
}else{
p.amount += reactPuddle(p.liquid, liquid, amount, p.tile, p.x, p.y);
}
}
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
/** Reacts two liquids together at a location. */
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fire.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot puddle
if(Mathf.chance(0.5f * amount)){
Effects.effect(Fx.steam, x, y);
}
return -0.1f * amount;
}else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold puddle
if(Mathf.chance(0.8f * amount)){
Effects.effect(Fx.steam, x, y);
}
return -0.4f * amount;
}
return 0f;
}
@Remote(called = Loc.server)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
public float getFlammability(){
return liquid.flammability * amount;
}
@Override
public TypeID getTypeID(){
return TypeIDs.puddle;
}
@Override
public byte version(){
return 0;
}
@Override
public void hitbox(Rect rect){
rect.setCenter(x, y).setSize(tilesize);
}
@Override
public void hitboxTile(Rect rect){
rect.setCenter(x, y).setSize(0f);
}
@Override
public void update(){
//no updating happens clientside
if(net.client()){
amount = Mathf.lerpDelta(amount, targetAmount, 0.15f);
}else{
//update code
float addSpeed = accepting > 0 ? 3f : 0f;
amount -= Time.delta() * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
accepting = 0f;
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Time.delta();
for(Point2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && other.block() == Blocks.air){
deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down puddle propagation
}
}
}
amount = Mathf.clamp(amount, 0, maxLiquid);
if(amount <= 0f){
Call.onPuddleRemoved(getID());
}
}
//effects-only code
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isFlying()) return;
unit.hitbox(rect2);
if(!rect.overlaps(rect2)) return;
unit.applyEffect(liquid.effect, 60 * 2);
if(unit.velocity().len() > 0.1){
Effects.effect(Fx.ripple, liquid.color, unit.x, unit.y);
}
});
if(liquid.temperature > 0.7f && (tile.link().entity != null) && Mathf.chance(0.3 * Time.delta())){
Fire.create(tile);
}
updateTime = 20f;
}
updateTime -= Time.delta();
}
@Override
public void draw(){
seeds = id;
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 20f;
Draw.color(tmp.set(liquid.color).shiftValue(-0.05f));
Fill.circle(x + Mathf.sin(Time.time() + seeds * 532, sscl, smag), y + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Time.time() + seeds * 532, sscl, smag),
y + ey + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 5f);
seeds++;
});
Draw.color();
if(liquid.lightColor.a > 0.001f && f > 0){
Color color = liquid.lightColor;
float opacity = color.a * f;
renderer.lights.add(tile.drawx(), tile.drawy(), 30f * f, color, opacity * 0.8f);
}
}
@Override
public float drawSize(){
return 20;
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.pos());
stream.writeFloat(x);
stream.writeFloat(y);
stream.writeByte(liquid.id);
stream.writeFloat(amount);
stream.writeByte(generation);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
this.loadedPosition = stream.readInt();
this.x = stream.readFloat();
this.y = stream.readFloat();
this.liquid = content.liquid(stream.readByte());
this.amount = stream.readFloat();
this.generation = stream.readByte();
add();
}
@Override
public void reset(){
loadedPosition = -1;
tile = null;
liquid = null;
amount = 0;
generation = 0;
accepting = 0;
}
@Override
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
}
}
@Override
public void removed(){
if(tile != null){
map.remove(tile.pos());
}
reset();
}
@Override
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(liquid.id);
data.writeShort((short)(amount * 4));
data.writeInt(tile.pos());
}
@Override
public void read(DataInput data) throws IOException{
x = data.readFloat();
y = data.readFloat();
liquid = content.liquid(data.readByte());
targetAmount = data.readShort() / 4f;
int pos = data.readInt();
tile = world.tile(pos);
map.put(pos, this);
}
@Override
public EntityGroup targetGroup(){
return puddleGroup;
}
}

View File

@@ -1,46 +0,0 @@
package mindustry.entities.effect;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Mathf;
import static mindustry.Vars.headless;
public class RubbleDecal extends Decal{
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;
RubbleDecal decal = new RubbleDecal();
decal.region = Core.atlas.find("rubble-" + size + "-" + Mathf.randomSeed(decal.id, 0, 1));
if(!Core.atlas.isFound(decal.region)){
return;
}
decal.set(x, y);
decal.add();
}
@Override
public float lifetime(){
return 8200f;
}
@Override
public void drawDecal(){
if(!Core.atlas.isFound(region)){
remove();
return;
}
Draw.rect(region, x, y, Mathf.randomSeed(id, 0, 4) * 90);
}
@Override
public float drawSize(){
return region.getWidth() * 3f;
}
}

View File

@@ -1,49 +0,0 @@
package mindustry.entities.effect;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.world.Tile;
import static mindustry.Vars.headless;
import static mindustry.Vars.world;
public class ScorchDecal extends Decal{
private static final int scorches = 5;
private static final TextureRegion[] regions = new TextureRegion[scorches];
public static void create(float x, float y){
if(headless) return;
if(regions[0] == null || regions[0].getTexture().isDisposed()){
for(int i = 0; i < regions.length; i++){
regions[i] = Core.atlas.find("scorch" + (i + 1));
}
}
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().liquidDrop != null) return;
ScorchDecal decal = new ScorchDecal();
decal.set(x, y);
decal.add();
}
@Override
public void drawDecal(){
for(int i = 0; i < 3; i++){
TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches - 1)];
float rotation = Mathf.randomSeed(id + i, 0, 360);
float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20) / 10f;
Draw.rect(region,
x + Angles.trnsx(rotation, space),
y + Angles.trnsy(rotation, space) + region.getHeight() / 2f * Draw.scl,
region.getWidth() * Draw.scl,
region.getHeight() * Draw.scl,
region.getWidth() / 2f * Draw.scl, 0, rotation - 90);
}
}
}

View File

@@ -1,13 +0,0 @@
package mindustry.entities.traits;
public interface AbsorbTrait extends Entity, TeamTrait, DamageTrait{
void absorb();
default boolean canBeAbsorbed(){
return true;
}
default float getShieldDamage(){
return damage();
}
}

View File

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

View File

@@ -1,22 +0,0 @@
package mindustry.entities.traits;
/** A class for gracefully merging mining and building traits.*/
public interface BuilderMinerTrait extends MinerTrait, BuilderTrait{
default void updateMechanics(){
updateBuilding();
//mine only when not building
if(buildRequest() == null){
updateMining();
}
}
default void drawMechanics(){
if(isBuilding()){
drawBuilding();
}else{
drawMining();
}
}
}

View File

@@ -1,406 +0,0 @@
package mindustry.entities.traits;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.Queue;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.type.TileEntity;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import java.io.*;
import java.util.*;
import static mindustry.Vars.*;
import static mindustry.entities.traits.BuilderTrait.BuildDataStatic.tmptr;
/** Interface for units that build things.*/
public interface BuilderTrait extends Entity, TeamTrait{
//these are not instance variables!
float placeDistance = 220f;
float mineDistance = 70f;
/** Updates building mechanism for this unit.*/
default void updateBuilding(){
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : placeDistance;
Unit unit = (Unit)this;
Iterator<BuildRequest> it = buildQueue().iterator();
while(it.hasNext()){
BuildRequest req = it.next();
Tile tile = world.tile(req.x, req.y);
if(tile == null || (req.breaking && tile.block() == Blocks.air) || (!req.breaking && (tile.rotation() == req.rotation || !req.block.rotate) && tile.block() == req.block)){
it.remove();
}
}
TileEntity core = unit.getClosestCore();
//nothing to build.
if(buildRequest() == null) return;
//find the next build request
if(buildQueue().size > 1){
int total = 0;
BuildRequest req;
while((dst((req = buildRequest()).tile()) > finalPlaceDst || shouldSkip(req, core)) && total < buildQueue().size){
buildQueue().removeFirst();
buildQueue().addLast(req);
total++;
}
}
BuildRequest current = buildRequest();
if(dst(current.tile()) > finalPlaceDst) return;
Tile tile = world.tile(current.x, current.y);
if(!(tile.block() instanceof BuildBlock)){
if(!current.initialized && canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
}else if(!current.initialized && canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
Call.beginBreak(getTeam(), current.x, current.y);
}else{
buildQueue().removeFirst();
return;
}
}else if(tile.getTeam() != getTeam()){
buildQueue().removeFirst();
return;
}
if(tile.entity instanceof BuildEntity && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking)));
current.initialized = true;
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){
return;
}
//otherwise, update it.
BuildEntity entity = tile.ent();
if(entity == null){
return;
}
if(unit.dst(tile) <= finalPlaceDst){
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
}
if(current.breaking){
entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.onTileConfig(null, tile, current.config);
}
}
}
current.stuck = Mathf.equal(current.progress, entity.progress);
current.progress = entity.progress;
}
/** @return whether this request should be skipped, in favor of the next one. */
default boolean shouldSkip(BuildRequest request, @Nullable TileEntity core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || request.breaking || !request.initialized || core == null) return false;
return request.stuck && !core.items.has(request.block.requirements);
}
default void removeRequest(int x, int y, boolean breaking){
//remove matching request
int idx = player.buildQueue().indexOf(req -> req.breaking == breaking && req.x == x && req.y == y);
if(idx != -1){
player.buildQueue().removeIndex(idx);
}
}
/** Returns the queue for storing build requests. */
Queue<BuildRequest> buildQueue();
/** Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. */
float getBuildPower(Tile tile);
/** Whether this type of builder can begin creating new blocks. */
default boolean canCreateBlocks(){
return true;
}
default void writeBuilding(DataOutput output) throws IOException{
BuildRequest request = buildRequest();
if(request != null && (request.block != null || request.breaking)){
output.writeByte(request.breaking ? 1 : 0);
output.writeInt(Pos.get(request.x, request.y));
output.writeFloat(request.progress);
if(!request.breaking){
output.writeShort(request.block.id);
output.writeByte(request.rotation);
}
}else{
output.writeByte(-1);
}
}
default void readBuilding(DataInput input) throws IOException{
readBuilding(input, true);
}
default void readBuilding(DataInput input, boolean applyChanges) throws IOException{
if(applyChanges) buildQueue().clear();
byte type = input.readByte();
if(type != -1){
int position = input.readInt();
float progress = input.readFloat();
BuildRequest request;
if(type == 1){ //remove
request = new BuildRequest(Pos.x(position), Pos.y(position));
}else{ //place
short block = input.readShort();
byte rotation = input.readByte();
request = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
}
request.progress = progress;
if(applyChanges){
buildQueue().addLast(request);
}else if(isBuilding()){
BuildRequest last = buildRequest();
last.progress = progress;
if(last.tile() != null && last.tile().entity instanceof BuildEntity){
((BuildEntity)last.tile().entity).progress = progress;
}
}
}
}
/** Return whether this builder's place queue contains items. */
default boolean isBuilding(){
return buildQueue().size != 0;
}
/** Clears the placement queue. */
default void clearBuilding(){
buildQueue().clear();
}
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place){
addBuildRequest(place, true);
}
/** Add another build requests to the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place, boolean tail){
BuildRequest replace = null;
for(BuildRequest request : buildQueue()){
if(request.x == place.x && request.y == place.y){
replace = request;
break;
}
}
if(replace != null){
buildQueue().remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.entity instanceof BuildEntity){
place.progress = tile.<BuildEntity>ent().progress;
}
if(tail){
buildQueue().addLast(place);
}else{
buildQueue().addFirst(place);
}
}
/**
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default @Nullable
BuildRequest buildRequest(){
return buildQueue().size == 0 ? null : buildQueue().first();
}
//due to iOS weirdness, this is apparently required
class BuildDataStatic{
static Vec2[] tmptr = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
}
/** Draw placement effects for an entity. */
default void drawBuilding(){
if(!isBuilding()) return;
Unit unit = (Unit)this;
BuildRequest request = buildRequest();
Tile tile = world.tile(request.x, request.y);
if(dst(tile) > placeDistance && !state.isEditor()){
return;
}
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);
float sz = Vars.tilesize * tile.block().size / 2f;
float ang = unit.angleTo(tile);
tmptr[0].set(tile.drawx() - sz, tile.drawy() - sz);
tmptr[1].set(tile.drawx() + sz, tile.drawy() - sz);
tmptr[2].set(tile.drawx() - sz, tile.drawy() + sz);
tmptr[3].set(tile.drawx() + sz, tile.drawy() + sz);
Arrays.sort(tmptr, (a, b) -> -Float.compare(Angles.angleDist(Angles.angle(unit.x, unit.y, a.x, a.y), ang),
Angles.angleDist(Angles.angle(unit.x, unit.y, b.x, b.y), ang)));
float x1 = tmptr[0].x, y1 = tmptr[0].y,
x3 = tmptr[1].x, y3 = tmptr[1].y;
Draw.alpha(1f);
Lines.line(px, py, x1, y1);
Lines.line(px, py, x3, y3);
Fill.circle(px, py, 1.6f + Mathf.absin(Time.time(), 0.8f, 1.5f));
Draw.color();
}
/** Class for storing build requests. Can be either a place or remove request. */
class BuildRequest{
/** Position and rotation of this request. */
public int x, y, rotation;
/** Block being placed. If null, this is a breaking request.*/
public @Nullable Block block;
/** Whether this is a break request.*/
public boolean breaking;
/** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/
public boolean hasConfig;
/** Config int. Not used unless hasConfig is true.*/
public int config;
/** Original position, only used in schematics.*/
public int originalX, originalY, originalWidth, originalHeight;
/** Last progress.*/
public float progress;
/** Whether construction has started for this request, and other special variables.*/
public boolean initialized, worldContext = true, stuck;
/** Visual scale. Used only for rendering.*/
public float animScale = 0f;
/** This creates a build request. */
public BuildRequest(int x, int y, int rotation, Block block){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
}
/** This creates a remove request. */
public BuildRequest(int x, int y){
this.x = x;
this.y = y;
this.rotation = -1;
this.block = world.tile(x, y).block();
this.breaking = true;
}
public BuildRequest(){
}
public BuildRequest copy(){
BuildRequest copy = new BuildRequest();
copy.x = x;
copy.y = y;
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
copy.progress = progress;
copy.initialized = initialized;
copy.animScale = animScale;
return copy;
}
public BuildRequest original(int x, int y, int originalWidth, int originalHeight){
originalX = x;
originalY = y;
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
return this;
}
public Rect bounds(Rect rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);
}else{
return block.bounds(x, y, rect);
}
}
public BuildRequest set(int x, int y, int rotation, Block block){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
return this;
}
public float drawx(){
return x*tilesize + block.offset();
}
public float drawy(){
return y*tilesize + block.offset();
}
public BuildRequest configure(int config){
this.config = config;
this.hasConfig = true;
return this;
}
public @Nullable Tile tile(){
return world.tile(x, y);
}
@Override
public String toString(){
return "BuildRequest{" +
"x=" + x +
", y=" + y +
", rotation=" + rotation +
", recipe=" + block +
", breaking=" + breaking +
", progress=" + progress +
", initialized=" + initialized +
'}';
}
}
}

View File

@@ -1,9 +0,0 @@
package mindustry.entities.traits;
public interface DamageTrait{
float damage();
default void killed(Entity other){
}
}

View File

@@ -1,10 +0,0 @@
package mindustry.entities.traits;
public interface DrawTrait extends Entity{
default float drawSize(){
return 20f;
}
void draw();
}

View File

@@ -1,42 +0,0 @@
package mindustry.entities.traits;
import mindustry.entities.EntityGroup;
public interface Entity extends MoveTrait{
int getID();
void resetID(int id);
default void update(){}
default void removed(){}
default void added(){}
EntityGroup targetGroup();
@SuppressWarnings("unchecked")
default void add(){
if(targetGroup() != null){
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;
}
}

View File

@@ -1,57 +0,0 @@
package mindustry.entities.traits;
import arc.math.Mathf;
public interface HealthTrait{
void health(float health);
float health();
float maxHealth();
boolean isDead();
void setDead(boolean dead);
default void kill(){
health(-1);
damage(1);
}
default void onHit(SolidTrait entity){
}
default void onDeath(){
}
default boolean damaged(){
return health() < maxHealth() - 0.0001f;
}
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);
}
}

View File

@@ -1,5 +0,0 @@
package mindustry.entities.traits;
public interface KillerTrait{
void killed(Entity other);
}

View File

@@ -1,119 +0,0 @@
package mindustry.entities.traits;
import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.Time;
import mindustry.content.*;
import mindustry.entities.Effects;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.gen.Call;
import mindustry.graphics.*;
import mindustry.type.Item;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public interface MinerTrait extends Entity{
/** Returns the range at which this miner can mine blocks.*/
default float getMiningRange(){
return 70f;
}
default boolean isMining(){
return getMineTile() != null;
}
/** Returns the tile this builder is currently mining. */
Tile getMineTile();
/** Sets the tile this builder is currently mining. */
void setMineTile(Tile tile);
/** Returns the mining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc. */
float getMinePower();
/** Returns whether or not this builder can mine a specific item type. */
boolean canMine(Item item);
/** @return whether to offload mined items immediately at the core. if false, items are collected and dropped in a burst. */
default boolean offloadImmediately(){
return false;
}
default void updateMining(){
Unit unit = (Unit)this;
Tile tile = getMineTile();
TileEntity core = unit.getClosestCore();
if(core != null && tile != null && tile.drop() != null && !unit.acceptsItem(tile.drop()) && unit.dst(core) < mineTransferRange){
int accepted = core.tile.block().acceptStack(unit.item().item, unit.item().amount, core.tile, unit);
if(accepted > 0){
Call.transferItemTo(unit.item().item, accepted,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), core.tile);
unit.clearItem();
}
}
if(tile == null || core == null || tile.block() != Blocks.air || dst(tile.worldx(), tile.worldy()) > getMiningRange()
|| tile.drop() == null || !unit.acceptsItem(tile.drop()) || !canMine(tile.drop())){
setMineTile(null);
}else{
Item item = tile.drop();
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(tile.worldx(), tile.worldy()), 0.4f);
if(Mathf.chance(Time.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){
if(unit.dst(core) < mineTransferRange && core.tile.block().acceptStack(item, 1, core.tile, unit) == 1 && offloadImmediately()){
Call.transferItemTo(item, 1,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), core.tile);
}else if(unit.acceptsItem(item)){
//this is clientside, since items are synced anyway
ItemTransfer.transferItemToUnit(item,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f),
unit);
}
}
if(Mathf.chance(0.06 * Time.delta())){
Effects.effect(Fx.pulverizeSmall,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
}
}
default void drawMining(){
Unit unit = (Unit)this;
Tile tile = getMineTile();
if(tile == null) return;
float focusLen = 4f + Mathf.absin(Time.time(), 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
float ex = tile.worldx() + Mathf.sin(Time.time() + 48, swingScl, swingMag);
float ey = tile.worldy() + Mathf.sin(Time.time() + 48, swingScl + 2f, swingMag);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);
if(unit instanceof Player && ((Player)unit).isLocal){
Lines.stroke(1f, Pal.accent);
Lines.poly(tile.worldx(), tile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time());
}
Draw.color();
}
}

View File

@@ -1,20 +0,0 @@
package mindustry.entities.traits;
import 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);
}
}

View File

@@ -1,8 +0,0 @@
package mindustry.entities.traits;
/**
* Marks an entity as serializable.
*/
public interface SaveTrait extends Entity, TypeTrait, Saveable{
byte version();
}

View File

@@ -1,8 +0,0 @@
package mindustry.entities.traits;
import java.io.*;
public interface Saveable{
void writeSave(DataOutput stream) throws IOException;
void readSave(DataInput stream, byte version) throws IOException;
}

View File

@@ -1,13 +0,0 @@
package mindustry.entities.traits;
import arc.util.Interval;
import mindustry.type.Weapon;
public interface ShooterTrait extends VelocityTrait, TeamTrait{
Interval getTimer();
int getShootTimer(boolean left);
Weapon getWeapon();
}

View File

@@ -1,38 +0,0 @@
package mindustry.entities.traits;
import arc.math.geom.*;
import arc.math.geom.QuadTree.QuadTreeObject;
import mindustry.Vars;
public interface SolidTrait extends QuadTreeObject, MoveTrait, VelocityTrait, Entity, Position{
void hitbox(Rect rect);
void hitboxTile(Rect rect);
Vec2 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 collides(SolidTrait other){
return true;
}
default void collision(SolidTrait other, float x, float y){
}
default void move(float x, float y){
Vars.collisions.move(this, x, y);
}
}

View File

@@ -1,18 +0,0 @@
package mindustry.entities.traits;
import arc.math.geom.Position;
import mindustry.entities.type.*;
import mindustry.world.Tile;
public interface SpawnerTrait extends TargetTrait, Position{
Tile getTile();
void updateSpawning(Player unit);
boolean hasUnit(Unit unit);
@Override
default boolean isValid(){
return getTile().entity instanceof SpawnerTrait;
}
}

View File

@@ -1,48 +0,0 @@
package mindustry.entities.traits;
import mindustry.net.Interpolator;
import java.io.*;
public interface SyncTrait extends Entity, TypeTrait{
/** Sets the position of this entity and updated the interpolator. */
default void setNet(float x, float y){
set(x, y);
if(getInterpolator() != null){
getInterpolator().target.set(x, y);
getInterpolator().last.set(x, y);
getInterpolator().pos.set(0, 0);
getInterpolator().updateSpacing = 16;
getInterpolator().lastUpdated = 0;
}
}
/** Interpolate entity position only. Override if you need to interpolate rotations or other values. */
default void interpolate(){
if(getInterpolator() == null){
throw new RuntimeException("This entity must have an interpolator to interpolate()!");
}
getInterpolator().update();
setX(getInterpolator().pos.x);
setY(getInterpolator().pos.y);
}
/** Return the interpolator used for smoothing the position. Optional. */
default Interpolator getInterpolator(){
return null;
}
/** Whether syncing is enabled for this entity; true by default. */
default boolean isSyncing(){
return true;
}
//Read and write sync data, usually position
void write(DataOutput data) throws IOException;
void read(DataInput data) throws IOException;
}

View File

@@ -1,35 +0,0 @@
package mindustry.entities.traits;
import arc.math.geom.Position;
import mindustry.game.Team;
/**
* Base interface for targetable entities.
*/
public interface TargetTrait extends Position, VelocityTrait{
boolean isDead();
Team getTeam();
default float getTargetVelocityX(){
if(this instanceof SolidTrait){
return ((SolidTrait)this).getDeltaX();
}
return velocity().x;
}
default float getTargetVelocityY(){
if(this instanceof SolidTrait){
return ((SolidTrait)this).getDeltaY();
}
return velocity().y;
}
/**
* Whether this entity is a valid target.
*/
default boolean isValid(){
return !isDead();
}
}

View File

@@ -1,7 +0,0 @@
package mindustry.entities.traits;
import mindustry.game.Team;
public interface TeamTrait extends Entity{
Team getTeam();
}

View File

@@ -1,23 +0,0 @@
package mindustry.entities.traits;
import arc.math.*;
import arc.util.Time;
public interface TimeTrait extends Scaled, 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
}

View File

@@ -1,45 +0,0 @@
package mindustry.entities.traits;
import mindustry.type.TypeID;
public interface TypeTrait{
TypeID getTypeID();
/*
int[] lastRegisteredID = {0};
Array<Supplier<? extends TypeTrait>> registeredTypes = new Array<>();
ObjectIntMap<Class<? extends TypeTrait>> typeToID = new ObjectIntMap<>();
/**
* Register and return a type ID. The supplier should return a fresh instace of that type.
static <T extends TypeTrait> void registerType(Class<T> type, Supplier<T> supplier){
if(typeToID.get(type, -1) != -1){
return; //already registered
}
registeredTypes.add(supplier);
int result = lastRegisteredID[0];
typeToID.put(type, result);
lastRegisteredID[0]++;
}
/**Gets a syncable type by ID.
static Supplier<? extends TypeTrait> getTypeByID(int id){
if(id == -1){
throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?");
}
return registeredTypes.get(id);
}
/**
* Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX.
* Do not override!
default int getTypeID(){
int id = typeToID.get(getClass(), -1);
if(id == -1)
throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?");
return id;
}*/
}

View File

@@ -1,36 +0,0 @@
package mindustry.entities.traits;
import arc.math.geom.Vec2;
import arc.util.Time;
public interface VelocityTrait extends MoveTrait{
Vec2 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());
}
}
}

View File

@@ -1,75 +0,0 @@
package mindustry.entities.type;
import mindustry.*;
import mindustry.entities.EntityGroup;
import 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++;
}
public int tileX(){
return Vars.world.toTile(x);
}
public int tileY(){
return Vars.world.toTile(y);
}
@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;
}
/** Increments this entity's ID. Used for pooled entities.*/
public void incrementID(){
id = lastid++;
}
}

View File

@@ -1,417 +0,0 @@
package mindustry.entities.type;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.ContentType;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.type.TypeID;
import mindustry.ui.Cicon;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.defense.DeflectorWall.*;
import mindustry.world.blocks.units.CommandCenter.*;
import mindustry.world.blocks.units.UnitFactory.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
/** Base class for AI units. */
public abstract class BaseUnit extends Unit implements ShooterTrait{
protected static int timerIndex = 0;
protected static final int timerTarget = timerIndex++;
protected static final int timerTarget2 = timerIndex++;
protected static final int timerShootLeft = timerIndex++;
protected static final int timerShootRight = timerIndex++;
protected boolean loaded;
protected UnitType type;
protected Interval timer = new Interval(5);
protected StateMachine state = new StateMachine();
protected TargetTrait target;
protected int spawner = noSpawner;
/** internal constructor used for deserialization, DO NOT USE */
public BaseUnit(){
}
@Remote(called = Loc.server)
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
if(net.server() || !net.active()){
UnitDrops.dropItems(unit);
}
unit.onSuperDeath();
unit.type.deathSound.at(unit);
//visual only.
if(net.client()){
Tile tile = world.tile(unit.spawner);
if(tile != null){
tile.block().unitRemoved(tile, unit);
}
unit.spawner = noSpawner;
}
//must run afterwards so the unit's group is not null when sending the removal packet
Core.app.post(unit::remove);
}
@Override
public float drag(){
return type.drag;
}
@Override
public TypeID getTypeID(){
return type.typeID;
}
@Override
public void onHit(SolidTrait entity){
if(entity instanceof Bullet && ((Bullet)entity).getOwner() instanceof DeflectorEntity && player != null && getTeam() != player.getTeam()){
Core.app.post(() -> {
if(isDead()){
Events.fire(Trigger.phaseDeflectHit);
}
});
}
}
public @Nullable
Tile getSpawner(){
return world.tile(spawner);
}
public boolean isCommanded(){
return indexer.getAllied(team, BlockFlag.comandCenter).size != 0 && indexer.getAllied(team, BlockFlag.comandCenter).first().entity instanceof CommandCenterEntity;
}
public @Nullable UnitCommand getCommand(){
if(isCommanded()){
return indexer.getAllied(team, BlockFlag.comandCenter).first().<CommandCenterEntity>ent().command;
}
return null;
}
/**Called when a command is recieved from the command center.*/
public void onCommand(UnitCommand command){
}
/** Initialize the type and team of this unit. Only call once! */
public void init(UnitType type, Team team){
if(this.type != null) throw new RuntimeException("This unit is already initialized!");
this.type = type;
this.team = team;
}
/** @return whether this unit counts toward the enemy amount in the wave UI. */
public boolean countsAsEnemy(){
return true;
}
public UnitType getType(){
return type;
}
public void setSpawner(Tile tile){
this.spawner = tile.pos();
}
public void rotate(float angle){
rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed);
}
public boolean targetHasFlag(BlockFlag flag){
return (target instanceof TileEntity && ((TileEntity)target).tile.block().flags.contains(flag)) ||
(target instanceof Tile && ((Tile)target).block().flags.contains(flag));
}
public void setState(UnitState state){
this.state.set(state);
}
public boolean retarget(){
return timer.get(timerTarget, 20);
}
/** Only runs when the unit has a target. */
public void behavior(){
}
public void updateTargeting(){
if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team))
|| (target instanceof TileEntity && ((TileEntity)target).tile.entity == null)){
target = null;
}
}
public void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, indexer.getAllied(team, flag));
if(target != null) this.target = target.entity;
}
public void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, indexer.getEnemy(team, flag));
if(target != null) this.target = target.entity;
}
public void targetClosest(){
TargetTrait newTarget = Units.closestTarget(team, x, y, Math.max(getWeapon().bullet.range(), type.range), u -> type.targetAir || !u.isFlying());
if(newTarget != null){
target = newTarget;
}
}
public @Nullable Tile getClosest(BlockFlag flag){
return Geometry.findClosest(x, y, indexer.getAllied(team, flag));
}
public @Nullable Tile getClosestSpawner(){
return Geometry.findClosest(x, y, Vars.spawner.getGroundSpawns());
}
public @Nullable TileEntity getClosestEnemyCore(){
return Vars.state.teams.closestEnemyCore(x, y, team);
}
public UnitState getStartState(){
return null;
}
public boolean isBoss(){
return hasEffect(StatusEffects.boss);
}
@Override
public float getDamageMultipler(){
return status.getDamageMultiplier() * Vars.state.rules.unitDamageMultiplier;
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public boolean isValid(){
return super.isValid() && isAdded();
}
@Override
public Interval getTimer(){
return timer;
}
@Override
public int getShootTimer(boolean left){
return left ? timerShootLeft : timerShootRight;
}
@Override
public Weapon getWeapon(){
return type.weapon;
}
@Override
public TextureRegion getIconRegion(){
return type.icon(Cicon.full);
}
@Override
public int getItemCapacity(){
return type.itemCapacity;
}
@Override
public void interpolate(){
super.interpolate();
if(interpolator.values.length > 0){
rotation = interpolator.values[0];
}
}
@Override
public float maxHealth(){
return type.health * Vars.state.rules.unitHealthMultiplier;
}
@Override
public float mass(){
return type.mass;
}
@Override
public boolean isFlying(){
return type.flying;
}
@Override
public void update(){
if(isDead()){
//dead enemies should get immediately removed
remove();
return;
}
hitTime -= Time.delta();
if(net.client()){
interpolate();
status.update(this);
return;
}
if(!isFlying() && (world.tileWorld(x, y) != null && !(world.tileWorld(x, y).block() instanceof BuildBlock) && world.tileWorld(x, y).solid())){
kill();
}
avoidOthers();
if(spawner != noSpawner && (world.tile(spawner) == null || !(world.tile(spawner).entity instanceof UnitFactoryEntity))){
kill();
}
updateTargeting();
state.update();
updateVelocityStatus();
if(target != null) behavior();
if(!isFlying()){
clampPosition();
}
}
@Override
public void draw(){
}
@Override
public float maxVelocity(){
return type.maxVelocity;
}
@Override
public void removed(){
super.removed();
Tile tile = world.tile(spawner);
if(tile != null && !net.client()){
tile.block().unitRemoved(tile, this);
}
spawner = noSpawner;
}
@Override
public float drawSize(){
return type.hitsize * 10;
}
@Override
public void onDeath(){
Call.onUnitDeath(this);
}
@Override
public void added(){
state.set(getStartState());
if(!loaded){
health(maxHealth());
}
if(isCommanded()){
onCommand(getCommand());
}
}
@Override
public void hitbox(Rect rect){
rect.setSize(type.hitsize).setCenter(x, y);
}
@Override
public void hitboxTile(Rect rect){
rect.setSize(type.hitsizeTile).setCenter(x, y);
}
@Override
public EntityGroup targetGroup(){
return unitGroup;
}
@Override
public byte version(){
return 0;
}
@Override
public void writeSave(DataOutput stream) throws IOException{
super.writeSave(stream);
stream.writeByte(type.id);
stream.writeInt(spawner);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
super.readSave(stream, version);
loaded = true;
byte type = stream.readByte();
this.spawner = stream.readInt();
this.type = content.getByID(ContentType.unit, type);
add();
}
@Override
public void write(DataOutput data) throws IOException{
super.writeSave(data);
data.writeByte(type.id);
data.writeInt(spawner);
}
@Override
public void read(DataInput data) throws IOException{
float lastx = x, lasty = y, lastrot = rotation;
super.readSave(data, version());
this.type = content.getByID(ContentType.unit, data.readByte());
this.spawner = data.readInt();
interpolator.read(lastx, lasty, x, y, rotation);
rotation = lastrot;
x = lastx;
y = lasty;
}
public void onSuperDeath(){
super.onDeath();
}
}

View File

@@ -1,323 +0,0 @@
package mindustry.entities.type;
import mindustry.annotations.Annotations.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.pooling.Pool.*;
import arc.util.pooling.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.game.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Bullet extends SolidEntity implements DamageTrait, Scaled, Poolable, DrawTrait, VelocityTrait, TimeTrait, TeamTrait, AbsorbTrait{
public Interval timer = new Interval(3);
private float lifeScl;
private Team team;
private Object data;
private boolean supressCollision, supressOnce, initialized, deflected;
protected BulletType type;
protected Entity owner;
protected float time;
/** Internal use only! */
public Bullet(){
}
public static Bullet create(BulletType type, TeamTrait owner, float x, float y, float angle){
return create(type, owner, owner.getTeam(), x, y, angle);
}
public static Bullet create(BulletType type, Entity owner, Team team, float x, float y, float angle){
return create(type, owner, team, x, y, angle, 1f);
}
public static Bullet create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){
return create(type, owner, team, x, y, angle, velocityScl, 1f, null);
}
public static Bullet create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, float lifetimeScl){
return create(type, owner, team, x, y, angle, velocityScl, lifetimeScl, null);
}
public static Bullet create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, float lifetimeScl, Object data){
Bullet bullet = Pools.obtain(Bullet.class, Bullet::new);
bullet.type = type;
bullet.owner = owner;
bullet.data = data;
bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl);
if(type.keepVelocity){
bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait)owner).velocity() : Vec2.ZERO);
}
bullet.team = team;
bullet.type = type;
bullet.lifeScl = lifetimeScl;
bullet.set(x - bullet.velocity.x * Time.delta(), y - bullet.velocity.y * Time.delta());
bullet.add();
return bullet;
}
public static Bullet create(BulletType type, Bullet parent, float x, float y, float angle){
return create(type, parent.owner, parent.team, x, y, angle);
}
public static Bullet create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){
return create(type, parent.owner, parent.team, x, y, angle, velocityScl);
}
@Remote(called = Loc.server, unreliable = true)
public static void createBullet(BulletType type, Team team, float x, float y, float angle, float velocityScl, float lifetimeScl){
create(type, null, team, x, y, angle, velocityScl, lifetimeScl, null);
}
public Entity getOwner(){
return owner;
}
public boolean collidesTiles(){
return type.collidesTiles;
}
public void deflect(){
supressCollision = true;
supressOnce = true;
deflected = true;
}
public boolean isDeflected(){
return deflected;
}
public BulletType getBulletType(){
return type;
}
public void resetOwner(Entity entity, Team team){
this.owner = entity;
this.team = team;
}
public void scaleTime(float add){
time += add;
}
public Object getData(){
return data;
}
public void setData(Object data){
this.data = data;
}
public float damageMultiplier(){
if(owner instanceof Unit){
return ((Unit)owner).getDamageMultipler();
}
return 1f;
}
@Override
public void killed(Entity other){
if(owner instanceof KillerTrait){
((KillerTrait)owner).killed(other);
}
}
@Override
public void absorb(){
supressCollision = true;
remove();
}
@Override
public float drawSize(){
return type.drawSize;
}
@Override
public float damage(){
if(owner instanceof Lightning && data instanceof Float){
return (Float)data;
}
return type.damage * damageMultiplier();
}
@Override
public Team getTeam(){
return team;
}
@Override
public float getShieldDamage(){
return Math.max(damage(), type.splashDamage);
}
@Override
public boolean collides(SolidTrait other){
return type.collides && (other != owner && !(other instanceof DamageTrait)) && !supressCollision && !(other instanceof Unit && ((Unit)other).isFlying() && !type.collidesAir);
}
@Override
public void collision(SolidTrait other, float x, float y){
if(!type.pierce) remove();
type.hit(this, x, y);
if(other instanceof Unit){
Unit unit = (Unit)other;
unit.velocity().add(Tmp.v3.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.mass()));
unit.applyEffect(type.status, type.statusDuration);
}
}
@Override
public void update(){
type.update(this);
x += velocity.x * Time.delta();
y += velocity.y * Time.delta();
velocity.scl(Mathf.clamp(1f - type.drag * Time.delta()));
time += Time.delta() * 1f / (lifeScl);
time = Mathf.clamp(time, 0, type.lifetime);
if(time >= type.lifetime){
if(!supressCollision) type.despawned(this);
remove();
}
if(type.hitTiles && collidesTiles() && !supressCollision && initialized){
world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> {
Tile tile = world.ltile(x, y);
if(tile == null) return false;
if(tile.entity != null && tile.entity.collide(this) && type.collides(this, tile) && !tile.entity.isDead() && (type.collidesTeam || tile.getTeam() != team)){
if(tile.getTeam() != team){
tile.entity.collision(this);
}
if(!supressCollision){
type.hitTile(this, tile);
remove();
}
return true;
}
return false;
});
}
if(supressOnce){
supressCollision = false;
supressOnce = false;
}
initialized = true;
}
@Override
public void reset(){
type = null;
owner = null;
velocity.setZero();
time = 0f;
timer.clear();
lifeScl = 1f;
team = null;
data = null;
supressCollision = false;
supressOnce = false;
deflected = false;
initialized = false;
}
@Override
public void hitbox(Rect rect){
rect.setSize(type.hitSize).setCenter(x, y);
}
@Override
public void hitboxTile(Rect rect){
rect.setSize(type.hitSize).setCenter(x, y);
}
@Override
public float lifetime(){
return type.lifetime;
}
@Override
public void time(float time){
this.time = time;
}
@Override
public float time(){
return time;
}
@Override
public void removed(){
Pools.free(this);
}
@Override
public EntityGroup targetGroup(){
return bulletGroup;
}
@Override
public void added(){
type.init(this);
}
@Override
public void draw(){
type.draw(this);
renderer.lights.add(x, y, 16f, Pal.powerLight, 0.3f);
}
@Override
public float fin(){
return time / type.lifetime;
}
@Override
public Vec2 velocity(){
return velocity;
}
public void velocity(float speed, float angle){
velocity.set(0, speed).setAngle(angle);
}
public void limit(float f){
velocity.limit(f);
}
/** Sets the bullet's rotation in degrees. */
public void rot(float angle){
velocity.setAngle(angle);
}
/** @return the bullet's rotation. */
public float rot(){
float angle = Mathf.atan2(velocity.x, velocity.y) * Mathf.radiansToDegrees;
if(angle < 0) angle += 360;
return angle;
}
}

View File

@@ -1,47 +0,0 @@
package mindustry.entities.type;
import mindustry.entities.traits.*;
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){
boolean wasDead = isDead();
onHit(other);
damage(((DamageTrait)other).damage());
if(!wasDead && isDead()){
((DamageTrait)other).killed(this);
}
}
}
@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;
}
}

View File

@@ -1,81 +0,0 @@
package mindustry.entities.type;
import arc.graphics.Color;
import arc.util.pooling.Pool.Poolable;
import arc.util.pooling.Pools;
import mindustry.entities.Effects;
import mindustry.entities.Effects.Effect;
import mindustry.entities.EntityGroup;
import mindustry.entities.traits.DrawTrait;
import mindustry.entities.traits.Entity;
import static mindustry.Vars.effectGroup;
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 EntityGroup targetGroup(){
//this should never actually be called
return effectGroup;
}
@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);
}
}

View File

@@ -1,956 +0,0 @@
package mindustry.entities.type;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.pooling.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.ContentType;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.io.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import java.io.*;
import static mindustry.Vars.*;
public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public static final int timerSync = 2;
public static final int timerAbility = 3;
private static final int timerShootLeft = 0;
private static final int timerShootRight = 1;
private static final float liftoffBoost = 0.2f;
private static final Rect rect = new Rect();
//region instance variables
public float baseRotation;
public float pointerX, pointerY;
public String name = "noname";
public @Nullable String uuid, usid;
public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile, isTyping, isBuilding = true;
public boolean buildWasAutoPaused = false;
public float boostHeat, shootHeat, destructTime;
public boolean achievedFlight;
public Color color = new Color();
public Mech mech = Mechs.starter;
public SpawnerTrait spawner, lastSpawner;
public int respawns;
public @Nullable NetConnection con;
public boolean isLocal = false;
public Interval timer = new Interval(6);
public TargetTrait target;
public TargetTrait moveTarget;
public @Nullable String lastText;
public float textFadeTime;
private float walktime, itemtime;
private Queue<BuildRequest> placeQueue = new Queue<>();
private Tile mining;
private Vec2 movement = new Vec2();
private boolean moved;
//endregion
//region unit and event overrides, utility methods
@Remote(targets = Loc.server, called = Loc.server)
public static void onPlayerDeath(Player player){
if(player == null) return;
player.dead = true;
player.placeQueue.clear();
player.onDeath();
}
@Override
public float getDamageMultipler(){
return status.getDamageMultiplier() * state.rules.playerDamageMultiplier;
}
@Override
public void hitbox(Rect rect){
rect.setSize(mech.hitsize).setCenter(x, y);
}
@Override
public void hitboxTile(Rect rect){
rect.setSize(mech.hitsize * 2f / 3f).setCenter(x, y);
}
@Override
public void onRespawn(Tile tile){
velocity.setZero();
boostHeat = 1f;
achievedFlight = true;
rotation = 90f;
baseRotation = 90f;
dead = false;
spawner = null;
respawns --;
Sounds.respawn.at(tile);
setNet(tile.drawx(), tile.drawy());
clearItem();
heal();
}
@Override
public boolean offloadImmediately(){
return true;
}
@Override
public TypeID getTypeID(){
return TypeIDs.player;
}
@Override
public void move(float x, float y){
if(!mech.flying){
collisions.move(this, x, y);
}else{
moveBy(x, y);
}
}
@Override
public float drag(){
return mech.drag;
}
@Override
public Interval getTimer(){
return timer;
}
@Override
public int getShootTimer(boolean left){
return left ? timerShootLeft : timerShootRight;
}
@Override
public Weapon getWeapon(){
return mech.weapon;
}
@Override
public float getMinePower(){
return mech.mineSpeed;
}
@Override
public TextureRegion getIconRegion(){
return mech.icon(Cicon.full);
}
@Override
public int getItemCapacity(){
return mech.itemCapacity;
}
@Override
public void interpolate(){
super.interpolate();
if(interpolator.values.length > 1){
baseRotation = interpolator.values[1];
}
if(interpolator.target.dst(interpolator.last) > 1f){
walktime += Time.delta();
}
}
@Override
public float getBuildPower(Tile tile){
return mech.buildPower;
}
@Override
public float maxHealth(){
return mech.health * state.rules.playerHealthMultiplier;
}
@Override
public Tile getMineTile(){
return mining;
}
@Override
public void setMineTile(Tile tile){
this.mining = tile;
}
@Override
public boolean canMine(Item item){
return item.hardness <= mech.drillPower;
}
@Override
public float calculateDamage(float amount){
return amount * Mathf.clamp(1f - (status.getArmorMultiplier() + mech.getExtraArmor(this)) / 100f);
}
@Override
public void added(){
baseRotation = 90f;
}
@Override
public float mass(){
return mech.mass;
}
@Override
public boolean isFlying(){
return mech.flying || boostHeat > liftoffBoost;
}
@Override
public void damage(float amount){
hitTime = hitDuration;
if(!net.client()){
health -= calculateDamage(amount);
}
if(health <= 0 && !dead){
Call.onPlayerDeath(this);
}
}
@Override
public void set(float x, float y){
this.x = x;
this.y = y;
}
@Override
public float maxVelocity(){
return mech.maxSpeed;
}
@Override
public Queue<BuildRequest> buildQueue(){
return placeQueue;
}
@Override
public String toString(){
return "Player{" + name + ", mech=" + mech.name + ", id=" + id + ", local=" + isLocal + ", " + x + ", " + y + "}";
}
@Override
public EntityGroup targetGroup(){
return playerGroup;
}
public void setTeam(Team team){
this.team = team;
}
//endregion
//region draw methods
@Override
public float drawSize(){
return isLocal ? Float.MAX_VALUE : 40 + placeDistance;
}
@Override
public void drawShadow(float offsetX, float offsetY){
float scl = mech.flying ? 1f : boostHeat / 2f;
Draw.rect(getIconRegion(), x + offsetX * scl, y + offsetY * scl, rotation - 90);
}
@Override
public void draw(){
if(dead) return;
if(!movement.isZero() && moved && !state.isPaused()){
walktime += movement.len() * getFloorOn().speedMultiplier * 2f;
baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f);
}
float ft = Mathf.sin(walktime, 6f, 2f) * (1f - boostHeat);
Floor floor = getFloorOn();
Draw.color();
Draw.mixcol(Color.white, hitTime / hitDuration);
if(!mech.flying){
if(floor.isLiquid){
Draw.color(Color.white, floor.color, 0.5f);
}
float boostTrnsY = -boostHeat * 3f;
float boostTrnsX = boostHeat * 3f;
float boostAng = boostHeat * 40f;
for(int i : Mathf.signs){
Draw.rect(mech.legRegion,
x + Angles.trnsx(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i),
y + Angles.trnsy(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i),
mech.legRegion.getWidth() * i * Draw.scl,
(mech.legRegion.getHeight() - Mathf.clamp(ft * i, 0, 2)) * Draw.scl,
baseRotation - 90 + boostAng * i);
}
Draw.rect(mech.baseRegion, x, y, baseRotation - 90);
}
if(floor.isLiquid){
Draw.color(Color.white, floor.color, drownTime);
}else{
Draw.color(Color.white);
}
Draw.rect(mech.region, x, y, rotation - 90);
mech.draw(this);
for(int i : Mathf.signs){
float tra = rotation - 90, trY = -mech.weapon.getRecoil(this, i > 0) + mech.weaponOffsetY;
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.region.getHeight() * Draw.scl,
rotation - 90);
}
Draw.reset();
}
public void drawBackItems(){
drawBackItems(itemtime, isLocal);
}
@Override
public void drawStats(){
mech.drawStats(this);
}
@Override
public void drawOver(){
if(dead) return;
if(isBuilding() && isBuilding){
if(!state.isPaused()){
drawBuilding();
}
}else{
drawMining();
}
}
@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(){
BitmapFont font = Fonts.def;
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
final float nameHeight = 11;
final float textHeight = 15;
boolean ints = font.usesIntegerPositions();
font.setUseIntegerPositions(false);
font.getData().setScale(0.25f / Scl.scl(1f));
layout.setText(font, name);
if(!isLocal){
Draw.color(0f, 0f, 0f, 0.3f);
Fill.rect(x, y + nameHeight - layout.height / 2, layout.width + 2, layout.height + 3);
Draw.color();
font.setColor(color);
font.draw(name, x, y + nameHeight, 0, Align.center, false);
if(isAdmin){
float s = 3f;
Draw.color(color.r * 0.5f, color.g * 0.5f, color.b * 0.5f, 1f);
Draw.rect(Icon.adminSmall.getRegion(), x + layout.width / 2f + 2 + 1, y + nameHeight - 1.5f, s, s);
Draw.color(color);
Draw.rect(Icon.adminSmall.getRegion(), x + layout.width / 2f + 2 + 1, y + nameHeight - 1f, s, s);
}
}
if(Core.settings.getBool("playerchat") && ((textFadeTime > 0 && lastText != null) || isTyping)){
String text = textFadeTime <= 0 || lastText == null ? "[LIGHT_GRAY]" + Strings.animated(Time.time(), 4, 15f, ".") : lastText;
float width = 100f;
float visualFadeTime = 1f - Mathf.curve(1f - textFadeTime, 0.9f);
font.setColor(1f, 1f, 1f, textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime);
layout.setText(font, text, Color.white, width, Align.bottom, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(x, y + textHeight + layout.height - layout.height/2f, layout.width + 2, layout.height + 3);
font.draw(text, x - width/2f, y + textHeight + layout.height, width, Align.center, true);
}
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(){
if(!isLocal) return;
for(BuildRequest request : buildQueue()){
if(request.progress > 0.01f || (buildRequest() == request && request.initialized && (dst(request.x * tilesize, request.y * tilesize) <= placeDistance || state.isEditor()))) continue;
request.animScale = 1f;
if(request.breaking){
control.input.drawBreaking(request);
}else{
request.block.drawRequest(request, control.input.allRequests(),
Build.validPlace(getTeam(), request.x, request.y, request.block, request.rotation) || control.input.requestMatches(request));
}
}
Draw.reset();
}
//endregion
//region update methods
@Override
public void updateMechanics(){
if(isBuilding){
updateBuilding();
}
//mine only when not building
if(buildRequest() == null || !isBuilding){
updateMining();
}
}
@Override
public void update(){
hitTime -= Time.delta();
textFadeTime -= Time.delta() / (60 * 5);
itemtime = Mathf.lerpDelta(itemtime, Mathf.num(item.amount > 0), 0.1f);
if(Float.isNaN(x) || Float.isNaN(y)){
velocity.set(0f, 0f);
x = 0;
y = 0;
setDead(true);
}
if(netServer.isWaitingForPlayers()){
setDead(true);
}
if(!isDead() && isOutOfBounds()){
destructTime += Time.delta();
if(destructTime >= boundsCountdown){
kill();
}
}else{
destructTime = 0f;
}
if(!isDead() && isFlying()){
loops.play(Sounds.thruster, this, Mathf.clamp(velocity.len() * 2f) * 0.3f);
}
BuildRequest request = buildRequest();
if(isBuilding() && isBuilding && request.tile() != null && (request.tile().withinDst(x, y, placeDistance) || state.isEditor())){
loops.play(Sounds.build, request.tile(), 0.75f);
}
if(isDead()){
isBoosting = false;
boostHeat = 0f;
if(respawns > 0 || !state.rules.limitedRespawns){
updateRespawning();
}
return;
}else{
spawner = null;
}
if(isLocal || net.server()){
avoidOthers();
}
Tile tile = world.tileWorld(x, y);
boostHeat = Mathf.lerpDelta(boostHeat, (tile != null && tile.solid()) || (isBoosting && ((!movement.isZero() && moved) || !isLocal)) ? 1f : 0f, 0.08f);
shootHeat = Mathf.lerpDelta(shootHeat, isShooting() ? 1f : 0f, 0.06f);
mech.updateAlt(this); //updated regardless
if(boostHeat > liftoffBoost + 0.1f){
achievedFlight = true;
}
if(boostHeat <= liftoffBoost + 0.05f && achievedFlight && !mech.flying){
if(tile != null){
if(mech.shake > 1f){
Effects.shake(mech.shake, mech.shake, this);
}
Effects.effect(Fx.unitLand, tile.floor().color, x, y, tile.floor().isLiquid ? 1f : 0.5f);
}
mech.onLand(this);
achievedFlight = false;
}
if(!isLocal){
interpolate();
updateMechanics(); //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(net.server()){
updateShooting(); //server simulates player shooting
}
return;
}else if(world.isZone()){
//unlock mech when used
data.unlockContent(mech);
}
if(control.input instanceof MobileInput){
updateTouch();
}else{
updateKeyboard();
}
isTyping = ui.chatfrag.shown();
updateMechanics();
if(!mech.flying){
clampPosition();
}
}
protected void updateKeyboard(){
Tile tile = world.tileWorld(x, y);
boolean canMove = !Core.scene.hasKeyboard() || ui.minimapfrag.shown();
isBoosting = Core.input.keyDown(Binding.dash) && !mech.flying;
//if player is in solid block
if(tile != null && tile.solid()){
isBoosting = true;
}
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
if(mech.flying){
//prevent strafing backwards, have a penalty for doing so
float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x
speed *= Mathf.lerp(1f, penalty, Angles.angleDist(rotation, velocity.angle()) / 180f);
}
movement.setZero();
float xa = Core.input.axis(Binding.move_x);
float ya = Core.input.axis(Binding.move_y);
if(!(Core.scene.getKeyboardFocus() instanceof TextField)){
movement.y += ya * speed;
movement.x += xa * speed;
}
if(Core.input.keyDown(Binding.mouse_move)){
movement.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * speed;
movement.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * speed;
}
Vec2 vec = Core.input.mouseWorld(control.input.getMouseX(), control.input.getMouseY());
pointerX = vec.x;
pointerY = vec.y;
updateShooting();
movement.limit(speed).scl(Time.delta());
if(canMove){
velocity.add(movement.x, movement.y);
}else{
isShooting = false;
}
float prex = x, prey = y;
updateVelocityStatus();
moved = dst(prex, prey) > 0.001f;
if(canMove){
float baseLerp = mech.getRotationAlpha(this);
if(!isShooting() || !mech.turnCursor){
if(!movement.isZero()){
rotation = Mathf.slerpDelta(rotation, mech.flying ? velocity.angle() : movement.angle(), 0.13f * baseLerp);
}
}else{
float angle = control.input.mouseAngle(x, y);
this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f * baseLerp);
}
}
}
protected void updateShooting(){
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
if(!mech.turnCursor){
//shoot forward ignoring cursor
mech.weapon.update(this, x + Angles.trnsx(rotation, mech.weapon.targetDistance), y + Angles.trnsy(rotation, mech.weapon.targetDistance));
}else{
mech.weapon.update(this, pointerX, pointerY);
}
}
}
protected void updateTouch(){
if(Units.invalidateTarget(target, this) &&
!(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range() && !(((TileEntity)target).block instanceof BuildBlock))){
target = null;
}
if(state.isEditor()){
target = null;
}
float targetX = Core.camera.position.x, targetY = Core.camera.position.y;
float attractDst = 15f;
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
if(moveTarget != null && !moveTarget.isDead()){
targetX = moveTarget.getX();
targetY = moveTarget.getY();
boolean tapping = moveTarget instanceof TileEntity && moveTarget.getTeam() == team;
attractDst = 0f;
if(tapping){
velocity.setAngle(angleTo(moveTarget));
}
if(dst(moveTarget) <= 2f * Time.delta()){
if(tapping && !isDead()){
Tile tile = ((TileEntity)moveTarget).tile;
tile.block().tapped(tile, this);
}
moveTarget = null;
}
}else{
moveTarget = null;
}
movement.set((targetX - x) / Time.delta(), (targetY - y) / Time.delta()).limit(speed);
movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f));
if(dst(targetX, targetY) < attractDst){
movement.setZero();
}
float expansion = 3f;
hitbox(rect);
rect.x -= expansion;
rect.y -= expansion;
rect.width += expansion * 2f;
rect.height += expansion * 2f;
isBoosting = collisions.overlapsTile(rect) || dst(targetX, targetY) > 85f;
velocity.add(movement.scl(Time.delta()));
if(velocity.len() <= 0.2f && mech.flying){
rotation += Mathf.sin(Time.time() + id * 99, 10f, 1f);
}else if(target == null){
rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len() / 10f);
}
float lx = x, ly = y;
updateVelocityStatus();
moved = dst(lx, ly) > 0.001f;
if(mech.flying){
//hovering effect
x += Mathf.sin(Time.time() + id * 999, 25f, 0.08f);
y += Mathf.cos(Time.time() + id * 999, 25f, 0.08f);
}
//update shooting if not building, not mining and there's ammo left
if(!isBuilding() && getMineTile() == null){
//autofire
if(target == null){
isShooting = false;
if(Core.settings.getBool("autotarget")){
target = Units.closestTarget(team, x, y, getWeapon().bullet.range(), u -> u.getTeam() != Team.derelict, u -> u.getTeam() != Team.derelict);
if(mech.canHeal && target == null){
target = Geometry.findClosest(x, y, indexer.getDamaged(Team.sharded));
if(target != null && dst(target) > getWeapon().bullet.range()){
target = null;
}else if(target != null){
target = ((Tile)target).entity;
}
}
if(target != null){
setMineTile(null);
}
}
}else if(target.isValid() || (target instanceof TileEntity && ((TileEntity)target).damaged() && target.getTeam() == team &&
mech.canHeal && dst(target) < getWeapon().bullet.range())){
//rotate toward and shoot the target
if(mech.turnCursor){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
}
Vec2 intercept = Predict.intercept(this, target, getWeapon().bullet.speed);
pointerX = intercept.x;
pointerY = intercept.y;
updateShooting();
isShooting = true;
}
}
}
//endregion
//region utility methods
public void sendMessage(String text){
if(isLocal){
if(Vars.ui != null){
Vars.ui.chatfrag.addMessage(text, null);
}
}else{
Call.sendMessage(con, text, null, null);
}
}
public void sendMessage(String text, Player from){
sendMessage(text, from, NetClient.colorizeName(from.id, from.name));
}
public void sendMessage(String text, Player from, String fromName){
if(isLocal){
if(Vars.ui != null){
Vars.ui.chatfrag.addMessage(text, fromName);
}
}else{
Call.sendMessage(con, text, fromName, from);
}
}
public PlayerInfo getInfo(){
if(uuid == null){
throw new IllegalArgumentException("Local players cannot be traced and do not have info.");
}else{
return netServer.admins.getInfo(uuid);
}
}
/** Resets all values of the player. */
public void reset(){
resetNoAdd();
add();
}
public void resetNoAdd(){
status.clear();
team = Team.sharded;
item.amount = 0;
placeQueue.clear();
dead = true;
lastText = null;
isBuilding = true;
textFadeTime = 0f;
target = null;
moveTarget = null;
isShooting = isBoosting = isTransferring = isTyping = false;
spawner = lastSpawner = null;
health = maxHealth();
mining = null;
boostHeat = drownTime = hitTime = 0f;
mech = Mechs.starter;
placeQueue.clear();
respawns = state.rules.respawns;
}
public boolean isShooting(){
return isShooting && (boostHeat < 0.1f || mech.flying) && mining == null;
}
public void updateRespawning(){
if(state.isEditor()){
//instant respawn at center of map.
set(world.width() * tilesize/2f, world.height() * tilesize/2f);
setDead(false);
}else if(spawner != null && spawner.isValid()){
spawner.updateSpawning(this);
}else if(!netServer.isWaitingForPlayers()){
if(!net.client()){
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;
this.lastSpawner = spawner;
this.dead = true;
setNet(spawner.getX(), spawner.getY());
spawner.updateSpawning(this);
}
//endregion
//region read and write methods
@Override
public byte version(){
return 0;
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeBoolean(isLocal);
if(isLocal){
stream.writeByte(mech.id);
stream.writeInt(lastSpawner == null ? noSpawner : lastSpawner.getTile().pos());
super.writeSave(stream, false);
}
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
boolean local = stream.readBoolean();
if(local){
byte mechid = stream.readByte();
int spawner = stream.readInt();
Tile stile = world.tile(spawner);
Player player = headless ? this : Vars.player;
player.readSaveSuper(stream, version);
player.mech = content.getByID(ContentType.mech, mechid);
player.dead = false;
if(stile != null && stile.entity instanceof SpawnerTrait){
player.lastSpawner = (SpawnerTrait)stile.entity;
}
}
}
private void readSaveSuper(DataInput stream, byte version) throws IOException{
super.readSave(stream, version);
add();
}
@Override
public void write(DataOutput buffer) throws IOException{
super.writeSave(buffer, !isLocal);
TypeIO.writeStringData(buffer, name);
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3)| (Pack.byteValue(isBuilding) << 4));
buffer.writeInt(color.rgba());
buffer.writeByte(mech.id);
buffer.writeInt(mining == null ? noSpawner : mining.pos());
buffer.writeInt(spawner == null || !spawner.hasUnit(this) ? noSpawner : spawner.getTile().pos());
buffer.writeShort((short)(baseRotation * 2));
writeBuilding(buffer);
}
@Override
public void read(DataInput buffer) throws IOException{
float lastx = x, lasty = y, lastrot = rotation, lastvx = velocity.x, lastvy = velocity.y;
super.readSave(buffer, version());
name = TypeIO.readStringData(buffer);
byte bools = buffer.readByte();
isAdmin = (bools & 1) != 0;
dead = (bools & 2) != 0;
boolean boosting = (bools & 4) != 0;
isTyping = (bools & 8) != 0;
boolean building = (bools & 16) != 0;
color.set(buffer.readInt());
mech = content.getByID(ContentType.mech, buffer.readByte());
int mine = buffer.readInt();
int spawner = buffer.readInt();
float baseRotation = buffer.readShort() / 2f;
readBuilding(buffer, !isLocal);
interpolator.read(lastx, lasty, x, y, rotation, baseRotation);
rotation = lastrot;
x = lastx;
y = lasty;
if(isLocal){
velocity.x = lastvx;
velocity.y = lastvy;
}else{
mining = world.tile(mine);
isBuilding = building;
isBoosting = boosting;
}
Tile tile = world.tile(spawner);
if(tile != null && tile.entity instanceof SpawnerTrait){
this.spawner = (SpawnerTrait)tile.entity;
}else{
this.spawner = null;
}
}
//endregion
}

View File

@@ -1,19 +0,0 @@
package mindustry.entities.type;
import arc.math.geom.Vec2;
import mindustry.entities.traits.SolidTrait;
public abstract class SolidEntity extends BaseEntity implements SolidTrait{
protected transient Vec2 velocity = new Vec2(0f, 0.0001f);
private transient Vec2 lastPosition = new Vec2();
@Override
public Vec2 lastPosition(){
return lastPosition;
}
@Override
public Vec2 velocity(){
return velocity;
}
}

View File

@@ -1,356 +0,0 @@
package mindustry.entities.type;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import arc.Events;
import arc.struct.Array;
import arc.struct.ObjectSet;
import arc.math.geom.Point2;
import arc.math.geom.Vec2;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.EntityGroup;
import mindustry.entities.traits.HealthTrait;
import mindustry.entities.traits.TargetTrait;
import mindustry.game.*;
import mindustry.game.EventType.BlockDestroyEvent;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.modules.*;
import java.io.*;
import static mindustry.Vars.*;
public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
public static final float timeToSleep = 60f * 1; //1 second to fall asleep
private static final ObjectSet<Tile> tmpTiles = new ObjectSet<>();
/** This value is only used for debugging. */
public static int sleepingEntities = 0;
public Tile tile;
public Block block;
public Interval timer;
public float health;
public float timeScale = 1f, timeScaleDuration;
public PowerModule power;
public ItemModule items;
public LiquidModule liquids;
public @Nullable ConsumeModule cons;
/** List of (cached) tiles with entities in proximity, used for outputting to */
private Array<Tile> proximity = new Array<>(8);
private boolean dead = false;
private boolean sleeping;
private float sleepTime;
private @Nullable SoundLoop sound;
@Remote(called = Loc.server, unreliable = true)
public static void onTileDamage(Tile tile, float health){
if(tile.entity != null){
tile.entity.health = health;
if(tile.entity.damaged()){
indexer.notifyTileDamaged(tile.entity);
}
}
}
@Remote(called = Loc.server)
public static void onTileDestroyed(Tile tile){
if(tile.entity == null) return;
tile.entity.onDeath();
}
/** Sets this tile entity data to this tile, and adds it if necessary. */
public TileEntity init(Tile tile, boolean shouldAdd){
this.tile = tile;
x = tile.drawx();
y = tile.drawy();
block = tile.block();
if(block.activeSound != Sounds.none){
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
}
health = block.health;
timer = new Interval(block.timers);
if(shouldAdd){
add();
}
return this;
}
/** Scaled delta. */
public float delta(){
return Time.delta() * timeScale;
}
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
public float efficiency(){
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
/** Call when nothing is happening to the entity. This increments the internal sleep timer. */
public void sleep(){
sleepTime += Time.delta();
if(!sleeping && sleepTime >= timeToSleep){
remove();
sleeping = true;
sleepingEntities++;
}
}
/** Call when this entity is updating. This wakes it up. */
public void noSleep(){
sleepTime = 0f;
if(sleeping){
add();
sleeping = false;
sleepingEntities--;
}
}
public boolean isSleeping(){
return sleeping;
}
public boolean isDead(){
return dead || tile.entity != this;
}
@CallSuper
public void write(DataOutput stream) throws IOException{
stream.writeShort((short)health);
stream.writeByte(Pack.byteByte((byte)8, tile.rotation())); //rotation + marker to indicate that team is moved (8 isn't valid)
stream.writeByte(tile.getTeamID());
if(items != null) items.write(stream);
if(power != null) power.write(stream);
if(liquids != null) liquids.write(stream);
if(cons != null) cons.write(stream);
}
@CallSuper
public void read(DataInput stream, byte revision) throws IOException{
health = stream.readUnsignedShort();
byte packedrot = stream.readByte();
byte team = Pack.leftByte(packedrot) == 8 ? stream.readByte() : Pack.leftByte(packedrot);
byte rotation = Pack.rightByte(packedrot);
tile.setTeam(Team.get(team));
tile.rotation(rotation);
if(items != null) items.read(stream);
if(power != null) power.read(stream);
if(liquids != null) liquids.read(stream);
if(cons != null) cons.read(stream);
}
/** Returns the version of this TileEntity IO code.*/
public byte version(){
return 0;
}
public boolean collide(Bullet other){
return true;
}
public void collision(Bullet other){
block.handleBulletHit(this, other);
}
public void kill(){
Call.onTileDestroyed(tile);
}
@Override
public void damage(float damage){
if(dead) return;
if(Mathf.zero(state.rules.blockHealthMultiplier)){
damage = health + 1;
}else{
damage /= state.rules.blockHealthMultiplier;
}
float preHealth = health;
Call.onTileDamage(tile, health - block.handleDamage(tile, damage));
if(health <= 0){
Call.onTileDestroyed(tile);
}
if(preHealth >= maxHealth() - 0.00001f && health < maxHealth() && world != null){ //when just damaged
indexer.notifyTileDamaged(this);
}
}
public Tile getTile(){
return tile;
}
public void removeFromProximity(){
block.onProximityRemoved(tile);
Point2[] nearby = Edges.getEdges(block.size);
for(Point2 point : nearby){
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
//remove this tile from all nearby tile's proximities
if(other != null){
other.block().onProximityUpdate(other);
if(other.entity != null){
other.entity.proximity.remove(tile, true);
}
}
}
}
public void updateProximity(){
tmpTiles.clear();
proximity.clear();
Point2[] nearby = Edges.getEdges(block.size);
for(Point2 point : nearby){
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
if(other == null) continue;
if(other.entity == null || !(other.interactable(tile.getTeam()))) continue;
//add this tile to proximity of nearby tiles
if(!other.entity.proximity.contains(tile, true)){
other.entity.proximity.add(tile);
}
tmpTiles.add(other);
}
//using a set to prevent duplicates
for(Tile tile : tmpTiles){
proximity.add(tile);
}
block.onProximityAdded(tile);
block.onProximityUpdate(tile);
for(Tile other : tmpTiles){
other.block().onProximityUpdate(other);
}
}
public Array<Tile> proximity(){
return proximity;
}
/** Tile configuration. Defaults to 0. Used for block rebuilding. */
public int config(){
return 0;
}
@Override
public void removed(){
if(sound != null){
sound.stop();
}
}
@Override
public void health(float health){
this.health = health;
}
@Override
public float health(){
return health;
}
@Override
public float maxHealth(){
return block.health;
}
@Override
public void setDead(boolean dead){
this.dead = dead;
}
@Override
public void onDeath(){
if(!dead){
dead = true;
Events.fire(new BlockDestroyEvent(tile));
block.breakSound.at(tile);
block.onDestroyed(tile);
tile.remove();
remove();
}
}
@Override
public Team getTeam(){
return tile.getTeam();
}
@Override
public Vec2 velocity(){
return Vec2.ZERO;
}
@Override
public void update(){
timeScaleDuration -= Time.delta();
if(timeScaleDuration <= 0f || !block.canOverdrive){
timeScale = 1f;
}
if(health <= 0){
onDeath();
return; //no need to update anymore
}
if(sound != null){
sound.update(x, y, block.shouldActiveSound(tile));
}
if(block.idleSound != Sounds.none && block.shouldIdleSound(tile)){
loops.play(block.idleSound, this, block.idleSoundVolume);
}
block.update(tile);
if(liquids != null){
liquids.update();
}
if(cons != null){
cons.update();
}
if(power != null){
power.graph.update();
}
}
@Override
public boolean isValid(){
return !isDead() && tile.entity == this;
}
@Override
public EntityGroup targetGroup(){
return tileGroup;
}
@Override
public String toString(){
return "TileEntity{" +
"tile=" + tile +
", health=" + health +
'}';
}
}

View File

@@ -1,33 +0,0 @@
package mindustry.entities.type;
import arc.util.pooling.Pool.*;
import mindustry.entities.traits.*;
public abstract class TimedEntity extends BaseEntity implements 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();
}
}

View File

@@ -1,459 +0,0 @@
package mindustry.entities.type;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import java.io.*;
import static mindustry.Vars.*;
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. */
public static final float velocityPercision = 8f;
/** Maximum absolute value of a velocity vector component. */
public static final float maxAbsVelocity = 127f / velocityPercision;
public static final int noSpawner = Pos.get(-1, 1);
private static final Vec2 moveVector = new Vec2();
public float rotation;
protected final Interpolator interpolator = new Interpolator();
protected final Statuses status = new Statuses();
protected final ItemStack item = new ItemStack(content.item(0), 0);
protected Team team = Team.sharded;
protected float drownTime, hitTime;
@Override
public boolean collidesGrid(int x, int y){
return !isFlying();
}
@Override
public Team getTeam(){
return team;
}
@Override
public void interpolate(){
interpolator.update();
x = interpolator.pos.x;
y = interpolator.pos.y;
if(interpolator.values.length > 0){
rotation = interpolator.values[0];
}
}
@Override
public Interpolator getInterpolator(){
return interpolator;
}
@Override
public void damage(float amount){
if(!net.client()){
super.damage(calculateDamage(amount));
}
hitTime = hitDuration;
}
@Override
public boolean collides(SolidTrait other){
if(isDead()) return false;
if(other instanceof DamageTrait){
return other instanceof TeamTrait && (((TeamTrait)other).getTeam()).isEnemy(team);
}else{
return other instanceof Unit && ((Unit)other).isFlying() == isFlying();
}
}
@Override
public void onDeath(){
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);
Sounds.bang.at(this);
item.amount = 0;
drownTime = 0f;
status.clear();
Events.fire(new UnitDestroyEvent(this));
if(explosiveness > 7f && this == player){
Events.fire(Trigger.suicideBomb);
}
}
@Override
public Vec2 velocity(){
return velocity;
}
@Override
public void move(float x, float y){
if(!isFlying()){
super.move(x, y);
}else{
moveBy(x, y);
}
}
@Override
public boolean isValid(){
return !isDead() && isAdded();
}
@Override
public void writeSave(DataOutput stream) throws IOException{
writeSave(stream, false);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
byte team = stream.readByte();
boolean dead = stream.readBoolean();
float x = stream.readFloat();
float y = stream.readFloat();
byte xv = stream.readByte();
byte yv = stream.readByte();
float rotation = stream.readShort() / 2f;
int health = stream.readShort();
byte itemID = stream.readByte();
short itemAmount = stream.readShort();
this.status.readSave(stream, version);
this.item.amount = itemAmount;
this.item.item = content.item(itemID);
this.dead = dead;
this.team = Team.get(team);
this.health = health;
this.x = x;
this.y = y;
this.velocity.set(xv / velocityPercision, yv / velocityPercision);
this.rotation = rotation;
}
public void writeSave(DataOutput stream, boolean net) throws IOException{
if(item.item == null) item.item = Items.copper;
stream.writeByte(team.id);
stream.writeBoolean(isDead());
stream.writeFloat(net ? interpolator.target.x : x);
stream.writeFloat(net ? interpolator.target.y : y);
stream.writeByte((byte)(Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeByte((byte)(Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision));
stream.writeShort((short)(rotation * 2));
stream.writeShort((short)health);
stream.writeByte(item.item.id);
stream.writeShort((short)item.amount);
status.writeSave(stream);
}
protected void clampPosition(){
x = Mathf.clamp(x, 0, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize - tilesize);
}
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 - status.getArmorMultiplier() / 100f);
}
public float getDamageMultipler(){
return status.getDamageMultiplier();
}
public boolean hasEffect(StatusEffect effect){
return status.hasEffect(effect);
}
public void avoidOthers(){
float radScl = 1.5f;
float fsize = getSize() / radScl;
moveVector.setZero();
float cx = x - fsize/2f, cy = y - fsize/2f;
avoid(unitGroup.intersect(cx, cy, fsize, fsize));
if(!(this instanceof Player)){
avoid(playerGroup.intersect(cx, cy, fsize, fsize));
}
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
}
private void avoid(Array<? extends Unit> arr){
float radScl = 1.5f;
for(Unit en : arr){
if(en.isFlying() != isFlying() || (en instanceof Player && en.getTeam() != getTeam()) || (this instanceof Player && en.isFlying())) continue;
float dst = dst(en);
float scl = Mathf.clamp(1f - dst / (getSize()/(radScl*2f) + en.getSize()/(radScl*2f)));
moveVector.add(Tmp.v1.set((x - en.x) * scl, (y - en.y) * scl).limit(0.4f));
}
}
public @Nullable TileEntity getClosestCore(){
return state.teams.closestCore(x, y, team);
}
public Floor getFloorOn(){
Tile tile = world.tileWorld(x, y);
return tile == null ? (Floor)Blocks.air : tile.floor();
}
public @Nullable Tile tileOn(){
return world.tileWorld(x, y);
}
public void onRespawn(Tile tile){
}
/** Updates velocity and status effects. */
public void updateVelocityStatus(){
Floor floor = getFloorOn();
Tile tile = world.tileWorld(x, y);
status.update(this);
item.amount = Mathf.clamp(this.item.amount, 0, getItemCapacity());
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();
}
//apply knockback based on spawns
if(getTeam() != state.rules.waveTeam){
float relativeSize = state.rules.dropZoneRadius + getSize()/2f + 1f;
for(Tile spawn : spawner.getGroundSpawns()){
if(withinDst(spawn.worldx(), spawn.worldy(), relativeSize)){
velocity.add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta()));
}
}
}
//repel player out of bounds
final float warpDst = 180f;
if(x < 0) velocity.x += (-x/warpDst);
if(y < 0) velocity.y += (-y/warpDst);
if(x > world.unitWidth()) velocity.x -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) velocity.y -= (y - world.unitHeight())/warpDst;
if(isFlying()){
drownTime = 0f;
move(velocity.x * Time.delta(), velocity.y * Time.delta());
}else{
boolean onLiquid = floor.isLiquid;
if(tile != null){
tile.block().unitOn(tile, this);
if(tile.block() != Blocks.air){
onLiquid = false;
}
}
if(onLiquid && velocity.len() > 0.4f && Mathf.chance((velocity.len() * floor.speedMultiplier) * 0.06f * Time.delta())){
Effects.effect(floor.walkEffect, floor.color, x, y);
}
if(onLiquid){
status.handleApply(this, floor.status, floor.statusDuration);
if(floor.damageTaken > 0f){
damagePeriodic(floor.damageTaken);
}
}
if(onLiquid && floor.drownTime > 0){
drownTime += Time.delta() * 1f / floor.drownTime;
if(Mathf.chance(Time.delta() * 0.05f)){
Effects.effect(floor.drownUpdateEffect, floor.color, x, y);
}
}else{
drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f);
}
drownTime = Mathf.clamp(drownTime);
if(drownTime >= 0.999f && !net.client()){
damage(health + 1);
if(this == player){
Events.fire(Trigger.drown);
}
}
float px = x, py = y;
move(velocity.x * floor.speedMultiplier * Time.delta(), velocity.y * floor.speedMultiplier * Time.delta());
if(Math.abs(px - x) <= 0.0001f) velocity.x = 0f;
if(Math.abs(py - y) <= 0.0001f) velocity.y = 0f;
}
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;
this.item.amount = Mathf.clamp(this.item.amount, 0, getItemCapacity());
}
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);
}
public void damagePeriodic(float amount){
damage(amount * Time.delta(), hitTime <= -20 + hitDuration);
}
public void damage(float amount, boolean withEffect){
float pre = hitTime;
damage(amount);
if(!withEffect){
hitTime = pre;
}
}
public void drawUnder(){
}
public void drawOver(){
}
public void drawStats(){
Draw.color(Color.black, team.color, healthf() + Mathf.absin(Time.time(), Math.max(healthf() * 5f, 1f), 1f - healthf()));
Draw.rect(getPowerCellRegion(), x, y, rotation - 90);
Draw.color();
drawBackItems(item.amount > 0 ? 1f : 0f, false);
drawLight();
}
public void drawLight(){
renderer.lights.add(x, y, 50f, Pal.powerLight, 0.6f);
}
public void drawBackItems(float itemtime, boolean number){
//draw back items
if(itemtime > 0.01f && item.item != null){
float backTrns = 5f;
float size = (itemSize + Mathf.absin(Time.time(), 5f, 1f)) * itemtime;
Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f));
Draw.rect(item.item.icon(Cicon.medium),
x + Angles.trnsx(rotation + 180f, backTrns),
y + Angles.trnsy(rotation + 180f, backTrns),
size, size, rotation);
Draw.mixcol();
Lines.stroke(1f, Pal.accent);
Lines.circle(
x + Angles.trnsx(rotation + 180f, backTrns),
y + Angles.trnsy(rotation + 180f, backTrns),
(3f + Mathf.absin(Time.time(), 5f, 1f)) * itemtime);
if(number){
Fonts.outline.draw(item.amount + "",
x + Angles.trnsx(rotation + 180f, backTrns),
y + Angles.trnsy(rotation + 180f, backTrns) - 3,
Pal.accent, 0.25f * itemtime / Scl.scl(1f), false, Align.center
);
}
}
Draw.reset();
}
public TextureRegion getPowerCellRegion(){
return Core.atlas.find("power-cell");
}
public void drawAll(){
if(!isDead()){
draw();
drawStats();
}
}
public void drawShadow(float offsetX, float offsetY){
Draw.rect(getIconRegion(), x + offsetX, y + offsetY, rotation - 90);
}
public float getSize(){
hitbox(Tmp.r1);
return Math.max(Tmp.r1.width, Tmp.r1.height) * 2f;
}
public abstract TextureRegion getIconRegion();
public abstract Weapon getWeapon();
public abstract int getItemCapacity();
public abstract float mass();
public abstract boolean isFlying();
}

View File

@@ -1,67 +0,0 @@
package mindustry.entities.type.base;
import arc.math.Mathf;
import arc.math.geom.Geometry;
import mindustry.entities.units.*;
import mindustry.world.Tile;
import mindustry.world.meta.BlockFlag;
import static mindustry.Vars.*;
public abstract class BaseDrone extends FlyingUnit{
public final UnitState retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= maxHealth()){
state.set(getStartState());
}else if(!targetHasFlag(BlockFlag.repair)){
if(retarget()){
Tile repairPoint = Geometry.findClosest(x, y, indexer.getAllied(team, BlockFlag.repair));
if(repairPoint != null){
target = repairPoint;
}else{
setState(getStartState());
}
}
}else{
circle(40f);
}
}
};
public boolean countsAsEnemy(){
return false;
}
@Override
public void onCommand(UnitCommand command){
//do nothing, normal commands are not applicable here
}
@Override
protected void updateRotation(){
if(target != null && shouldRotate() && target.dst(this) < type.range){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f);
}
}
@Override
public void behavior(){
if(health <= maxHealth() * type.retreatPercent && !state.is(retreat) && Geometry.findClosest(x, y, indexer.getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}
public boolean shouldRotate(){
return state.is(getStartState());
}
@Override
public abstract UnitState getStartState();
}

View File

@@ -1,239 +0,0 @@
package mindustry.entities.type.base;
import arc.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.Teams.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import java.io.*;
import static mindustry.Vars.*;
public class BuilderDrone extends BaseDrone implements BuilderTrait{
private static final StaticReset reset = new StaticReset();
private static final IntIntMap totals = new IntIntMap();
protected Queue<BuildRequest> placeQueue = new Queue<>();
protected BuildRequest lastFound;
protected boolean isBreaking;
protected Player playerTarget;
public final UnitState
build = new UnitState(){
public void entered(){
if(!(target instanceof BuildEntity)){
target = null;
}
}
public void update(){
BuildEntity entity = (BuildEntity)target;
TileEntity core = getClosestCore();
if(isBuilding() && entity == null && canRebuild()){
target = world.tile(buildRequest().x, buildRequest().y);
circle(placeDistance * 0.7f);
target = null;
BuildRequest request = buildRequest();
if(world.tile(request.x, request.y).entity instanceof BuildEntity){
target = world.tile(request.x, request.y).entity;
}
}else if(entity != null && core != null && (entity.progress < 1f || entity.progress > 0f) && entity.tile.block() instanceof BuildBlock){ //building is valid
if(!isBuilding() && dst(target) < placeDistance * 0.9f){ //within distance, begin placing
if(isBreaking){
buildQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y));
}else{
buildQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.rotation(), entity.cblock));
if(lastFound != null && lastFound.hasConfig){
buildQueue().last().configure(lastFound.config);
}
}
}
circle(placeDistance * 0.7f);
velocity.scl(0.74f);
}else{ //else, building isn't valid, follow a player
target = null;
if(playerTarget == null || playerTarget.getTeam() != team || !playerTarget.isValid()){
playerTarget = null;
if(retarget()){
float minDst = Float.POSITIVE_INFINITY;
int minDrones = Integer.MAX_VALUE;
//find player with min amount of drones
for(Player player : playerGroup.all()){
if(player.getTeam() == team){
int drones = getDrones(player);
float dst = dst2(player);
if(playerTarget == null || drones < minDrones || (drones == minDrones && dst < minDst)){
minDrones = drones;
minDst = dst;
playerTarget = player;
}
}
}
}
if(getSpawner() != null){
target = getSpawner();
circle(40f);
target = null;
}
}else{
incDrones(playerTarget);
TargetTrait prev = target;
target = playerTarget;
float dst = 90f + (id % 10)*3;
float tdst = dst(target);
float scale = (Mathf.lerp(1f, 0.2f, 1f - Mathf.clamp((tdst - dst) / dst)));
circle(dst);
velocity.scl(scale);
target = prev;
}
}
}
};
public BuilderDrone(){
if(reset.check()){
Events.on(BuildSelectEvent.class, event -> {
if(!(event.tile.entity instanceof BuildEntity)) return;
for(BaseUnit unit : unitGroup.all()){
if(unit instanceof BuilderDrone && unit.getTeam() == getTeam()){
BuilderDrone drone = (BuilderDrone)unit;
if(drone.isBuilding()){
//stop building if opposite building begins.
BuildRequest req = drone.buildRequest();
if(req.breaking != event.breaking && req.x == event.tile.x && req.y == event.tile.y){
drone.clearBuilding();
drone.target = null;
}
}
}
}
});
}
}
int getDrones(Player player){
return Pack.leftShort(totals.get(player.id, 0));
}
void incDrones(Player player){
int num = totals.get(player.id, 0);
int amount = Pack.leftShort(num), frame = Pack.rightShort(num);
short curFrame = (short)(Core.graphics.getFrameId() % Short.MAX_VALUE);
if(frame != curFrame){
totals.put(player.id, Pack.shortInt((short)1, curFrame));
}else{
totals.put(player.id, Pack.shortInt((short)(amount + 1), curFrame));
}
}
boolean canRebuild(){
return true;
}
@Override
public float getBuildPower(Tile tile){
return type.buildPower;
}
@Override
public Queue<BuildRequest> buildQueue(){
return placeQueue;
}
@Override
public void update(){
super.update();
if(!isBuilding() && timer.get(timerTarget2, 15)){
for(Player player : playerGroup.all()){
if(player.getTeam() == team && player.buildRequest() != null){
BuildRequest req = player.buildRequest();
Tile tile = world.tile(req.x, req.y);
if(tile != null && tile.entity instanceof BuildEntity){
BuildEntity b = tile.ent();
float dist = Math.min(b.dst(x, y) - placeDistance, 0);
if(dist / type.maxVelocity < b.buildCost * 0.9f){
lastFound = req;
target = b;
this.isBreaking = req.breaking;
setState(build);
break;
}
}
}
}
if(timer.get(timerTarget, 80) && Units.closestEnemy(getTeam(), x, y, 100f, u -> !(u instanceof BaseDrone)) == null && !isBuilding()){
TeamData data = team.data();
if(!data.brokenBlocks.isEmpty()){
BrokenBlock block = data.brokenBlocks.removeLast();
if(Build.validPlace(getTeam(), block.x, block.y, content.block(block.block), block.rotation)){
placeQueue.addFirst(new BuildRequest(block.x, block.y, block.rotation, content.block(block.block)).configure(block.config));
setState(build);
}
}
}
}
updateBuilding();
}
@Override
public boolean shouldRotate(){
return isBuilding();
}
@Override
public UnitState getStartState(){
return build;
}
@Override
public void drawOver(){
drawBuilding();
}
@Override
public float drawSize(){
return isBuilding() ? placeDistance * 2f : 30f;
}
@Override
public boolean canCreateBlocks(){
return true;
}
@Override
public void write(DataOutput data) throws IOException{
super.write(data);
writeBuilding(data);
}
@Override
public void read(DataInput data) throws IOException{
super.read(data);
readBuilding(data);
}
}

Some files were not shown because too many files have changed in this diff Show More