Merge remote-tracking branch 'upstream/master' into burning-affects-tiles

This commit is contained in:
Leonwang4234
2020-10-02 17:47:11 -07:00
895 changed files with 41849 additions and 24819 deletions

View File

@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
/** Utility class for damaging in an area. */
public class Damage{
private static Tile furthest;
private static Rect rect = new Rect();
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2();
@@ -30,24 +31,26 @@ public class Damage{
private static Unit tmpUnit;
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color, boolean damage){
if(damage){
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)));
}
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), Bullets.fireball.damage, 1, 1));
}
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), Bullets.fireball.damage, 1, 1));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
}
}
if(explosiveness > 15f){
@@ -59,7 +62,7 @@ public class Damage{
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effect.shake(shake, shake, x, y);
Fx.dynamicExplosion.at(x, y, radius / 8f);
}
@@ -74,6 +77,28 @@ public class Damage{
}
}
public static float findLaserLength(Bullet b, float length){
Tmp.v1.trns(b.rotation(), length);
furthest = null;
boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
}
/** Collides a bullet with blocks in a laser, taking into account absorption blocks. Resulting length is stored in the bullet's fdata. */
public static float collideLaser(Bullet b, float length, boolean large){
float resultLength = findLaserLength(b, length);
collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength, large);
b.fdata = resultLength;
return resultLength;
}
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
collideLine(hitter, team, effect, x, y, angle, length, false);
}
@@ -83,11 +108,13 @@ public class Damage{
* 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){
length = findLaserLength(hitter, length);
collidedBlocks.clear();
tr.trns(angle, length);
Intc2 collider = (cx, cy) -> {
Building tile = world.build(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team != team && tile.collide(hitter)){
tile.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.type.hit(hitter, tile.x, tile.y);
@@ -130,13 +157,8 @@ public class Damage{
if(!e.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround)) return;
e.hitbox(hitrect);
Rect other = hitrect;
other.y -= expand;
other.x -= expand;
other.width += expand * 2;
other.height += expand * 2;
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, other);
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, hitrect.grow(expand * 2));
if(vec != null){
effect.at(vec.x, vec.y);
@@ -162,7 +184,6 @@ public class Damage{
Building tile = world.build(cx, cy);
if(tile != null && tile.team != hitter.team){
tmpBuilding = tile;
//TODO return tile
return true;
}
return false;

View File

@@ -1,15 +1,26 @@
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.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effect{
private static final float shakeFalloff = 10000f;
private static final EffectContainer container = new EffectContainer();
private static int lastid = 0;
private static final Seq<Effect> all = new Seq<>();
public final int id;
public final Cons<EffectContainer> renderer;
@@ -21,14 +32,15 @@ public class Effect{
public float groundDuration;
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
this.id = lastid++;
this.id = all.size;
this.lifetime = life;
this.renderer = renderer;
this.size = clipsize;
all.add(this);
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
this(life,50f, renderer);
}
public Effect ground(){
@@ -43,42 +55,123 @@ public class Effect{
}
public void at(Position pos){
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
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);
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);
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);
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);
create(this, x, y, rotation, color, null);
}
public void at(float x, float y, Color color){
Effects.create(this, x, y, 0, color, null);
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);
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);
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){
public float render(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
container.set(id, color, life, lifetime, rotation, x, y, data);
Draw.z(ground ? Layer.debris : Layer.effect);
Draw.reset();
renderer.get(container);
Draw.reset();
return container.lifetime;
}
public static @Nullable Effect get(int id){
return id >= all.size || id < 0 ? null : all.get(id);
}
private static void shake(float intensity, float duration){
if(!headless){
Vars.renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public 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);
if(view.overlaps(pos)){
EffectState entity = EffectState.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();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
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;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) 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;
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);
}
public static class EffectContainer implements Scaled{

View File

@@ -1,92 +0,0 @@
package mindustry.entities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
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 float shakeFalloff = 10000f;
private static void shake(float intensity, float duration){
if(!headless){
renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public 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);
if(view.overlaps(pos)){
EffectState entity = EffectState.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();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
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;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) 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;
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

@@ -8,7 +8,7 @@ import mindustry.gen.*;
import java.util.*;
import static mindustry.Vars.collisions;
import static mindustry.Vars.*;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
@@ -57,7 +57,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
each(Entityc::update);
}
public void copy(Seq arr){
public void copy(Seq<T> arr){
arr.addAll(array);
}
@@ -101,7 +101,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
T t = map.get(id);
if(t != null){ //remove if present in map already
remove(t);
t.remove();
}
}

View File

@@ -17,7 +17,7 @@ public class Fires{
/** 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.
if(net.client() || tile == null || !state.rules.fire) return; //not clientside.
Fire fire = map.get(tile.pos());

View File

@@ -8,7 +8,7 @@ class GroupDefs<G>{
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
@GroupDef(value = Buildingc.class) G tile;
@GroupDef(value = Buildingc.class) G build;
@GroupDef(value = Syncc.class, mapping = true) G sync;
@GroupDef(value = Drawc.class) G draw;
@GroupDef(value = WeatherStatec.class) G weather;

View File

@@ -24,17 +24,15 @@ public class Lightning{
/** Create a lighting branch at a location. Use Team.derelict to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
createLightningInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
}
/** Create a lighting branch at a location. Uses bullet parameters. */
public static void create(Bullet bullet, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
createLightningInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
}
//TODO remote method
//@Remote(called = Loc.server, unreliable = true)
private static void createLightingInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
private static void createLightningInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
random.setSeed(seed);
hit.clear();

View File

@@ -83,7 +83,7 @@ public class Puddles{
(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);
Bullets.fireball.createNet(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 Puddle
if(Mathf.chance(0.5f * amount)){

View File

@@ -1,5 +1,6 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
@@ -19,8 +20,41 @@ public class Units{
private static boolean boolResult;
@Remote(called = Loc.server)
public static void unitDeath(Unit unit){
unit.killed();
public static void unitCapDeath(Unit unit){
if(unit != null){
unit.dead = true;
Fx.unitCapKill.at(unit);
Core.app.post(() -> Call.unitDestroy(unit.id));
}
}
@Remote(called = Loc.server)
public static void unitDeath(int uid){
Unit unit = Groups.unit.getByID(uid);
//if there's no unit don't add it later and get it stuck as a ghost
if(netClient != null){
netClient.addRemovedEntity(uid);
}
if(unit != null){
unit.killed();
}
}
//destroys immediately
@Remote(called = Loc.server)
public static void unitDestroy(int uid){
Unit unit = Groups.unit.getByID(uid);
//if there's no unit don't add it later and get it stuck as a ghost
if(netClient != null){
netClient.addRemovedEntity(uid);
}
if(unit != null){
unit.destroy();
}
}
@Remote(called = Loc.server)
@@ -36,7 +70,7 @@ public class Units{
public static int getCap(Team team){
//wave team has no cap
if((team == state.rules.waveTeam && state.rules.waves) || (state.isCampaign() && team == state.rules.waveTeam)){
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
return Integer.MAX_VALUE;
}
return state.rules.unitCap + indexer.getExtraUnits(team);
@@ -90,7 +124,7 @@ public class Units{
nearby(x, y, width, height, unit -> {
if(boolResult) return;
if(unit.isGrounded() == ground){
if((unit.isGrounded() && !unit.type().hovering) == ground){
unit.hitbox(hitrect);
if(hitrect.overlaps(x, y, width, height)){
@@ -141,6 +175,18 @@ public class Units{
}
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static Teamc bestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Building> tilePred, Sortf sort){
if(team == Team.derelict) return null;
Unit unit = bestEnemy(team, x, y, range, unitPred, sort);
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, tilePred);
}
}
/** 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){
if(team == Team.derelict) return null;
@@ -161,6 +207,26 @@ public class Units{
return result;
}
/** Returns the closest enemy of this team using a custom comparison function. Filter by predicate. */
public static Unit bestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate, Sortf sort){
if(team == Team.derelict) return null;
result = null;
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.dead() || !predicate.get(e) || !e.within(x, y, range)) return;
float cost = sort.cost(e, x, y);
if(result == null || cost < cdist){
result = e;
cdist = cost;
}
});
return result;
}
/** Returns the closest ally of this team. Filter by predicate. No range. */
public static Unit closest(Team team, float x, float y, Boolf<Unit> predicate){
result = null;
@@ -242,9 +308,20 @@ public class Units{
/** 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){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
if(team.active()){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
}
}else{
//inactive teams have no cache, check everything
//TODO cache all teams with units OR blocks
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
nearby(other, x, y, width, height, cons);
}
}
}
}
/** Iterates over all units that are enemies of this team. */
@@ -252,4 +329,7 @@ public class Units{
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
public interface Sortf{
float cost(Unit unit, float x, float y);
}
}

View File

@@ -2,7 +2,16 @@ package mindustry.entities.abilities;
import mindustry.gen.*;
public interface Ability{
default void update(Unit unit){}
default void draw(Unit unit){}
public abstract class Ability implements Cloneable{
public void update(Unit unit){}
public void draw(Unit unit){}
public Ability copy(){
try{
return (Ability)clone();
}catch(CloneNotSupportedException e){
//I am disgusted
throw new RuntimeException("java sucks", e);
}
}
}

View File

@@ -11,7 +11,7 @@ import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class ForceFieldAbility implements Ability{
public class ForceFieldAbility extends Ability{
/** Shield radius. */
public float radius = 60f;
/** Shield regen speed in damage/tick. */
@@ -21,21 +21,26 @@ public class ForceFieldAbility implements Ability{
/** Cooldown after the shield is broken, in ticks. */
public float cooldown = 60f * 5;
private float realRad;
private Unit paramUnit;
private final Cons<Shielderc> shieldConsumer = trait -> {
if(trait.team() != paramUnit.team && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
/** State. */
protected float radiusScale, alpha;
private static float realRad;
private static Unit paramUnit;
private static ForceFieldAbility paramField;
private static final Cons<Bullet> shieldConsumer = trait -> {
if(trait.team != paramUnit.team && trait.type.absorbable && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
trait.absorb();
Fx.absorb.at(trait);
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= cooldown * regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, radius, paramUnit.team.color);
paramUnit.shield -= paramField.cooldown * paramField.regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color);
}
paramUnit.shield -= trait.damage();
paramUnit.shieldAlpha = 1f;
paramField.alpha = 1f;
}
};
@@ -54,14 +59,17 @@ public class ForceFieldAbility implements Ability{
unit.shield += Time.delta * regen;
}
alpha = Math.max(alpha - Time.delta/10f, 0f);
if(unit.shield > 0){
unit.timer2 = Mathf.lerpDelta(unit.timer2, 1f, 0.06f);
radiusScale = Mathf.lerpDelta(radiusScale, 1f, 0.06f);
paramUnit = unit;
paramField = this;
checkRadius(unit);
Groups.bullet.intersect(unit.x - realRad, unit.y - realRad, realRad * 2f, realRad * 2f, shieldConsumer);
}else{
unit.timer2 = 0f;
radiusScale = 0f;
}
}
@@ -72,7 +80,7 @@ public class ForceFieldAbility implements Ability{
if(unit.shield > 0){
Draw.z(Layer.shields);
Draw.color(unit.team.color, Color.white, Mathf.clamp(unit.shieldAlpha));
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
if(Core.settings.getBool("animatedshields")){
Fill.poly(unit.x, unit.y, 6, realRad);
@@ -88,6 +96,6 @@ public class ForceFieldAbility implements Ability{
private void checkRadius(Unit unit){
//timer2 is used to store radius scale as an effect
realRad = unit.timer2 * radius;
realRad = radiusScale * radius;
}
}

View File

@@ -5,12 +5,13 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class HealFieldAbility implements Ability{
public class HealFieldAbility extends Ability{
public float amount = 1, reload = 100, range = 60;
public Effect healEffect = Fx.heal;
public Effect activeEffect = Fx.healWave;
public Effect activeEffect = Fx.healWaveDynamic;
private boolean wasHealed = false;
protected float timer;
protected boolean wasHealed = false;
HealFieldAbility(){}
@@ -22,9 +23,9 @@ public class HealFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer1 += Time.delta;
timer += Time.delta;
if(unit.timer1 >= reload){
if(timer >= reload){
wasHealed = false;
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
@@ -36,10 +37,10 @@ public class HealFieldAbility implements Ability{
});
if(wasHealed){
activeEffect.at(unit);
activeEffect.at(unit, range);
}
unit.timer1 = 0f;
timer = 0f;
}
}
}

View File

@@ -5,12 +5,13 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class ShieldFieldAbility implements Ability{
public class ShieldFieldAbility extends Ability{
public float amount = 1, max = 100f, reload = 100, range = 60;
public Effect applyEffect = Fx.shieldApply;
public Effect activeEffect = Fx.shieldWave;
private boolean applied = false;
protected float timer;
protected boolean applied = false;
ShieldFieldAbility(){}
@@ -23,9 +24,9 @@ public class ShieldFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer1 += Time.delta;
timer += Time.delta;
if(unit.timer1 >= reload){
if(timer >= reload){
applied = false;
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
@@ -41,7 +42,7 @@ public class ShieldFieldAbility implements Ability{
activeEffect.at(unit);
}
unit.timer1 = 0f;
timer = 0f;
}
}
}

View File

@@ -1,21 +1,22 @@
package mindustry.entities.abilities;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
public class StatusFieldAbility implements Ability{
public @NonNull StatusEffect effect;
public class StatusFieldAbility extends Ability{
public StatusEffect effect;
public float duration = 60, reload = 100, range = 20;
public Effect applyEffect = Fx.heal;
public Effect activeEffect = Fx.overdriveWave;
protected float timer;
StatusFieldAbility(){}
public StatusFieldAbility(@NonNull StatusEffect effect, float duration, float reload, float range){
public StatusFieldAbility(StatusEffect effect, float duration, float reload, float range){
this.duration = duration;
this.reload = reload;
this.range = range;
@@ -24,9 +25,9 @@ public class StatusFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer2 += Time.delta;
timer += Time.delta;
if(unit.timer2 >= reload){
if(timer >= reload){
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
other.apply(effect, duration);
@@ -34,7 +35,7 @@ public class StatusFieldAbility implements Ability{
activeEffect.at(unit);
unit.timer2 = 0f;
timer = 0f;
}
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.entities.abilities;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
public class UnitSpawnAbility extends Ability{
public UnitType type;
public float spawnTime = 60f, spawnX, spawnY;
public Effect spawnEffect = Fx.spawn;
protected float timer;
public UnitSpawnAbility(UnitType type, float spawnTime, float spawnX, float spawnY){
this.type = type;
this.spawnTime = spawnTime;
this.spawnX = spawnX;
this.spawnY = spawnY;
}
public UnitSpawnAbility(){
}
@Override
public void update(Unit unit){
timer += Time.delta;
if(timer >= spawnTime && Units.canCreate(unit.team, type)){
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
spawnEffect.at(x, y);
Unit u = type.create(unit.team);
u.set(x, y);
u.rotation = unit.rotation;
if(!Vars.net.client()){
u.add();
}
timer = 0f;
}
}
@Override
public void draw(Unit unit){
if(Units.canCreate(unit.team, type)){
Draw.draw(Draw.z(), () -> {
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
Drawf.construct(x, y, type.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
});
}
}
}

View File

@@ -1,16 +1,17 @@
package mindustry.entities.bullet;
import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.graphics.*;
/** An extended BulletType for most ammo-based bullets shot from turrets and units. */
public class BasicBulletType extends BulletType{
public Color backColor = Pal.bulletYellowBack, frontColor = Pal.bulletYellow;
public Color mixColorFrom = new Color(1f, 1f, 1f, 0f), mixColorTo = new Color(1f, 1f, 1f, 0f);
public float width = 5f, height = 7f;
public float shrinkX = 0f, shrinkY = 0.5f;
public float spin = 0;
@@ -45,10 +46,15 @@ public class BasicBulletType extends BulletType{
float width = this.width * ((1f - shrinkX) + shrinkX * b.fout());
float offset = -90 + (spin != 0 ? Mathf.randomSeed(b.id, 360f) + b.time * spin : 0f);
Color mix = Tmp.c1.set(mixColorFrom).lerp(mixColorTo, b.fin());
Draw.mixcol(mix, mix.a);
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, width, height, b.rotation() + offset);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, width, height, b.rotation() + offset);
Draw.color();
Draw.reset();
}
}

View File

@@ -24,7 +24,7 @@ public abstract class BulletType extends Content{
public float hitSize = 4;
public float drawSize = 40f;
public float drag = 0f;
public boolean pierce;
public boolean pierce, pierceBuilding;
public Effect hitEffect, despawnEffect;
/** Effect created when shooting. */
@@ -54,7 +54,7 @@ public abstract class BulletType extends Content{
/** Status effect applied on hit. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect in terms of duration. */
public float statusDuration = 60 * 10f;
public float statusDuration = 60 * 8f;
/** Whether this bullet type collides with tiles. */
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */
@@ -69,13 +69,23 @@ public abstract class BulletType extends Content{
public boolean scaleVelocity;
/** Whether this bullet can be hit by point defense. */
public boolean hittable = true;
/** Whether this bullet can be reflected. */
public boolean reflectable = true;
/** Whether this projectile can be absorbed by shields. */
public boolean absorbable = true;
/** Whether to move the bullet back depending on delta to fix some delta-time realted issues.
* Do not change unless you know what you're doing. */
public boolean backMove = true;
/** Bullet range override. */
public float range = -1f;
//additional effects
public float fragCone = 360f;
public float fragAngle = 0f;
public int fragBullets = 9;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public BulletType fragBullet = null;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
public @Nullable BulletType fragBullet = null;
public Color hitColor = Color.white;
public Color trailColor = Pal.missileYellowBack;
@@ -92,14 +102,17 @@ public abstract class BulletType extends Content{
public float homingPower = 0f;
public float homingRange = 50f;
public Color lightningColor = Pal.surge;
public int lightning;
public int lightningLength = 5;
public int lightningLength = 5, lightningLengthRand = 0;
/** Use a negative value to use default bullet damage. */
public float lightningDamage = -1;
public float lightningCone = 360f;
public float lightningAngle = 0f;
public float weaveScale = 1f;
public float weaveMag = -1f;
public float hitShake = 0f;
public float hitShake = 0f, despawnShake = 0f;
public int puddles;
public float puddleRange;
@@ -123,20 +136,24 @@ public abstract class BulletType extends Content{
/** Returns maximum distance the bullet this bullet type has can travel. */
public float range(){
return speed * lifetime * (1f - drag);
return Math.max(speed * lifetime * (1f - drag), range);
}
public boolean collides(Bullet bullet, Building tile){
return true;
}
public void hitTile(Bullet b, Building tile){
public void hitTile(Bullet b, Building tile, float initialHealth){
if(status == StatusEffects.burning) {
Damage.createIncend(b.x, b.y, damage/10f, (int) damage/10);
}
hit(b);
}
public void hitEntity(Bullet b, Hitboxc other, float initialHealth){
}
public void hit(Bullet b){
hit(b, b.x, b.y);
}
@@ -145,13 +162,13 @@ public abstract class BulletType extends Content{
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(b);
Effects.shake(hitShake, hitShake, b);
Effect.shake(hitShake, hitShake, b);
if(fragBullet != null){
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = b.rotation() + Mathf.range(fragCone/2);
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
float a = b.rotation() + Mathf.range(fragCone/2) + fragAngle;
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
}
}
@@ -175,7 +192,7 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightning; i++){
Lightning.create(b, Pal.surge, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, b.rotation() + Mathf.range(lightningCone/2) + lightningAngle, lightningLength + Mathf.random(lightningLengthRand));
}
}
@@ -183,6 +200,8 @@ public abstract class BulletType extends Content{
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
hitSound.at(b);
Effect.shake(despawnShake, despawnShake, b);
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
hit(b);
}
@@ -201,7 +220,7 @@ public abstract class BulletType extends Content{
}
if(instantDisappear){
b.time(lifetime);
b.time = lifetime;
}
}
@@ -249,6 +268,10 @@ public abstract class BulletType extends Content{
return create(parent.owner(), parent.team, x, y, angle);
}
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl, float lifeScale){
return create(parent.owner(), parent.team, x, y, angle, velocityScl, lifeScale);
}
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl){
return create(parent.owner(), parent.team, x, y, angle, velocityScl);
}
@@ -259,7 +282,11 @@ public abstract class BulletType extends Content{
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);
if(backMove){
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
}else{
bullet.set(x, y);
}
bullet.lifetime = lifetime * lifetimeScl;
bullet.data = data;
bullet.drag = drag;
@@ -267,13 +294,12 @@ public abstract class BulletType extends Content{
bullet.damage = damage < 0 ? this.damage : damage;
bullet.add();
if(keepVelocity && owner instanceof Hitboxc) bullet.vel.add(((Hitboxc)owner).deltaX(), ((Hitboxc)owner).deltaY());
if(keepVelocity && owner instanceof Velc) bullet.vel.add(((Velc)owner).vel().x, ((Velc)owner).vel().y);
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);
Call.createBullet(this, team, x, y, angle, damage, velocityScl, lifetimeScl);
}
@Remote(called = Loc.server, unreliable = true)

View File

@@ -12,10 +12,13 @@ import mindustry.graphics.*;
public class ContinuousLaserBulletType extends BulletType{
public float length = 220f;
public float shake = 1f;
public float fadeTime = 16f;
public Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
public float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
public float[] strokes = {2f, 1.5f, 1f, 0.3f};
public float[] lenscales = {1f, 1.12f, 1.15f, 1.17f};
public float width = 9f, oscScl = 0.8f, oscMag = 1.5f;
public boolean largeHit = true;
public ContinuousLaserBulletType(float damage){
super(0.001f, damage);
@@ -25,6 +28,7 @@ public class ContinuousLaserBulletType extends BulletType{
hitSize = 4;
drawSize = 420f;
lifetime = 16f;
keepVelocity = false;
pierce = true;
hittable = false;
hitColor = colors[2];
@@ -32,6 +36,8 @@ public class ContinuousLaserBulletType extends BulletType{
incendAmount = 1;
incendSpread = 5;
incendChance = 0.4f;
lightColor = Color.orange;
absorbable = false;
}
protected ContinuousLaserBulletType(){
@@ -43,37 +49,45 @@ public class ContinuousLaserBulletType extends BulletType{
return length;
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public void update(Bullet b){
//TODO possible laser absorption from blocks
//damage every 5 ticks
if(b.timer(1, 5f)){
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, true);
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, largeHit);
}
if(shake > 0){
Effects.shake(shake, shake, b);
Effect.shake(shake, shake, b);
}
}
@Override
public void draw(Bullet b){
float baseLen = length * b.fout();
float realLength = Damage.findLaserLength(b, length);
float fout = Mathf.clamp(b.time > b.lifetime - fadeTime ? 1f - (b.time - (lifetime - fadeTime)) / fadeTime : 1f);
float baseLen = realLength * fout;
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
for(int s = 0; s < colors.length; s++){
Draw.color(Tmp.c1.set(colors[s]).mul(1f + Mathf.absin(Time.time(), 1f, 0.1f)));
for(int i = 0; i < tscales.length; i++){
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
Lines.stroke((width + Mathf.absin(Time.time(), oscScl, oscMag)) * fout * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
}
}
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, Color.orange, 0.7f);
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, lightColor, 0.7f);
Draw.reset();
}

View File

@@ -21,6 +21,7 @@ public class HealBulletType extends BulletType{
despawnEffect = Fx.hitLaser;
collidesTeam = true;
hittable = false;
reflectable = false;
}
public HealBulletType(){
@@ -29,7 +30,7 @@ public class HealBulletType extends BulletType{
@Override
public boolean collides(Bullet b, Building tile){
return tile.team() != b.team || tile.healthf() < 1f;
return tile.team != b.team || tile.healthf() < 1f;
}
@Override
@@ -43,11 +44,11 @@ public class HealBulletType extends BulletType{
}
@Override
public void hitTile(Bullet b, Building tile){
public void hitTile(Bullet b, Building tile, float initialHealth){
super.hit(b);
if(tile.team() == b.team && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block().size, Pal.heal);
if(tile.team == b.team && !(tile.block instanceof ConstructBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block.size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}
}

View File

@@ -8,20 +8,17 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.world;
public class LaserBulletType extends BulletType{
protected static Tile furthest;
protected Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
protected Effect laserEffect = Fx.lancerLaserShootSmoke;
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 Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
public Effect laserEffect = Fx.lancerLaserShootSmoke;
public float length = 160f;
public float width = 15f;
public float lengthFalloff = 0.5f;
public float sideLength = 29f, sideWidth = 0.7f;
public float sideAngle = 90f;
public float lightningSpacing = -1, lightningDelay = 0.1f, lightningAngleRand;
public boolean largeHit = false;
public LaserBulletType(float damage){
super(0.01f, damage);
@@ -42,6 +39,13 @@ public class LaserBulletType extends BulletType{
this(1f);
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public float range(){
return length;
@@ -49,24 +53,35 @@ public class LaserBulletType extends BulletType{
@Override
public void init(Bullet b){
Tmp.v1.trns(b.rotation(), length);
float resultLength = Damage.collideLaser(b, length, largeHit), rot = b.rotation();
furthest = null;
laserEffect.at(b.x, b.y, rot, resultLength * 0.75f);
world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
if(lightningSpacing > 0){
int idx = 0;
for(float i = 0; i <= resultLength; i += lightningSpacing){
float cx = b.x + Angles.trnsx(rot, i),
cy = b.y + Angles.trnsy(rot, i);
float resultLength = furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
int f = idx++;
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), resultLength);
if(furthest != null) b.data(resultLength);
laserEffect.at(b.x, b.y, b.rotation(), resultLength * 0.75f);
for(int s : Mathf.signs){
Time.run(f * lightningDelay, () -> {
if(b.isAdded() && b.type == this){
Lightning.create(b, lightningColor,
lightningDamage < 0 ? damage : lightningDamage,
cx, cy, rot + 90*s + Mathf.range(lightningAngleRand),
lightningLength + Mathf.random(lightningLengthRand));
}
});
}
}
}
}
@Override
public void draw(Bullet b){
float realLength = b.data() == null ? length : (Float)b.data();
float realLength = b.fdata;
float f = Mathf.curve(b.fin(), 0f, 0.2f);
float baseLen = realLength * f;
@@ -74,11 +89,10 @@ public class LaserBulletType extends BulletType{
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);
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, false);
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());
@@ -89,7 +103,6 @@ public class LaserBulletType extends BulletType{
compound *= lengthFalloff;
}
Lines.precise(false);
Draw.reset();
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);

View File

@@ -13,7 +13,7 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class LiquidBulletType extends BulletType{
public @NonNull Liquid liquid;
public Liquid liquid;
public float puddleSize = 6f;
public LiquidBulletType(@Nullable Liquid liquid){
@@ -24,8 +24,9 @@ public class LiquidBulletType extends BulletType{
this.status = liquid.effect;
}
lifetime = 74f;
statusDuration = 90f;
ammoMultiplier = 1f;
lifetime = 34f;
statusDuration = 60f * 2f;
despawnEffect = Fx.none;
hitEffect = Fx.hitLiquid;
smokeEffect = Fx.none;
@@ -61,7 +62,7 @@ public class LiquidBulletType extends BulletType{
public void draw(Bullet b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 3f);
Fill.circle(b.x, b.y, puddleSize / 2);
}
@Override
@@ -69,7 +70,7 @@ public class LiquidBulletType extends BulletType{
super.despawned(b);
//don't create liquids when the projectile despawns
hitEffect.at(b.x, b.y, liquid.color);
hitEffect.at(b.x, b.y, b.rotation(), liquid.color);
}
@Override
@@ -78,7 +79,7 @@ public class LiquidBulletType extends BulletType{
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
float intensity = 400f;
float intensity = 400f * puddleSize/6f;
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
for(Point2 p : Geometry.d4){
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);

View File

@@ -1,15 +1,14 @@
package mindustry.entities.bullet;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.content.Fx;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
import mindustry.graphics.*;
import mindustry.world.blocks.distribution.MassDriver.*;
import static mindustry.Vars.content;
import static mindustry.Vars.*;
public class MassDriverBolt extends BulletType{

View File

@@ -15,6 +15,7 @@ public class MissileBulletType extends BasicBulletType{
height = 8f;
hitSound = Sounds.explosion;
trailChance = 0.2f;
lifetime = 49f;
}
public MissileBulletType(float speed, float damage){

View File

@@ -0,0 +1,70 @@
package mindustry.entities.bullet;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class PointBulletType extends BulletType{
private static float cdist = 0f;
private static Unit result;
public float trailSpacing = 10f;
public PointBulletType(){
scaleVelocity = true;
lifetime = 100f;
collides = false;
keepVelocity = false;
backMove = false;
}
@Override
public void init(Bullet b){
super.init(b);
float px = b.x + b.lifetime * b.vel.x,
py = b.y + b.lifetime * b.vel.y,
rot = b.rotation();
Geometry.iterateLine(0f, b.x, b.y, px, py, trailSpacing, (x, y) -> {
trailEffect.at(x, y, rot);
});
b.time = b.lifetime;
b.set(px, py);
//calculate hit entity
cdist = 0f;
result = null;
float range = 1f;
Units.nearbyEnemies(b.team, px - range, py - range, range*2f, range*2f, e -> {
if(e.dead()) return;
e.hitbox(Tmp.r1);
if(!Tmp.r1.contains(px, py)) return;
float dst = e.dst(px, py) - e.hitSize;
if((result == null || dst < cdist)){
result = e;
cdist = dst;
}
});
if(result != null){
b.collision(result, px, py);
}else{
Building build = Vars.world.buildWorld(px, py);
if(build != null && build.team != b.team){
build.collision(b);
}
}
b.remove();
b.vel.setZero();
}
}

View File

@@ -0,0 +1,60 @@
package mindustry.entities.bullet;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
//TODO this class is bad for multiple reasons, remove/replace it.
//- effects unreliable
//- not really hitscan but works like it
//- buggy trails
//- looks bad
//- generally unreliable
public class RailBulletType extends BulletType{
public Effect pierceEffect = Fx.hitBulletSmall, updateEffect = Fx.none;
/** Multiplier of damage decreased per health pierced. */
public float pierceDamageFactor = 1f;
public RailBulletType(){
pierceBuilding = true;
pierce = true;
reflectable = false;
hitEffect = Fx.none;
despawnEffect = Fx.none;
}
void handle(Bullet b, Posc pos, float initialHealth){
float sub = initialHealth*pierceDamageFactor;
if(sub >= b.damage){
//cause a despawn
b.remove();
}
//subtract health from each consecutive pierce
b.damage -= Math.min(b.damage, sub);
if(b.damage > 0){
pierceEffect.at(pos.getX(), pos.getY(), b.rotation());
}
hitEffect.at(pos.getX(), pos.getY());
}
@Override
public void update(Bullet b){
if(b.timer(1, 0.9f)){
updateEffect.at(b.x, b.y, b.rotation());
}
}
@Override
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
handle(b, entity, initialHealth);
}
@Override
public void hitTile(Bullet b, Building tile, float initialHealth){
handle(b, tile, initialHealth);
}
}

View File

@@ -39,10 +39,15 @@ public class SapBulletType extends BulletType{
Draw.reset();
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 15f * b.fout(), lightColor, 0.6f);
Drawf.light(b.team, b.x, b.y, Tmp.v1.x, Tmp.v1.y, 15f * b.fout(), lightColor, 0.6f);
}
}
@Override
public void drawLight(Bullet b){
}
@Override
public float range(){
return length;

View File

@@ -13,9 +13,10 @@ public class ShrapnelBulletType extends BulletType{
public float length = 100f;
public float width = 20f;
public Color fromColor = Color.white, toColor = Pal.lancerLaser;
public boolean hitLarge = false;
public int serrations = 7;
public float serrationLenScl = 10f, serrationWidth = 4f, serrationSpacing = 8f, serrationSpaceOffset = 80f;
public float serrationLenScl = 10f, serrationWidth = 4f, serrationSpacing = 8f, serrationSpaceOffset = 80f, serrationFadeOffset = 0.5f;
public ShrapnelBulletType(){
speed = 0.01f;
@@ -24,23 +25,39 @@ public class ShrapnelBulletType extends BulletType{
lifetime = 10f;
despawnEffect = Fx.none;
pierce = true;
keepVelocity = false;
hittable = false;
}
@Override
public void init(Bullet b){
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length);
Damage.collideLaser(b, length, hitLarge);
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public float range(){
return length;
}
@Override
public void draw(Bullet b){
float realLength = b.fdata;
Draw.color(fromColor, toColor, b.fin());
for(int i = 0; i < serrations; i++){
for(int i = 0; i < (int)(serrations * realLength / length); i++){
Tmp.v1.trns(b.rotation(), i * serrationSpacing);
float sl = Mathf.clamp(b.fout() - 0.5f) * (serrationSpaceOffset - i * serrationLenScl);
float sl = Mathf.clamp(b.fout() - serrationFadeOffset) * (serrationSpaceOffset - i * serrationLenScl);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() - 90);
}
Drawf.tri(b.x, b.y, width * b.fout(), (length + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), (realLength + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
}

View File

@@ -0,0 +1,28 @@
package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.units.*;
@Component
abstract class AmmoDistributeComp implements Unitc{
@Import float x, y;
@Import UnitType type;
@Import Team team;
@Import float ammo;
private transient float ammoCooldown;
@Override
public void update(){
if(ammoCooldown > 0f) ammoCooldown -= Time.delta;
if(ammo > 0 && ammoCooldown <= 0f && ResupplyPoint.resupply(team, x, y, type.ammoResupplyRange, Math.min(type.ammoResupplyAmount, ammo), type.ammoType.color, u -> u != self())){
ammo -= Math.min(type.ammoResupplyAmount, ammo);
ammoCooldown = 5f;
}
}
}

View File

@@ -18,16 +18,16 @@ abstract class BlockUnitComp implements Unitc{
this.tile = tile;
//sets up block stats
maxHealth(tile.block().health);
maxHealth(tile.block.health);
health(tile.health());
hitSize(tile.block().size * tilesize * 0.7f);
hitSize(tile.block.size * tilesize * 0.7f);
set(tile);
}
@Override
public void update(){
if(tile != null){
team = tile.team();
team = tile.team;
}
}
@@ -61,7 +61,7 @@ abstract class BlockUnitComp implements Unitc{
public void team(Team team){
if(tile != null && this.team != team){
this.team = team;
if(tile.team() != team){
if(tile.team != team){
tile.team(team);
}
}

View File

@@ -16,7 +16,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import java.util.*;
@@ -29,13 +29,7 @@ abstract class BuilderComp implements Unitc{
@Import float x, y, rotation;
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
transient boolean updateBuilding = true;
@Override
public void controller(UnitController next){
//reset building state so AI controlled units will always start off building
updateBuilding = true;
}
@SyncLocal transient boolean updateBuilding = true;
@Override
public void update(){
@@ -77,20 +71,20 @@ abstract class BuilderComp implements Unitc{
Tile tile = world.tile(current.x, current.y);
if(within(tile, finalPlaceDst)){
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
lookAt(angleTo(tile));
}
if(!(tile.block() instanceof BuildBlock)){
if(!(tile.block() instanceof ConstructBlock)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team(), current.x, current.y, current.rotation)){
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
if(hasAll){
Build.beginPlace(current.block, team(), current.x, current.y, current.rotation);
Call.beginPlace(current.block, team(), current.x, current.y, 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);
Call.beginBreak(team(), current.x, current.y);
}else{
plans.removeFirst();
return;
@@ -100,27 +94,23 @@ abstract class BuilderComp implements Unitc{
return;
}
if(tile.build instanceof BuildEntity && !current.initialized){
if(tile.build instanceof ConstructBuild && !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 && !infinite) || !(tile.build instanceof BuildEntity)){
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild)){
return;
}
//otherwise, update it.
BuildEntity entity = tile.bc();
ConstructBuild entity = tile.bc();
if(current.breaking){
entity.deconstruct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.tileConfig(null, tile.build, current.config);
}
}
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
@@ -150,7 +140,6 @@ abstract class BuilderComp implements Unitc{
boolean shouldSkip(BuildPlan request, @Nullable Building core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || team().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);
}
@@ -190,8 +179,8 @@ abstract class BuilderComp implements Unitc{
plans.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.build instanceof BuildEntity){
place.progress = tile.<BuildEntity>bc().progress;
if(tile != null && tile.build instanceof ConstructBuild){
place.progress = tile.<ConstructBuild>bc().progress;
}
if(tail){
plans.addLast(place);

View File

@@ -18,11 +18,13 @@ import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -37,9 +39,9 @@ import static mindustry.Vars.*;
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
@Component(base = true)
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable{
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable{
//region vars and initialization
static final float timeToSleep = 60f * 1;
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
static final Seq<Building> tempTileEnts = new Seq<>();
static final Seq<Tile> tempTiles = new Seq<>();
@@ -54,6 +56,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient boolean updateFlow;
transient byte dump;
transient int rotation;
transient boolean enabled = true;
transient float enabledControlTime;
PowerModule power;
ItemModule items;
@@ -72,6 +76,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public Building init(Tile tile, Team team, boolean shouldAdd, int rotation){
if(!initialized){
create(tile.block(), team);
}else{
if(block.hasPower){
//reinit power graph
power.graph = new PowerGraph();
power.graph.add(self());
}
}
this.rotation = rotation;
this.tile = tile;
@@ -84,7 +94,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
created();
return base();
return self();
}
/** Sets up all the necessary variables, but does not add this entity anywhere. */
@@ -97,21 +107,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
}
health(block.health);
health = block.health;
maxHealth(block.health);
timer(new Interval(block.timers));
cons = new ConsumeModule(base());
cons = new ConsumeModule(self());
if(block.hasItems) items = new ItemModule();
if(block.hasLiquids) liquids = new LiquidModule();
if(block.hasPower){
power = new PowerModule();
power.graph.add(base());
power.graph.add(self());
}
initialized = true;
return base();
return self();
}
@Override
@@ -131,8 +141,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void writeBase(Writes write){
write.f(health);
write.b(rotation);
write.b(rotation | 0b10000000);
write.b(team.id);
write.b(0); //extra padding for later use
if(items != null) items.write(write);
if(power != null) power.write(write);
if(liquids != null) liquids.write(write);
@@ -141,12 +152,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void readBase(Reads read){
health = read.f();
rotation(read.b());
byte rot = read.b();
team = Team.get(read.b());
if(items != null) items.read(read);
if(power != null) power.read(read);
if(liquids != null) liquids.read(read);
if(cons != null) cons.read(read);
rotation = rot & 0b01111111;
boolean legacy = true;
if((rot & 0b10000000) != 0){
read.b(); //padding
legacy = false;
}
if(items != null) items.read(read, legacy);
if(power != null) power.read(read, legacy);
if(liquids != null) liquids.read(read, legacy);
if(cons != null) cons.read(read, legacy);
}
public void writeAll(Writes write){
@@ -176,17 +195,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void configure(Object value){
//save last used config
block.lastConfig = value;
Call.tileConfig(player, base(), value);
Call.tileConfig(player, self(), value);
}
/** Configure from a server. */
public void configureAny(Object value){
Call.tileConfig(null, base(), value);
Call.tileConfig(null, self(), value);
}
/** Deselect this tile from configuration. */
public void deselect(){
if(!headless && control.input.frag.config.getSelectedTile() == base()){
if(!headless && control.input.frag.config.getSelectedTile() == self()){
control.input.frag.config.hideConfig();
}
}
@@ -294,6 +313,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
public float efficiency(){
//disabled -> 0 efficiency
if(!enabled) return 0;
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@@ -325,10 +346,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** Called when this block is dropped as a payload. */
public void dropped(){
}
/** This is for logic blocks. */
public void handleString(Object value){
}
public void created(){}
public boolean shouldConsume(){
return true;
return enabled;
}
public boolean productionValid(){
@@ -341,7 +372,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Returns the amount of items this block can accept. */
public int acceptStack(Item item, int amount, Teamc source){
if(acceptItem(base(), item) && block.hasItems && (source == null || source.team() == team)){
if(acceptItem(self(), item) && block.hasItems && (source == null || source.team() == team)){
return Math.min(getMaximumAccepted(item) - items.get(item), amount);
}else{
return 0;
@@ -390,12 +421,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @param todump payload to dump.
* @return whether the payload was moved successfully
*/
public boolean movePayload(@NonNull Payload todump){
public boolean movePayload(Payload todump){
int trns = block.size/2 + 1;
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
if(next != null && next.build != null && next.build.team() == team && next.build.acceptPayload(base(), todump)){
next.build.handlePayload(base(), todump);
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
next.build.handlePayload(self(), todump);
return true;
}
@@ -407,7 +438,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @param todump payload to dump.
* @return whether the payload was moved successfully
*/
public boolean dumpPayload(@NonNull Payload todump){
public boolean dumpPayload(Payload todump){
if(proximity.size == 0) return false;
int dump = this.dump;
@@ -415,8 +446,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptPayload(base(), todump)){
other.handlePayload(base(), todump);
if(other.team == team && other.acceptPayload(self(), todump)){
other.handlePayload(self(), todump);
incrementDump(proximity.size);
return true;
}
@@ -449,7 +480,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
other = other.getLiquidDestination(base(), liquid);
other = other.getLiquidDestination(self(), liquid);
if(other != null && other.team == team && other.block.hasLiquids && canDumpLiquid(other, liquid) && other.liquids != null){
float ofract = other.liquids.get(liquid) / other.block.liquidCapacity;
@@ -468,8 +499,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void transferLiquid(Building next, float amount, Liquid liquid){
float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f, amount);
if(next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
if(next.acceptLiquid(self(), liquid, flow)){
next.handleLiquid(self(), liquid, flow);
liquids.remove(liquid, flow);
}
}
@@ -492,36 +523,33 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public float moveLiquid(Building next, Liquid liquid){
if(next == null) return 0;
next = next.getLiquidDestination(base(), liquid);
next = next.getLiquidDestination(self(), liquid);
if(next.team() == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f);
if(next.acceptLiquid(base(), liquid, 0f)){
float ofract = next.liquids().get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids().get(liquid) - 0.001f);
if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid, flow)){
next.handleLiquid(self(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
if(flow > 0f && ofract <= fract && next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(ofract > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
Liquid other = next.liquids().current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
Liquid other = next.liquids.current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
}
}
@@ -530,7 +558,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public Building getLiquidDestination(Building from, Liquid liquid){
return base();
return self();
}
public @Nullable Payload getPayload(){
@@ -552,13 +580,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
return;
}
}
handleItem(base(), item);
handleItem(self(), item);
}
/**
@@ -570,8 +598,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
return true;
}
}
@@ -603,16 +631,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int ii = 0; ii < content.items().size; ii++){
Item item = content.item(ii);
if(other.team() == team && items.has(item) && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && items.has(item) && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
items.remove(item, 1);
incrementDump(proximity.size);
return true;
}
}
}else{
if(other.team() == team && other.acceptItem(base(), todump) && canDump(other, todump)){
other.handleItem(base(), todump);
if(other.team == team && other.acceptItem(self(), todump) && canDump(other, todump)){
other.handleItem(self(), todump);
items.remove(todump, 1);
incrementDump(proximity.size);
return true;
@@ -637,8 +665,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Try offloading an item to a nearby container in its facing direction. Returns true if success. */
public boolean moveForward(Item item){
Building other = front();
if(other != null && other.team() == team && other.acceptItem(base(), item)){
other.handleItem(base(), item);
if(other != null && other.team == team && other.acceptItem(self(), item)){
other.handleItem(self(), item);
return true;
}
return false;
@@ -668,13 +696,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return;
}
power.graph.remove(base());
power.graph.remove(self());
for(int i = 0; i < power.links.size; i++){
Tile other = world.tile(power.links.get(i));
if(other != null && other.build != null && other.build.power != null){
other.build.power.links.removeValue(pos());
}
}
power.links.clear();
}
public Seq<Building> getPowerConnections(Seq<Building> out){
@@ -715,11 +744,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void drawStatus(){
if(block.consumes.any()){
if(block.enableDrawStatus && block.consumes.any()){
float brcx = tile.drawx() + (block.size * tilesize / 2f) - (tilesize / 2f);
float brcy = tile.drawy() - (block.size * tilesize / 2f) + (tilesize / 2f);
Draw.z(Layer.blockOver);
Draw.z(Layer.power + 1);
Draw.color(Pal.gray);
Fill.square(brcx, brcy, 2.5f, 45);
Draw.color(cons.status().color);
@@ -741,6 +770,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void drawSelect(){
}
public void drawDisabled(){
Draw.color(Color.scarlet);
Draw.alpha(0.8f);
float size = 6f;
Draw.rect(Icon.cancel.getRegion(), x, y, size, size);
Draw.reset();
}
public void draw(){
Draw.rect(block.region, x, y, block.rotate ? rotdeg() : 0);
@@ -780,10 +819,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Called after the block is placed by this client. */
@CallSuper
public void playerPlaced(){
if(block.saveConfig && block.lastConfig != null){
configure(block.lastConfig);
}
public void playerPlaced(Object config){
}
/** Called after the block is placed by anyone. */
@@ -796,10 +833,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
tempTiles.clear();
Geometry.circle(tileX(), tileY(), range, (x, y) -> {
Building other = world.build(x, y);
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, base()) && !PowerNode.insulated(other, base())
&& !other.proximity().contains(this.<Building>base()) &&
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, self()) && !PowerNode.insulated(other, self())
&& !other.proximity().contains(this.<Building>self()) &&
!(block.outputsPower && proximity.contains(p -> p.power != null && p.power.graph == other.power.graph))){
tempTiles.add(other.tile());
tempTiles.add(other.tile);
}
});
tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile)));
@@ -812,6 +849,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
/**
* Called when a block is placed over some other blocks. This seq will always have at least one item.
* Should load some previous state, if necessary. */
public void overwrote(Seq<Building> previous){
}
public void onRemoved(){
}
@@ -824,7 +868,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called when arbitrary configuration is applied to a tile. */
public void configured(@Nullable Player player, @Nullable Object value){
public void configured(@Nullable Unit builder, @Nullable Object value){
//null is of type void.class; anonymous classes use their superclass.
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
@@ -833,8 +877,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
/** Called when the block is tapped.*/
public void tapped(Player player){
/** Called when the block is tapped by the local player. */
public void tapped(){
}
@@ -861,7 +905,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
power += this.power.status * block.consumes.getPower().capacity;
}
if(block.hasLiquids){
if(block.hasLiquids && state.rules.damageExplosions){
liquids.each((liquid, amount) -> {
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
@@ -877,9 +921,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
});
}
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame, state.rules.damageExplosions);
if(!floor().solid && !floor().isLiquid){
Effects.rubble(x, y, block.size);
Effect.rubble(x, y, block.size);
}
}
@@ -934,7 +979,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.row();
table.table(this::displayConsumption).growX();
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow");
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow") && block.displayFlow;
if(displayFlow){
String ps = " " + StatUnit.perSecond.localized();
@@ -972,9 +1017,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(liquids != null){
table.row();
table.table(l -> {
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
boolean[] had = {false};
Runnable rebuild = () -> {
l.clearChildren();
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
};
l.update(() -> {
if(!had[0] && liquids.hadFlow()){
had[0] = true;
rebuild.run();
}
});
}).left();
}
}
@@ -987,13 +1044,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.left();
for(Consume cons : block.consumes.all()){
if(cons.isOptional() && cons.isBoost()) continue;
cons.build(base(), table);
cons.build(self(), table);
}
}
public void displayBars(Table table){
for(Func<Building, Bar> bar : block.bars.list()){
table.add(bar.get(base())).growX();
table.add(bar.get(self())).growX();
table.row();
}
}
@@ -1019,7 +1076,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @return whether or not this block should be deselected.
*/
public boolean onConfigureTileTapped(Building other){
return base() != other;
return self() != other;
}
/** Returns whether this config menu should show when the specified player taps it. */
@@ -1060,6 +1117,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return true;
}
public boolean canPickup(){
return true;
}
public void removeFromProximity(){
onProximityRemoved();
tmpTiles.clear();
@@ -1074,7 +1135,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
for(Building other : tmpTiles){
other.proximity.remove(base(), true);
other.proximity.remove(self(), true);
other.onProximityUpdate();
}
}
@@ -1090,8 +1151,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(other == null || !(other.tile.interactable(team))) continue;
//add this tile to proximity of nearby tiles
if(!other.proximity.contains(base(), true)){
other.proximity.add(base());
if(!other.proximity.contains(self(), true)){
other.proximity.add(self());
}
tmpTiles.add(other);
@@ -1130,13 +1191,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Replace
@Override
public boolean isValid(){
return tile.build == base() && !dead();
return tile.build == self() && !dead();
}
@Replace
@Override
public void kill(){
Call.tileDestroyed(base());
Call.tileDestroyed(self());
}
@Replace
@@ -1150,10 +1211,59 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
damage /= state.rules.blockHealthMultiplier;
}
Call.tileDamage(base(), health - handleDamage(damage));
Call.tileDamage(self(), health - handleDamage(damage));
if(health <= 0){
Call.tileDestroyed(base());
Call.tileDestroyed(self());
}
}
@Override
public double sense(LAccess sensor){
return switch(sensor){
case x -> x;
case y -> y;
case team -> team.id;
case health -> health;
case maxHealth -> maxHealth();
case efficiency -> efficiency();
case rotation -> rotation;
case totalItems -> items == null ? 0 : items.total();
case totalLiquids -> liquids == null ? 0 : liquids.total();
case totalPower -> power == null || !block.consumes.hasPower() ? 0 : power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
case itemCapacity -> block.itemCapacity;
case liquidCapacity -> block.liquidCapacity;
case powerCapacity -> block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
case powerNetIn -> power == null ? 0 : power.graph.getLastScaledPowerIn() * 60;
case powerNetOut -> power == null ? 0 : power.graph.getLastScaledPowerOut() * 60;
case powerNetStored -> power == null ? 0 : power.graph.getLastPowerStored();
case powerNetCapacity -> power == null ? 0 : power.graph.getLastCapacity();
case enabled -> enabled ? 1 : 0;
default -> 0;
};
}
@Override
public Object senseObject(LAccess sensor){
return switch(sensor){
case type -> block;
default -> noSensed;
};
}
@Override
public double sense(Content content){
if(content instanceof Item && items != null) return items.get((Item)content);
if(content instanceof Liquid && liquids != null) return liquids.get((Liquid)content);
return 0;
}
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.enabled){
enabled = !Mathf.zero((float)p1);
enabledControlTime = timeToUncontrol;
}
}
@@ -1181,15 +1291,25 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
timeScale = 1f;
}
if(block.autoResetEnabled){
enabledControlTime -= Time.delta;
if(enabledControlTime <= 0){
enabled = true;
}
}
if(sound != null){
sound.update(x, y, shouldActiveSound());
}
if(block.idleSound != Sounds.none && shouldIdleSound()){
loops.play(block.idleSound, base(), block.idleSoundVolume);
loops.play(block.idleSound, self(), block.idleSoundVolume);
}
updateTile();
if(enabled || !block.noUpdateDisabled){
updateTile();
}
if(items != null){
items.update(updateFlow);

View File

@@ -25,27 +25,36 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
Object data;
BulletType type;
float damage;
float fdata;
@Override
public void getCollisions(Cons<QuadTree> consumer){
for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team));
if(team.active()){
for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team));
}
}else{
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
consumer.get(teamIndex.tree(other));
}
}
}
}
@Override
public void drawBullets(){
type.draw(base());
type.draw(self());
}
@Override
public void add(){
type.init(base());
type.init(self());
}
@Override
public void remove(){
type.despawned(base());
type.despawned(self());
collided.clear();
}
@@ -83,10 +92,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@MethodPriority(100)
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(base(), x, y);
type.hit(self(), x, y);
float health = 0f;
if(other instanceof Healthc){
Healthc h = (Healthc)other;
health = h.health();
h.damage(damage);
}
@@ -102,30 +113,40 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}else{
collided.add(other.id());
}
type.hitEntity(self(), other, health);
}
@Override
public void update(){
type.update(base());
type.update(self());
if(type.collidesTiles && type.collides && type.collidesGround){
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Building tile = world.build(x, y);
if(tile == null) return false;
if(tile == null || !isAdded()) return false;
if(tile.collide(base()) && type.collides(base(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team)){
if(tile.collide(self()) && type.collides(self(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team) && !(type.pierceBuilding && collided.contains(tile.id))){
boolean remove = false;
float health = tile.health;
if(tile.team != team){
remove = tile.collision(base());
remove = tile.collision(self());
}
if(remove || type.collidesTeam){
type.hitTile(base(), tile);
remove();
if(!type.pierceBuilding){
remove();
}else{
collided.add(tile.id);
}
}
return true;
type.hitTile(self(), tile, health);
return !type.pierceBuilding;
}
return false;
@@ -137,8 +158,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public void draw(){
Draw.z(Layer.bullet);
type.draw(base());
type.drawLight(base());
type.draw(self());
type.drawLight(self());
}
/** Sets the bullet's rotation in degrees. */

View File

@@ -1,10 +1,13 @@
package mindustry.entities.comp;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.ai.formations.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -12,16 +15,19 @@ import mindustry.gen.*;
@Component
abstract class CommanderComp implements Unitc{
private static final Seq<FormationMember> members = new Seq<>();
private static final Seq<Unit> units = new Seq<>();
@Import float x, y, rotation;
transient @Nullable Formation formation;
transient Seq<Unit> controlling = new Seq<>();
/** minimum speed of any unit in the formation. */
transient float minFormationSpeed;
@Override
public void update(){
if(formation != null){
formation.anchor.set(x, y, rotation);
formation.anchor.set(x, y, /*rotation*/ 0); //TODO rotation set to 0 because rotating is pointless
formation.updateSlots();
}
}
@@ -42,21 +48,51 @@ abstract class CommanderComp implements Unitc{
clearCommand();
}
void commandNearby(FormationPattern pattern){
commandNearby(pattern, u -> true);
}
void commandNearby(FormationPattern pattern, Boolf<Unit> include){
Formation formation = new Formation(new Vec3(x, y, rotation), pattern);
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
units.clear();
Units.nearby(team(), x, y, 200f, u -> {
if(u.isAI() && include.get(u) && u != self()){
units.add(u);
}
});
units.sort(u -> u.dst2(this));
units.truncate(type().commandLimit);
command(formation, units);
}
void command(Formation formation, Seq<Unit> units){
clearCommand();
float spacing = hitSize() * 0.65f;
minFormationSpeed = type().speed;
controlling.addAll(units);
for(Unit unit : units){
unit.controller(new FormationAI(base(), formation));
FormationAI ai;
unit.controller(ai = new FormationAI(self(), formation));
spacing = Math.max(spacing, ai.formationSize());
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
}
this.formation = formation;
//update formation spacing based on max size
formation.pattern.spacing = spacing;
members.clear();
for(Unitc u : units){
members.add((FormationAI)u.controller());
}
//TODO doesn't handle units that don't fit a formation
formation.addMembers(members);
}
@@ -68,7 +104,7 @@ abstract class CommanderComp implements Unitc{
void clearCommand(){
//reset controlled units
for(Unit unit : controlling){
if(unit.controller().isBeingControlled(base())){
if(unit.controller().isBeingControlled(self())){
unit.controller(unit.type().createController());
}
}

View File

@@ -27,7 +27,7 @@ abstract class DecalComp implements Drawc, Timedc, Rotc, Posc{
@Replace
public float clipSize(){
return region.getWidth()*2;
return region.width *2;
}
}

View File

@@ -8,13 +8,16 @@ import mindustry.gen.*;
@EntityDef(value = {EffectStatec.class, Childc.class}, pooled = true, serialize = false)
@Component(base = true)
abstract class EffectStateComp implements Posc, Drawc, Timedc, Rotc, Childc{
@Import float time, lifetime, rotation, x, y;
@Import int id;
Color color = new Color(Color.white);
Effect effect;
Object data;
@Override
public void draw(){
effect.render(id(), color, time(), rotation(), x(), y(), data);
lifetime = effect.render(id, color, time, lifetime, rotation, x, y, data);
}
@Replace

View File

@@ -1,23 +1,18 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
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);
}
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::solid;
}
}

View File

@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
import static mindustry.Vars.player;
import static mindustry.Vars.*;
@Component
@BaseComponent
@@ -40,7 +40,7 @@ abstract class EntityComp{
return false;
}
<T extends Entityc> T base(){
<T extends Entityc> T self(){
return (T)this;
}

View File

@@ -15,14 +15,14 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec{
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
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;
private transient Block block;
private transient float baseFlammability = -1, puddleFlammability;
@Override
public void update(){
@@ -97,6 +97,11 @@ abstract class FireComp implements Timedc, Posc, Firec{
@Override
public void afterRead(){
Fires.register(base());
Fires.register(self());
}
@Override
public void afterSync(){
Fires.register(self());
}
}

View File

@@ -1,20 +1,22 @@
package mindustry.entities.comp;
import arc.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.net;
import static mindustry.Vars.*;
@Component
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
@Import float x, y;
@Import float x, y, speedMultiplier;
@Import Vec2 vel;
@SyncLocal float elevation;
@@ -56,7 +58,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
float floorSpeedMultiplier(){
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier;
return on.speedMultiplier * speedMultiplier;
}
@Override
@@ -74,7 +76,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
}
if(!hovering && isGrounded() && floor.isLiquid){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= 7f){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
splashTimer = 0f;
}
@@ -90,7 +92,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
//TODO is the netClient check necessary?
if(drownTime >= 0.999f && !net.client()){
kill();
//TODO drown event!
Events.fire(new UnitDrownEvent(self()));
}
}else{
drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f);

View File

@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HealthComp implements Entityc{
abstract class HealthComp implements Entityc, Posc{
static final float hitDuration = 9f;
float health;

View File

@@ -2,8 +2,8 @@ package mindustry.entities.comp;
import arc.func.*;
import arc.math.*;
import arc.math.geom.QuadTree.*;
import arc.math.geom.*;
import arc.math.geom.QuadTree.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@@ -61,7 +61,8 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
}
public void hitboxTile(Rect rect){
float scale = 0.66f;
rect.setCentered(x, y, hitSize * scale, hitSize * scale);
//tile hitboxes are never bigger than a tile, otherwise units get stuck
float size = Math.min(hitSize * 0.66f, 7.9f);
rect.setCentered(x, y, size, size);
}
}

View File

@@ -44,7 +44,7 @@ abstract class LaunchCoreComp implements Drawc, Timedc{
Draw.z(Layer.weather - 1);
TextureRegion region = block.icon(Cicon.full);
float rw = region.getWidth() * Draw.scl * scale, rh = region.getHeight() * Draw.scl * scale;
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
Draw.alpha(alpha);
Draw.rect(region, cx, cy, rw, rh, rotation - 45);

View File

@@ -4,16 +4,16 @@ import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Import float x, y;
@@ -26,34 +26,52 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public void move(float cx, float cy){
collisions.moveCheck(this, cx, cy, !type.allowLegStep ? EntityCollisions::solid : EntityCollisions::legsSolid);
public SolidPred solidity(){
return !type.allowLegStep ? EntityCollisions::solid : EntityCollisions::legsSolid;
}
@Override
@Replace
public int pathType(){
return Pathfinder.costLegs;
}
@Override
public void add(){
resetLegs();
}
public void resetLegs(){
float rot = baseRotation;
int count = type.legCount;
float legLength = type.legLength;
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
}
@Override
public void update(){
if(Mathf.dst(deltaX(), deltaY()) > 0.001f){
baseRotation = Mathf.slerpDelta(baseRotation, Mathf.angle(deltaX(), deltaY()), 0.1f);
baseRotation = Angles.moveToward(baseRotation, Mathf.angle(deltaX(), deltaY()), type.rotateSpeed);
}
float rot = baseRotation;
int count = type.legCount;
float legLength = type.legLength;
//set up initial leg positions
if(legs.length != type.legCount){
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
resetLegs();
}
float moveSpeed = type.legSpeed;
@@ -100,7 +118,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
//shake when legs contact ground
if(type.landShake > 0){
Effects.shake(type.landShake, type.landShake, l.base);
Effect.shake(type.landShake, type.landShake, l.base);
}
if(type.legSplashDamage > 0){

View File

@@ -1,19 +1,86 @@
package mindustry.entities.comp;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@Component
abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, ElevationMovec{
@Import float x, y, hitSize;
@Import UnitType type;
@SyncField(false) @SyncLocal float baseRotation;
transient float walkTime;
transient float walkTime, walkExtension;
transient private boolean walked;
@Override
public void update(){
float len = deltaLen();
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed));
walkTime += Time.delta *len;
//trigger animation only when walking manually
if(walked || net.client()){
float len = deltaLen();
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed / Time.delta) * Time.delta);
walkTime += len;
walked = false;
}
//update mech effects
float extend = walkExtend(false);
float base = walkExtend(true);
float extendScl = base % 1f;
float lastExtend = walkExtension;
if(extendScl < lastExtend && base % 2f > 1f){
int side = -Mathf.sign(extend);
float width = hitSize / 2f * side, length = type.mechStride * 1.35f;
float cx = x + Angles.trnsx(baseRotation, length, width),
cy = y + Angles.trnsy(baseRotation, length, width);
if(type.mechStepShake > 0){
Effect.shake(type.mechStepShake, type.mechStepShake, cx, cy);
}
if(type.mechStepParticles){
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Color color = tile.floor().mapColor;
Fx.unitLand.at(cx, cy, hitSize/8f, color);
}
}
}
walkExtension = extendScl;
}
public float walkExtend(boolean scaled){
//now ranges from -maxExtension to maxExtension*3
float raw = walkTime % (type.mechStride * 4);
if(scaled) return raw / type.mechStride;
if(raw > type.mechStride*3) raw = raw - type.mechStride * 4;
else if(raw > type.mechStride*2) raw = type.mechStride * 2 - raw;
else if(raw > type.mechStride) raw = type.mechStride * 2 - raw;
return raw;
}
@Override
public void moveAt(Vec2 vector, float acceleration){
if(!vector.isZero()){
//mark walking state when moving in a controlled manner
walked = true;
}
}
}

View File

@@ -4,8 +4,8 @@ import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
@@ -33,52 +33,56 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
}
boolean mining(){
return mineTile != null;
return mineTile != null && !(((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding());
}
@Override
public void update(){
Building core = closestCore();
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange)){
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange) && !offloadImmediately()){
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());
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
clearItem();
}
}
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding())
|| mineTile.drop() == null || !acceptsItem(mineTile.drop()) || !canMine(mineTile.drop())){
|| mineTile.drop() == null || !canMine(mineTile.drop())){
mineTile = null;
mineTimer = 0f;
}else{
}else if(mining()){
Item item = mineTile.drop();
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
lookAt(angleTo(mineTile.worldx(), mineTile.worldy()));
mineTimer += Time.delta *type.mineSpeed;
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);
}
if(mineTimer >= 50f + item.hardness*10f){
mineTimer = 0;
if(within(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());
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
}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);
}else{
mineTile = null;
mineTimer = 0f;
}
}
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);
}
}
}
@@ -101,7 +105,6 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
Drawf.laser(team(), 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());

View File

@@ -6,17 +6,36 @@ 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 mindustry.world.blocks.payloads.*;
/** An entity that holds a payload. */
@Component
abstract class PayloadComp implements Posc, Rotc, Hitboxc{
abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
@Import float x, y, rotation;
@Import UnitType type;
Seq<Payload> payloads = new Seq<>();
float payloadUsed(){
return payloads.sumf(p -> p.size() * p.size());
}
boolean canPickup(Unit unit){
return payloadUsed() + unit.hitSize * unit.hitSize <= type.payloadCapacity + 0.001f;
}
boolean canPickup(Building build){
return payloadUsed() + build.block.size * build.block.size * Vars.tilesize * Vars.tilesize <= type.payloadCapacity + 0.001f;
}
boolean canPickupPayload(Payload pay){
return payloadUsed() + pay.size()*pay.size() <= type.payloadCapacity + 0.001f;
}
boolean hasPayload(){
return payloads.size > 0;
}
@@ -29,10 +48,13 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
unit.remove();
payloads.add(new UnitPayload(unit));
Fx.unitPickup.at(unit);
if(Vars.net.client()){
Vars.netClient.clearRemovedEntity(unit.id);
}
}
void pickup(Building tile){
tile.tile().remove();
tile.tile.remove();
payloads.add(new BlockPayload(tile));
Fx.unitPickup.at(tile);
}
@@ -52,6 +74,11 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
boolean tryDropPayload(Payload payload){
Tile on = tileOn();
//clear removed state of unit so it can be synced
if(Vars.net.client() && payload instanceof UnitPayload){
Vars.netClient.clearRemovedEntity(((UnitPayload)payload).unit.id);
}
//drop off payload on an acceptor if possible
if(on != null && on.build != null && on.build.acceptPayload(on.build, payload)){
Fx.unitDrop.at(on.build);
@@ -71,15 +98,21 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
Unit u = payload.unit;
//can't drop ground units
if((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f){
if(!u.canPass(tileX(), tileY())){
return false;
}
Fx.unitDrop.at(this);
//clients do not drop payloads
if(Vars.net.client()) return true;
u.set(this);
u.trns(Tmp.v1.rnd(Mathf.random(2f)));
u.rotation(rotation);
//reset the ID to a new value to make sure it's synced
u.id = EntityGroup.nextId();
u.add();
Fx.unitDrop.at(u);
return true;
}
@@ -87,9 +120,9 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
/** @return whether the tile has been successfully placed. */
boolean dropBlock(BlockPayload payload){
Building tile = payload.entity;
int tx = Vars.world.toTile(x - tile.block().offset), ty = Vars.world.toTile(y - tile.block().offset);
int tx = Vars.world.toTile(x - tile.block.offset), ty = Vars.world.toTile(y - tile.block.offset);
Tile on = Vars.world.tile(tx, ty);
if(on != null && Build.validPlace(tile.block(), tile.team(), tx, ty, tile.rotation)){
if(on != null && Build.validPlace(tile.block, tile.team, tx, ty, tile.rotation, false)){
int rot = (int)((rotation + 45f) / 90f) % 4;
payload.place(on, rot);

View File

@@ -33,11 +33,13 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Import float x, y;
@NonNull @ReadOnly Unit unit = Nulls.unit;
@ReadOnly Unit unit = Nulls.unit;
transient private Unit lastReadUnit = Nulls.unit;
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean admin, typing, shooting, boosting;
@SyncLocal boolean typing, shooting, boosting;
boolean admin;
@SyncLocal float mouseX, mouseY;
String name = "noname";
Color color = new Color();
@@ -54,11 +56,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit instanceof Minerc;
}
public @Nullable CoreEntity closestCore(){
public @Nullable CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable CoreEntity core(){
public @Nullable CoreBuild core(){
return team.core();
}
@@ -69,6 +71,10 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit.icon();
}
public boolean displayAmmo(){
return unit instanceof BlockUnitc || state.rules.unitAmmo;
}
public void reset(){
team = state.rules.defaultTeam;
admin = typing = false;
@@ -86,11 +92,17 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Replace
public float clipSize(){
return unit.isNull() ? 20 : unit.type().hitsize * 2f;
return unit.isNull() ? 20 : unit.type().hitSize * 2f;
}
@Override
public void afterSync(){
//simulate a unit change after sync
Unit set = unit;
unit = lastReadUnit;
unit(set);
lastReadUnit = unit;
unit.aim(mouseX, mouseY);
//this is only necessary when the thing being controlled isn't synced
unit.controlWeapons(shooting, shooting);
@@ -104,7 +116,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
clearUnit();
}
CoreEntity core = closestCore();
CoreBuild core = closestCore();
if(!dead()){
set(unit);
@@ -122,7 +134,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
deathTimer += Time.delta;
if(deathTimer >= deathDelay){
//request spawn - this happens serverside only
core.requestSpawn(base());
core.requestSpawn(self());
deathTimer = 0;
}
}
@@ -163,6 +175,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public void unit(Unit unit){
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit == unit) return;
if(this.unit != Nulls.unit){
//un-control the old unit
this.unit.controller(this.unit.type().createController());
@@ -171,9 +184,14 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
if(unit != Nulls.unit){
unit.team(team);
unit.controller(this);
//this player just became remote, snap the interpolation so it doesn't go wild
if(unit.isRemote()){
unit.snapInterpolation();
}
}
Events.fire(new UnitChangeEvent(base(), unit));
Events.fire(new UnitChangeEvent(self(), unit));
}
boolean dead(){

View File

@@ -8,7 +8,7 @@ import mindustry.content.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.world;
import static mindustry.Vars.*;
@Component
abstract class PosComp implements Position{
@@ -45,11 +45,16 @@ abstract class PosComp implements Position{
return tile == null || tile.block() != Blocks.air ? (Floor)Blocks.air : tile.floor();
}
Block blockOn(){
Block blockOn(){
Tile tile = tileOn();
return tile == null ? Blocks.air : tile.block();
}
boolean onSolid(){
Tile tile = tileOn();
return tile != null && tile.solid();
}
@Nullable Tile tileOn(){
return world.tileWorld(x, y);
}

View File

@@ -128,6 +128,6 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Override
public void afterRead(){
Puddles.register(base());
Puddles.register(self());
}
}

View File

@@ -5,7 +5,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
import static mindustry.Vars.minArmorDamage;
import static mindustry.Vars.*;
@Component
abstract class ShieldComp implements Healthc, Posc{
@@ -23,7 +23,6 @@ abstract class ShieldComp implements Healthc, Posc{
@Override
public void damage(float amount){
//apply armor
//TODO balancing of armor stats & minArmorDamage
amount = Math.max(amount - armor, minArmorDamage * amount);
hitTime = 1f;

View File

@@ -1,7 +1,6 @@
package mindustry.entities.comp;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
@@ -22,10 +21,7 @@ abstract class StatusComp implements Posc, Flyingc{
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, armorMultiplier = 1, reloadMultiplier = 1;
/** @return damage taken based on status armor multipliers */
float getShieldDamage(float amount){
return amount * Mathf.clamp(1f - armorMultiplier / 100f);
}
@Import UnitType type;
/** Apply a status effect for 1 tick (for permanent effects) **/
void apply(StatusEffect effect){
@@ -46,7 +42,7 @@ abstract class StatusComp implements Posc, Flyingc{
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition(base(), effect, entry.time, duration, StatusEntry.tmp);
entry.effect.getTransition(self(), effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(StatusEntry.tmp.effect != entry.effect){
@@ -102,7 +98,7 @@ abstract class StatusComp implements Posc, Flyingc{
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded()){
if(isGrounded() && !type.hovering){
//apply effect
apply(floor.status, floor.statusDuration);
}
@@ -129,14 +125,14 @@ abstract class StatusComp implements Posc, Flyingc{
armorMultiplier *= entry.effect.armorMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
reloadMultiplier *= entry.effect.reloadMultiplier;
entry.effect.update(base(), entry.time);
entry.effect.update(self(), entry.time);
}
}
}
public void draw(){
for(StatusEntry e : statuses){
e.effect.draw(base());
e.effect.draw(self());
}
}

View File

@@ -13,6 +13,7 @@ abstract class SyncComp implements Entityc{
//all these method bodies are internally generated
void snapSync(){}
void snapInterpolation(){}
void readSync(Reads read){}
void writeSync(Writes write){}
void readSyncManual(FloatBuffer buffer){}

View File

@@ -5,7 +5,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.state;
import static mindustry.Vars.*;
@Component
abstract class TeamComp implements Posc{

View File

@@ -5,16 +5,21 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.abilities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -23,18 +28,19 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable{
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable{
@Import boolean hovering;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health;
@Import boolean dead;
@Import boolean hovering, dead;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo;
@Import Team team;
@Import int id;
private UnitController controller;
private UnitType type;
boolean spawnedByCore, deactivated;
boolean spawnedByCore;
transient float timer1, timer2;
transient Seq<Ability> abilities = new Seq<>(0);
private transient float resupplyTime = Mathf.random(10f);
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
@@ -50,13 +56,66 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
lookAt(x, y);
}
public boolean inRange(Position other){
return within(other, type.range);
}
public boolean hasWeapons(){
return type.hasWeapons();
}
public float range(){
return type.range;
}
@Replace
public float clipSize(){
return type.region.getWidth() * 2f;
return Math.max(type.region.width * 2f, type.clipSize);
}
@Override
public double sense(LAccess sensor){
return switch(sensor){
case totalItems -> stack().amount;
case rotation -> rotation;
case health -> health;
case maxHealth -> maxHealth;
case x -> x;
case y -> y;
case team -> team.id;
case shooting -> isShooting() ? 1 : 0;
case shootX -> aimX();
case shootY -> aimY();
default -> 0;
};
}
@Override
public Object senseObject(LAccess sensor){
return switch(sensor){
case type -> type;
default -> noSensed;
};
}
@Override
public double sense(Content content){
if(content == stack().item) return stack().amount;
return 0;
}
@Override
@Replace
public boolean canDrown(){
return isGrounded() && !hovering && type.canDrown;
}
@Override
@Replace
public boolean canShoot(){
//cannot shoot while boosting
return !(type.canBoost && isFlying());
}
@Override
@@ -72,7 +131,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void controller(UnitController next){
this.controller = next;
if(controller.unit() != base()) controller.unit(base());
if(controller.unit() != self()) controller.unit(self());
}
@Override
@@ -102,8 +161,13 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type;
}
/** @return pathfinder path type for calculating costs */
public int pathType(){
return Pathfinder.costGround;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta);
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta * speedMultiplier());
}
public void lookAt(Position pos){
@@ -126,23 +190,26 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return Units.getCap(team);
}
private void setStats(UnitType type){
public void setStats(UnitType type){
this.type = type;
this.maxHealth = type.health;
this.drag = type.drag;
this.armor = type.armor;
this.hitSize = type.hitsize;
this.hitSize = type.hitSize;
this.hovering = type.hovering;
if(controller == null) controller(type.createController());
if(mounts().length != type.weapons.size) setupWeapons(type);
if(abilities.size != type.abilities.size){
abilities = type.abilities.map(Ability::copy);
}
}
@Override
public void afterSync(){
//set up type info after reading
setStats(this.type);
controller.unit(base());
controller.unit(self());
}
@Override
@@ -154,46 +221,55 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void add(){
teamIndex.updateCount(team, type, 1);
//check if over unit cap
if(count() > cap() && !spawnedByCore){
deactivated = true;
}else{
teamIndex.updateActiveCount(team, type, 1);
if(count() > cap() && !spawnedByCore && !dead){
Call.unitCapDeath(self());
teamIndex.updateCount(team, type, -1);
}
}
@Override
public void remove(){
teamIndex.updateCount(team, type, -1);
controller.removed(base());
controller.removed(self());
}
@Override
public void landed(){
if(type.landShake > 0f){
Effects.shake(type.landShake, type.landShake, this);
Effect.shake(type.landShake, type.landShake, this);
}
type.landed(base());
type.landed(self());
}
@Override
public void update(){
//activate the unit when possible
if(!net.client() && deactivated && teamIndex.countActive(team, type) < Units.getCap(team)){
teamIndex.updateActiveCount(team, type, 1);
deactivated = false;
type.update(self());
if(state.rules.unitAmmo && ammo < type.ammoCapacity - 0.0001f){
resupplyTime += Time.delta;
//resupply only at a fixed interval to prevent lag
if(resupplyTime > 10f){
type.ammoType.resupply(self());
resupplyTime = 0f;
}
}
if(!deactivated) type.update(base());
if(abilities.size > 0){
for(Ability a : abilities){
a.update(self());
}
}
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;
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(within(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));
@@ -202,7 +278,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
//simulate falling down
if(dead){
if(dead || health <= 0){
//less drag when dead
drag = 0.01f;
@@ -237,7 +313,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(tile != null && isGrounded() && !type.hovering){
//unit block update
if(tile.build != null){
tile.build.unitOn(base());
tile.build.unitOn(self());
}
//apply damage
@@ -246,19 +322,29 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
//kill entities on tiles that are solid to them
if(tile != null && !canPassOn()){
//boost if possible
if(type.canBoost){
elevation = 1f;
}else if(!net.client()){
kill();
}
}
//AI only updates on the server
if(!net.client() && !dead && !deactivated){
if(!net.client() && !dead){
controller.updateUnit();
}
//do not control anything when deactivated
if(deactivated){
controlWeapons(false, false);
//clear controller when it becomes invalid
if(!controller.isValidController()){
resetController();
}
//remove units spawned by the core
if(spawnedByCore && !isPlayer()){
Call.unitDespawn(base());
Call.unitDespawn(self());
}
}
@@ -269,18 +355,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** Actually destroys the unit, removing it and creating explosions. **/
public void destroy(){
float explosiveness = 2f + item().explosiveness * stack().amount;
float flammability = item().flammability * stack().amount;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame);
float explosiveness = 2f + item().explosiveness * stack().amount / 2f;
float flammability = item().flammability * stack().amount / 2f;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame, state.rules.damageExplosions);
float shake = hitSize / 3f;
Effects.scorch(x, y, (int)(hitSize / 5));
Effect.scorch(x, y, (int)(hitSize / 5));
Fx.explosion.at(this);
Effects.shake(shake, shake, this);
Effect.shake(shake, shake, this);
type.deathSound.at(this);
Events.fire(new UnitDestroyEvent(base()));
Events.fire(new UnitDestroyEvent(self()));
if(explosiveness > 7f && isLocal()){
Events.fire(Trigger.suicideBomb);
@@ -288,15 +374,15 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//if this unit crash landed (was flying), damage stuff in a radius
if(type.flying){
Damage.damage(team,x, y, hitSize * 1.1f, hitSize * type.crashDamageMultiplier, true, false, true);
Damage.damage(team,x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f, true, false, true);
}
if(!headless){
for(int i = 0; i < type.wreckRegions.length; i++){
if(type.wreckRegions[i].found()){
float range = type.hitsize/4f;
float range = type.hitSize /4f;
Tmp.v1.rnd(range);
Effects.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
Effect.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
}
}
}
@@ -306,7 +392,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void display(Table table){
type.display(base(), table);
type.display(self(), table);
}
@Override
@@ -316,7 +402,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void draw(){
type.draw(base());
type.draw(self());
}
@Override
@@ -335,7 +421,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
dead = true;
//don't waste time when the unit is already on the ground, just destroy it
if(isGrounded()){
if(!type.flying){
destroy();
}
}
@@ -346,6 +432,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(dead || net.client()) return;
//deaths are synced; this calls killed()
Call.unitDeath(base());
Call.unitDeath(id);
}
}

View File

@@ -1,10 +1,15 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@Component
abstract class VelComp implements Posc{
@Import float x, y;
@@ -18,7 +23,24 @@ abstract class VelComp implements Posc{
@Override
public void update(){
move(vel.x * Time.delta, vel.y * Time.delta);
vel.scl(1f - drag * Time.delta);
vel.scl(Mathf.clamp(1f - drag * Time.delta));
}
/** @return function to use for check solid state. if null, no checking is done. */
@Nullable
SolidPred solidity(){
return null;
}
/** @return whether this entity can move through a location*/
boolean canPass(int tileX, int tileY){
SolidPred s = solidity();
return s == null || !s.solid(tileX, tileY);
}
/** @return whether this entity can exist on its current location*/
boolean canPassOn(){
return canPass(tileX(), tileY());
}
boolean moving(){
@@ -26,7 +48,13 @@ abstract class VelComp implements Posc{
}
void move(float cx, float cy){
x += cx;
y += cy;
SolidPred check = solidity();
if(check != null){
collisions.move(self(), cx, cy, check);
}else{
x += cx;
y += cy;
}
}
}

View File

@@ -4,16 +4,17 @@ import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
//just a proof of concept
@Component
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@@ -35,13 +36,26 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
}
}
@Override
@Replace
public int pathType(){
return Pathfinder.costWater;
}
@Override
public void add(){
tleft.clear();
tright.clear();
}
@Override
public void draw(){
float z = Draw.z();
Draw.z(Layer.debris);
Floor floor = floorOn();
Color color = Tmp.c1.set(floor.mapColor).mul(1.5f);
Floor floor = tileOn() == null ? Blocks.air.asFloor() : tileOn().floor();
Color color = Tmp.c1.set(floor.mapColor.equals(Color.black) ? Blocks.water.mapColor : floor.mapColor).mul(1.5f);
trailColor.lerp(color, Mathf.clamp(Time.delta * 0.04f));
tleft.draw(trailColor, type.trailScl);
@@ -52,19 +66,8 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public void move(float cx, float cy){
if(isGrounded()){
collisions.moveCheck(this, cx, cy, EntityCollisions::waterSolid);
}else{
x += cx;
y += cy;
}
}
@Replace
@Override
public boolean canDrown(){
return false;
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::waterSolid;
}
@Replace
@@ -72,5 +75,10 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.isDeep() ? 1.3f : 1f;
}
public boolean onLiquid(){
Tile tile = tileOn();
return tile != null && tile.floor().isLiquid;
}
}

View File

@@ -13,38 +13,35 @@ import mindustry.type.*;
import static mindustry.Vars.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Import float x, y, rotation, reloadMultiplier;
@Import Vec2 vel;
@Import UnitType type;
/** minimum cursor distance from unit, fixes 'cross-eyed' shooting */
static final float minAimDst = 18f;
/** temporary weapon sequence number */
static int sequenceNum = 0;
/** weapon mount array, never null */
@SyncLocal WeaponMount[] mounts = {};
@ReadOnly transient float range, aimX, aimY;
@ReadOnly transient float aimX, aimY;
@ReadOnly transient boolean isRotate;
boolean isShooting;
float ammo;
float ammof(){
return ammo / type.ammoCapacity;
}
void setWeaponRotation(float rotation){
for(WeaponMount mount : mounts){
mount.rotation = rotation;
}
}
boolean inRange(Position other){
return within(other, range);
}
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
range = def.range;
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());
}
}
@@ -68,7 +65,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
/** 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);
if(Tmp.v1.len() < type.aimDst) Tmp.v1.setLength(type.aimDst);
x = Tmp.v1.x + this.x;
y = Tmp.v1.y + this.y;
@@ -82,13 +79,50 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
aimY = y;
}
boolean canShoot(){
return true;
}
@Override
public void remove(){
for(WeaponMount mount : mounts){
if(mount.bullet != null){
mount.bullet.time = mount.bullet.lifetime - 10f;
mount.bullet = null;
}
}
}
/** Update shooting and rotation for this unit. */
@Override
public void update(){
boolean can = canShoot();
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
float weaponRotation = this.rotation - 90 + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
//update continuous state
if(weapon.continuous && mount.bullet != null){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime){
mount.bullet = null;
}else{
mount.bullet.rotation(weaponRotation + 90);
mount.bullet.set(shootX, shootY);
vel.add(Tmp.v1.trns(rotation + 180f, mount.bullet.type.recoil));
}
}else{
//heat decreases when not firing
mount.heat = Math.max(mount.heat - Time.delta * reloadMultiplier / mount.weapon.cooldownTime, 0);
}
//flip weapon shoot side for alternating weapons at half reload
if(weapon.otherSide != -1 && weapon.alternate && mount.side == weapon.flipSprite &&
mount.reload + Time.delta > weapon.reload/2f && mount.reload <= weapon.reload/2f){
@@ -97,38 +131,28 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot)){
float axisX = this.x + Angles.trnsx(rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(rotation - 90, weapon.x, weapon.y);
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
float axisX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation;
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - this.rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else{
}else if(!weapon.rotate){
mount.rotation = 0;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
//shoot if applicable
if(mount.shoot && //must be shooting
can && //must be able to shoot
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
(!weapon.alternate || mount.side == weapon.flipSprite) &&
vel.len() >= mount.weapon.minShootVelocity && //check velocity requirements
//TODO checking for velocity this way isn't entirely correct
(vel.len() >= mount.weapon.minShootVelocity || (net.active() && !isLocal())) && //check velocity requirements
mount.reload <= 0.0001f && //reload has to be 0
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
){
float rotation = this.rotation - 90;
float weaponRotation = rotation + (weapon.rotate ? mount.rotation : 0);
//m a t h
float mountX = this.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(rotation, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, 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, mount.aimX, mount.aimY, shootAngle, Mathf.sign(weapon.x));
shoot(mount, shootX, shootY, mount.aimX, mount.aimY, shootAngle, Mathf.sign(weapon.x));
mount.reload = weapon.reload;
@@ -138,7 +162,8 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
}
private void shoot(Weapon weapon, float x, float y, float aimX, float aimY, float rotation, int side){
private void shoot(WeaponMount mount, float x, float y, float aimX, float aimY, float rotation, int side){
Weapon weapon = mount.weapon;
float baseX = this.x, baseY = this.y;
@@ -148,27 +173,46 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(x, y, aimX, aimY) / ammo.range()) : 1f;
sequenceNum = 0;
if(weapon.shotDelay > 0.01f){
if(weapon.shotDelay + weapon.firstShotDelay > 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), lifeScl));
Time.run(sequenceNum * weapon.shotDelay + weapon.firstShotDelay, () -> {
if(!isAdded()) return;
mount.bullet = bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl);
});
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> mount.bullet = bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
}
if(this instanceof Velc){
((Velc)this).vel().add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
}
boolean parentize = ammo.keepVelocity;
Effects.shake(weapon.shake, weapon.shake, x, y);
if(weapon.firstShotDelay > 0){
Time.run(weapon.firstShotDelay, () -> {
if(!isAdded()) return;
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
});
}else{
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
}
weapon.ejectEffect.at(x, y, rotation * side);
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
apply(weapon.shootStatus, weapon.shootStatusDuration);
}
private void bullet(Weapon weapon, float x, float y, float angle, float lifescl){
weapon.bullet.create(this, team(), x, y, angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
private Bullet bullet(Weapon weapon, float x, float y, float angle, float lifescl){
float xr = Mathf.range(weapon.xRand);
return weapon.bullet.create(this, team(),
x + Angles.trnsx(angle, 0, xr),
y + Angles.trnsy(angle, 0, xr),
angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
}
}

View File

@@ -2,51 +2,178 @@ package mindustry.entities.units;
import arc.math.*;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.indexer;
import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0;
protected static final int timerTarget2 = 1;
protected static final int timerTarget3 = 2;
protected Unit unit;
protected Teamc target;
protected Interval timer = new Interval(4);
/** main target that is being faced */
protected Teamc target;
/** targets for each weapon */
protected Teamc[] targets = {};
{
timer.reset(0, Mathf.random(40f));
timer.reset(1, Mathf.random(60f));
}
protected void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team, flag));
if(target != null) this.target = target.build;
@Override
public void updateUnit(){
updateVisuals();
updateTargeting();
updateMovement();
}
protected void targetClosestEnemyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team, flag));
if(target != null) this.target = target.build;
protected UnitCommand command(){
return unit.team.data().command;
}
protected void updateVisuals(){
if(unit.isFlying()){
unit.wobble();
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
}
}
protected void updateMovement(){
}
protected void updateTargeting(){
if(unit.hasWeapons()){
updateWeapons();
}
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
float rotation = unit.rotation - 90;
boolean ret = retarget();
if(ret){
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
}
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y)){
target = null;
}
for(int i = 0; i < targets.length; i++){
WeaponMount mount = unit.mounts[i];
Weapon weapon = mount.weapon;
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
if(unit.type().singleTarget){
targets[i] = target;
}else{
if(ret){
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
}
if(Units.invalidateTarget(targets[i], unit.team, mountX, mountY, weapon.bullet.range())){
targets[i] = null;
}
}
boolean shoot = false;
if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range());
if(shoot){
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
}
mount.shoot = shoot;
mount.rotate = shoot;
}
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build;
}
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
}
protected boolean retarget(){
return timer.get(timerTarget, 30);
}
protected void targetClosest(){
Teamc newTarget = Units.closestTarget(unit.team, unit.x, unit.y, Math.max(unit.range(), unit.type().range), u -> (unit.type().targetAir && u.isFlying()) || (unit.type().targetGround && !u.isFlying()));
if(newTarget != null){
target = newTarget;
}
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
return target(x, y, range, air, ground);
}
protected void init(){
}
protected @Nullable Tile getClosestSpawner(){
return Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
protected void circle(Position target, float circleLength){
circle(target, circleLength, unit.type().speed);
}
protected void circle(Position target, float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed);
unit.moveAt(vec);
}
protected void moveTo(Position target, float circleLength){
if(target == null) return;
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
@Override
public void unit(Unit unit){
if(this.unit == unit) return;

View File

@@ -16,8 +16,6 @@ public class BuildPlan{
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 Object config;
/** Original position, only used in schematics.*/
@@ -40,6 +38,16 @@ public class BuildPlan{
this.breaking = false;
}
/** This creates a build request with a config. */
public BuildPlan(int x, int y, int rotation, Block block, Object config){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
this.config = config;
}
/** This creates a remove request. */
public BuildPlan(int x, int y){
this.x = x;
@@ -53,7 +61,8 @@ public class BuildPlan{
}
public static Object pointConfig(Object config, Cons<Point2> cons){
/** Transforms the internal position of this config using the specified function, and return the result. */
public static Object pointConfig(Block block, Object config, Cons<Point2> cons){
if(config instanceof Point2){
config = ((Point2)config).cpy();
cons.get((Point2)config);
@@ -65,14 +74,15 @@ public class BuildPlan{
cons.get(result[i++]);
}
config = result;
}else if(block != null){
config = block.pointConfig(config, cons);
}
return config;
}
/** If this requests's config is a Point2 or an array of Point2s, this returns a copy of them for transformation.
* Otherwise does nothing. */
/** Transforms the internal position of this config using the specified function. */
public void pointConfig(Cons<Point2> cons){
this.config = pointConfig(this.config, cons);
this.config = pointConfig(block, this.config, cons);
}
public BuildPlan copy(){
@@ -82,7 +92,6 @@ public class BuildPlan{
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
@@ -125,12 +134,6 @@ public class BuildPlan{
return y*tilesize + block.offset;
}
public BuildPlan configure(Object config){
this.config = config;
this.hasConfig = true;
return this;
}
public @Nullable Tile tile(){
return world.tile(x, y);
}

View File

@@ -1,35 +0,0 @@
package mindustry.entities.units;
public class StateMachine{
private UnitState state;
public void update(){
if(state != null) state.update();
}
public void set(UnitState next){
if(next == state) return;
if(state != null) state.exited();
this.state = next;
if(next != null) next.entered();
}
public UnitState current(){
return state;
}
public boolean is(UnitState state){
return this.state == state;
}
public interface UnitState{
default void entered(){
}
default void exited(){
}
default void update(){
}
}
}

View File

@@ -3,7 +3,7 @@ package mindustry.entities.units;
import arc.*;
public enum UnitCommand{
attack, retreat, rally;
attack, rally, idle;
private final String localized;
public static final UnitCommand[] all = values();

View File

@@ -1,5 +1,7 @@
package mindustry.entities.units;
import arc.util.ArcAnnotate.*;
import mindustry.gen.*;
import mindustry.type.*;
public class WeaponMount{
@@ -11,6 +13,8 @@ public class WeaponMount{
public float rotation;
/** destination rotation; do not modify! */
public float targetRotation;
/** current heat, 0 to 1*/
public float heat;
/** aiming position in world coordinates */
public float aimX, aimY;
/** whether to shoot right now */
@@ -19,6 +23,8 @@ public class WeaponMount{
public boolean rotate = false;
/** extra state for alternating weapons */
public boolean side;
/** current bullet for continuous weapons */
public @Nullable Bullet bullet;
public WeaponMount(Weapon weapon){
this.weapon = weapon;