Merging changes from private branch
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user