Add lifeRnd and extraLife for Weapons and more (#11477)

* lifeRnd and extraLife

like velocityRnd and extraVelocity but with bullet lifetimes. random is applied before the scaling with scaleLife.

* Update contributors

* particle aspect ratio system

* fix bug

* seperate fin and color fin

* Update ParticleEffect.java

* New Triangle effect 

i'm trying my best bro

* ok i found the bug and i fixed it

* bug squashed 2

* addede extraLife and lifeRnd, and extraVelocity for turrets

* Okay okay, one last change.

---------

Co-authored-by: Anuken <arnukren@gmail.com>
This commit is contained in:
Rylid-Umil
2026-08-27 12:34:23 -04:00
committed by GitHub
co-authored by Anuken
parent e488a79096
commit b05d9a11f1
5 changed files with 121 additions and 10 deletions
+3 -2
View File
@@ -194,8 +194,9 @@ Jovinull
Alon
硫缺铅/PyratiteNoLead
Space
DeterMination
rylid-umil
NeilGreenFly
DeterMination
Sunky.MP3G
jhonnata.andre
Zaxerf1234
Zaxerf1234
@@ -14,8 +14,11 @@ public class ParticleEffect extends Effect{
private static final Rand rand = new Rand();
private static final Vec2 rv = new Vec2();
/** Particle color. */
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
/** Number of particles created. */
public int particles = 6;
/** If true, the particle's length is set in a random range from 0 to length. */
public boolean randLength = true;
/** Gives the effect flipping compatability like casing effects. */
public boolean casingFlip;
@@ -24,8 +27,13 @@ public class ParticleEffect extends Effect{
public Interp interp = Interp.linear;
/** Particle size interpolation. Null to use interp. */
public @Nullable Interp sizeInterp = null;
/** Particle color interpolation. Null to use interp. */
public @Nullable Interp colorInterp = null;
/** Offset position of the particle. */
public float offsetX, offsetY;
/** Particle light properties. */
public float lightScl = 2f, lightOpacity = 0.6f;
/** Color of the light each particle emits. */
public @Nullable Color lightColor;
//region only
@@ -36,12 +44,22 @@ public class ParticleEffect extends Effect{
public float sizeFrom = 2f, sizeTo = 0f;
/** Controls the amount of ticks the effect waits before changing size. */
public float sizeChangeStart = 0f;
/** Controls the amount of ticks the effect waits before changing width. */
public float widthChangeStart = 0f;
/** Controls the amount of ticks the effect waits before changing height. */
public float heightChangeStart = 0f;
/** Whether the rotation adds with the parent */
public boolean useRotation = true;
/** Rotation offset. */
public float offset = 0;
/** Sprite to draw. */
public String region = "circle";
/** Particle width and height properties as a ratio of its radius. Does nothing to line particles. */
public float widthFrom = 1f, widthTo = 1f, heightFrom = 1f, heightTo = 1f;
/** Particle width interpolation. Null to use sizeInterp. */
public @Nullable Interp widthInterp = null;
/** Particle height interpolation. Null to use sizeInterp. */
public @Nullable Interp heightInterp = null;
//line only
public boolean line;
@@ -55,6 +73,9 @@ public class ParticleEffect extends Effect{
clip = Math.max(clip, length + Math.max(sizeFrom, sizeTo));
sizeChangeStart = Mathf.clamp(sizeChangeStart, 0f, lifetime);
if(sizeInterp == null) sizeInterp = interp;
if(widthInterp == null) widthInterp = sizeInterp;
if(heightInterp == null) heightInterp = sizeInterp;
if(colorInterp == null) colorInterp = interp;
}
@Override
@@ -65,10 +86,13 @@ public class ParticleEffect extends Effect{
int flip = casingFlip ? -Mathf.sign(e.rotation) : 1;
float rawfin = e.fin();
float fin = e.fin(interp);
float colFin = e.fin(colorInterp);
float rad = sizeInterp.apply(sizeFrom, sizeTo, Mathf.curve(rawfin, sizeChangeStart / lifetime, 1f)) * 2;
float width = rad * (widthInterp.apply(widthFrom, widthTo, Mathf.curve(rawfin, widthChangeStart / lifetime, 1f)) * 2);
float height = rad * (heightInterp.apply(heightFrom, heightTo, Mathf.curve(rawfin, heightChangeStart / lifetime, 1f)) * 2);
float ox = e.x + Angles.trnsx(realRotation, offsetX * flip, offsetY), oy = e.y + Angles.trnsy(realRotation, offsetX * flip, offsetY);
Draw.color(colorFrom, colorTo, fin);
Draw.color(colorFrom, colorTo, colFin);
Color lightColor = this.lightColor == null ? Draw.getColor() : this.lightColor;
if(line){
@@ -91,7 +115,7 @@ public class ParticleEffect extends Effect{
rv.trns(realRotation + rand.range(cone), !randLength ? l : rand.random(l));
float x = rv.x, y = rv.y;
Draw.rect(tex, ox + x, oy + y, rad, rad / tex.ratio(), realRotation + offset + e.time * spin);
Draw.rect(tex, ox + x, oy + y, width, height / tex.ratio(), realRotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity * Draw.getColorAlpha());
}
}
@@ -0,0 +1,72 @@
package mindustry.entities.effect;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.graphics.*;
/** Creates a triangle with heavily customizable parameters. */
public class TriangleEffect extends Effect{
private static final Rand rand = new Rand();
/** Triangle color. */
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
/** Gives the effect flipping compatability like casing effects. */
public boolean flippable;
/** Triangle position interpolation. Also used as a fallback.*/
public Interp interp = Interp.linear;
/** Width interpolation. Null to use interp. */
public @Nullable Interp widthInterp = null;
/** Height interpolation. Null to use interp. */
public @Nullable Interp heightInterp = null;
/** Triangle color interpolation. Null to use interp. */
public @Nullable Interp colorInterp = null;
/** Controls the starting and ending positions of the triangle. */
public float startX = 0, startY = 0, endX = 0, endY = 0;
/** Triangle light properties. */
public float lightScl = 8f, lightOpacityFrom = 0.6f, lightOpacityTo = 0f;
/** Color of the light the triangle emits. */
public @Nullable Color lightColor;
/** Controls the initial and final positions of each point on the triangle. */
public float widthFrom = 4f, widthTo = 0f, heightFrom = 4f, heightTo = 4f;
/** Whether the rotation adds with the parent */
public boolean useRotation = true;
/** Spin in degrees/tick. */
public int spin = 0;
/** Rotation offset. */
public float offset = 0;
@Override
public void init(){
if(widthInterp == null) widthInterp = interp;
if(heightInterp == null) heightInterp = interp;
if(colorInterp == null) colorInterp = interp;
}
@Override
public void render(EffectContainer e){
float realRotation = (useRotation ? (flippable ? Math.abs(e.rotation) : e.rotation) : baseRotation);
int flip = flippable ? -Mathf.sign(e.rotation) : 1;
float rawfin = e.fin();
float colFin = e.fin(interp);
float width = widthInterp.apply(widthFrom, widthTo, Mathf.curve(rawfin, 0, 1f));
float height = heightInterp.apply(heightFrom, heightTo, Mathf.curve(rawfin, 0, 1f));
float lightOpac = colorInterp.apply(lightOpacityFrom, lightOpacityTo, Mathf.curve(rawfin, 0, 1f));
float cx = interp.apply(startX, endX, Mathf.curve(rawfin, 0, 1f));
float cy = interp.apply(startY, endY, Mathf.curve(rawfin, 0, 1f));
Vec2 pos = new Vec2(cx, cy);
pos.rotate(realRotation);
Draw.color(colorFrom, colorTo, colFin);
Color lightColor = this.lightColor == null ? Draw.getColor() : this.lightColor;
Drawf.tri(pos.x + e.x, pos.y + e.y, width, height, realRotation + offset + e.time * spin);
Drawf.light(pos.x + e.x, pos.y + e.y, lightScl, lightColor, lightOpac * Draw.getColorAlpha());
}
}
+10 -3
View File
@@ -101,6 +101,10 @@ public class Weapon implements Cloneable{
public float velocityRnd = 0f;
/** extra velocity that is added as a fraction */
public float extraVelocity = 0f;
/** fraction of lifetime that is random */
public float lifeRnd = 0f;
/** extra lifetime that is added as a fraction */
public float extraLife = 0f;
/** The half-radius of the cone in which shooting will start. */
public float shootCone = 5f;
/** Cone in which the weapon can rotate relative to its mount. */
@@ -117,8 +121,10 @@ public class Weapon implements Cloneable{
public boolean ignoreRotation = false;
/** If true, this weapon cannot be used to attack targets. */
public boolean noAttack = false;
/** min velocity required for this weapon to shoot */
/** min velocity required for this weapon to shoot. -1 to disable the limit. */
public float minShootVelocity = -1f;
/** max velocity at which this weapon to shoot. -1 to disable the limit. */
public float maxShootVelocity = -1f;
/** should the shoot effects follow the unit (effects need followParent set to true for this to work) */
public boolean parentizeEffects;
/** internal value used for alternation - do not change! */
@@ -434,7 +440,7 @@ public class Weapon implements Cloneable{
!(bullet.killShooter && mount.totalShots > 0) && //if the bullet kills the shooter, you should only ever be able to shoot once
(!alternate || wasFlipped == flipSprite) &&
mount.warmup >= minWarmup && //must be warmed up
velLen >= minShootVelocity && //check velocity requirements
velLen >= minShootVelocity && (maxShootVelocity == -1 || velLen <= maxShootVelocity) && //check velocity requirements
(mount.reload <= 0.0001f || (alwaysContinuous && mount.bullet == null)) && //reload has to be 0, or it has to be an always-continuous weapon
(alwaysShooting || Angles.within(rotate ? mount.rotation : unit.rotation + baseRotation, mount.targetRotation, shootCone)) //has to be within the cone
){
@@ -497,7 +503,8 @@ public class Weapon implements Cloneable{
bulletX = mountX + Angles.trnsx(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset + ySpread),
bulletY = mountY + Angles.trnsy(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset + ySpread),
shootAngle = bulletRotation(unit, mount, bulletX, bulletY) + angleOffset,
lifeScl = bullet.scaleLife ? Mathf.clamp(Mathf.dst(bulletX, bulletY, mount.aimX, mount.aimY) / bullet.range) : 1f,
baseLife = (1f - lifeRnd) + Mathf.random(lifeRnd) + extraLife,
lifeScl = bullet.scaleLife ? baseLife * Mathf.clamp(Mathf.dst(bulletX, bulletY, mount.aimX, mount.aimY) / bullet.range) : baseLife,
angle = shootAngle + Mathf.range(inaccuracy + bullet.inaccuracy);
Entityc shooter = unit.controller() instanceof MissileAI ai ? ai.shooter : unit; //Pass the missile's shooter down to its bullets
@@ -58,6 +58,12 @@ public class Turret extends ReloadTurret{
public float inaccuracy = 0f;
/** Fraction of bullet velocity that is random. */
public float velocityRnd = 0f;
/** extra velocity that is added as a fraction */
public float extraVelocity = 0f;
/** fraction of lifetime that is random */
public float lifeRnd = 0f;
/** extra lifetime that is added as a fraction */
public float extraLife = 0f;
/** Fraction of lifetime that is added to bullets with lifeScale. */
public float scaleLifetimeOffset = 0f;
/** Maximum angle difference in degrees at which turret will still try to shoot. */
@@ -785,11 +791,12 @@ public class Turret extends ReloadTurret{
bulletX = x + Angles.trnsx(rotation - 90, shootX + xOffset + xSpread, shootY + yOffset),
bulletY = y + Angles.trnsy(rotation - 90, shootX + xOffset + xSpread, shootY + yOffset),
shootAngle = rotation + angleOffset + Mathf.range(inaccuracy + type.inaccuracy);
float lifeScl = type.scaleLife ? Mathf.clamp((1 + scaleLifetimeOffset) * Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y) / type.range, minRange() / type.range, range() / type.range) : 1f;
float baseLife = (1f - lifeRnd) + Mathf.random(lifeRnd) + extraLife,
lifeScl = type.scaleLife ? Mathf.clamp((baseLife + scaleLifetimeOffset) * Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y) / type.range, minRange() / type.range, range() / type.range) : baseLife;
//TODO aimX / aimY for multi shot turrets?
handleBullet(type.create(this, team, bulletX, bulletY, shootAngle, -1f, (1f - velocityRnd) + Mathf.random(velocityRnd), lifeScl, null, mover, targetPos.x, targetPos.y), xOffset, yOffset, shootAngle - rotation);
handleBullet(type.create(this, team, bulletX, bulletY, shootAngle, -1f, (1f - velocityRnd) + Mathf.random(velocityRnd) + extraVelocity, lifeScl, null, mover, targetPos.x, targetPos.y), xOffset, yOffset, shootAngle - rotation);
(shootEffect == null ? type.shootEffect : shootEffect).at(bulletX, bulletY, rotation + angleOffset, type.hitColor);
(smokeEffect == null ? type.smokeEffect : smokeEffect).at(bulletX, bulletY, rotation + angleOffset, type.hitColor);