Partial 7.0 merge - API preview

This commit is contained in:
Anuken
2021-06-02 11:08:08 -04:00
parent ea75a357ca
commit 28b235ef07
531 changed files with 12356 additions and 6286 deletions

View File

@@ -83,8 +83,17 @@ public class Damage{
}
}
public static @Nullable Building findAbsorber(Team team, float x1, float y1, float x2, float y2){
tmpBuilding = null;
boolean found = world.raycast(World.toTile(x1), World.toTile(y1), World.toTile(x2), World.toTile(y2),
(x, y) -> (tmpBuilding = world.build(x, y)) != null && tmpBuilding.team != team && tmpBuilding.block.absorbLasers);
return found ? tmpBuilding : null;
}
public static float findLaserLength(Bullet b, float length){
Tmp.v1.trns(b.rotation(), length);
Tmp.v1.trnsExact(b.rotation(), length);
furthest = null;
@@ -125,7 +134,7 @@ public class Damage{
if(laser) length = findLaserLength(hitter, length);
collidedBlocks.clear();
tr.trns(angle, length);
tr.trnsExact(angle, length);
Intc2 collider = (cx, cy) -> {
Building tile = world.build(cx, cy);
@@ -378,7 +387,7 @@ 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(damage * Math.min((in.block.size), baseRadius * 0.45f));
in.damage(team, damage * Math.min((in.block.size), baseRadius * 0.4f));
//no need to continue with the explosion
return;
}
@@ -435,7 +444,7 @@ public class Damage{
int cx = Point2.x(e.key), cy = Point2.y(e.key);
var build = world.build(cx, cy);
if(build != null){
build.damage(e.value);
build.damage(team, e.value);
}
}
});

View File

@@ -29,6 +29,8 @@ public class Effect{
public float lifetime = 50f;
/** Clip size. */
public float clip;
/** If true, parent unit is data are followed. */
public boolean followParent;
public float layer = Layer.effect;
public float layerDuration;
@@ -53,6 +55,11 @@ public class Effect{
public void init(){}
public Effect followParent(boolean follow){
followParent = follow;
return this;
}
public Effect layer(float l){
layer = l;
return this;
@@ -148,11 +155,11 @@ public class Effect{
EffectState entity = EffectState.create();
entity.effect = effect;
entity.rotation = rotation;
entity.data = (data);
entity.lifetime = (effect.lifetime);
entity.data = data;
entity.lifetime = effect.lifetime;
entity.set(x, y);
entity.color.set(color);
if(data instanceof Posc) entity.parent = ((Posc)data);
if(effect.followParent && data instanceof Posc) entity.parent = ((Posc)data);
entity.add();
}
}

View File

@@ -1,5 +1,7 @@
package mindustry.entities;
public interface Sized{
import arc.math.geom.*;
public interface Sized extends Position{
float hitSize();
}

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
@@ -20,6 +21,7 @@ public class Units{
private static Unit result;
private static float cdist;
private static boolean boolResult;
private static int intResult;
@Remote(called = Loc.server)
public static void unitCapDeath(Unit unit){
@@ -75,7 +77,7 @@ public class Units{
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
return Integer.MAX_VALUE;
}
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + indexer.getExtraUnits(team) : state.rules.unitCap);
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + team.data().unitCap : state.rules.unitCap);
}
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
@@ -138,23 +140,28 @@ public class Units{
return boolResult;
}
/** Returns the neareset damaged tile. */
/** Returns the nearest damaged tile. */
public static Building findDamagedTile(Team team, float x, float y){
return Geometry.findClosest(x, y, indexer.getDamaged(team));
}
/** Returns the neareset ally tile in a range. */
/** Returns the nearest ally tile in a range. */
public static Building findAllyTile(Team team, float x, float y, float range, Boolf<Building> pred){
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
if(team == Team.derelict) return null;
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** Iterates through all buildings in a range. */
public static void nearbyBuildings(float x, float y, float range, Cons<Building> cons){
indexer.allBuildings(x, y, range, cons);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static Teamc closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unit::isValid);
@@ -199,7 +206,7 @@ public class Units{
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.dead() || !predicate.get(e) || e.team == Team.derelict) return;
float dst2 = e.dst2(x, y);
float dst2 = e.dst2(x, y) - (e.hitSize * e.hitSize);
if(dst2 < range*range && (result == null || dst2 < cdist)){
result = e;
cdist = dst2;
@@ -302,13 +309,40 @@ public class Units{
return result;
}
/** @return whether any units exist in this square (centered) */
public static int count(float x, float y, float size, Boolf<Unit> filter){
return count(x - size/2f, y - size/2f, size, size, filter);
}
/** @return whether any units exist in this rectangle */
public static int count(float x, float y, float width, float height, Boolf<Unit> filter){
intResult = 0;
Groups.unit.intersect(x, y, width, height, v -> {
if(filter.get(v)){
intResult ++;
}
});
return intResult;
}
/** @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;
}
/** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
team.data().tree().intersect(x, y, width, height, cons);
public static void nearby(@Nullable Team team, float x, float y, float width, float height, Cons<Unit> cons){
if(team != null){
team.data().tree().intersect(x, y, width, height, cons);
}else{
for(var other : state.teams.getActive()){
other.tree().intersect(x, y, width, height, cons);
}
}
}
/** Iterates over all units in a circle around this position. */
public static void nearby(Team team, float x, float y, float radius, Cons<Unit> cons){
public static void nearby(@Nullable Team team, float x, float y, float radius, Cons<Unit> cons){
nearby(team, x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.within(x, y, radius + unit.hitSize/2f)){
cons.get(unit);
@@ -336,6 +370,15 @@ public class Units{
}
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float radius, Cons<Unit> cons){
nearbyEnemies(team, x - radius, y - radius, radius * 2f, radius * 2f, u -> {
if(u.within(x, y, radius + u.hitSize/2f)){
cons.get(u);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rect rect, Cons<Unit> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);

View File

@@ -0,0 +1,145 @@
package mindustry.entities.abilities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
public class EnergyFieldAbility extends Ability{
private static final Seq<Healthc> all = new Seq<>();
public float damage = 1, repair = 20f, reload = 100, range = 60;
public Effect healEffect = Fx.heal, hitEffect = Fx.hitLaserBlast, damageEffect = Fx.chainLightning;
public StatusEffect status = StatusEffects.electrified;
public float statusDuration = 60f * 6f;
public float x, y;
public boolean hitBuildings = true;
public int maxTargets = 25;
public float healPercent = 3f;
public float layer = Layer.bullet - 0.001f, blinkScl = 20f;
public float effectRadius = 5f, sectorRad = 0.14f, rotateSpeed = 0.5f;
public int sectors = 5;
public Color color = Pal.heal;
protected float timer, curStroke;
protected boolean anyNearby = false;
EnergyFieldAbility(){}
public EnergyFieldAbility(float damage, float reload, float range){
this.damage = damage;
this.reload = reload;
this.range = range;
}
@Override
public String localized(){
return Core.bundle.format("ability.energyfield", damage, range / Vars.tilesize, maxTargets);
}
@Override
public void draw(Unit unit){
super.draw(unit);
Draw.z(layer);
Draw.color(color);
Tmp.v1.trns(unit.rotation - 90, x, y).add(unit.x, unit.y);
float rx = Tmp.v1.x, ry = Tmp.v1.y;
float orbRadius = effectRadius * (1f + Mathf.absin(blinkScl, 0.1f));
Fill.circle(rx, ry, orbRadius);
Draw.color();
Fill.circle(rx, ry, orbRadius / 2f);
Lines.stroke((0.7f + Mathf.absin(blinkScl, 0.7f)), color);
for(int i = 0; i < sectors; i++){
float rot = unit.rotation + i * 360f/sectors - Time.time * rotateSpeed;
Lines.swirl(rx, ry, orbRadius + 3f, sectorRad, rot);
}
Lines.stroke(Lines.getStroke() * curStroke);
if(curStroke > 0){
for(int i = 0; i < sectors; i++){
float rot = unit.rotation + i * 360f/sectors + Time.time * rotateSpeed;
Lines.swirl(rx, ry, range, sectorRad, rot);
}
}
Drawf.light(rx, ry, range * 1.5f, color, curStroke * 0.8f);
Draw.reset();
}
@Override
public void update(Unit unit){
curStroke = Mathf.lerpDelta(curStroke, anyNearby ? 1 : 0, 0.09f);
if((timer += Time.delta) >= reload){
Tmp.v1.trns(unit.rotation - 90, x, y).add(unit.x, unit.y);
float rx = Tmp.v1.x, ry = Tmp.v1.y;
anyNearby = false;
all.clear();
Units.nearby(null, rx, ry, range, other -> {
if(other != unit){
all.add(other);
}
});
if(hitBuildings){
Units.nearbyBuildings(rx, ry, range, all::add);
}
all.sort(h -> h.dst2(rx, ry));
int len = Math.min(all.size, maxTargets);
for(int i = 0; i < len; i++){
Healthc other = all.get(i);
//lightning gets absorbed by plastanium
var absorber = Damage.findAbsorber(unit.team, rx, ry, other.getX(), other.getY());
if(absorber != null){
other = absorber;
}
if(((Teamc)other).team() == unit.team){
if(other.damaged()){
anyNearby = true;
other.heal(healPercent / 100f * other.maxHealth());
healEffect.at(other);
damageEffect.at(rx, ry, 0f, color, other);
hitEffect.at(rx, ry, unit.angleTo(other), color);
if(other instanceof Building b){
Fx.healBlockFull.at(b.x, b.y, b.block.size, color);
}
}
}else{
anyNearby = true;
other.damage(damage);
if(other instanceof Statusc s){
s.apply(status, statusDuration);
}
hitEffect.at(other.x(), other.y(), unit.angleTo(other), color);
damageEffect.at(rx, ry, 0f, color, other);
hitEffect.at(rx, ry, unit.angleTo(other), color);
}
}
timer = 0f;
}
}
}

View File

@@ -33,13 +33,13 @@ public class ShieldRegenFieldAbility extends Ability{
if(other.shield < max){
other.shield = Math.max(other.shield + amount, max);
other.shieldAlpha = 1f; //TODO may not be necessary
applyEffect.at(unit);
applyEffect.at(unit.x, unit.y, unit.team.color);
applied = true;
}
});
if(applied){
activeEffect.at(unit);
activeEffect.at(unit.x, unit.y, unit.team.color);
}
timer = 0f;

View File

@@ -1,5 +1,6 @@
package mindustry.entities.abilities;
import arc.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -23,6 +24,11 @@ public class StatusFieldAbility extends Ability{
this.effect = effect;
}
@Override
public String localized(){
return Core.bundle.format("ability.statusfield", effect.emoji());
}
@Override
public void update(Unit unit){
timer += Time.delta;

View File

@@ -54,7 +54,7 @@ public class UnitSpawnAbility extends Ability{
if(Units.canCreate(unit.team, this.unit)){
Draw.draw(Draw.z(), () -> {
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
Drawf.construct(x, y, this.unit.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
Drawf.construct(x, y, this.unit.fullIcon, unit.rotation - 90, timer / spawnTime, 1f, timer);
});
}
}

View File

@@ -18,6 +18,24 @@ public class ArtilleryBulletType extends BasicBulletType{
hitSound = Sounds.explosion;
shootEffect = Fx.shootBig;
trailEffect = Fx.artilleryTrail;
//default settings:
shrinkX = 0.15f;
shrinkY = 0.63f;
//for trail:
/*
trailLength = 27;
trailWidth = 3.5f;
trailEffect = Fx.none;
trailColor = Pal.bulletYellowBack;
trailInterp = Interp.slope;
shrinkX = 0.8f;
shrinkY = 0.3f;
*/
}
public ArtilleryBulletType(float speed, float damage){
@@ -39,15 +57,13 @@ public class ArtilleryBulletType extends BasicBulletType{
@Override
public void draw(Bullet b){
float baseScale = 0.7f;
float scale = (baseScale + b.fslope() * (1f - baseScale));
float height = this.height * ((1f - shrinkY) + shrinkY * b.fout());
drawTrail(b);
float xscale = (1f - shrinkX + b.fslope() * (shrinkX)), yscale = (1f - shrinkY + b.fslope() * (shrinkY)), rot = b.rotation();
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, width * scale, height * scale, b.rotation() - 90);
Draw.rect(backRegion, b.x, b.y, width * xscale, height * yscale, rot - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, width * scale, height * scale, b.rotation() - 90);
Draw.rect(frontRegion, b.x, b.y, width * xscale, height * yscale, rot - 90);
Draw.color();
}
}

View File

@@ -42,6 +42,7 @@ public class BasicBulletType extends BulletType{
@Override
public void draw(Bullet b){
super.draw(b);
float height = this.height * ((1f - shrinkY) + shrinkY * b.fout());
float width = this.width * ((1f - shrinkX) + shrinkX * b.fout());
float offset = -90 + (spin != 0 ? Mathf.randomSeed(b.id, 360f) + b.time * spin : 0f);

View File

@@ -1,39 +1,60 @@
package mindustry.entities.bullet;
import arc.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.defense.Wall.*;
import static mindustry.Vars.*;
public abstract class BulletType extends Content{
public class BulletType extends Content implements Cloneable{
/** Lifetime in ticks. */
public float lifetime = 40f;
public float speed;
public float damage;
/** Speed in units/tick. */
public float speed = 1f;
/** Direct damage dealt on hit. */
public float damage = 1f;
/** Hitbox size. */
public float hitSize = 4;
/** Clipping hitbox. */
public float drawSize = 40f;
/** Drag as fraction of velocity. */
public float drag = 0f;
public boolean pierce, pierceBuilding;
/** Whether to pierce units. */
public boolean pierce;
/** Whether to pierce buildings. */
public boolean pierceBuilding;
/** Maximum # of pierced objects. */
public int pierceCap = -1;
public Effect hitEffect, despawnEffect;
/** Z layer to drawn on. */
public float layer = Layer.bullet;
/** Effect shown on direct hit. */
public Effect hitEffect = Fx.hitBulletSmall;
/** Effect shown when bullet despawns. */
public Effect despawnEffect = Fx.hitBulletSmall;
/** Effect created when shooting. */
public Effect shootEffect = Fx.shootSmall;
/** Extra smoke effect created when shooting. */
public Effect smokeEffect = Fx.shootSmallSmoke;
/** Sound made when hitting something or getting removed.*/
public Sound hitSound = Sounds.none;
/** Sound made when hitting something or getting removed.*/
public Sound despawnSound = Sounds.none;
/** Pitch of the sound made when hitting something*/
public float hitSoundPitch = 1;
/** Volume of the sound made when hitting something*/
@@ -80,15 +101,17 @@ public abstract class BulletType extends Content{
public boolean reflectable = true;
/** Whether this projectile can be absorbed by shields. */
public boolean absorbable = true;
/** Whether to move the bullet back depending on delta to fix some delta-time realted issues.
/** Whether to move the bullet back depending on delta to fix some delta-time related issues.
* Do not change unless you know what you're doing. */
public boolean backMove = true;
/** Bullet range override. */
public float maxRange = -1f;
/** % of block health healed **/
public float healPercent = 0f;
/** whether to make fire on impact */
/** Whether to make fire on impact */
public boolean makeFire = 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;
//additional effects
@@ -101,8 +124,14 @@ public abstract class BulletType extends Content{
public Color trailColor = Pal.missileYellowBack;
public float trailChance = -0.0001f;
public float trailInterval = 0f;
public Effect trailEffect = Fx.missileTrail;
public float trailParam = 2f;
public boolean trailRotation = false;
public Interp trailInterp = Interp.one;
/** Any value <= 0 disables the trail. */
public int trailLength = -1;
public float trailWidth = 2f;
/** Use a negative value to disable splash damage. */
public float splashDamageRadius = -1f;
@@ -134,19 +163,27 @@ public abstract class BulletType extends Content{
public float puddleAmount = 5f;
public Liquid puddleLiquid = Liquids.water;
public float lightRadius = 16f;
public float lightRadius = -1f;
public float lightOpacity = 0.3f;
public Color lightColor = Pal.powerLight;
public BulletType(float speed, float damage){
this.speed = speed;
this.damage = damage;
hitEffect = Fx.hitBulletSmall;
despawnEffect = Fx.hitBulletSmall;
}
public BulletType(){
this(1f, 1f);
}
public BulletType copy(){
try{
BulletType copy = (BulletType)clone();
copy.id = (short)Vars.content.getBy(getContentType()).size;
Vars.content.handleContent(copy);
return copy;
}catch(Exception e){
throw new RuntimeException("death to checked exceptions", e);
}
}
/** @return estimated damage per shot. this can be very inaccurate. */
@@ -181,13 +218,13 @@ public abstract class BulletType extends Content{
if(healPercent > 0f && build.team == b.team && !(build.block instanceof ConstructBlock)){
Fx.healBlockFull.at(build.x, build.y, build.block.size, Pal.heal);
build.heal(healPercent / 100f * build.maxHealth());
build.heal(healPercent / 100f * build.maxHealth);
}else if(build.team != b.team && direct){
hit(b);
}
}
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
public void hitEntity(Bullet b, Hitboxc entity, float health){
if(entity instanceof Healthc h){
h.damage(b.damage);
}
@@ -198,6 +235,11 @@ public abstract class BulletType extends Content{
unit.impulse(Tmp.v3);
unit.apply(status, statusDuration);
}
//for achievements
if(b.owner instanceof WallBuild && player != null && b.team == player.team() && entity instanceof Unit unit && unit.dead){
Events.fire(Trigger.phaseDeflectHit);
}
}
public void hit(Bullet b){
@@ -205,7 +247,6 @@ public abstract class BulletType extends Content{
}
public void hit(Bullet b, float x, float y){
b.hit = true;
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(x, y, hitSoundPitch, hitSoundVolume);
@@ -226,7 +267,7 @@ public abstract class BulletType extends Content{
}
}
if(Mathf.chance(incendChance)){
if(incendChance > 0 && Mathf.chance(incendChance)){
Damage.createIncend(x, y, incendSpread, incendAmount);
}
@@ -245,9 +286,7 @@ public abstract class BulletType extends Content{
}
if(makeFire){
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> {
Fires.create(other.tile);
});
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> Fires.create(other.tile));
}
}
@@ -256,21 +295,40 @@ public abstract class BulletType extends Content{
}
}
/** Called when the bullet reaches the end of its lifetime of is destroyed by something external. */
public void despawned(Bullet b){
if(despawnHit){
hit(b);
}
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
hitSound.at(b);
despawnSound.at(b);
Effect.shake(despawnShake, despawnShake, b);
}
if(!b.hit && (fragBullet != null || splashDamageRadius > 0 || lightning > 0)){
hit(b);
/** Called when the bullet is removed for any reason. */
public void removed(Bullet b){
if(trailLength > 0 && b.trail != null && b.trail.size() > 0){
Fx.trailFade.at(b.x, b.y, trailWidth, trailColor, b.trail.copy());
}
}
public void draw(Bullet b){
drawTrail(b);
}
public void drawTrail(Bullet b){
if(trailLength > 0 && b.trail != null){
//draw below bullets? TODO
float z = Draw.z();
Draw.z(z - 0.0001f);
b.trail.draw(trailColor, trailWidth);
Draw.z(z);
}
}
public void drawLight(Bullet b){
if(lightOpacity <= 0f || lightRadius <= 0f) return;
Drawf.light(b.team, b, lightRadius, lightColor, lightOpacity);
}
@@ -283,11 +341,37 @@ public abstract class BulletType extends Content{
if(instantDisappear){
b.time = lifetime;
}
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
despawnHit = true;
}
if(lightRadius == -1){
lightRadius = Math.max(18, hitSize * 5f);
}
drawSize = Math.max(drawSize, trailLength * speed * 2f);
}
public void update(Bullet b){
if(!headless && trailLength > 0){
if(b.trail == null){
b.trail = new Trail(trailLength);
}
b.trail.length = trailLength;
b.trail.update(b.x, b.y, trailInterp.apply(b.fin()));
}
if(homingPower > 0.0001f && b.time >= homingDelay){
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround), t -> collidesGround);
Teamc target;
//home in on allies if possible
if(healPercent > 0){
target = Units.closestTarget(null, b.x, b.y, homingRange,
e -> e.checkTarget(collidesAir, collidesGround) && e.team != b.team,
t -> collidesGround && (t.team != b.team || t.damaged()));
}else{
target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround), t -> collidesGround);
}
if(target != null){
b.vel.setAngle(Angles.moveToward(b.rotation(), b.angleTo(target), homingPower * Time.delta * 50f));
}
@@ -299,7 +383,13 @@ public abstract class BulletType extends Content{
if(trailChance > 0){
if(Mathf.chanceDelta(trailChance)){
trailEffect.at(b.x, b.y, trailParam, trailColor);
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
if(trailInterval > 0f){
if(b.timer(0, trailInterval)){
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
}
@@ -366,6 +456,10 @@ public abstract class BulletType extends Content{
bullet.drag = drag;
bullet.hitSize = hitSize;
bullet.damage = (damage < 0 ? this.damage : damage) * bullet.damageMultiplier();
//reset trail
if(bullet.trail != null){
bullet.trail.clear();
}
bullet.add();
if(keepVelocity && owner instanceof Velc v) bullet.vel.add(v.vel().x, v.vel().y);

View File

@@ -0,0 +1,70 @@
package mindustry.entities.bullet;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class EmpBulletType extends BasicBulletType{
public float radius = 100f;
public float timeIncrease = 2.5f, timeDuration = 60f * 10f;
public float powerDamageScl = 2f, powerSclDecrease = 0.2f;
public Effect hitPowerEffect = Fx.hitEmpSpark, chainEffect = Fx.chainEmp, applyEffect = Fx.heal;
public boolean hitUnits = true;
public float unitDamageScl = 0.7f;
@Override
public void hit(Bullet b, float x, float y){
super.hit(b, x, y);
if(!b.absorbed){
Vars.indexer.allBuildings(x, y, radius, other -> {
if(other.team == b.team){
if(other.block.hasPower && other.block.canOverdrive && other.timeScale < timeIncrease){
if(timeIncrease >= other.timeScale){
other.timeScale = Math.max(other.timeScale, timeIncrease);
}
other.timeScaleDuration = Math.max(other.timeScaleDuration, timeDuration);
chainEffect.at(x, y, 0, hitColor, other);
applyEffect.at(other, other.block.size * 7f);
}
if(other.block.hasPower && other.damaged()){
other.heal(healPercent / 100f * other.maxHealth());
Fx.healBlockFull.at(other.x, other.y, other.block.size, hitColor);
applyEffect.at(other, other.block.size * 7f);
}
}else if(other.power != null){
var absorber = Damage.findAbsorber(b.team, x, y, other.x, other.y);
if(absorber != null){
other = absorber;
}
if(other.power != null && other.power.graph.getLastPowerProduced() > 0f){
other.timeScale = Math.min(other.timeScale, powerSclDecrease);
other.timeScaleDuration = timeDuration;
other.damage(damage * powerDamageScl);
hitPowerEffect.at(other.x, other.y, b.angleTo(other), hitColor);
chainEffect.at(x, y, 0, hitColor, other);
}
}
});
if(hitUnits){
Units.nearbyEnemies(b.team, x, y, radius, other -> {
if(other.team != b.team){
var absorber = Damage.findAbsorber(b.team, x, y, other.x, other.y);
if(absorber != null){
return;
}
hitPowerEffect.at(other.x, other.y, b.angleTo(other), hitColor);
chainEffect.at(x, y, 0, hitColor, other);
other.damage(damage * unitDamageScl);
other.apply(status, statusDuration);
}
});
}
}
}
}

View File

@@ -6,7 +6,7 @@ import mindustry.entities.*;
import mindustry.gen.*;
public class FlakBulletType extends BasicBulletType{
public float explodeRange = 30f;
public float explodeRange = 30f, explodeDelay = 5f;
public FlakBulletType(float speed, float damage){
super(speed, damage, "shell");
@@ -25,17 +25,21 @@ public class FlakBulletType extends BasicBulletType{
@Override
public void update(Bullet b){
super.update(b);
if(b.data() instanceof Integer) return;
//don't check for targets if primed to explode
if(b.fdata < 0f) return;
if(b.timer(2, 6)){
Units.nearbyEnemies(b.team, Tmp.r1.setSize(explodeRange * 2f).setCenter(b.x, b.y), unit -> {
if(b.data() instanceof Float || !unit.checkTarget(collidesAir, collidesGround)) return;
//fadata < 0 means it's primed to explode
if(b.fdata < 0f || !unit.checkTarget(collidesAir, collidesGround)) return;
if(unit.dst(b) < explodeRange){
b.data(0);
Time.run(5f, () -> {
if(b.data() instanceof Integer){
b.time(b.lifetime());
if(unit.within(b, explodeRange)){
//mark as primed
b.fdata = -1f;
Time.run(explodeDelay, () -> {
//explode
if(b.fdata < 0){
b.time = b.lifetime;
}
});
}

View File

@@ -3,6 +3,7 @@ package mindustry.entities.bullet;
import arc.graphics.g2d.*;
import mindustry.gen.*;
import mindustry.content.*;
import mindustry.graphics.*;
public class LaserBoltBulletType extends BasicBulletType{
public float width = 2f, height = 7f;
@@ -15,6 +16,8 @@ public class LaserBoltBulletType extends BasicBulletType{
despawnEffect = Fx.hitLaser;
hittable = false;
reflectable = false;
lightColor = Pal.heal;
lightOpacity = 0.6f;
}
public LaserBoltBulletType(){
@@ -23,6 +26,7 @@ public class LaserBoltBulletType extends BasicBulletType{
@Override
public void draw(Bullet b){
super.draw(b);
Draw.color(backColor);
Lines.stroke(width);
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height);

View File

@@ -63,9 +63,10 @@ public class LiquidBulletType extends BulletType{
@Override
public void draw(Bullet b){
super.draw(b);
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, orbSize);
Draw.reset();
}
@Override

View File

@@ -69,9 +69,9 @@ public class RailBulletType extends BulletType{
}
@Override
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
handle(b, entity, initialHealth);
super.hitEntity(b, entity, initialHealth);
public void hitEntity(Bullet b, Hitboxc entity, float health){
handle(b, entity, health);
super.hitEntity(b, entity, health);
}
@Override

View File

@@ -25,6 +25,8 @@ public class SapBulletType extends BulletType{
hittable = false;
hitEffect = Fx.hitLiquid;
status = StatusEffects.sapped;
lightColor = Pal.sap;
lightOpacity = 0.6f;
statusDuration = 60f * 3f;
impact = true;
}
@@ -40,7 +42,7 @@ public class SapBulletType extends BulletType{
Draw.reset();
Drawf.light(b.team, b.x, b.y, Tmp.v1.x, Tmp.v1.y, 15f * b.fout(), lightColor, 0.6f);
Drawf.light(b.team, b.x, b.y, Tmp.v1.x, Tmp.v1.y, 15f * b.fout(), lightColor, lightOpacity);
}
}

View File

@@ -29,6 +29,7 @@ public class ShrapnelBulletType extends BulletType{
pierce = true;
hittable = false;
absorbable = false;
lightOpacity = 0.6f;
}
@Override
@@ -52,11 +53,11 @@ public class ShrapnelBulletType extends BulletType{
@Override
public void draw(Bullet b){
float realLength = b.fdata;
float realLength = b.fdata, rot = b.rotation();
Draw.color(fromColor, toColor, b.fin());
for(int i = 0; i < (int)(serrations * realLength / length); i++){
Tmp.v1.trns(b.rotation(), i * serrationSpacing);
Tmp.v1.trns(rot, i * serrationSpacing);
float sl = Mathf.clamp(b.fout() - serrationFadeOffset) * (serrationSpaceOffset - i * serrationLenScl);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() - 90);
@@ -64,5 +65,7 @@ public class ShrapnelBulletType extends BulletType{
Drawf.tri(b.x, b.y, width * b.fout(), (realLength + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
Drawf.light(b.team, b.x, b.y, b.x + Angles.trnsx(rot, realLength), b.y + Angles.trnsy(rot, realLength), width * 2.5f * b.fout(), toColor, lightOpacity);
}
}

View File

@@ -4,7 +4,6 @@ import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
@@ -34,7 +33,7 @@ abstract class BlockUnitComp implements Unitc{
@Replace
@Override
public TextureRegion icon(){
return tile.block.icon(Cicon.full);
return tile.block.fullIcon;
}
@Override

View File

@@ -9,7 +9,7 @@ import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 180f;
static final float warpDst = 40f;
@Import float x, y;
@Import Vec2 vel;
@@ -17,11 +17,16 @@ abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
@Override
public void update(){
if(!net.client() || isLocal()){
float dx = 0f, dy = 0f;
//repel unit out of bounds
if(x < 0) vel.x += (-x/warpDst);
if(y < 0) vel.y += (-y/warpDst);
if(x > world.unitWidth()) vel.x -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) vel.y -= (y - world.unitHeight())/warpDst;
if(x < 0) dx += (-x/warpDst);
if(y < 0) dy += (-y/warpDst);
if(x > world.unitWidth()) dx -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) dy -= (y - world.unitHeight())/warpDst;
velAddNet(dx, dy);
}
//clamp position if not flying

View File

@@ -20,6 +20,7 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -69,7 +70,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
}
}
Building core = core();
var core = core();
//nothing to build.
if(buildPlan() == null) return;
@@ -109,7 +110,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
plans.removeFirst();
return;
}
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && (cb.cblock != current.block || cb.tile != current.tile()))){
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && (cb.current != current.block || cb.tile != current.tile()))){
plans.removeFirst();
return;
}

View File

@@ -48,8 +48,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//region vars and initialization
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
static final Seq<Building> tempTileEnts = new Seq<>();
static final Seq<Tile> tempTiles = new Seq<>();
static final Seq<Building> tempBuilds = new Seq<>();
static int sleepingEntities = 0;
@Import float x, y, health, maxHealth;
@@ -57,7 +56,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient Tile tile;
transient Block block;
transient Seq<Building> proximity = new Seq<>(8);
transient Seq<Building> proximity = new Seq<>(6);
transient boolean updateFlow;
transient byte cdump;
transient int rotation;
@@ -71,6 +70,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
ConsumeModule cons;
private transient float timeScale = 1f, timeScaleDuration;
private transient float dumpAccum;
private transient @Nullable SoundLoop sound;
@@ -213,8 +213,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(self() instanceof ConstructBuild entity){
//update block to reflect the fact that something was being constructed
if(entity.cblock != null && entity.cblock.synthetic() && entity.wasConstructing){
block = entity.cblock;
if(entity.current != null && entity.current.synthetic() && entity.wasConstructing){
block = entity.current;
overrideConfig = entity.lastConfig;
}else{
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
@@ -401,6 +401,29 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** @return whether the player can select (but not actually control) this building. */
public boolean canControlSelect(Player player){
return false;
}
/** Called when a player control-selects this building - not called for ControlBlock subclasses. */
public void onControlSelect(Player player){
}
public void acceptPlayerPayload(Player player, Cons<Payload> grabber){
Fx.spawn.at(player);
var unit = player.unit();
player.clearUnit();
//player.deathTimer = Player.deathDelay + 1f; //for instant respawn
unit.remove();
grabber.get(new UnitPayload(unit));
Fx.unitDrop.at(unit);
if(Vars.net.client()){
Vars.netClient.clearRemovedEntity(unit.id);
}
}
public boolean canUnload(){
return block.unloadable;
}
@@ -467,10 +490,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void onProximityUpdate(){
noSleep();
}
public boolean acceptPayload(Building source, Payload payload){
return false;
}
@@ -688,13 +707,27 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(Vars.state.rules.sector != null && team == state.rules.defaultTeam) Vars.state.rules.sector.info.handleProduction(item, amount);
}
/** Try dumping any item near the */
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
public void dumpAccumulate(){
dumpAccumulate(null);
}
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
public void dumpAccumulate(Item item){
dumpAccum += delta();
while(dumpAccum >= 1f){
dump(item);
dumpAccum -=1f;
}
}
/** Try dumping any item near the building. */
public boolean dump(){
return dump(null);
}
/**
* Try dumping a specific item near the
* Try dumping a specific item near the building.
* @param todump Item to dump. Can be null to dump anything.
*/
public boolean dump(Item todump){
@@ -753,20 +786,27 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return false;
}
/** Called shortly before this building is removed. */
public void onProximityRemoved(){
if(power != null){
powerGraphRemoved();
}
}
/** in overrides, this does the exact same thing as onProximityUpdate, use that instead */
/** Called after this building is created in the world. May be called multiple times, or when adjacent buildings change. */
public void onProximityAdded(){
if(block.hasPower) updatePowerGraph();
if(power != null){
updatePowerGraph();
}
}
/** Called when anything adjacent to this building is placed/removed, including itself. */
public void onProximityUpdate(){
noSleep();
}
public void updatePowerGraph(){
for(Building other : getPowerConnections(tempTileEnts)){
for(Building other : getPowerConnections(tempBuilds)){
if(other.power != null){
other.power.graph.addGraph(power.graph);
}
@@ -970,7 +1010,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called when the block is destroyed. */
/** Called *after* the tile has been removed. */
public void afterDestroyed(){
}
/** Called when the block is destroyed. The tile is still intact at this stage. */
public void onDestroyed(){
float explosiveness = block.baseExplosiveness;
float flammability = 0f;
@@ -1022,7 +1067,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public TextureRegion getDisplayIcon(){
return block.icon(Cicon.medium);
return block.uiIcon;
}
@Override
@@ -1063,7 +1108,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
l.left();
for(Item item : content.items()){
if(items.hasFlowItem(item)){
l.image(item.icon(Cicon.small)).padRight(3f);
l.image(item.uiIcon).padRight(3f);
l.label(() -> items.getFlowRate(item) < 0 ? "..." : Strings.fixed(items.getFlowRate(item), 1) + ps).color(Color.lightGray);
l.row();
}
@@ -1090,7 +1135,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
Runnable rebuild = () -> {
l.clearChildren();
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.image(() -> liquids.current().uiIcon).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
};
@@ -1189,11 +1234,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Handle a bullet collision.
* @return whether the bullet should be removed. */
public boolean collision(Bullet other){
damage(other.damage() * other.type().buildingDamageMultiplier);
damage(other.team, other.damage() * other.type().buildingDamageMultiplier);
return true;
}
/** Used to handle damage from splash damage for certain types of blocks. */
public void damage(@Nullable Team source, float damage){
damage(damage);
}
/** Changes this building's team in a safe manner. */
public void changeTeam(Team next){
indexer.removeIndex(tile);
this.team = next;
indexer.addIndex(tile);
}
public boolean canPickup(){
return true;
}
@@ -1366,11 +1423,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Override
public void control(LAccess type, Object p1, double p2, double p3, double p4){
//don't execute configure instructions that copy logic building configures; this can cause extreme lag
if(type == LAccess.configure && block.logicConfigurable && !(p1 instanceof LogicBuild)){
if(type == LAccess.config && block.logicConfigurable && !(p1 instanceof LogicBuild)){
//change config only if it's new
if(senseObject(LAccess.config) != p1){
configured(null, p1);
}
configured(null, p1);
}
}
@@ -1388,6 +1443,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
onDestroyed();
tile.remove();
remove();
afterDestroyed();
}
@Final
@@ -1409,7 +1465,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
if(team == Team.derelict){
if(team == Team.derelict || !block.supportsEnv(state.rules.environment)){
enabled = false;
}

View File

@@ -1,6 +1,5 @@
package mindustry.entities.comp;
import arc.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -10,12 +9,10 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.*;
import mindustry.entities.bullet.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.blocks.defense.Wall.*;
import static mindustry.Vars.*;
@@ -31,6 +28,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
BulletType type;
float fdata;
transient boolean absorbed, hit;
transient @Nullable Trail trail;
@Override
public void getCollisions(Cons<QuadTree> consumer){
@@ -42,11 +40,6 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
}
@Override
public void drawBullets(){
type.draw(self());
}
@Override
public void add(){
type.init(self());
@@ -54,13 +47,17 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Override
public void remove(){
type.despawned(self());
//'despawned' only counts when the bullet is killed externally or reaches the end of life
if(!hit){
type.despawned(self());
}
type.removed(self());
collided.clear();
}
@Override
public float damageMultiplier(){
if(owner instanceof Unit) return ((Unit)owner).damageMultiplier() * state.rules.unitDamageMultiplier;
if(owner instanceof Unit u) return u.damageMultiplier() * state.rules.unitDamageMultiplier;
if(owner instanceof Building) return state.rules.blockDamageMultiplier;
return 1f;
@@ -80,8 +77,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Replace
@Override
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc && ((Teamc)other).team() != team)
&& !(other instanceof Flyingc && !((Flyingc)other).checkTarget(type.collidesAir, type.collidesGround))
return type.collides && (other instanceof Teamc t && t.team() != team)
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
&& !(type.pierce && collided.contains(other.id())); //prevent multiple collisions
}
@@ -89,24 +86,16 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(self(), x, y);
float health = 0f;
if(other instanceof Healthc h){
health = h.health();
}
//must be last.
if(!type.pierce){
hit = true;
remove();
}else{
collided.add(other.id());
}
type.hitEntity(self(), other, health);
if(owner instanceof WallBuild && player != null && team == player.team() && other instanceof Unit unit && unit.dead){
Events.fire(Trigger.phaseDeflectHit);
}
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
}
@Override
@@ -130,6 +119,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
if(remove || type.collidesTeam){
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(tile.id);
@@ -146,13 +136,14 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
if(type.pierceCap != -1 && collided.size >= type.pierceCap){
hit = true;
remove();
}
}
@Override
public void draw(){
Draw.z(Layer.bullet);
Draw.z(type.layer);
type.draw(self());
type.drawLight(self());

View File

@@ -83,7 +83,7 @@ abstract class CommanderComp implements Entityc, Posc{
clearCommand();
units.shuffle();
float spacing = hitSize * 0.8f;
float spacing = hitSize * 0.9f;
minFormationSpeed = type.speed;
controlling.addAll(units);

View File

@@ -40,6 +40,7 @@ abstract class EntityComp{
return false;
}
/** Replaced with `this` after code generation. */
<T extends Entityc> T self(){
return (T)this;
}
@@ -48,11 +49,6 @@ abstract class EntityComp{
return (T)this;
}
<T> T with(Cons<T> cons){
cons.get((T)this);
return (T)this;
}
@InternalImpl
abstract int classId();

View File

@@ -1,5 +1,7 @@
package mindustry.entities.comp;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
@@ -9,6 +11,7 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -16,24 +19,30 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
private static final float spreadChance = 0.04f, fireballChance = 0.06f;
abstract class FireComp implements Timedc, Posc, Syncc, Drawc{
public static final int frames = 40, duration = 90;
private static final float spreadDelay = 22f, fireballDelay = 40f,
ticksPerFrame = (float)duration / frames, warmupDuration = 20f, damageDelay = 40f, tileDamage = 1.8f, unitDamage = 3f;
public static final TextureRegion[] regions = new TextureRegion[frames];
@Import float time, lifetime, x, y;
Tile tile;
private transient Block block;
private transient float baseFlammability = -1, puddleFlammability;
private transient float
baseFlammability = -1, puddleFlammability, damageTimer = Mathf.random(40f),
spreadTimer = Mathf.random(spreadDelay), fireballTimer = Mathf.random(fireballDelay),
warmup = 0f,
animation = Mathf.random(frames);
@Override
public void update(){
if(Mathf.chance(0.09 * Time.delta)){
Fx.fire.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.05 * Time.delta)){
Fx.fireSmoke.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
animation += Time.delta / ticksPerFrame;
warmup += Time.delta;
animation %= frames;
if(!headless){
control.sound.loop(Sounds.fire, this, 0.07f);
@@ -55,46 +64,71 @@ abstract class FireComp implements Timedc, Posc, Firec, Syncc{
Building entity = tile.build;
boolean damage = entity != null;
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.getFlammability();
block = tile.block();
}
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.build == null ? 0 : tile.getFlammability();
block = tile.block();
}
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Time.delta;
}
if(flammability > 1f && Mathf.chance(spreadChance * Time.delta * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
if(flammability > 1f && (spreadTimer += Time.delta * Mathf.clamp(flammability / 5f, 0.3f, 2f)) >= spreadDelay){
spreadTimer = 0f;
Point2 p = Geometry.d4[Mathf.random(3)];
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
Fires.create(other);
if(Mathf.chance(fireballChance * Time.delta * Mathf.clamp(flammability / 10f))){
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1, 1);
}
}
if(Mathf.chance(0.025 * Time.delta)){
if(flammability > 0 && (fireballTimer += Time.delta * Mathf.clamp(flammability / 10f, 0f, 0.5f)) >= fireballDelay){
fireballTimer = 0f;
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1, 1);
}
//apply damage to nearby units & building
if((damageTimer += Time.delta) >= damageDelay){
damageTimer = 0f;
Puddlec p = Puddles.get(tile);
puddleFlammability = p != null ? p.getFlammability() / 3f : 0;
if(damage){
entity.damage(1.6f);
entity.damage(tileDamage);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, unitDamage,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.apply(StatusEffects.burning, 60 * 5));
}
}
@Override
public void draw(){
if(regions[0] == null){
for(int i = 0; i < frames; i++){
regions[i] = Core.atlas.find("fire" + i);
}
}
Draw.alpha(Mathf.clamp(warmup / warmupDuration));
Draw.z(Layer.effect);
Draw.rect(regions[(int)animation], x, y);
Draw.reset();
}
@Replace
@Override
public float clipSize(){
return 25;
}
@Override
public void remove(){
Fx.fireRemove.at(x, y, animation);
Fires.remove(tile);
}

View File

@@ -43,7 +43,7 @@ abstract class LaunchCoreComp implements Drawc, Timedc{
Draw.z(Layer.weather - 1);
TextureRegion region = block.icon(Cicon.full);
TextureRegion region = block.fullIcon;
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
Draw.alpha(alpha);

View File

@@ -83,8 +83,8 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
Tile on = tileOn();
//clear removed state of unit so it can be synced
if(Vars.net.client() && payload instanceof UnitPayload){
Vars.netClient.clearRemovedEntity(((UnitPayload)payload).unit.id);
if(Vars.net.client() && payload instanceof UnitPayload u){
Vars.netClient.clearRemovedEntity(u.unit.id);
}
//drop off payload on an acceptor if possible
@@ -106,7 +106,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
Unit u = payload.unit;
//can't drop ground units
if(!u.canPass(tileX(), tileY())){
if(!u.canPass(tileX(), tileY()) || Units.count(x, y, u.physicSize(), o -> o.isGrounded()) > 1){
return false;
}
@@ -159,7 +159,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
}
for(Payload p : payloads){
table.image(p.icon(Cicon.small)).size(itemSize).padRight(pad);
table.image(p.icon()).size(itemSize).padRight(pad);
}
}
}

View File

@@ -11,7 +11,7 @@ import mindustry.gen.*;
* Has mass.*/
@Component
abstract class PhysicsComp implements Velc, Hitboxc, Flyingc{
@Import float hitSize;
@Import float hitSize, x, y;
@Import Vec2 vel;
transient PhysicRef physref;

View File

@@ -19,7 +19,6 @@ import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
@@ -33,31 +32,31 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Import float x, y;
@ReadOnly Unit unit = Nulls.unit;
transient private Unit lastReadUnit = Nulls.unit;
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean typing, shooting, boosting;
boolean admin;
@SyncLocal float mouseX, mouseY;
String name = "noname";
boolean admin;
String name = "frog";
Color color = new Color();
//locale should not be synced.
transient String locale = "en";
transient float deathTimer;
transient String lastText = "";
transient float textFadeTime;
transient private Unit lastReadUnit = Nulls.unit;
public boolean isBuilder(){
return unit.canBuild();
}
public @Nullable CoreBuild closestCore(){
public @Nullable
CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable CoreBuild core(){
public @Nullable
CoreBuild core(){
return team.core();
}
@@ -69,7 +68,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.icon(Cicon.full) : ((CoreBlock)core().block).unitType.icon(Cicon.full);
if(dead()) return core() == null ? UnitTypes.alpha.fullIcon : ((CoreBlock)core().block).unitType.fullIcon;
return unit.icon();
}
@@ -133,8 +132,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
//update some basic state to sync things
if(unit.type.canBoost){
Tile tile = unit.tileOn();
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, unit.type.riseSpeed);
unit.elevation = Mathf.approachDelta(unit.elevation, unit.onSolid() || boosting || (unit.isFlying() && !unit.canLand()) ? 1f : 0f, unit.type.riseSpeed);
}
}else if((core = bestCore()) != null){
//have a small delay before death to prevent the camera from jumping around too quickly
@@ -266,9 +264,9 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
layout.setText(font, text, Color.white, width, Align.bottom, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(unit.x, unit.y + textHeight + layout.height - layout.height/2f, layout.width + 2, layout.height + 3);
font.draw(text, unit.x - width/2f, unit.y + textHeight + layout.height, width, Align.center, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(unit.x, unit.y + textHeight + layout.height - layout.height / 2f, layout.width + 2, layout.height + 3);
font.draw(text, unit.x - width / 2f, unit.y + textHeight + layout.height, width, Align.center, true);
}
Draw.reset();
@@ -294,7 +292,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
sendMessage(text, from, NetClient.colorizeName(from.id(), from.name));
}
void sendMessage(String text, Player from, String fromName){
void sendMessage(String text, Player from, String fromName){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, fromName);

View File

@@ -21,10 +21,10 @@ import static mindustry.entities.Puddles.*;
@Component(base = true)
abstract class PuddleComp implements Posc, Puddlec, Drawc{
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect(), rect2 = new Rect();
private static int seeds;
@Import int id;
@Import float x, y;
transient float accepting, updateTime, lastRipple;
@@ -92,13 +92,13 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
public void draw(){
Draw.z(Layer.debris - 1);
seeds = id();
seeds = id;
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 25f;
Draw.color(tmp.set(liquid.color).shiftValue(-0.05f));
Draw.color(Tmp.c1.set(liquid.color).shiftValue(-0.05f));
Fill.circle(x + Mathf.sin(Time.time + seeds * 532, sscl, smag), y + Mathf.sin(Time.time + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id(), 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Time.time + seeds * 532, sscl, smag),

View File

@@ -3,6 +3,7 @@ package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -11,10 +12,11 @@ import static mindustry.Vars.*;
abstract class ShieldComp implements Healthc, Posc{
@Import float health, hitTime, x, y, healthMultiplier;
@Import boolean dead;
@Import Team team;
/** Absorbs health damage. */
float shield;
/** Substracts an amount from damage. */
/** Subtracts an amount from damage. */
float armor;
/** Shield opacity. */
transient float shieldAlpha = 0f;
@@ -60,7 +62,7 @@ abstract class ShieldComp implements Healthc, Posc{
}
if(hadShields && shield <= 0.0001f){
Fx.unitShieldBreak.at(x, y, 0, this);
Fx.unitShieldBreak.at(x, y, 0, team.color, this);
}
}
}

View File

@@ -19,8 +19,9 @@ abstract class StatusComp implements Posc, Flyingc{
private Seq<StatusEntry> statuses = new Seq<>();
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1, buildSpeedMultiplier = 1, dragMultiplier = 1;
@ReadOnly transient boolean disarmed = false;
//these are considered read-only
transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1, buildSpeedMultiplier = 1, dragMultiplier = 1;
transient boolean disarmed = false;
@Import UnitType type;
@@ -46,15 +47,8 @@ abstract class StatusComp implements Posc, Flyingc{
if(entry.effect == effect){
entry.time = Math.max(entry.time, duration);
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition(self(), effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(StatusEntry.tmp.effect != entry.effect){
entry.effect = StatusEntry.tmp.effect;
}
}else if(entry.effect.applyTransition(self(), effect, entry, duration)){ //find reaction
//TODO effect may react with multiple other effects
//stop looking when one is found
return;
}
@@ -69,6 +63,11 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
float getDuration(StatusEffect effect){
var entry = statuses.find(e -> e.effect == effect);
return entry == null ? 0 : entry.time;
}
void clearStatuses(){
statuses.clear();
}
@@ -149,6 +148,10 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
public Bits statusBits(){
return applied;
}
public void draw(){
for(StatusEntry e : statuses){
e.effect.draw(self());

View File

@@ -4,6 +4,7 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@@ -18,17 +19,17 @@ abstract class TeamComp implements Posc{
}
@Nullable
public Building core(){
public CoreBuild core(){
return team.core();
}
@Nullable
public Building closestCore(){
public CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
@Nullable
public Building closestEnemyCore(){
public CoreBuild closestEnemyCore(){
return state.teams.closestEnemyCore(x, y, team);
}
}

View File

@@ -46,8 +46,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
double flag;
transient Seq<Ability> abilities = new Seq<>(0);
transient float healTime;
private transient float resupplyTime = Mathf.random(10f);
private transient boolean wasPlayer;
private transient float lastHealth;
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
@@ -67,6 +69,16 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
lookAt(x, y);
}
/** @return approx. square size of the physical hitbox for physics */
public float physicSize(){
return hitSize * 0.7f;
}
/** @return whether there is solid, un-occupied ground under this unit. */
public boolean canLand(){
return !onSolid() && Units.count(x, y, physicSize(), f -> f != self() && f.isGrounded()) == 0;
}
public boolean inRange(Position other){
return within(other, type.range);
}
@@ -98,7 +110,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return angleTo(buildPlan());
}else if(mineTile != null){
return angleTo(mineTile);
}else if(moving()){
}else if(moving() && type.omniMovement){
return vel().angle();
}
return rotation;
@@ -114,7 +126,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(isBuilding()){
return state.rules.infiniteResources ? Float.MAX_VALUE : Math.max(type.clipSize, type.region.width) + buildingRange + tilesize*4f;
}
return Math.max(type.region.width * 2f, type.clipSize);
return type.clipSize;
}
@Override
@@ -146,7 +158,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
controller instanceof FormationAI ? ctrlFormation :
0;
case commanded -> controller instanceof FormationAI && isValid() ? 1 : 0;
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
case payloadCount -> ((Object)this) instanceof Payloadc pay ? pay.payloads().size : 0;
case size -> hitSize / tilesize;
default -> Float.NaN;
};
@@ -159,7 +171,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case name -> controller instanceof Player p ? p.name : null;
case firstItem -> stack().amount == 0 ? null : item();
case controller -> !isValid() ? null : controller instanceof LogicAI log ? log.controller : controller instanceof FormationAI form ? form.leader : this;
case payloadType -> self() instanceof Payloadc pay ?
case payloadType -> ((Object)this) instanceof Payloadc pay ?
(pay.payloads().isEmpty() ? null :
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
@@ -313,6 +325,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
type.update(self());
if(health > lastHealth && lastHealth > 0 && healTime <= -1f){
healTime = 1f;
}
healTime -= Time.delta / 20f;
lastHealth = health;
//check if environment is unsupported
if(!type.supportsEnv(state.rules.environment) && !dead){
Call.unitCapDeath(self());
team.data().updateCount(type, -1);
}
if(state.rules.unitAmmo && ammo < type.ammoCapacity - 0.0001f){
resupplyTime += Time.delta;
@@ -336,7 +360,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
vel().add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta));
velAddNet(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta));
}
}
}
@@ -367,7 +391,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
elevation -= type.fallSpeed * Time.delta;
if(isGrounded()){
destroy();
Call.unitDestroy(id);
}
}
@@ -414,7 +438,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return a preview icon for this unit. */
public TextureRegion icon(){
return type.icon(Cicon.full);
return type.fullIcon;
}
/** Actually destroys the unit, removing it and creating explosions. **/

View File

@@ -1,5 +1,6 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -20,7 +21,11 @@ abstract class VelComp implements Posc{
@MethodPriority(-1)
@Override
public void update(){
float px = x, py = y;
move(vel.x * Time.delta, vel.y * Time.delta);
if(Mathf.equal(px, x)) vel.x = 0;
if(Mathf.equal(py, y)) vel.y = 0;
vel.scl(Math.max(1f - drag * Time.delta, 0));
}
@@ -45,6 +50,10 @@ abstract class VelComp implements Posc{
return !vel.isZero(0.01f);
}
void move(Vec2 v){
move(v.x, v.y);
}
void move(float cx, float cy){
SolidPred check = solidity();
@@ -55,4 +64,20 @@ abstract class VelComp implements Posc{
y += cy;
}
}
void velAddNet(Vec2 v){
vel.add(v);
if(isRemote()){
x += v.x;
y += v.y;
}
}
void velAddNet(float vx, float vy){
vel.add(vx, vy);
if(isRemote()){
x += vx;
y += vy;
}
}
}

View File

@@ -1,28 +1,18 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Import float x, y, rotation, reloadMultiplier;
@Import float x, y;
@Import boolean disarmed;
@Import Vec2 vel;
@Import UnitType type;
/** temporary weapon sequence number */
static int sequenceNum = 0;
/** weapon mount array, never null */
@SyncLocal WeaponMount[] mounts = {};
@ReadOnly transient boolean isRotate;
@@ -43,7 +33,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i));
mounts[i] = def.weapons.get(i).mountType.get(def.weapons.get(i));
}
}
@@ -53,8 +43,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
void controlWeapons(boolean rotate, boolean shoot){
for(WeaponMount mount : mounts){
mount.rotate = rotate;
mount.shoot = shoot;
if(mount.weapon.controllable){
mount.rotate = rotate;
mount.shoot = shoot;
}
}
isRotate = rotate;
isShooting = shoot;
@@ -73,8 +65,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
y = Tmp.v1.y + this.y;
for(WeaponMount mount : mounts){
mount.aimX = x;
mount.aimY = y;
if(mount.weapon.controllable){
mount.aimX = x;
mount.aimY = y;
}
}
aimX = x;
@@ -88,7 +82,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Override
public void remove(){
for(WeaponMount mount : mounts){
if(mount.bullet != null){
if(mount.bullet != null && mount.bullet.owner == self()){
mount.bullet.time = mount.bullet.lifetime - 10f;
mount.bullet = null;
}
@@ -102,136 +96,8 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
/** Update shooting and rotation for this unit. */
@Override
public void update(){
boolean can = canShoot();
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
float weaponRotation = this.rotation - 90 + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
//update continuous state
if(weapon.continuous && mount.bullet != null){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime || mount.bullet.type != weapon.bullet){
mount.bullet = null;
}else{
mount.bullet.rotation(weaponRotation + 90);
mount.bullet.set(shootX, shootY);
mount.reload = weapon.reload;
vel.add(Tmp.v1.trns(rotation + 180f, mount.bullet.type.recoil));
if(weapon.shootSound != Sounds.none && !headless){
if(mount.sound == null) mount.sound = new SoundLoop(weapon.shootSound, 1f);
mount.sound.update(x, y, true);
}
}
}else{
//heat decreases when not firing
mount.heat = Math.max(mount.heat - Time.delta * reloadMultiplier / mount.weapon.cooldownTime, 0);
if(mount.sound != null){
mount.sound.update(x, y, false);
}
}
//flip weapon shoot side for alternating weapons at half reload
if(weapon.otherSide != -1 && weapon.alternate && mount.side == weapon.flipSprite &&
mount.reload + Time.delta * reloadMultiplier > weapon.reload/2f && mount.reload <= weapon.reload/2f){
mounts[weapon.otherSide].side = !mounts[weapon.otherSide].side;
mount.side = !mount.side;
}
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
float axisX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - this.rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else if(!weapon.rotate){
mount.rotation = 0;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
//shoot if applicable
if(mount.shoot && //must be shooting
can && //must be able to shoot
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
(!weapon.alternate || mount.side == weapon.flipSprite) &&
//TODO checking for velocity this way isn't entirely correct
(vel.len() >= mount.weapon.minShootVelocity || (net.active() && !isLocal())) && //check velocity requirements
mount.reload <= 0.0001f && //reload has to be 0
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
){
shoot(mount, shootX, shootY, mount.aimX, mount.aimY, mountX, mountY, shootAngle, Mathf.sign(weapon.x));
mount.reload = weapon.reload;
ammo--;
if(ammo < 0) ammo = 0;
}
mount.weapon.update(self(), mount);
}
}
private void shoot(WeaponMount mount, float x, float y, float aimX, float aimY, float mountX, float mountY, float rotation, int side){
Weapon weapon = mount.weapon;
float baseX = this.x, baseY = this.y;
boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f;
(delay ? weapon.chargeSound : weapon.continuous ? Sounds.none : weapon.shootSound).at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
BulletType ammo = weapon.bullet;
float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(x, y, aimX, aimY) / ammo.range()) : 1f;
sequenceNum = 0;
if(delay){
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
Time.run(sequenceNum * weapon.shotDelay + weapon.firstShotDelay, () -> {
if(!isAdded()) return;
mount.bullet = bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl);
});
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> mount.bullet = bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
}
boolean parentize = ammo.keepVelocity;
if(delay){
Time.run(weapon.firstShotDelay, () -> {
if(!isAdded()) return;
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
if(!weapon.continuous){
weapon.shootSound.at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
}
});
}else{
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
}
weapon.ejectEffect.at(mountX, mountY, rotation * side);
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
apply(weapon.shootStatus, weapon.shootStatusDuration);
}
private Bullet bullet(Weapon weapon, float x, float y, float angle, float lifescl){
float xr = Mathf.range(weapon.xRand);
return weapon.bullet.create(this, team(),
x + Angles.trnsx(angle, 0, xr),
y + Angles.trnsy(angle, 0, xr),
angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
}
}

View File

@@ -0,0 +1,47 @@
package mindustry.entities.effect;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.entities.*;
import mindustry.graphics.*;
import static arc.graphics.g2d.Draw.*;
import static arc.graphics.g2d.Lines.*;
import static arc.math.Angles.*;
public class ExplosionEffect extends Effect{
public Color waveColor = Pal.missileYellow, smokeColor = Color.gray, sparkColor = Pal.missileYellowBack;
public float waveLife = 6f, waveStroke = 3f, waveRad = 15f, waveRadBase = 2f, sparkStroke = 1f, sparkRad = 23f, sparkLen = 3f, smokeSize = 4f, smokeSizeBase = 0.5f, smokeRad = 23f;
public int smokes = 5, sparks = 4;
public ExplosionEffect(){
clip = 100f;
lifetime = 22;
renderer = e -> {
color(waveColor);
e.scaled(waveLife, i -> {
stroke(waveStroke * i.fout());
Lines.circle(e.x, e.y, waveRadBase + i.fin() * waveRad);
});
color(smokeColor);
if(smokeSize > 0){
randLenVectors(e.id, smokes, 2f + smokeRad * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * smokeSize + smokeSizeBase);
});
}
color(sparkColor);
stroke(e.fout() * sparkStroke);
randLenVectors(e.id + 1, sparks, 1f + sparkRad * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * sparkLen);
Drawf.light(e.x + x, e.y + y, e.fout() * sparkLen * 4f, sparkColor, 0.7f);
});
};
}
}

View File

@@ -6,17 +6,28 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.graphics.*;
/** The most essential effect class. Can create particles in various shapes. */
public class ParticleEffect extends Effect{
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
public int particles = 6;
public float cone = 180f, length = 20f, baseLength = 0f;
/** Particle size/length/radius interpolation. */
public Interp interp = Interp.linear;
public float offsetX, offsetY;
public float lightScl = 2f, lightOpacity = 0.6f;
public @Nullable Color lightColor;
//region only
/** Spin in degrees per tick. */
public float spin = 0f;
/** Controls the initial and final sprite sizes. */
public float sizeFrom = 2f, sizeTo = 0f;
/** Rotation offset. */
public float offset = 0;
/** Sprite to draw. */
public String region = "circle";
//line only
@@ -37,19 +48,23 @@ public class ParticleEffect extends Effect{
float rawfin = e.fin();
float fin = e.fin(interp);
float rad = interp.apply(sizeFrom, sizeTo, rawfin) * 2;
float ox = e.x + Angles.trnsx(e.rotation, offsetX, offsetY), oy = e.y + Angles.trnsy(e.rotation, offsetX, offsetY);
Draw.color(colorFrom, colorTo, fin);
Color lightColor = this.lightColor == null ? Draw.getColor() : this.lightColor;
if(line){
Lines.stroke(interp.apply(strokeFrom, strokeTo, rawfin));
float len = interp.apply(lenFrom, lenTo, rawfin);
Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> {
Lines.lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), len);
Lines.lineAngle(ox + x, oy + y, Mathf.angle(x, y), len);
Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity);
});
}else{
Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> {
Draw.rect(tex, e.x + x, e.y + y, rad, rad, e.rotation + offset);
Draw.rect(tex, ox + x, oy + y, rad, rad, e.rotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity);
});
}
}

View File

@@ -3,16 +3,21 @@ package mindustry.entities.effect;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.graphics.*;
/** Effect that renders a basic shockwave. */
public class WaveEffect extends Effect{
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
public float sizeFrom = 0f, sizeTo = 100f;
public @Nullable Color lightColor;
public float sizeFrom = 0f, sizeTo = 100f, lightScl = 3f, lightOpacity = 0.8f;
public int sides = -1;
public float rotation = 0f;
public float strokeFrom = 2f, strokeTo = 0f;
public Interp interp = Interp.linear;
public Interp lightInterp = Interp.reverse;
public float offsetX, offsetY;
@Override
public void init(){
@@ -23,11 +28,14 @@ public class WaveEffect extends Effect{
public void render(EffectContainer e){
float fin = e.fin();
float ifin = e.fin(interp);
float ox = e.x + Angles.trnsx(e.rotation, offsetX, offsetY), oy = e.y + Angles.trnsy(e.rotation, offsetX, offsetY);
Draw.color(colorFrom, colorTo, ifin);
Lines.stroke(interp.apply(strokeFrom, strokeTo, fin));
float rad = interp.apply(sizeFrom, sizeTo, fin);
Lines.poly(e.x, e.y, sides <= 0 ? Lines.circleVertices(rad) : sides, rad, rotation + e.rotation);
Lines.poly(ox, oy, sides <= 0 ? Lines.circleVertices(rad) : sides, rad, rotation + e.rotation);
Drawf.light(ox, oy, rad * lightScl, lightColor == null ? Draw.getColor() : lightColor, lightOpacity * e.fin(lightInterp));
}
}

View File

@@ -23,8 +23,6 @@ public class AIController implements UnitController{
/** main target that is being faced */
protected Teamc target;
/** targets for each weapon */
protected Teamc[] targets = {};
{
timer.reset(0, Mathf.random(40f));
@@ -94,8 +92,6 @@ public class AIController implements UnitController{
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
float rotation = unit.rotation - 90;
boolean ret = retarget();
@@ -109,39 +105,39 @@ public class AIController implements UnitController{
unit.isShooting = false;
for(int i = 0; i < targets.length; i++){
WeaponMount mount = unit.mounts[i];
for(var mount : unit.mounts){
Weapon weapon = mount.weapon;
//let uncontrollable weapons do their own thing
if(!weapon.controllable) continue;
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
if(unit.type.singleTarget){
targets[i] = target;
mount.target = target;
}else{
if(ret){
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
mount.target = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
}
if(checkTarget(targets[i], mountX, mountY, weapon.bullet.range())){
targets[i] = null;
if(checkTarget(mount.target, mountX, mountY, weapon.bullet.range())){
mount.target = null;
}
}
boolean shoot = false;
if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range()) && shouldShoot();
if(mount.target != null){
shoot = mount.target.within(mountX, mountY, weapon.bullet.range() + (mount.target instanceof Sized s ? s.hitSize()/2f : 0f)) && shouldShoot();
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
Vec2 to = Predict.intercept(unit, mount.target, weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
mount.shoot = shoot;
mount.rotate = shoot;
unit.isShooting |= (mount.shoot = mount.rotate = shoot);
unit.isShooting |= shoot;
if(shoot){
unit.aimX = mount.aimX;
unit.aimY = mount.aimY;

View File

@@ -3,8 +3,6 @@ package mindustry.entities.units;
import mindustry.type.*;
public class StatusEntry{
public static final StatusEntry tmp = new StatusEntry();
public StatusEffect effect;
public float time;

View File

@@ -28,6 +28,10 @@ public class WeaponMount{
public @Nullable Bullet bullet;
/** sound loop for continuous weapons */
public @Nullable SoundLoop sound;
/** current target; used for autonomous weapons and AI */
public @Nullable Teamc target;
/** retarget counter */
public float retarget = 0f;
public WeaponMount(Weapon weapon){
this.weapon = weapon;