Partial 7.0 merge - API preview
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package mindustry.entities;
|
||||
|
||||
public interface Sized{
|
||||
import arc.math.geom.*;
|
||||
|
||||
public interface Sized extends Position{
|
||||
float hitSize();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
145
core/src/mindustry/entities/abilities/EnergyFieldAbility.java
Normal file
145
core/src/mindustry/entities/abilities/EnergyFieldAbility.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
70
core/src/mindustry/entities/bullet/EmpBulletType.java
Normal file
70
core/src/mindustry/entities/bullet/EmpBulletType.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. **/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
47
core/src/mindustry/entities/effect/ExplosionEffect.java
Normal file
47
core/src/mindustry/entities/effect/ExplosionEffect.java
Normal 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);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user