Merging changes from private branch

This commit is contained in:
Anuken
2025-04-04 11:47:35 -04:00
parent cf5c6d0905
commit b7dbe54d76
161 changed files with 2484 additions and 1137 deletions
+15 -6
View File
@@ -70,23 +70,27 @@ public class Damage{
}
}
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage){
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, Fx.dynamicExplosion);
}
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx){
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx);
}
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx, float baseShake){
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx, baseShake);
}
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam){
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, Fx.dynamicExplosion);
}
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx){
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, explosionFx, 3f);
}
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx, float baseShake){
if(damage){
for(int i = 0; i < Mathf.clamp(power / 700, 0, 8); i++){
int length = 5 + Mathf.clamp((int)(Mathf.pow(power, 0.98f) / 500), 1, 18);
@@ -123,7 +127,7 @@ public class Damage{
Fx.bigShockwave.at(x, y);
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
float shake = Math.min(explosiveness / 4f + baseShake, 9f);
Effect.shake(shake, shake, x, y);
explosionFx.at(x, y, radius / 8f);
}
@@ -549,7 +553,12 @@ public class Damage{
//this needs to be compensated
if(in != null && in.team != team && in.block.size > 1 && in.health > damage){
//deal the damage of an entire side, to be equivalent with maximum 'standard' damage
in.damage(team, damage * Math.min((in.block.size), baseRadius * 0.4f));
float d = damage * Math.min((in.block.size), baseRadius * 0.4f);
if(source != null){
in.damage(source, team, d);
}else{
in.damage(team, d);
}
//no need to continue with the explosion
return;
}
@@ -166,7 +166,7 @@ public class EntityCollisions{
}
}
private boolean collide(float x1, float y1, float w1, float h1, float vx1, float vy1,
public static boolean collide(float x1, float y1, float w1, float h1, float vx1, float vy1,
float x2, float y2, float w2, float h2, float vx2, float vy2, Vec2 out){
float px = vx1, py = vy1;
+2 -2
View File
@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
public class Lightning{
private static final Rand random = new Rand();
private static final Rect rect = new Rect();
private static final Seq<Unitc> entities = new Seq<>();
private static final Seq<Unit> entities = new Seq<>();
private static final IntSet hit = new IntSet();
private static final int maxChain = 8;
private static final float hitRange = 30f;
@@ -74,7 +74,7 @@ public class Lightning{
});
}
Unitc furthest = Geometry.findFurthest(x, y, entities);
Unit furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
hit.add(furthest.id());
+7 -1
View File
@@ -97,6 +97,12 @@ public class Puddles{
}
}
public static boolean hasLiquid(Tile tile, Liquid liquid){
if(tile == null) return false;
var p = get(tile);
return p != null && p.liquid == liquid && p.amount >= 0.5f;
}
public static void remove(Tile tile){
if(tile == null) return;
@@ -126,7 +132,7 @@ public class Puddles{
if(Mathf.chance(0.8f * amount)){
Fx.steam.at(x, y);
}
return -0.4f * amount;
return -0.7f * amount;
}
return dest.react(liquid, amount, tile, x, y);
}
@@ -4,7 +4,9 @@ package mindustry.entities;
public class TargetPriority{
public static final float
//nobody cares about walls
wall = -2f,
wall = -3f,
//anything that has underBullets gets this priority (it's probably still more important than a wall)
under = -2f,
//transport infrastructure isn't as important as factories
transport = -1f,
//most blocks
@@ -1,6 +1,7 @@
package mindustry.entities;
import arc.math.*;
import mindustry.content.*;
import mindustry.entities.Units.*;
import mindustry.gen.*;
@@ -11,4 +12,9 @@ public class UnitSorts{
farthest = (u, x, y) -> -u.dst2(x, y),
strongest = (u, x, y) -> -u.maxHealth + Mathf.dst2(u.x, u.y, x, y) / 6400f,
weakest = (u, x, y) -> u.maxHealth + Mathf.dst2(u.x, u.y, x, y) / 6400f;
public static BuildingPriorityf
buildingDefault = b -> b.block.priority,
buildingWater = b -> b.block.priority + (b.liquids != null && b.liquids.get(Liquids.water) > 5f ? 10f : 0f);
}
+8 -14
View File
@@ -95,7 +95,7 @@ public class Units{
public static int getCap(Team team){
//wave team has no cap
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam) || state.rules.disableUnitCap){
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam) || state.rules.disableUnitCap || team.ignoreUnitCap){
return Integer.MAX_VALUE;
}
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + team.data().unitCap : state.rules.unitCap);
@@ -197,18 +197,8 @@ public class Units{
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
return findEnemyTile(team, x, y, range, false, pred);
}
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, boolean checkUnder, Boolf<Building> pred){
if(team == Team.derelict) return null;
if(checkUnder){
Building target = indexer.findEnemyTile(team, x, y, range, build -> !build.block.underBullets && pred.get(build));
if(target != null) return target;
}
return indexer.findEnemyTile(team, x, y, range, pred);
}
@@ -258,7 +248,7 @@ public class Units{
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, true, tilePred);
return findEnemyTile(team, x, y, range, tilePred);
}
}
@@ -270,7 +260,7 @@ public class Units{
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, true, tilePred);
return findEnemyTile(team, x, y, range, tilePred);
}
}
@@ -409,7 +399,7 @@ public class Units{
/** @return whether any units exist in this rectangle */
public static boolean any(float x, float y, float width, float height, Boolf<Unit> filter){
return count(x, y, width, height, filter) > 0;
return Groups.unit.intersect(x, y, width, height, filter);
}
/** Iterates over all units in a rectangle. */
@@ -494,4 +484,8 @@ public class Units{
public interface Sortf{
float cost(Unit unit, float x, float y);
}
public interface BuildingPriorityf{
float priority(Building build);
}
}
@@ -4,6 +4,7 @@ import arc.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
public abstract class Ability implements Cloneable{
protected static final float descriptionWidth = 350f;
@@ -13,11 +14,26 @@ public abstract class Ability implements Cloneable{
public float data;
public void update(Unit unit){}
public void draw(Unit unit){}
public void death(Unit unit){}
public void created(Unit unit){}
public void init(UnitType type){}
public void displayBars(Unit unit, Table bars){}
public void display(Table t){
t.table(Styles.grayPanel, a -> {
a.add("[accent]" + localized()).padBottom(4).center().top().expandX();
a.row();
a.left().top().defaults().left();
addStats(a);
}).pad(5).margin(10).growX().top().uniformX();
}
public void addStats(Table t){
if(Core.bundle.has(getBundle() + ".description")){
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
@@ -13,8 +13,8 @@ import static mindustry.Vars.*;
public class LiquidRegenAbility extends Ability{
public Liquid liquid;
public float slurpSpeed = 9f;
public float regenPerSlurp = 2.9f;
public float slurpSpeed = 5f;
public float regenPerSlurp = 6f;
public float slurpEffectChance = 0.4f;
public Effect slurpEffect = Fx.heal;
@@ -31,7 +31,7 @@ public class LiquidRegenAbility extends Ability{
//TODO timer?
//TODO effects?
if(unit.damaged()){
if(unit.damaged() && !unit.isFlying()){
boolean healed = false;
int tx = unit.tileX(), ty = unit.tileY();
int rad = Math.max((int)(unit.hitSize / tilesize * 0.6f), 1);
@@ -1,6 +1,7 @@
package mindustry.entities.abilities;
import arc.graphics.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
@@ -9,8 +10,9 @@ import mindustry.gen.*;
public class MoveEffectAbility extends Ability{
public float minVelocity = 0.08f;
public float interval = 3f;
public float x, y, rotation;
public float interval = 3f, chance = 0f;
public int amount = 1;
public float x, y, rotation, rangeX, rangeY, rangeLengthMin, rangeLengthMax;
public boolean rotateEffect = false;
public float effectParam = 3f;
public boolean teamColor = false;
@@ -38,10 +40,17 @@ public class MoveEffectAbility extends Ability{
if(Vars.headless) return;
counter += Time.delta;
if(unit.vel.len2() >= minVelocity * minVelocity && (counter >= interval) && !unit.inFogTo(Vars.player.team())){
Tmp.v1.trns(unit.rotation - 90f, x, y);
if(unit.vel.len2() >= minVelocity * minVelocity && (counter >= interval || (chance > 0 && Mathf.chanceDelta(chance))) && !unit.inFogTo(Vars.player.team())){
if(rangeLengthMax > 0){
Tmp.v1.trns(unit.rotation - 90f, x, y).add(Tmp.v2.rnd(Mathf.random(rangeLengthMin, rangeLengthMax)));
}else{
Tmp.v1.trns(unit.rotation - 90f, x + Mathf.range(rangeX), y + Mathf.range(rangeY));
}
counter %= interval;
effect.at(Tmp.v1.x + unit.x, Tmp.v1.y + unit.y, (rotateEffect ? unit.rotation : effectParam) + rotation, teamColor ? unit.team.color : color, parentizeEffects ? unit : null);
for(int i = 0; i < amount; i++){
effect.at(Tmp.v1.x + unit.x, Tmp.v1.y + unit.y, (rotateEffect ? unit.rotation : effectParam) + rotation, teamColor ? unit.team.color : color, parentizeEffects ? unit : null);
}
}
}
}
@@ -30,16 +30,24 @@ public class BulletType extends Content implements Cloneable{
/** Lifetime in ticks. */
public float lifetime = 40f;
/** Min/max multipliers for lifetime applied to this bullet when spawned. */
public float lifeScaleRandMin = 1f, lifeScaleRandMax = 1f;
/** Speed in units/tick. */
public float speed = 1f;
/** Min/max multipliers for velocity applied to this bullet when spawned. */
public float velocityScaleRandMin = 1f, velocityScaleRandMax = 1f;
/** Direct damage dealt on hit. */
public float damage = 1f;
/** Hitbox size. */
public float hitSize = 4;
/** Clipping hitbox. */
public float drawSize = 40f;
/** Angle offset applied to bullet when spawned each time. */
public float angleOffset = 0f, randomAngleOffset = 0f;
/** Drag as fraction of velocity. */
public float drag = 0f;
/** Acceleration per frame. */
public float accel = 0f;
/** Whether to pierce units. */
public boolean pierce;
/** Whether to pierce buildings. */
@@ -155,6 +163,8 @@ public class BulletType extends Content implements Cloneable{
public float healAmount = 0f;
/** Whether to make fire on impact */
public boolean makeFire = false;
/** Whether this bullet will always hit blocks under it. */
public boolean hitUnder = false;
/** Whether to create hit effects on despawn. Forced to true if this bullet has any special effects like splash damage. */
public boolean despawnHit = false;
/** If true, this bullet will create bullets when it hits anything, not just when it despawns. */
@@ -163,6 +173,10 @@ public class BulletType extends Content implements Cloneable{
public boolean fragOnAbsorb = true;
/** If true, unit armor is ignored in damage calculations. */
public boolean pierceArmor = false;
/** If true, the bullet will "stick" to enemies and get deactivated on collision. */
public boolean sticky = false;
/** Extra time added to bullet when it sticks to something. */
public float stickyExtraLifetime = 0f;
/** Whether status and despawnHit should automatically be set. */
public boolean setDefaults = true;
/** Amount of shaking produced when this bullet hits something or despawns. */
@@ -195,7 +209,7 @@ public class BulletType extends Content implements Cloneable{
public float bulletInterval = 20f;
/** Number of bullet spawned per interval. */
public int intervalBullets = 1;
/** Random spread of interval bullets. */
/** Random angle added to interval bullets. */
public float intervalRandomSpread = 360f;
/** Angle spread between individual interval bullets. */
public float intervalSpread = 0f;
@@ -204,6 +218,9 @@ public class BulletType extends Content implements Cloneable{
/** Use a negative value to disable interval bullet delay. */
public float intervalDelay = -1f;
/** If true, this bullet is rendered underwater. Highly experimental! */
public boolean underwater = false;
/** Color used for hit/despawn effects. */
public Color hitColor = Color.white;
/** Color used for block heal effects. */
@@ -212,6 +229,8 @@ public class BulletType extends Content implements Cloneable{
public Effect healEffect = Fx.healBlockFull;
/** Bullets spawned when this bullet is created. Rarely necessary, used for visuals. */
public Seq<BulletType> spawnBullets = new Seq<>();
/** Random angle spread of spawn bullets. */
public float spawnBulletRandomSpread = 0f;
/** Unit spawned _instead of_ this bullet. Useful for missiles. */
public @Nullable UnitType spawnUnit;
/** Unit spawned when this bullet hits something or despawns due to it hitting the end of its lifetime. */
@@ -233,8 +252,12 @@ public class BulletType extends Content implements Cloneable{
public float trailChance = -0.0001f;
/** Uniform interval in which trail effect is spawned. */
public float trailInterval = 0f;
/** Min velocity required for trail effect to spawn. */
public float trailMinVelocity = 0f;
/** Trail effect that is spawned. */
public Effect trailEffect = Fx.missileTrail;
/** Random offset of trail effect. */
public float trailSpread = 0f;
/** Rotation/size parameter that is passed to trail. Usually, this controls size. */
public float trailParam = 2f;
/** Whether the parameter passed to the trail is the bullet rotation, instead of a flat value. */
@@ -247,6 +270,14 @@ public class BulletType extends Content implements Cloneable{
public float trailWidth = 2f;
/** If trailSinMag > 0, these values are applied as a sine curve to trail width. */
public float trailSinMag = 0f, trailSinScl = 3f;
/** If true, the bullet will attempt to circle around its shooting entity. */
public boolean circleShooter = false;
/** Radius that the bullet attempts to circle at. */
public float circleShooterRadius = 13f;
/** Smooth extra radius value for circling. */
public float circleShooterRadiusSmooth = 10f;
/** Multiplier of speed that is used to adjust velocity when circling. */
public float circleShooterRotateSpeed = 0.3f;
/** Use a negative value to disable splash damage. */
public float splashDamageRadius = -1f;
@@ -299,6 +330,8 @@ public class BulletType extends Content implements Cloneable{
public float weaveMag = 0f;
/** If true, the bullet weave will randomly switch directions on spawn. */
public boolean weaveRandom = true;
/** Rotation speed of the bullet velocity as it travels. */
public float rotateSpeed = 0f;
/** Number of individual puddles created. */
public int puddles;
@@ -624,7 +657,7 @@ public class BulletType extends Content implements Cloneable{
if(spawnBullets.size > 0){
for(var bullet : spawnBullets){
bullet.create(b, b.x, b.y, b.rotation());
bullet.create(b, b.x, b.y, b.rotation() + Mathf.range(spawnBulletRandomSpread));
}
}
}
@@ -678,18 +711,40 @@ public class BulletType extends Content implements Cloneable{
if(weaveMag != 0){
b.vel.rotateRadExact((float)Math.sin((b.time + Math.PI * weaveScale/2f) / weaveScale) * weaveMag * (weaveRandom ? (Mathf.randomSeed(b.id, 0, 1) == 1 ? -1 : 1) : 1f) * Time.delta * Mathf.degRad);
}
if(rotateSpeed != 0){
b.vel.rotate(rotateSpeed * Time.delta);
}
if(circleShooter && b.owner instanceof Healthc h && h.isValid()){
Tmp.v1.set(h).sub(b);
Tmp.v1.rotate(90f * Mathf.lerp(0f, 1f, 1f - Mathf.clamp((Tmp.v1.len() - circleShooterRadius) / circleShooterRadiusSmooth)));
b.vel.add(Tmp.v1.limit(speed * circleShooterRotateSpeed * Time.delta)).limit(speed);
}
}
public void updateTrailEffects(Bullet b){
if(trailChance > 0){
boolean canSpawn = trailMinVelocity <= 0f || b.vel.len2() >= trailMinVelocity * trailMinVelocity;
if(trailChance > 0 && canSpawn){
if(Mathf.chanceDelta(trailChance)){
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
if(trailSpread > 0){
Tmp.v1.rnd(Mathf.random(trailSpread));
}else{
Tmp.v1.setZero();
}
trailEffect.at(b.x + Tmp.v1.x, b.y + Tmp.v1.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
if(trailInterval > 0f){
if(trailInterval > 0f && canSpawn){
if(b.timer(0, trailInterval)){
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
if(trailSpread > 0){
Tmp.v1.rnd(Mathf.random(trailSpread));
}else{
Tmp.v1.setZero();
}
trailEffect.at(b.x + Tmp.v1.x, b.y + Tmp.v1.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
}
@@ -800,6 +855,8 @@ public class BulletType extends Content implements Cloneable{
@Nullable Entityc owner, @Nullable Entityc shooter, Team team, float x, float y, float angle, float damage, float velocityScl,
float lifetimeScl, Object data, @Nullable Mover mover, float aimX, float aimY, @Nullable Teamc target
){
angle += angleOffset + Mathf.range(randomAngleOffset);
if(!Mathf.chance(createChance)) return null;
if(ignoreSpawnAngle) angle = 0;
if(spawnUnit != null){
@@ -846,13 +903,13 @@ public class BulletType extends Content implements Cloneable{
bullet.aimX = aimX;
bullet.aimY = aimY;
bullet.initVel(angle, speed * velocityScl);
bullet.initVel(angle, speed * velocityScl * (velocityScaleRandMin != 1f || velocityScaleRandMax != 1f ? Mathf.random(velocityScaleRandMin, velocityScaleRandMax) : 1f));
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.lifetime = lifetime * lifetimeScl * (lifeScaleRandMin != 1f || lifeScaleRandMax != 1f ? Mathf.random(lifeScaleRandMin, lifeScaleRandMax) : 1f);
bullet.data = data;
bullet.drag = drag;
bullet.hitSize = hitSize;
@@ -0,0 +1,52 @@
package mindustry.entities.bullet;
import arc.util.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class InterceptorBulletType extends BasicBulletType{
public InterceptorBulletType(float speed, float damage){
super(speed, damage);
}
public InterceptorBulletType(){
}
public InterceptorBulletType(float speed, float damage, String bulletSprite){
super(speed, damage, bulletSprite);
}
@Override
public void update(Bullet b){
super.update(b);
if(b.data instanceof Bullet other){
if(other.isAdded()){
//check for an overlap between the two bullet trajectories; it is the responsibility of the creator to make sure the bullet is a valid target
if(EntityCollisions.collide(
b.x, b.y,
b.hitSize, b.hitSize,
b.deltaX, b.deltaY,
other.x, other.y,
other.hitSize, other.hitSize,
other.deltaX, other.deltaY, Tmp.v1)){
b.set(Tmp.v1);
hit(b, b.x, b.y);
b.remove();
if(other.damage > damage){
other.damage -= b.damage;
}else{
other.remove();
}
}
}else{
b.data = null;
}
}
}
}
@@ -0,0 +1,61 @@
package mindustry.entities.bullet;
import arc.util.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
/** A fake bullet type that spawns multiple sub-bullets when "fired". */
public class MultiBulletType extends BulletType{
public BulletType[] bullets = {};
/** Amount of times the bullet array is repeated. */
public int repeat = 1;
public MultiBulletType(BulletType... bullets){
this.bullets = bullets;
}
public MultiBulletType(int repeat, BulletType... bullets){
this.repeat = repeat;
this.bullets = bullets;
}
public MultiBulletType(){
}
@Override
public float estimateDPS(){
float sum = 0f;
for(var b : bullets){
sum += b.estimateDPS();
}
return sum;
}
@Override
protected float calculateRange(){
float max = 0f;
for(var b : bullets){
max = Math.max(max, b.calculateRange());
}
return max;
}
@Override
public @Nullable Bullet create(
@Nullable Entityc owner, @Nullable Entityc shooter, Team team, float x, float y, float angle, float damage, float velocityScl,
float lifetimeScl, Object data, @Nullable Mover mover, float aimX, float aimY, @Nullable Teamc target
){
angle += angleOffset;
Bullet last = null;
for(int i = 0; i < repeat; i++){
for(var bullet : bullets){
last = bullet.create(owner, shooter, team, x, y, angle, damage, velocityScl, lifetimeScl, data, mover, aimX, aimY, target);
}
}
return last;
}
}
@@ -3,6 +3,7 @@ package mindustry.entities.bullet;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
@@ -11,7 +12,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
public class SapBulletType extends BulletType{
public float length = 100f;
public float length = 100f, lengthRand = 0f;
public float sapStrength = 0.5f;
public Color color = Color.white.cpy();
public float width = 0.4f;
@@ -72,7 +73,9 @@ public class SapBulletType extends BulletType{
public void init(Bullet b){
super.init(b);
Healthc target = Damage.linecast(b, b.x, b.y, b.rotation(), length);
float len = Mathf.random(length, length + lengthRand);
Healthc target = Damage.linecast(b, b.x, b.y, b.rotation(), len);
b.data = target;
if(target != null){
@@ -92,7 +95,7 @@ public class SapBulletType extends BulletType{
hit(b, tile.x, tile.y);
}
}else{
b.data = new Vec2().trns(b.rotation(), length).add(b.x, b.y);
b.data = new Vec2().trns(b.rotation(), len).add(b.x, b.y);
}
}
}
@@ -63,6 +63,11 @@ abstract class BlockUnitComp implements Unitc{
return tile != null && tile.isValid();
}
@Replace
public boolean isAdded(){
return tile != null && tile.isValid();
}
@Replace
public void team(Team team){
if(tile != null && this.team != team){
@@ -1,58 +0,0 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 30f;
@Import UnitType type;
@Import float x, y;
@Import Team team;
@Override
public void update(){
if(!type.bounded) return;
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
//TODO hidden map rules only apply to player teams? should they?
if(state.rules.limitMapArea && !team.isAI()){
bot = state.rules.limitY * tilesize;
left = state.rules.limitX * tilesize;
top = state.rules.limitHeight * tilesize + bot;
right = state.rules.limitWidth * tilesize + left;
}
if(!net.client() || isLocal()){
float dx = 0f, dy = 0f;
//repel unit out of bounds
if(x < left) dx += (-(x - left)/warpDst);
if(y < bot) dy += (-(y - bot)/warpDst);
if(x > right) dx -= (x - right)/warpDst;
if(y > top) dy -= (y - top)/warpDst;
velAddNet(dx * Time.delta, dy * Time.delta);
}
//clamp position if not flying
if(isGrounded()){
x = Mathf.clamp(x, left, right - tilesize);
y = Mathf.clamp(y, bot, top - tilesize);
}
//kill when out of bounds
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
kill();
}
}
}
@@ -136,7 +136,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
}
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true)){
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true, true)){
if(Build.checkNoUnitOverlap(current.block, current.x, current.y)){
boolean hasAll = infinite || current.isRotation(team) ||
//derelict repair
@@ -16,7 +16,6 @@ import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
@@ -47,7 +46,7 @@ import java.util.*;
import static mindustry.Vars.*;
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
@Component(base = true)
@Component(base = true, genInterface = false)
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Sized, Senseable, Controllable, Settable{
//region vars and initialization
static final float timeToSleep = 60f * 1, recentDamageTime = 60f * 5f;
@@ -63,7 +62,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient Tile tile;
transient Block block;
transient Seq<Building> proximity = new Seq<>(6);
transient Seq<Building> proximity = new Seq<>(true, 6, Building.class);
transient int cdump;
transient int rotation;
transient float payloadRotation;
@@ -99,8 +98,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
private transient float timeScale = 1f, timeScaleDuration;
private transient float dumpAccum;
private transient @Nullable SoundLoop sound;
private transient boolean sleeping;
private transient float sleepTime;
private transient boolean initialized;
@@ -126,6 +123,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
add();
}
checkAllowUpdate();
created();
return self();
@@ -136,10 +134,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
this.block = block;
this.team = team;
if(block.loopSound != Sounds.none){
sound = new SoundLoop(block.loopSound, block.loopSoundVolume);
}
health = block.health;
maxHealth(block.health);
timer(new Interval(block.timers));
@@ -342,13 +336,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public @Nullable Tile findClosestEdge(Position to, Boolf<Tile> solid){
if(to == null) return null;
Tile best = null;
float mindst = 0f;
for(var point : Edges.getEdges(block.size)){
Tile other = Vars.world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && !solid.get(other) && (best == null || to.dst2(other) < mindst)){
best = other;
mindst = other.dst2(other);
mindst = other.dst2(to);
}
}
return best;
@@ -473,6 +468,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return lastDamageTime + recentDamageTime >= Time.time;
}
public void eachEdge(Cons<Tile> cons){
for(var edge : block.getEdges()){
Tile other = world.tile(tile.x + edge.x, tile.y + edge.y);
if(other != null){
cons.get(other);
}
}
}
public Building nearby(int dx, int dy){
return world.build(tile.x + dx, tile.y + dy);
}
@@ -809,6 +813,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return false;
}
public boolean canBeReplaced(Block other){
return other.canReplace(block);
}
public void handleItem(Building source, Item item){
items.add(item, 1);
}
@@ -1066,7 +1074,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void incrementDump(int prox){
cdump = ((cdump + 1) % prox);
//this is possible if transferring an item changed a block
if(prox != 0){
cdump = ((cdump + 1) % prox);
}
}
/** Used for dumping items. */
@@ -1092,6 +1103,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called after this building is created in the world. May be called multiple times, or when adjacent buildings change. */
//TODO ??? this is just onProximityUpdate ?
public void onProximityAdded(){
if(power != null){
updatePowerGraph();
@@ -1156,16 +1168,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return getProgressIncrease(1f) / edelta();
}
/** @return whether this block should play its active sound.*/
public boolean shouldActiveSound(){
return false;
}
/** @return volume cale of active sound. */
public float activeSoundVolume(){
return 1f;
}
/** @return whether this block should play its idle sound.*/
public boolean shouldAmbientSound(){
return shouldConsume();
@@ -1315,14 +1317,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void onRemoved(){
}
/** Called every frame a unit is on this */
/** Called every frame a unit is on this. Hovering/flying/steppy units do not apply. */
public void unitOn(Unit unit){
}
/** Called every frame a unit is on this. Applies to any unit. */
public void unitOnAny(Unit unit){
}
/** Called when a unit that spawned at this tile is removed. */
public void unitRemoved(Unit unit){
}
/** Called when a puddle is on this building. Only called at an interval (40 ticks). */
public void puddleOn(Puddle puddle){
}
/** Called when arbitrary configuration is applied to a tile. */
public void configured(@Nullable Unit builder, @Nullable Object value){
//null is of type void.class; anonymous classes use their superclass.
@@ -1372,6 +1383,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return block.itemCapacity;
}
public void splashLiquid(Liquid liquid, float amount){
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
Time.run(i / 2f, () -> {
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
if(other != null){
Puddles.deposit(other, liquid, splash);
}
});
}
}
/** Called when a block begins (not finishes!) deconstruction. The building is still present at this point. */
public void onDeconstructed(@Nullable Unit builder){
//deposit non-incinerable liquid on ground
@@ -1383,9 +1407,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Called when the block is destroyed. The tile is still intact at this stage. */
public void onDestroyed(){
if(sound != null){
sound.stop();
}
float explosiveness = block.baseExplosiveness;
float flammability = 0f;
@@ -1411,22 +1432,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(block.hasLiquids && state.rules.damageExplosions){
liquids.each((liquid, amount) -> {
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
Time.run(i / 2f, () -> {
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
if(other != null){
Puddles.deposit(other, liquid, splash);
}
});
}
});
liquids.each(this::splashLiquid);
}
//cap explosiveness so fluid tanks/vaults don't instakill units
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect);
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect, block.baseShake);
if(block.createRubble && !floor().solid && !floor().isLiquid){
Effect.rubble(x, y, block.size);
@@ -1657,8 +1667,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
damage = Damage.applyArmor(damage, block.armor);
}
damage(other.team, damage);
Events.fire(bulletDamageEvent.set(self(), other));
damage(other, other.team, damage);
if(health <= 0 && !wasDead){
Events.fire(new BuildingBulletDestroyEvent(self(), other));
@@ -1681,6 +1690,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Changes this building's team in a safe manner. */
public void changeTeam(Team next){
if(this.team == next) return;
if(block.forceTeam != null) team = block.forceTeam;
Team last = this.team;
boolean was = isValid();
@@ -1693,10 +1703,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
indexer.addIndex(tile);
Events.fire(teamChangeEvent.set(last, self()));
}
checkAllowUpdate();
}
public boolean canPickup(){
return true;
return block.canPickup;
}
/** Called right before this building is picked up. */
@@ -1764,6 +1776,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public void onNearbyBuildAdded(Building other){}
public void consume(){
for(Consume cons : block.consumers){
cons.trigger(self());
@@ -2094,22 +2108,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return true;
}
@Override
public void remove(){
stopSound();
}
public void stopSound(){
if(sound != null){
sound.stop();
}
}
@Override
public void killed(){
dead = true;
Events.fire(new BlockDestroyEvent(tile));
block.destroySound.at(tile);
block.destroySound.at(tile, Mathf.random(block.destroyPitchMin, block.destroyPitchMax));
onDestroyed();
if(tile != emptyTile){
tile.remove();
@@ -2118,48 +2121,44 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
afterDestroyed();
}
public void checkAllowUpdate(){
if(!allowUpdate()){
enabled = false;
}
}
@Final
@Replace
@Override
public void update(){
//TODO should just avoid updating buildings instead
if(state.isEditor()) return;
//TODO refactor to timestamp-based system?
if((timeScaleDuration -= Time.delta) <= 0f || !block.canOverdrive){
timeScale = 1f;
}
if(!allowUpdate()){
enabled = false;
}
if(!headless && !wasVisible && state.rules.fog && !inFogTo(player.team())){
visibleFlags |= (1L << player.team().id);
wasVisible = true;
renderer.blocks.updateShadow(self());
renderer.minimap.update(tile);
}
//TODO separate system for sound? AudioSource, etc
if(!headless){
if(sound != null){
sound.update(x, y, shouldActiveSound(), activeSoundVolume());
}
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
}
//TODO separate multithreaded system for sound? AudioSource, etc
if(!headless && block.ambientSound != Sounds.none && shouldAmbientSound()){
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
}
updateConsumption();
//TODO just handle per-block instead
if(enabled || !block.noUpdateDisabled){
updateTile();
}
}
/** When a block is newly revealed outside of camera view range, it is updated on the minimap. */
public void updateFogVisibility(){
if(!wasVisible && !inFogTo(player.team())){
visibleFlags |= (1L << player.team().id);
wasVisible = true;
renderer.blocks.updateShadow(self());
renderer.minimap.update(tile);
}
}
@Override
public void hitbox(Rect out){
out.setCentered(x, y, block.size * tilesize, block.size * tilesize);
@@ -39,6 +39,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
//setting this variable to true prevents lifetime from decreasing for a frame.
transient boolean keepAlive;
/** Unlike the owner, the shooter is the original entity that created this bullet. For a second-stage missile, the shooter would be the turret, but the owner would be the last missile stage.*/
transient Entityc shooter;
transient @Nullable Tile aimTile;
transient float aimX, aimY;
@@ -48,6 +49,9 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
transient @Nullable Trail trail;
transient int frags;
transient Posc stickyTarget;
transient float stickyX, stickyY, stickyRotation, stickyRotationOffset;
@Override
public void getCollisions(Cons<QuadTree> consumer){
Seq<TeamData> data = state.teams.present;
@@ -106,24 +110,43 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Override
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc t && t.team() != team)
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
&& !(type.pierce && hasCollided(other.id())); //prevent multiple collisions
&& !(other instanceof Unit f && !f.checkTarget(type.collidesAir, type.collidesGround))
&& !(type.pierce && hasCollided(other.id())) && stickyTarget == null; //prevent multiple collisions
}
@MethodPriority(100)
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(self(), x, y);
//must be last.
if(!type.pierce){
hit = true;
remove();
if(type.sticky){
if(stickyTarget == null){
//tunnel into the target a bit for better visuals
this.x = x + vel.x;
this.y = y + vel.y;
stickTo(other);
}
}else{
collided.add(other.id());
}
type.hit(self(), x, y);
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
//must be last.
if(!type.pierce){
hit = true;
remove();
}else{
collided.add(other.id());
}
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
}
}
public void stickTo(Posc other){
lifetime += type.stickyExtraLifetime;
//sticky bullets don't actually hit anything.
stickyX = this.x - other.x();
stickyY = this.y - other.y();
stickyTarget = other;
stickyRotationOffset = rotation;
stickyRotation = (other instanceof Rotc rot ? rot.rotation() : 0f);
}
@Override
@@ -132,9 +155,21 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
mover.move(self());
}
if(type.accel != 0){
vel.setLength(vel.len() + type.accel * Time.delta);
}
type.update(self());
if(type.collidesTiles && type.collides && type.collidesGround){
if(stickyTarget != null){
//only stick to things that still exist in the world
if(stickyTarget instanceof Healthc h && h.isValid()){
float rotate = (stickyTarget instanceof Rotc rot ? rot.rotation() - stickyRotation : 0f);
set(Tmp.v1.set(stickyX, stickyY).rotate(rotate).add(stickyTarget));
this.rotation = rotate + stickyRotationOffset;
vel.setAngle(this.rotation);
}
}else if(type.collidesTiles && type.collides && type.collidesGround){
tileRaycast(World.toTile(lastX), World.toTile(lastY), tileX(), tileY());
}
@@ -165,6 +200,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
(!build.block.underBullets ||
//direct hit on correct tile
(aimTile != null && aimTile.build == build) ||
//bullet type allows hitting under bullets
type.hitUnder ||
//same team has no 'under build' mechanics
(build.team == team) ||
//a piercing bullet overshot the aim tile, it's fine to hit things now
@@ -201,31 +238,46 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
&& build.collide(self()) && type.testCollision(self(), build)
&& !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
boolean remove = false;
float health = build.health;
if(type.sticky){
if(build.team != team){
//stick to edge of block
Vec2 hit = Geometry.raycastRect(lastX, lastY, x, y, Tmp.r1.setCentered(x * tilesize, y * tilesize, tilesize, tilesize));
if(hit != null){
this.x = hit.x;
this.y = hit.y;
}
if(build.team != team){
remove = build.collision(self());
}
stickTo(build);
if(remove || type.collidesTeam){
if(Mathf.dst2(lastX, lastY, x * tilesize, y * tilesize) < Mathf.dst2(lastX, lastY, this.x, this.y)){
this.x = x * tilesize;
this.y = y * tilesize;
return;
}
}else{
boolean remove = false;
float health = build.health;
if(build.team != team){
remove = build.collision(self());
}
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(build.id);
if(remove || type.collidesTeam){
if(Mathf.dst2(lastX, lastY, x * tilesize, y * tilesize) < Mathf.dst2(lastX, lastY, this.x, this.y)){
this.x = x * tilesize;
this.y = y * tilesize;
}
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(build.id);
}
}
type.hitTile(self(), build, x * tilesize, y * tilesize, health, true);
//stop raycasting when building is hit
if(type.pierceBuilding) return;
}
type.hitTile(self(), build, x * tilesize, y * tilesize, health, true);
//stop raycasting when building is hit
if(type.pierceBuilding) return;
}
if(x == x2 && y == y2) break;
@@ -247,7 +299,11 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public void draw(){
Draw.z(type.layer);
type.draw(self());
if(type.underwater){
Drawf.underwater(() -> type.draw(self()));
}else{
type.draw(self());
}
type.drawLight(self());
Draw.reset();
@@ -1,10 +1,8 @@
package mindustry.entities.comp;
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.*;
@@ -22,7 +20,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
@Import float x, y, speedMultiplier, rotation, hitSize;
@Import UnitType type;
@Import Team team;
@Import Vec2 vel;
transient Floor lastDeepFloor;
transient float lastCrawlSlowdown = 1f;
@@ -31,13 +28,7 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
@Replace
@Override
public SolidPred solidity(){
return EntityCollisions::legsSolid;
}
@Override
@Replace
public int pathType(){
return Pathfinder.costLegs;
return ignoreSolids() ? null : EntityCollisions::legsSolid;
}
@Override
@@ -110,6 +101,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
}
segmentRot = Angles.clampRange(segmentRot, rotation, type.segmentMaxRot);
crawlTime += vel.len() * Time.delta;
crawlTime += deltaLen();
}
}
@@ -6,13 +6,13 @@ import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
@Component
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
abstract class ElevationMoveComp implements Velc, Posc, Hitboxc, Unitc{
@Import float x, y;
@Replace
@Override
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::solid;
return isFlying() || ignoreSolids() ? null : EntityCollisions::solid;
}
}
@@ -59,12 +59,16 @@ abstract class EntityComp{
}
void beforeWrite(){
}
void afterRead(){
}
/** Called after *all* entities are read. */
void afterAllRead(){
//called after all entities have been read (useful for ID resolution)
void afterReadAll(){
}
}
@@ -1,123 +0,0 @@
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.type.*;
import mindustry.world.blocks.environment.*;
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, speedMultiplier, hitSize;
@Import Vec2 vel;
@Import UnitType type;
@SyncLocal float elevation;
private transient boolean wasFlying;
transient boolean hovering;
transient float drownTime;
transient float splashTimer;
transient @Nullable Floor lastDrownFloor;
boolean checkTarget(boolean targetAir, boolean targetGround){
return (isGrounded() && targetGround) || (isFlying() && targetAir);
}
boolean isGrounded(){
return elevation < 0.001f;
}
boolean isFlying(){
return elevation >= 0.09f;
}
boolean canDrown(){
return isGrounded() && !hovering;
}
@Nullable Floor drownFloor(){
return canDrown() ? floorOn() : null;
}
boolean emitWalkSound(){
return true;
}
void landed(){
}
void wobble(){
x += Mathf.sin(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
y += Mathf.cos(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
}
void moveAt(Vec2 vector, float acceleration){
Vec2 t = tmp1.set(vector); //target vector
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
vel.add(tmp2);
}
float floorSpeedMultiplier(){
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier * speedMultiplier;
}
@Override
public void update(){
Floor floor = floorOn();
if(isFlying() != wasFlying){
if(wasFlying){
if(tileOn() != null){
Fx.unitLand.at(x, y, floorOn().isLiquid ? 1f : 0.5f, tileOn().floor().mapColor);
}
}
wasFlying = isFlying();
}
if(!hovering && isGrounded()){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
splashTimer = 0f;
if(emitWalkSound()){
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
}
}
}
updateDrowning();
}
public void updateDrowning(){
Floor floor = drownFloor();
if(floor != null && floor.isLiquid && floor.drownTime > 0){
lastDrownFloor = floor;
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
if(Mathf.chanceDelta(0.05f)){
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
}
if(drownTime >= 0.999f && !net.client()){
kill();
Events.fire(new UnitDrownEvent(self()));
}
}else{
drownTime -= Time.delta / 50f;
}
drownTime = Mathf.clamp(drownTime);
}
}
@@ -4,7 +4,6 @@ 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.*;
@@ -19,7 +18,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
abstract class LegsComp implements Posc, Rotc, Hitboxc, Unitc{
private static final Vec2 straightVec = new Vec2();
@Import float x, y, rotation, speedMultiplier;
@@ -37,13 +36,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public SolidPred solidity(){
return type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
}
@Override
@Replace
public int pathType(){
return type.allowLegStep ? Pathfinder.costLegs : Pathfinder.costGround;
return ignoreSolids() ? null : type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
}
@Override
@@ -111,6 +104,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
legs[i] = l;
}
totalLength = Mathf.random(100f);
}
@Override
@@ -12,7 +12,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, ElevationMovec{
abstract class MechComp implements Posc, Hitboxc, Unitc, Mechc, ElevationMovec{
@Import float x, y, hitSize;
@Import UnitType type;
@@ -68,7 +68,7 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
}
}
}
return canDrown() ? floorOn() : null;
return floorOn();
}
public float walkExtend(boolean scaled){
@@ -10,7 +10,7 @@ import mindustry.gen.*;
* Will bounce off of other objects that are at similar elevations.
* Has mass.*/
@Component
abstract class PhysicsComp implements Velc, Hitboxc, Flyingc{
abstract class PhysicsComp implements Velc, Hitboxc{
@Import float hitSize, x, y;
@Import Vec2 vel;
@@ -23,7 +23,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
private static Puddle paramPuddle;
private static Cons<Unit> unitCons = unit -> {
if(unit.isGrounded() && !unit.hovering){
if(unit.isGrounded() && !unit.type.hovering){
unit.hitbox(rect2);
if(rect.overlaps(rect2)){
unit.apply(paramPuddle.liquid.effect, 60 * 2);
@@ -104,6 +104,10 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
}
updateTime = 40f;
if(tile.build != null){
tile.build.puddleOn(self());
}
}
if(!headless && liquid.particleEffect != Fx.none){
@@ -0,0 +1,158 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.async.*;
import mindustry.gen.*;
import mindustry.type.*;
@Component
abstract class SegmentComp implements Posc, Rotc, Hitboxc, Unitc, Segmentc{
@Import float x, y, rotation;
@Import UnitType type;
@Import Vec2 vel;
transient @Nullable Segmentc parentSegment, childSegment, headSegment;
transient int segmentIndex;
int parentId;
public boolean isHead(){
return parentSegment == null;
}
public void addChild(Unit other){
if(other == self()) return;
if(childSegment != null){
childSegment.parentSegment(null);
}
if(other instanceof Segmentc seg){
if(seg.parentSegment() != null){
seg.parentSegment().childSegment(null);
}
childSegment = seg;
seg.parentSegment(this);
}
}
@Override
@Replace
public boolean ignoreSolids(){
return isFlying() || parentSegment != null;
}
//TODO make it phase through things.
@Override
public void update(){
if(childSegment != null && !childSegment.isValid()){
childSegment = null;
}
if(parentSegment != null && !parentSegment.isValid()){
parentSegment = null;
}
if(parentSegment == null){
segmentIndex = 0;
if(childSegment != null){
headSegment = this;
childSegment.updateSegment(this, this, 1);
}
}
}
@Replace
@Override
public boolean playerControllable(){
return type.playerControllable && isHead();
}
@Override
@Replace
public boolean shouldUpdateController(){
return isHead();
}
@Override
@Replace
public boolean moving(){
if(isHead()){
return !vel.isZero(0.01f);
}else{
return deltaLen() / Time.delta >= 0.01f;
}
}
@Override
@Replace
public int collisionLayer(){
if(parentSegment != null) return -1;
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
}
@Replace
@Override
public boolean isCommandable(){
return parentSegment == null && controller() instanceof CommandAI;
}
@Override
public void afterSync(){
checkParent();
}
@Override
public void afterReadAll(){
checkParent();
}
@Override
public void beforeWrite(){
parentId = parentSegment == null ? -1 : parentSegment.id();
}
public void checkParent(){
if(parentId != -1){
var parent = Groups.unit.getByID(parentId);
if(parent instanceof Segmentc seg){
parentSegment = seg;
seg.childSegment(this);
return;
}
parentId = -1;
}
//TODO should this unassign the parent's child too?
parentSegment = null;
}
public void updateSegment(Segmentc head, Segmentc parent, int index){
rotation = Angles.clampRange(rotation, parent.rotation(), type.segmentRotationRange);
segmentIndex = index;
headSegment = head;
float headDelta = head.deltaLen();
//TODO should depend on the head's speed.
if(headDelta > 0.001f){
rotation = Mathf.slerpDelta(rotation, parent.rotation(), type.baseRotateSpeed * Mathf.clamp(headDelta / type().speed / Time.delta));
}
Vec2 moveVec = Tmp.v1.trns(rotation + 180f, type.segmentSpacing).add(parent).sub(x, y);
float prefSpeed = type.speed * Time.delta * 9999f;
move(moveVec.limit(prefSpeed)); //TODO other segments are left behind
if(childSegment != null){
childSegment.updateSegment(head, this, index + 1);
}
}
}
@@ -15,7 +15,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class StatusComp implements Posc, Flyingc{
abstract class StatusComp implements Posc{
private Seq<StatusEntry> statuses = new Seq<>(4);
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
@@ -28,12 +28,12 @@ abstract class StatusComp implements Posc, Flyingc{
@Import float maxHealth;
/** Apply a status effect for 1 tick (for permanent effects) **/
void apply(StatusEffect effect){
public void apply(StatusEffect effect){
apply(effect, 1);
}
/** Adds a status effect to this unit. */
void apply(StatusEffect effect, float duration){
public void apply(StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || isImmune(effect)) return; //don't apply empty or immune effects
//unlock status effects regardless of whether they were applied to friendly units
@@ -67,18 +67,18 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
float getDuration(StatusEffect effect){
public float getDuration(StatusEffect effect){
var entry = statuses.find(e -> e.effect == effect);
return entry == null ? 0 : entry.time;
}
void clearStatuses(){
public void clearStatuses(){
statuses.each(e -> e.effect.onRemoved(self()));
statuses.clear();
}
/** Removes a status effect. */
void unapply(StatusEffect effect){
public void unapply(StatusEffect effect){
statuses.remove(e -> {
if(e.effect == effect){
e.effect.onRemoved(self());
@@ -89,13 +89,15 @@ abstract class StatusComp implements Posc, Flyingc{
});
}
boolean isBoss(){
public boolean isBoss(){
return hasEffect(StatusEffects.boss);
}
abstract boolean isImmune(StatusEffect effect);
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
Color statusColor(){
public Color statusColor(){
if(statuses.size == 0){
return Tmp.c1.set(Color.white);
}
@@ -168,6 +170,8 @@ abstract class StatusComp implements Posc, Flyingc{
applyDynamicStatus().armorOverride = armor;
}
public abstract boolean isGrounded();
@Override
public void update(){
Floor floor = floorOn();
@@ -237,7 +241,7 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
boolean hasEffect(StatusEffect effect){
public boolean hasEffect(StatusEffect effect){
return applied.get(effect.id);
}
}
+18 -14
View File
@@ -17,9 +17,9 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec{
abstract class TankComp implements Posc, Hitboxc, Unitc, ElevationMovec{
@Import float x, y, hitSize, rotation, speedMultiplier;
@Import boolean hovering, disarmed;
@Import boolean disarmed;
@Import UnitType type;
@Import Team team;
@@ -27,6 +27,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
transient float treadTime;
transient boolean walked;
transient Floor lastDeepFloor;
@Override
public void update(){
@@ -51,6 +52,9 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
}
lastDeepFloor = null;
boolean anyNonDeep = false;
//calculate overlapping tiles so it slows down when going "over" walls
int r = Math.max((int)(hitSize * 0.6f / tilesize), 0);
@@ -62,6 +66,12 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
solids ++;
}
if(t.floor().isDeep()){
lastDeepFloor = t.floor();
}else{
anyNonDeep = true;
}
//TODO should this apply to the player team(s)? currently PvE due to balancing
if(type.crushDamage > 0 && !disarmed && (walked || deltaLen() >= 0.01f) && t != null
//damage radius is 1 tile smaller to prevent it from just touching walls as it passes
@@ -76,6 +86,10 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
}
if(anyNonDeep){
lastDeepFloor = null;
}
lastSlowdown = Mathf.lerp(1f, type.crawlSlowdown, Mathf.clamp((float)solids / total / type.crawlSlowdownFrac));
//trigger animation only when walking manually
@@ -89,7 +103,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
@Override
@Replace
public float floorSpeedMultiplier(){
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
//TODO take into account extra blocks
return on.speedMultiplier * speedMultiplier * lastSlowdown;
}
@@ -97,17 +111,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
@Replace
@Override
public @Nullable Floor drownFloor(){
//tanks can only drown when all the nearby floors are deep
//TODO implement properly
if(hitSize >= 12 && canDrown()){
for(Point2 p : Geometry.d8){
Floor f = world.floorWorld(x + p.x * tilesize, y + p.y * tilesize);
if(!f.isDeep()){
return null;
}
}
}
return canDrown() ? floorOn() : null;
return canDrown() ? lastDeepFloor : null;
}
@Override
@@ -0,0 +1,39 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.async.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@Component
abstract class UnderwaterMoveComp implements WaterMovec{
@Import UnitType type;
@MethodPriority(10f)
@Replace
public void draw(){
//TODO draw status effects?
Drawf.underwater(() -> {
type.draw(self());
});
}
@Override
public int collisionLayer(){
return PhysicsProcess.layerUnderwater;
}
@Override
public boolean hittable(){
return false && type.hittable(self());
}
@Override
public boolean targetable(Team targeter){
return false && type.targetable(self(), targeter);
}
}
+152 -29
View File
@@ -7,7 +7,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.async.*;
@@ -34,10 +33,12 @@ import static mindustry.Vars.*;
import static mindustry.logic.GlobalVars.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
static final float warpDst = 30f;
@Import boolean hovering, dead, disarmed;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
@Import boolean dead, disarmed;
@Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
@Import Team team;
@Import int id;
@Import @Nullable Tile mineTile;
@@ -62,6 +63,48 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
private transient boolean wasPlayer;
private transient boolean wasHealed;
@SyncLocal float elevation;
private transient boolean wasFlying;
transient float drownTime;
transient float splashTimer;
transient @Nullable Floor lastDrownFloor;
public boolean checkTarget(boolean targetAir, boolean targetGround){
return (isGrounded() && targetGround) || (isFlying() && targetAir);
}
public boolean isGrounded(){
return elevation < 0.001f;
}
public boolean isFlying(){
return elevation >= 0.09f;
}
public boolean canDrown(){
return isGrounded() && type.canDrown;
}
public @Nullable Floor drownFloor(){
return floorOn();
}
public void wobble(){
x += Mathf.sin(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
y += Mathf.cos(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
}
public void moveAt(Vec2 vector, float acceleration){
Vec2 t = tmp1.set(vector); //target vector
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
vel.add(tmp2);
}
public float floorSpeedMultiplier(){
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier * speedMultiplier;
}
/** Called when this unit was unloaded from a factory or spawn point. */
public void unloaded(){
@@ -352,12 +395,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
@Override
@Replace
public boolean canDrown(){
return isGrounded() && !hovering && type.canDrown;
}
@Override
@Replace
public boolean canShoot(){
@@ -420,11 +457,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
}
/** @return pathfinder path type for calculating costs. This is used for wave AI only. (TODO: remove) */
public int pathType(){
return Pathfinder.costGround;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta * speedMultiplier());
}
@@ -445,8 +477,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return controller instanceof CommandAI;
}
public boolean canTarget(Unit other){
return other != null && other.checkTarget(type.targetAir, type.targetGround);
public boolean canTarget(Teamc other){
return other != null && (other instanceof Unit u ? u.checkTarget(type.targetAir, type.targetGround) : (other instanceof Building b && type.targetGround));
}
public CommandAI command(){
@@ -475,9 +507,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
this.drag = type.drag;
this.armor = type.armor;
this.hitSize = type.hitSize;
this.hovering = type.hovering;
if(controller == null) controller(type.createController(self()));
if(mounts().length != type.weapons.size) setupWeapons(type);
if(abilities.length != type.abilities.size){
abilities = new Ability[type.abilities.size];
@@ -485,6 +515,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
abilities[i] = type.abilities.get(i).copy();
}
}
if(controller == null) controller(type.createController(self()));
}
public boolean playerControllable(){
return type.playerControllable;
}
public boolean targetable(Team targeter){
@@ -504,7 +539,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void afterRead(){
afterSync();
setType(this.type);
controller.unit(self());
//reset controller state
if(!(controller instanceof AIController ai && ai.keepState())){
controller(type.createController(self()));
@@ -512,7 +548,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
@Override
public void afterAllRead(){
public void afterReadAll(){
controller.afterRead(self());
}
@@ -555,11 +591,98 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
public void updateDrowning(){
Floor floor = drownFloor();
if(floor != null && floor.isLiquid && floor.drownTime > 0 && canDrown()){
lastDrownFloor = floor;
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
if(Mathf.chanceDelta(0.05f)){
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
}
if(drownTime >= 0.999f && !net.client()){
kill();
Events.fire(new UnitDrownEvent(self()));
}
}else{
drownTime -= Time.delta / 50f;
}
drownTime = Mathf.clamp(drownTime);
}
@Override
public void update(){
type.update(self());
//update bounds
if(type.bounded){
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
//TODO hidden map rules only apply to player teams? should they?
if(state.rules.limitMapArea && !team.isAI()){
bot = state.rules.limitY * tilesize;
left = state.rules.limitX * tilesize;
top = state.rules.limitHeight * tilesize + bot;
right = state.rules.limitWidth * tilesize + left;
}
if(!net.client() || isLocal()){
float dx = 0f, dy = 0f;
//repel unit out of bounds
if(x < left) dx += (-(x - left)/warpDst);
if(y < bot) dy += (-(y - bot)/warpDst);
if(x > right) dx -= (x - right)/warpDst;
if(y > top) dy -= (y - top)/warpDst;
velAddNet(dx * Time.delta, dy * Time.delta);
}
//clamp position if not flying
if(isGrounded()){
x = Mathf.clamp(x, left, right - tilesize);
y = Mathf.clamp(y, bot, top - tilesize);
}
//kill when out of bounds
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
kill();
}
}
//update drown/flying state
Floor floor = floorOn();
Tile tile = tileOn();
if(isFlying() != wasFlying){
if(wasFlying){
if(tile != null){
Fx.unitLand.at(x, y, floor.isLiquid ? 1f : 0.5f, tile.floor().mapColor);
}
}
wasFlying = isFlying();
}
if(!type.hovering && isGrounded() && type.emitWalkEffect){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
splashTimer = 0f;
if(type.emitWalkSound){
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
}
}
}
updateDrowning();
if(wasHealed && healTime <= -1f){
healTime = 1f;
}
@@ -647,8 +770,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
Tile tile = tileOn();
Floor floor = floorOn();
if(tile != null && tile.build != null){
tile.build.unitOnAny(self());
}
if(tile != null && isGrounded() && !type.hovering){
//unit block update
@@ -673,7 +797,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
//AI only updates on the server
if(!net.client() && !dead){
if(!net.client() && !dead && shouldUpdateController()){
controller.updateUnit();
}
@@ -688,6 +812,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
public boolean shouldUpdateController(){
return true;
}
/** @return a preview UI icon for this unit. */
public TextureRegion icon(){
return type.uiIcon;
@@ -770,11 +898,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
type.display(self(), table);
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public void draw(){
type.draw(self());
@@ -39,6 +39,10 @@ abstract class VelComp implements Posc{
return null;
}
boolean ignoreSolids(){
return false;
}
/** @return whether this entity can move through a location*/
boolean canPass(int tileX, int tileY){
SolidPred s = solidity();
@@ -0,0 +1,38 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
@Component
abstract class WaterCrawlComp implements Posc, Velc, Hitboxc, Unitc, Crawlc{
@Import float x, y, rotation, speedMultiplier;
@Import UnitType type;
@Replace
public SolidPred solidity(){
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
}
@Replace
public boolean onSolid(){
return EntityCollisions.waterSolid(tileX(), tileY());
}
@Replace
public float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return (on.shallow ? 1f : 1.3f) * speedMultiplier;
}
public boolean onLiquid(){
Tile tile = tileOn();
return tile != null && tile.floor().isLiquid;
}
}
@@ -4,7 +4,6 @@ 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.*;
@@ -18,7 +17,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Unitc{
@Import float x, y, rotation, speedMultiplier;
@Import UnitType type;
@@ -38,19 +37,6 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
}
}
@Override
@Replace
public int pathType(){
return Pathfinder.costNaval;
}
//don't want obnoxious splashing
@Override
@Replace
public boolean emitWalkSound(){
return false;
}
@Override
public void add(){
tleft.clear();
@@ -59,6 +45,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@Override
public void draw(){
//TODO: move to UnitType
float z = Draw.z();
Draw.z(Layer.debris);
@@ -76,7 +63,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::waterSolid;
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
}
@Replace
@@ -23,6 +23,8 @@ public class RegionPart extends DrawPart{
public boolean mirror = false;
/** If true, an outline is drawn under the part. */
public boolean outline = true;
/** If true, this part has an outline created 'in-place'. Currently vanilla only, do not use this! */
public boolean replaceOutline = false;
/** If true, the base + outline regions are drawn. Set to false for heat-only regions. */
public boolean drawRegion = true;
/** If true, the heat region produces light. */
@@ -38,7 +40,8 @@ public class RegionPart extends DrawPart{
public Blending blending = Blending.normal;
public float layer = -1, layerOffset = 0f, heatLayerOffset = 1f, turretHeatLayer = Layer.turretHeat;
public float outlineLayerOffset = -0.001f;
public float x, y, xScl = 1f, yScl = 1f, rotation;
//note that origin DOES NOT AFFECT child parts
public float x, y, xScl = 1f, yScl = 1f, rotation, originX, originY;
public float moveX, moveY, growX, growY, moveRot;
public float heatLightOpacity = 0.3f;
public @Nullable Color color, colorTo, mixColor, mixColorTo;
@@ -99,16 +102,21 @@ public class RegionPart extends DrawPart{
float sign = (i == 0 ? 1 : -1) * params.sideMultiplier;
Tmp.v1.set((x + mx) * sign, y + my).rotateRadExact((params.rotation - 90) * Mathf.degRad);
Draw.xscl *= sign;
if(originX != 0f || originY != 0f){
//correct for offset caused by origin shift
Tmp.v1.sub(Tmp.v2.set(-originX * Draw.xscl, -originY * Draw.yscl).rotate(params.rotation - 90f).add(originX * Draw.xscl, originY * Draw.yscl));
}
float
rx = params.x + Tmp.v1.x,
ry = params.y + Tmp.v1.y,
rot = mr * sign + params.rotation - 90;
Draw.xscl *= sign;
if(outline && drawRegion){
Draw.z(prevZ + outlineLayerOffset);
Draw.rect(outlines[Math.min(i, regions.length - 1)], rx, ry, rot);
rect(outlines[Math.min(i, regions.length - 1)], rx, ry, rot);
Draw.z(prevZ);
}
@@ -126,7 +134,7 @@ public class RegionPart extends DrawPart{
}
Draw.blend(blending);
Draw.rect(region, rx, ry, rot);
rect(region, rx, ry, rot);
Draw.blend();
if(color != null) Draw.color();
}
@@ -134,7 +142,7 @@ public class RegionPart extends DrawPart{
if(heat.found()){
float hprog = heatProgress.getClamp(params, clampProgress);
heatColor.write(Tmp.c1).a(hprog * heatColor.a);
Drawf.additive(heat, Tmp.c1, rx, ry, rot, turretShading ? turretHeatLayer : Draw.z() + heatLayerOffset);
Drawf.additive(heat, Tmp.c1, 1f, rx, ry, rot, turretShading ? turretHeatLayer : Draw.z() + heatLayerOffset, originX, originY);
if(heatLight) Drawf.light(rx, ry, light.found() ? light : heat, rot, Tmp.c1, heatLightOpacity * hprog);
}
@@ -167,6 +175,11 @@ public class RegionPart extends DrawPart{
Draw.scl(preXscl, preYscl);
}
void rect(TextureRegion region, float x, float y, float rotation){
float w = region.width * region.scl() * Draw.xscl, h = region.height * region.scl() * Draw.yscl;
Draw.rect(region, x, y, w, h, w / 2f + originX * Draw.xscl, h / 2f + originY * Draw.yscl, rotation);
}
@Override
public void load(String name){
String realName = this.name == null ? name + suffix : this.name;
@@ -6,6 +6,20 @@ import arc.util.*;
public class ShootHelix extends ShootPattern{
public float scl = 2f, mag = 1.5f, offset = Mathf.PI * 1.25f;
public ShootHelix(float scl, float mag){
this.scl = scl;
this.mag = mag;
}
public ShootHelix(float scl, float mag, float offset){
this.scl = scl;
this.mag = mag;
this.offset = offset;
}
public ShootHelix(){
}
@Override
public void shoot(int totalShots, BulletHandler handler, @Nullable Runnable barrelIncrementer){
for(int i = 0; i < shots; i++){
@@ -14,6 +14,10 @@ public class ShootSpread extends ShootPattern{
public ShootSpread(){
}
public static ShootSpread circle(int points){
return new ShootSpread(points, 360f / points);
}
@Override
public void shoot(int totalShots, BulletHandler handler, @Nullable Runnable barrelIncrementer){
for(int i = 0; i < shots; i++){
@@ -21,11 +21,12 @@ public class AIController implements UnitController{
protected Unit unit;
protected Interval timer = new Interval(4);
protected AIController fallback;
protected @Nullable AIController fallback;
protected float noTargetTime;
/** main target that is being faced */
protected Teamc target;
protected @Nullable Teamc target;
protected @Nullable Teamc bomberTarget;
{
resetTimers();
@@ -124,15 +125,27 @@ public class AIController implements UnitController{
}
public void pathfind(int pathTarget){
int costType = unit.pathType();
pathfind(pathTarget, true);
}
public void pathfind(int pathTarget, boolean stopAtTargetTile){
int costType = unit.type.flowfieldPathType;
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile);
if(tile == targetTile || !unit.canPass(targetTile.x, targetTile.y)) return;
if((tile == targetTile && stopAtTargetTile) || !unit.canPass(targetTile.x, targetTile.y)) return;
unit.movePref(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), prefSpeed()));
unit.movePref(alterPathfind(vec.set(targetTile.worldx(), targetTile.worldy()).sub(tile.worldx(), tile.worldy()).setLength(prefSpeed())));
}
public Vec2 alterPathfind(Vec2 vec){
return vec;
}
public void targetInvalidated(){
//TODO: try this for normal units, reset the target timer
}
public void updateWeapons(){
@@ -146,6 +159,9 @@ public class AIController implements UnitController{
noTargetTime += Time.delta;
if(invalid(target)){
if(target != null && !target.isAdded()){
targetInvalidated();
}
target = null;
}else{
noTargetTime = 0f;
@@ -185,6 +201,13 @@ public class AIController implements UnitController{
if(mount.target != null){
shoot = mount.target.within(mountX, mountY, wrange + (mount.target instanceof Sized s ? s.hitSize()/2f : 0f)) && shouldShoot();
if(unit.type.autoDropBombs && !shoot){
if(bomberTarget == null || !bomberTarget.isAdded() || !bomberTarget.within(unit, unit.hitSize/2f + ((Sized)bomberTarget).hitSize()/2f)){
bomberTarget = Units.closestTarget(unit.team, unit.x, unit.y, unit.hitSize, u -> !u.isFlying(), t -> true);
}
shoot = bomberTarget != null;
}
Vec2 to = Predict.intercept(unit, mount.target, weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
@@ -31,8 +31,4 @@ public interface UnitController{
default void afterRead(Unit unit){
}
default boolean isBeingControlled(Unit player){
return false;
}
}