Merge branch 'Anuken:master' into balancing_burst-drill-optional-multiplier

This commit is contained in:
SomeonesShade
2025-02-08 16:28:05 +08:00
committed by GitHub
191 changed files with 10028 additions and 6094 deletions
+1 -1
View File
@@ -196,7 +196,7 @@ public class Build{
if(closest != null && closest.team != team){
return false;
}
}else if(state.teams.anyEnemyCoresWithin(team, x * tilesize + type.offset, y * tilesize + type.offset, state.rules.enemyCoreBuildRadius + tilesize)){
}else if(state.teams.anyEnemyCoresWithinBuildRadius(team, x * tilesize + type.offset, y * tilesize + type.offset)){
return false;
}
}
@@ -0,0 +1,29 @@
package mindustry.world.blocks;
import arc.audio.*;
import mindustry.gen.*;
public interface LaunchAnimator{
void drawLaunch();
default void drawLaunchGlobalZ(){}
void beginLaunch(boolean launching);
void endLaunch();
void updateLaunch();
float launchDuration();
default Music landMusic(){
return Musics.land;
}
default Music launchMusic(){
return Musics.launch;
}
float zoomLaunch();
}
@@ -3,61 +3,140 @@ package mindustry.world.blocks.campaign;
import arc.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.storage.*;
import static mindustry.Vars.*;
public class Accelerator extends Block{
public @Load("launch-arrow") TextureRegion arrowRegion;
public @Load(value = "@-launch-arrow", fallback = "launch-arrow") TextureRegion arrowRegion;
public @Load("select-arrow-small") TextureRegion selectArrowRegion;
//TODO dynamic
public Block launching = Blocks.coreNucleus;
public int[] capacities = {};
/** Core block that is launched. Should match the starting core of the planet being launched to. */
public Block launchBlock = Blocks.coreNucleus;
public float powerBufferRequirement;
/** Override for planets that this block can launch to. If null, the planet's launch candidates are used. */
public @Nullable Seq<Planet> launchCandidates;
//TODO: launching needs audio!
public Music launchMusic = Musics.coreLaunch;
public float launchDuration = 120f;
public float chargeDuration = 220f;
public float buildDuration = 120f;
public Interp landZoomInterp = Interp.pow4In, chargeZoomInterp = Interp.pow4In;
public float landZoomFrom = 0.02f, landZoomTo = 4f, chargeZoomTo = 5f;
public int chargeRings = 4;
public float ringRadBase = 60f, ringRadSpacing = 25f, ringRadPow = 1.6f, ringStroke = 3f, ringSpeedup = 1.4f, chargeRingMerge = 2f, ringArrowRad = 3f;
public float ringHandleTilt = 0.8f, ringHandleLen = 30f;
public Color ringColor = Pal.accent;
public int launchLightning = 20;
public Color lightningColor = Pal.accent;
public float lightningDamage = 40;
public float lightningOffset = 24f;
public int lightningLengthMin = 5, lightningLengthMax = 25;
public double lightningLaunchChance = 0.8;
protected int[] capacities = {};
public Accelerator(String name){
super(name);
update = true;
solid = true;
hasItems = true;
hasPower = true;
itemCapacity = 8000;
configurable = true;
emitLight = true;
lightRadius = 70f;
lightColor = Pal.accent;
}
@Override
public void init(){
itemCapacity = 0;
capacities = new int[content.items().size];
for(ItemStack stack : launching.requirements){
for(ItemStack stack : launchBlock.requirements){
capacities[stack.item.id] = stack.amount;
itemCapacity += stack.amount;
}
consumeItems(launching.requirements);
consumeItems(launchBlock.requirements);
super.init();
}
@Override
public void setBars(){
super.setBars();
if(powerBufferRequirement > 0f){
addBar("powerBufferRequirement", b -> new Bar(
() -> Core.bundle.format("bar.powerbuffer",UI.formatAmount((long)b.power.graph.getBatteryStored()), UI.formatAmount((long)powerBufferRequirement)),
() -> Pal.powerBar,
() -> b.power.graph.getBatteryStored() / powerBufferRequirement
));
}
}
@Override
public boolean outputsItems(){
return false;
}
public class AcceleratorBuild extends Building{
public class AcceleratorBuild extends Building implements LaunchAnimator{
public float heat, statusLerp;
public float progress;
public float time, launchHeat;
public boolean launching;
protected float cloudSeed;
@Override
public void updateTile(){
super.updateTile();
heat = Mathf.lerpDelta(heat, efficiency, 0.05f);
heat = Mathf.lerpDelta(heat, launching ? 1f : efficiency, 0.05f);
statusLerp = Mathf.lerpDelta(statusLerp, power.status, 0.05f);
if(!launching){
time += Time.delta * efficiency;
}else{
time = Mathf.slerpDelta(time, 0f, 0.4f);
}
launchHeat = Mathf.lerpDelta(launchHeat, launching ? 1f : 0f, 0.1f);
if(efficiency >= 0f){
progress += Time.delta * efficiency / buildDuration;
progress = Math.min(progress, 1f);
}
}
@Override
public float progress(){
return progress;
}
@Override
@@ -74,53 +153,107 @@ public class Accelerator extends Block{
}
}
{
if(launching){
Draw.reset();
Draw.rect(launchBlock.fullIcon, x, y);
}else{
Drawf.shadow(x, y, launchBlock.size * tilesize * 2f, progress);
Draw.draw(Layer.blockBuilding, () -> {
Draw.color(Pal.accent, heat);
for(TextureRegion region : launchBlock.getGeneratedIcons()){
Shaders.blockbuild.region = region;
Shaders.blockbuild.time = time;
Shaders.blockbuild.progress = progress;
Draw.rect(region, x, y);
Draw.flush();
}
Draw.color();
});
}
Draw.reset();
}
if(heat < 0.0001f) return;
float rad = size * tilesize / 2f * 0.74f;
float rad = size * tilesize / 2f * 0.74f * Mathf.lerp(1f, 1.3f, launchHeat);
float scl = 2f;
Draw.z(Layer.bullet - 0.0001f);
Lines.stroke(1.75f * heat, Pal.accent);
Lines.square(x, y, rad * 1.22f, 45f);
Lines.square(x, y, rad * 1.22f, Mathf.lerp(45f, 0f, launchHeat));
//TODO: lock time when launching
Lines.stroke(3f * heat, Pal.accent);
Lines.square(x, y, rad, Time.time / scl);
Lines.square(x, y, rad, -Time.time / scl);
Lines.square(x, y, rad * Mathf.lerp(1f, 1.3f, launchHeat), 45f + time / scl);
Lines.square(x, y, rad * Mathf.lerp(1f, 1.8f, launchHeat), Mathf.lerp(45f, 0f, launchHeat) - time / scl);
Draw.color(team.color);
Draw.alpha(Mathf.clamp(heat * 3f));
for(int i = 0; i < 4; i++){
float rot = i*90f + 45f + (-Time.time /3f)%360f;
float length = 26f * heat;
float rot = i*90f + 45f + (-time/3f)%360f;
float length = 26f * heat * Mathf.lerp(1f, 1.5f, launchHeat);
Draw.rect(arrowRegion, x + Angles.trnsx(rot, length), y + Angles.trnsy(rot, length), rot + 180f);
}
Draw.reset();
}
@Override
public void drawLight(){
Drawf.light(x, y, lightRadius, lightColor, launchHeat);
}
public boolean canLaunch(){
return isValid() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching;
}
@Override
public Cursor getCursor(){
return !state.isCampaign() || efficiency <= 0f ? SystemCursor.arrow : super.getCursor();
return canLaunch() ? SystemCursor.hand : super.getCursor();
}
@Override
public void drawSelect(){
super.drawSelect();
if(power.graph.getBatteryStored() < powerBufferRequirement && !launching){
drawPlaceText(Core.bundle.get("bar.nobatterypower"), tile.x, tile.y, false);
}
}
@Override
public void buildConfiguration(Table table){
deselect();
if(!state.isCampaign() || efficiency <= 0f) return;
if(!canLaunch()) return;
ui.showInfo("This block has been removed from the tech tree as of v7, and no longer has a use.\n\nWill it ever be used for anything? Who knows.");
ui.planet.showPlanetLaunch(state.rules.sector, launchCandidates == null ? state.rules.sector.planet.launchCandidates : launchCandidates, sector -> {
if(canLaunch()){
consume();
power.graph.useBatteries(powerBufferRequirement);
progress = 0f;
if(false)
ui.planet.showPlanetLaunch(state.rules.sector, sector -> {
//TODO cutscene, etc...
renderer.showLaunch(this);
//TODO should consume resources based on destination schem
consume();
Time.runTask(launchDuration() - 6f, () -> {
//unlock right before launch
launching = false;
sector.planet.unlockedOnLand.each(UnlockableContent::unlock);
universe.clearLoadoutInfo();
universe.updateLoadout(sector.planet.generator.defaultLoadout.findCore(), sector.planet.generator.defaultLoadout);
universe.clearLoadoutInfo();
universe.updateLoadout((CoreBlock)launchBlock);
control.playSector(sector);
});
}
});
Events.fire(Trigger.acceleratorUse);
@@ -135,5 +268,349 @@ public class Accelerator extends Block{
public boolean acceptItem(Building source, Item item){
return items.get(item) < getMaximumAccepted(item);
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
write.f(progress);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
if(revision >= 1){
progress = read.f();
}
}
//launch animator stuff:
@Override
public float launchDuration(){
return launchDuration + chargeDuration;
}
@Override
public Music landMusic(){
//unused
return launchMusic;
}
@Override
public Music launchMusic(){
return launchMusic;
}
@Override
public void beginLaunch(boolean launching){
if(!launching) return;
this.launching = true;
Fx.coreLaunchConstruct.at(x, y, launchBlock.size);
cloudSeed = Mathf.random(1f);
float margin = 30f;
Image image = new Image();
image.color.a = 0f;
image.touchable = Touchable.disabled;
image.setFillParent(true);
image.actions(Actions.delay((launchDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
image.update(() -> {
image.toFront();
ui.loadfrag.toFront();
if(state.isMenu()){
image.remove();
}
});
Core.scene.add(image);
Time.run(chargeDuration, () -> {
Fx.coreLaunchConstruct.at(x, y, launchBlock.size);
Fx.launchAccelerator.at(x, y);
Effect.shake(10f, 14f, this);
for(int i = 0; i < launchLightning; i++){
float a = Mathf.random(360f);
Lightning.create(team, lightningColor, lightningDamage, x + Angles.trnsx(a, lightningOffset), y + Angles.trnsy(a, lightningOffset), a, Mathf.random(lightningLengthMin, lightningLengthMax));
}
float spacing = 12f;
for(int i = 0; i < 13; i++){
int fi = i;
Time.run(i * 1.1f, () -> {
float radius = block.size/2f + 1 + spacing * fi;
int rays = Mathf.ceil(radius * Mathf.PI * 2f / 6f);
for(int r = 0; r < rays; r++){
if(Mathf.chance(0.7f - fi * 0.02f)){
float angle = r * 360f / (float)rays;
float ox = Angles.trnsx(angle, radius), oy = Angles.trnsy(angle, radius);
Tile t = world.tileWorld(x + ox, y + oy);
if(t != null){
Fx.coreLandDust.at(t.worldx(), t.worldy(), angle + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.7f + Mathf.range(0.15f)));
}
}
}
});
}
});
}
@Override
public void endLaunch(){
launching = false;
}
@Override
public float zoomLaunch(){
float rawTime = launchDuration() - renderer.getLandTime();
Core.camera.position.set(this);
if(rawTime < chargeDuration){
float fin = rawTime / chargeDuration;
return chargeZoomInterp.apply(Scl.scl(landZoomTo), Scl.scl(chargeZoomTo), fin);
}else{
float rawFin = renderer.getLandTimeIn();
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), fin);
}
}
@Override
public void updateLaunch(){
float in = renderer.getLandTimeIn() * launchDuration();
float tsize = Mathf.sample(CoreBlock.thrusterSizes, (in + 35f) / launchDuration());
float rawFin = renderer.getLandTimeIn();
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
float chargeFout = 1f - chargeFin;
if(in > launchDuration){
if(Mathf.chanceDelta(lightningLaunchChance * Interp.pow3In.apply(chargeFout))){
float a = Mathf.random(360f);
Lightning.create(team, lightningColor, lightningDamage, x + Angles.trnsx(a, lightningOffset), y + Angles.trnsy(a, lightningOffset), a, Mathf.random(lightningLengthMin, lightningLengthMax));
}
}
}
@Override
public void drawLaunch(){
var clouds = Core.assets.get("sprites/clouds.png", Texture.class);
float rawFin = renderer.getLandTimeIn();
float rawTime = launchDuration() - renderer.getLandTime();
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
float chargeFout = 1f - chargeFin;
float cameraScl = renderer.getDisplayScale();
float fout = 1f - fin;
float scl = Scl.scl(4f) / cameraScl;
float pfin = Interp.pow3Out.apply(fin), pf = Interp.pow2In.apply(fout);
//draw particles
Draw.color(Pal.lightTrail);
Angles.randLenVectors(1, pfin, 100, 800f * scl * pfin, (ax, ay, ffin, ffout) -> {
Lines.stroke(scl * ffin * pf * 3f);
Lines.lineAngle(x + ax, y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl);
});
Draw.color();
if(rawTime >= chargeDuration){
drawLanding(fin, x, y);
}
Draw.color();
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
//accent tint indicating that the core was just constructed
if(renderer.isLaunching()){
float f = Mathf.clamp(1f - fout * 12f);
if(f > 0.001f){
Draw.mixcol(Pal.accent, f);
}
}
//draw clouds
if(state.rules.cloudColor.a > 0.0001f){
float scaling = CoreBlock.cloudScaling;
float sscl = Math.max(1f + Mathf.clamp(fin + CoreBlock.cfinOffset) * CoreBlock.cfinScl, 0f) * cameraScl;
Tmp.tr1.set(clouds);
Tmp.tr1.set(
(Core.camera.position.x - Core.camera.width/2f * sscl) / scaling,
(Core.camera.position.y - Core.camera.height/2f * sscl) / scaling,
(Core.camera.position.x + Core.camera.width/2f * sscl) / scaling,
(Core.camera.position.y + Core.camera.height/2f * sscl) / scaling);
Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed);
Draw.alpha(Mathf.sample(CoreBlock.cloudAlphas, fin + CoreBlock.calphaFinOffset) * CoreBlock.cloudAlpha);
Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a);
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, Core.camera.height);
Draw.reset();
}
}
@Override
public void drawLaunchGlobalZ(){
float rawFin = renderer.getLandTimeIn();
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
float fout = 1f - fin;
float chargeFout = 1f - chargeFin;
//fade out rings during launch.
chargeFout = Mathf.clamp(chargeFout - fout * 2f);
float
spacing = 1f / (chargeRings + chargeRingMerge);
for(int i = 0; i < chargeRings; i++){
float cfin = Mathf.clamp((chargeFout*ringSpeedup - spacing * i) / (spacing * (1f + chargeRingMerge)));
if(cfin > 0){
drawRing(ringRadBase + ringRadSpacing * Mathf.pow(i, ringRadPow), cfin);
}
}
}
protected void drawRing(float radius, float fin){
Draw.z(Layer.effect);
float fout = 1f - fin;
float rotate = Interp.pow4In.apply(fout) * 90f;
float rad = radius + 20f * Interp.pow4In.apply(fout);
Lines.stroke(ringStroke * fin, ringColor);
Draw.color(Pal.command, ringColor, fin);
//handles
for(int i = 0; i < 4; i++){
float angle = i * 90f + 45f + rotate;
Lines.beginLine();
Lines.linePoint(Tmp.v1.trns(angle - ringHandleLen, rad * ringHandleTilt).add(x, y));
Lines.linePoint(Tmp.v2.trns(angle, rad).add(x, y));
Lines.linePoint(Tmp.v3.trns(angle + ringHandleLen, rad * ringHandleTilt).add(x, y));
Lines.endLine(false);
}
Draw.scl(fin);
//selection triangles
for(int i = 0; i < 4; i++){
float angle = i * 90f + rotate;
Draw.rect(selectArrowRegion, x + Angles.trnsx(angle, rad), y + Angles.trnsy(angle, rad), angle + 180f + 45f);
//shape variant:
//Lines.poly(x + Angles.trnsx(angle, rad), y + Angles.trnsy(angle, rad), 3, ringArrowRad * fin, angle + 180f);
}
Draw.scl();
}
protected void drawLanding(float fin, float x, float y){
float rawTime = launchDuration() - renderer.getLandTime();
float fout = 1f - fin;
float scl = rawTime < chargeDuration ? 1f : Scl.scl(4f) / renderer.getDisplayScale();
float shake = 0f;
float s = launchBlock.region.width * launchBlock.region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
float rotation = Interp.pow2In.apply(fout) * 135f;
x += Mathf.range(shake);
y += Mathf.range(shake);
float thrustOpen = 0.25f;
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
float thrusterSize = Mathf.sample(CoreBlock.thrusterSizes, fin);
//when launching, thrusters stay out the entire time.
if(renderer.isLaunching()){
Interp i = Interp.pow2Out;
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
thrusterSize = i.apply(Mathf.clamp(fout*9f));
}
Draw.color(Pal.lightTrail);
//TODO spikier heat
Draw.rect("circle-shadow", x, y, s, s);
Draw.scl(scl);
//draw thruster flame
float strength = (1f + (launchBlock.size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
float offset = (launchBlock.size - 3) * 3f * scl;
for(int i = 0; i < 4; i++){
Tmp.v1.trns(i * 90 + rotation, 1f);
Tmp.v1.setLength((launchBlock.size * tilesize/2f + 1f)*scl + strength*2f + offset);
Draw.color(team.color);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
Tmp.v1.setLength((launchBlock.size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
Draw.color(Color.white);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
}
drawLandingThrusters(x, y, rotation, thrusterFrame);
Drawf.spinSprite(launchBlock.region, x, y, rotation);
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
drawLandingThrusters(x, y, rotation, thrusterFrame);
Draw.alpha(1f);
if(launchBlock.teamRegions[team.id] == launchBlock.teamRegion) Draw.color(team.color);
Drawf.spinSprite(launchBlock.teamRegions[team.id], x, y, rotation);
Draw.color();
Draw.scl();
Draw.reset();
}
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
CoreBlock core = (CoreBlock)launchBlock;
float length = core.thrusterLength * (frame - 1f) - 1f/4f;
float alpha = Draw.getColorAlpha();
//two passes for consistent lighting
for(int j = 0; j < 2; j++){
for(int i = 0; i < 4; i++){
var reg = i >= 2 ? core.thruster2 : core.thruster1;
float rot = (i * 90) + rotation % 90f;
Tmp.v1.trns(rot, length * Draw.xscl);
//second pass applies extra layer of shading
if(j == 1){
Tmp.v1.rotate(-90f);
Draw.alpha((rotation % 90f) / 90f * alpha);
rot -= 90f;
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}else{
Draw.alpha(alpha);
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}
}
}
Draw.alpha(1f);
}
}
}
@@ -0,0 +1,477 @@
package mindustry.world.blocks.campaign;
import arc.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LandingPad extends Block{
static ObjectMap<Item, Seq<LandingPadBuild>> waiting = new ObjectMap<>();
static long lastUpdateId = -1;
static{
Events.on(ResetEvent.class, e -> {
waiting.clear();
lastUpdateId = -1;
});
}
public @Load(value = "@-pod", fallback = "advanced-launch-pad-pod") TextureRegion podRegion;
public float arrivalDuration = 150f;
public float cooldownTime = 150f;
public float consumeLiquidAmount = 100f;
public Liquid consumeLiquid = Liquids.water;
public Effect landEffect = Fx.podLandShockwave;
public Effect coolingEffect = Fx.none;
public float coolingEffectChance = 0.2f;
public float liquidPad = 2f;
public Color bottomColor = Pal.darkerMetal;
public LandingPad(String name){
super(name);
hasItems = true;
hasLiquids = true;
solid = true;
update = true;
configurable = true;
acceptsItems = false;
canOverdrive = false; //overdriving can't do anything meaningful besides decrease cooldown, which is very small anyway, so don't bother
emitLight = true;
lightRadius = 90f;
config(Item.class, (LandingPadBuild build, Item item) -> {
if(!build.accessible()) return;
build.config = item;
});
configClear((LandingPadBuild build) -> {
if(!build.accessible()) return;
build.config = null;
});
}
@Override
public void init(){
consume(new ConsumeLiquid(consumeLiquid, consumeLiquidAmount){
@Override
public void build(Building build, Table table){
table.add(new ReqImage(liquid.uiIcon, () -> build.liquids.get(liquid) >= amount)).size(iconMed).top().left();
}
@Override
public float efficiency(Building build){
return build.liquids.get(consumeLiquid) >= amount ? 1f : 0f;
}
@Override
public void display(Stats stats){
stats.add(Stat.input, liquid, amount, false);
}
}).update(false);
super.init();
}
@Override
public void setBars(){
super.setBars();
addLiquidBar(consumeLiquid);
//TODO: does cooldown even need to exist?
addBar("cooldown", (LandingPadBuild entity) -> new Bar("bar.cooldown", Pal.lightOrange, () -> entity.cooldown));
}
@Override
public boolean outputsItems(){
return true;
}
@Remote(called = Loc.server)
public static void landingPadLanded(Tile tile){
if(tile == null || !(tile.build instanceof LandingPadBuild build)) return;
build.handleLanding();
}
public class LandingPadBuild extends Building{
public @Nullable Item config;
//priority collisions are possible, but should be extremely rare
public int priority = Mathf.rand.nextInt();
public float cooldown = 0f, landParticleTimer;
public float arrivingTimer = 0f;
public @Nullable Item arriving;
public float liquidRemoved;
public void handleLanding(){
if(config == null) return;
cooldown = 1f;
arriving = config;
arrivingTimer = 0f;
liquidRemoved = 0f;
if(state.isCampaign() && !isFake()){
state.rules.sector.info.importCooldownTimers.put(config, 0f);
}
}
public boolean accessible(){
//In custom games, this block can be configured by anyone except the player team; this allows for enemy builder AI to use it
return state.rules.editor || state.rules.allowEditWorldProcessors || state.isCampaign() || state.rules.infiniteResources || (team != state.rules.defaultTeam && !state.rules.pvp && team != Team.derelict);
}
public void updateTimers(){
if(state.isCampaign() && lastUpdateId != state.updateId){
lastUpdateId = state.updateId;
float[] imports = state.rules.sector.info.getImportRates(state.getPlanet());
for(Item item : content.items()){
float importedPerFrame = imports[item.id]/60f;
if(importedPerFrame > 0f){
float framesBetweenArrival = itemCapacity / importedPerFrame;
state.rules.sector.info.importCooldownTimers.increment(item, 0f, 1f / framesBetweenArrival * Time.delta);
}else{
//nothing is being imported, so reset the timer
state.rules.sector.info.importCooldownTimers.put(item, 0f);
}
}
waiting.each((item, pads) -> {
if(pads.size > 0){
pads.sort(p -> p.priority);
var first = pads.first();
var head = pads.peek();
Call.landingPadLanded(first.tile);
//swap priorities, moving this block to the end of the list (if there is only one block waiting, this does nothing)
var tmp = first.priority;
first.priority = head.priority;
head.priority = tmp;
pads.clear();
}
});
}
}
@Override
public void draw(){
if(consumeLiquid != null){
Draw.color(bottomColor);
Fill.square(x, y, size * tilesize/2f - liquidPad);
Draw.color();
LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, consumeLiquid, liquids.get(consumeLiquid) / liquidCapacity);
}
super.draw();
if(arriving != null){
float fin = Mathf.clamp(arrivingTimer), fout = 1f - fin;
float alpha = Interp.pow5Out.apply(fin);
float scale = (1f - alpha) * 1.3f + 1f;
float
cx = x,
cy = y + Interp.pow4In.apply(fout) * (100f + Mathf.randomSeedRange(id() + 2, 30f));
float rotation = fout * (90f + Mathf.randomSeedRange(id(), 50f));
Draw.z(Layer.effect + 0.001f);
Draw.color(Pal.engine);
float rad = 0.15f + Interp.pow5Out.apply(Mathf.slope(fin));
Fill.light(cx, cy, 10, 25f * (rad + scale-1f), Tmp.c2.set(Pal.engine).a(alpha), Tmp.c1.set(Pal.engine).a(0f));
Draw.alpha(alpha);
for(int i = 0; i < 4; i++){
Drawf.tri(cx, cy, 6f, 40f * (rad + scale-1f), i * 90f + rotation);
}
Draw.color();
Draw.z(Layer.weather - 1);
scale *= podRegion.scl();
float rw = podRegion.width * scale, rh = podRegion.height * scale;
Draw.alpha(alpha);
Drawf.shadow(cx, cy, size * tilesize, fin);
Draw.rect(podRegion, cx, cy, rw, rh, rotation);
Tmp.v1.trns(225f, Interp.pow3In.apply(fout) * 250f);
Draw.z(Layer.flyingUnit + 1);
Draw.color(0, 0, 0, 0.22f * alpha);
Draw.rect(podRegion, cx + Tmp.v1.x, cy + Tmp.v1.y, rw, rh, rotation);
}else if(cooldown > 0f){
Drawf.shadow(x, y, size * tilesize, cooldown);
Draw.alpha(cooldown);
Draw.mixcol(Pal.accent, 1f - cooldown);
Draw.rect(podRegion, x, y);
}
Draw.reset();
}
@Override
public void drawLight(){
Drawf.light(x, y, lightRadius, Pal.accent, Mathf.clamp(Math.max(cooldown, arrivingTimer * 1.5f)));
}
@Override
public void updateTile(){
updateTimers();
if(arriving != null){
if(!headless){ //pod particles
float fin = arrivingTimer;
float tsize = Interp.pow5Out.apply(fin);
landParticleTimer += tsize * Time.delta / 2f;
if(landParticleTimer >= 1f){
tile.getLinkedTiles(t -> {
if(Mathf.chance(0.1f)){
Fx.podLandDust.at(t.worldx(), t.worldy(), angleTo(t.worldx(), t.worldy()) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f)));
}
});
landParticleTimer = 0f;
}
}
arrivingTimer += Time.delta / arrivalDuration;
float toRemove = Math.min(consumeLiquidAmount / arrivalDuration * Time.delta, consumeLiquidAmount - liquidRemoved);
liquidRemoved += toRemove;
liquids.remove(consumeLiquid, toRemove);
if(Mathf.chanceDelta(coolingEffectChance * Interp.pow5Out.apply(arrivingTimer))){
coolingEffect.at(this);
}
if(arrivingTimer >= 1f){
//remove any leftovers to make sure it's precise
liquids.remove(consumeLiquid, consumeLiquidAmount - liquidRemoved);
landEffect.at(this);
Effect.shake(3f, 3f, this);
items.set(arriving, itemCapacity);
if(!isFake()){
state.getSector().info.handleItemImport(arriving, itemCapacity);
}
arriving = null;
arrivingTimer = 0f;
}
}
if(items.total() > 0){
dumpAccumulate(config == null || items.get(config) != items.total() ? null : config);
}
if(arriving == null){
cooldown -= delta() / cooldownTime;
cooldown = Mathf.clamp(cooldown);
}
if(config != null && (isFake() || (state.isCampaign() && !state.getPlanet().campaignRules.legacyLaunchPads))){
if(cooldown <= 0f && efficiency > 0f && items.total() == 0 && (isFake() || (state.rules.sector.info.getImportRate(state.getPlanet(), config) > 0f && state.rules.sector.info.importCooldownTimers.get(config, 0f) >= 1f))){
if(isFake()){
//there is no queue for enemy team blocks, it's all fake
Call.landingPadLanded(tile);
}else{
//queue landing for next frame
waiting.get(config, Seq::new).add(this);
}
}
}
}
/** @return whether this pad should receive items forever, essentially acting as an item source for maps. */
public boolean isFake(){
return team != state.rules.defaultTeam || !state.isCampaign();
}
@Override
public boolean canDump(Building to, Item item){
//hack: canDump is only ever called right before item offload, so count the item as "produced" before that.
produced(item);
return true;
}
@Override
public void drawSelect(){
if(config != null){
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(config.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(config.fullIcon, dx, dy, s, s);
}
}
@Override
public Cursor getCursor(){
return !accessible() ? SystemCursor.arrow : super.getCursor();
}
@Override
public boolean shouldShowConfigure(Player player){
return accessible();
}
@Override
public boolean onConfigureBuildTapped(Building other){
if(this == other || !accessible()){
deselect();
return false;
}
return super.onConfigureBuildTapped(other);
}
@Override
public void buildConfiguration(Table table){
ItemSelection.buildTable(LandingPad.this, table, content.items(), () -> config, this::configure, selectionRows, selectionColumns);
if(!net.client() && !isFake()){
table.row();
table.table(t -> {
t.background(Styles.black6);
t.button(Icon.downOpen, Styles.clearNonei, 40f, () -> {
if(config != null && state.isCampaign()){
for(Sector sector : state.getPlanet().sectors){
if(sector.hasBase() && sector != state.getSector() && sector.info.destination != state.getSector() && sector.info.hasExport(config)){
sector.info.destination = state.getSector();
sector.saveInfo();
}
}
state.getSector().info.refreshImportRates(state.getPlanet());
}
}).disabled(b -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(s -> s.hasBase() && s.info.hasExport(config) && s.info.destination != state.getSector())))
.tooltip("@sectors.redirect").get();
}).fillX().left();
}
}
@Override
public void display(Table table){
super.display(table);
if(!state.isCampaign() || net.client() || team != player.team() || isFake()) return;
table.row();
table.label(() -> {
if(!state.isCampaign() || isFake()) return "";
if(state.getPlanet().campaignRules.legacyLaunchPads){
return Core.bundle.get("landingpad.legacy.disabled");
}
if(config == null) return "";
int sources = 0;
float perSecond = 0f;
for(var s : state.getPlanet().sectors){
if(s != state.getSector() && s.hasBase() && s.info.destination == state.getSector()){
float amount = s.info.getExport(config);
if(amount > 0){
sources ++;
perSecond += s.info.getExport(config);
}
}
}
String str = Core.bundle.format("landing.sources", sources == 0 ? Core.bundle.get("none") : sources);
if(perSecond > 0){
str += "\n" + Core.bundle.format("landing.import", config.emoji(), (int)(perSecond * 60f));
}
return str;
}).pad(4).wrap().width(200f).left();
}
@Override
public boolean acceptItem(Building source, Item item){
return false;
}
@Override
public @Nullable Object config(){
return config;
}
@Override
public byte version(){
return 1;
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
config = TypeIO.readItem(read);
priority = read.i();
cooldown = read.f();
if(revision >= 1){
arriving = TypeIO.readItem(read);
arrivingTimer = read.f();
liquidRemoved = read.f();
}
}
@Override
public void write(Writes write){
super.write(write);
TypeIO.writeItem(write, config);
write.i(priority);
write.f(cooldown);
TypeIO.writeItem(write, arriving);
write.f(arrivingTimer);
write.f(liquidRemoved);
}
}
}
@@ -22,18 +22,27 @@ import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LaunchPad extends Block{
/** Time inbetween launches. */
/** Time between launches. */
public float launchTime = 1f;
public Sound launchSound = Sounds.none;
public @Load("@-light") TextureRegion lightRegion;
public @Load(value = "@-pod", fallback = "launchpod") TextureRegion podRegion;
public Color lightColor = Color.valueOf("eab678");
public boolean acceptMultipleItems = false;
public float lightStep = 1f;
public int lightSteps = 3;
public float liquidPad = 2f;
public @Nullable Liquid drawLiquid;
public Color bottomColor = Pal.darkerMetal;
public LaunchPad(String name){
super(name);
@@ -86,20 +95,23 @@ public class LaunchPad extends Block{
@Override
public void draw(){
super.draw();
if(hasLiquids && drawLiquid != null){
Draw.color(bottomColor);
Fill.square(x, y, size * tilesize/2f - liquidPad);
Draw.color();
LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, drawLiquid, liquids.get(drawLiquid) / liquidCapacity);
}
if(!state.isCampaign()) return;
super.draw();
if(lightRegion.found()){
Draw.color(lightColor);
float progress = Math.min((float)items.total() / itemCapacity, launchCounter / launchTime);
int steps = 3;
float step = 1f;
for(int i = 0; i < 4; i++){
for(int j = 0; j < steps; j++){
float alpha = Mathf.curve(progress, (float)j / steps, (j+1f) / steps);
float offset = -(j - 1f) * step;
for(int j = 0; j < lightSteps; j++){
float alpha = Mathf.curve(progress, (float)j / lightSteps, (j+1f) / lightSteps);
float offset = -(j - 1f) * lightStep;
Draw.color(Pal.metalGrayDark, lightColor, alpha);
Draw.rect(lightRegion, x + Geometry.d8edge(i).x * offset, y + Geometry.d8edge(i).y * offset, i * 90);
@@ -109,6 +121,7 @@ public class LaunchPad extends Block{
Draw.reset();
}
Drawf.shadow(x, y, size * tilesize);
Draw.rect(podRegion, x, y);
Draw.reset();
@@ -116,12 +129,11 @@ public class LaunchPad extends Block{
@Override
public boolean acceptItem(Building source, Item item){
return items.total() < itemCapacity;
return items.total() < itemCapacity && (acceptMultipleItems || items.total() == 0 || items.first() == item);
}
@Override
public void updateTile(){
if(!state.isCampaign()) return;
//increment launchCounter then launch when full and base conditions are met
if((launchCounter += edelta()) >= launchTime && items.total() >= itemCapacity){
@@ -149,7 +161,7 @@ public class LaunchPad extends Block{
table.row();
table.label(() -> {
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.getRealDestination();
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.destination;
return Core.bundle.format("launch.destination",
dest == null || !dest.hasBase() ? Core.bundle.get("sectors.nonelaunch") :
@@ -157,6 +169,11 @@ public class LaunchPad extends Block{
}).pad(4).wrap().width(200f).left();
}
@Override
public boolean shouldShowConfigure(Player player){
return state.isCampaign();
}
@Override
public void buildConfiguration(Table table){
if(!state.isCampaign() || net.client()){
@@ -167,7 +184,11 @@ public class LaunchPad extends Block{
table.button(Icon.upOpen, Styles.cleari, () -> {
ui.planet.showSelect(state.rules.sector, other -> {
if(state.isCampaign() && other.planet == state.rules.sector.planet){
var prev = state.rules.sector.info.destination;
state.rules.sector.info.destination = other;
if(prev != null){
prev.info.refreshImportRates(state.getPlanet());
}
}
});
deselect();
@@ -260,26 +281,24 @@ public class LaunchPad extends Block{
@Override
public void remove(){
if(!state.isCampaign()) return;
if(!state.isCampaign() || net.client()) return;
Sector destsec = state.rules.sector.info.getRealDestination();
Sector destsec = state.rules.sector.info.destination;
//actually launch the items upon removal
if(team() == state.rules.defaultTeam){
if(destsec != null && (destsec != state.rules.sector || net.client())){
ItemSeq dest = new ItemSeq();
if(team() == state.rules.defaultTeam && destsec != null && destsec != state.rules.sector){
ItemSeq dest = new ItemSeq();
for(ItemStack stack : stacks){
dest.add(stack);
for(ItemStack stack : stacks){
dest.add(stack);
//update export
state.rules.sector.info.handleItemExport(stack);
Events.fire(new LaunchItemEvent(stack));
}
//update export statistics
state.rules.sector.info.handleItemExport(stack);
Events.fire(new LaunchItemEvent(stack));
}
if(!net.client()){
destsec.addItems(dest);
}
if(state.getPlanet().campaignRules.legacyLaunchPads){
destsec.addItems(dest);
}
}
}
@@ -26,6 +26,7 @@ public class Door extends Wall{
public Effect openfx = Fx.dooropen;
public Effect closefx = Fx.doorclose;
public Sound doorSound = Sounds.door;
public boolean chainEffect = false;
public @Load("@-open") TextureRegion openRegion;
public Door(String name){
@@ -35,17 +36,23 @@ public class Door extends Wall{
consumesTap = true;
config(Boolean.class, (DoorBuild base, Boolean open) -> {
doorSound.at(base);
base.effect();
if(!world.isGenerating()){
doorSound.at(base);
base.effect();
}
for(DoorBuild entity : base.chained){
doorQueue.clear();
doorQueue.add(base);
for(DoorBuild entity : base.chained.isEmpty() ? doorQueue : base.chained){
//skip doors with things in them
if((Units.anyEntities(entity.tile) && !open) || entity.open == open){
continue;
}
if(chainEffect) entity.effect();
entity.open = open;
pathfinder.updateTile(entity.tile());
if(!world.isGenerating()) pathfinder.updateTile(entity.tile());
}
});
}
@@ -55,7 +55,7 @@ public class ForceProjector extends Block{
bullet.absorb();
paramEffect.at(bullet);
paramEntity.hit = 1f;
paramEntity.buildup += bullet.damage;
paramEntity.buildup += bullet.type.shieldDamage(bullet);
}
};
@@ -105,7 +105,7 @@ public class ForceProjector extends Block{
if(consItems && itemConsumer instanceof ConsumeItems coni){
stats.remove(Stat.booster);
stats.add(Stat.booster, StatValues.itemBoosters("+{0} " + StatUnit.shieldHealth.localized(), stats.timePeriod, phaseShieldBoost, phaseRadiusBoost, coni.items, this::consumesItem));
stats.add(Stat.booster, StatValues.itemBoosters("+{0} " + StatUnit.shieldHealth.localized(), stats.timePeriod, phaseShieldBoost, phaseRadiusBoost, coni.items));
stats.add(Stat.booster, StatValues.speedBoosters("", coolantConsumption, Float.MAX_VALUE, true, this::consumesLiquid));
}
}
@@ -59,7 +59,7 @@ public class MendProjector extends Block{
stats.add(Stat.booster, StatValues.itemBoosters(
"{0}" + StatUnit.timesSpeed.localized(),
stats.timePeriod, (phaseBoost + healPercent) / healPercent, phaseRangeBoost,
cons.items, this::consumesItem)
cons.items)
);
}
}
@@ -71,7 +71,7 @@ public class OverdriveProjector extends Block{
if(hasBoost && findConsumer(f -> f instanceof ConsumeItems) instanceof ConsumeItems items){
stats.remove(Stat.booster);
stats.add(Stat.booster, StatValues.itemBoosters("+{0}%", stats.timePeriod, speedBoostPhase * 100f, phaseRangeBoost, items.items, this::consumesItem));
stats.add(Stat.booster, StatValues.itemBoosters("+{0}%", stats.timePeriod, speedBoostPhase * 100f, phaseRangeBoost, items.items));
}
}
@@ -97,7 +97,7 @@ public class RegenProjector extends Block{
stats.add(Stat.booster, StatValues.itemBoosters(
"{0}" + StatUnit.timesSpeed.localized(),
stats.timePeriod, optionalMultiplier, 0f,
cons.items, this::consumesItem)
cons.items)
);
}
}
@@ -54,6 +54,8 @@ public class Turret extends ReloadTurret{
public float inaccuracy = 0f;
/** Fraction of bullet velocity that is random. */
public float velocityRnd = 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. */
public float shootCone = 8f;
/** Turret shoot point. */
@@ -66,10 +68,12 @@ public class Turret extends ReloadTurret{
public float minRange = 0f;
/** Minimum warmup needed to fire. */
public float minWarmup = 0f;
/** If true, this turret will accurately target moving targets with respect to charge time. */
/** If true, this turret will accurately target moving targets with respect to shoot.firstShotDelay. */
public boolean accurateDelay = true;
/** If false, this turret can't move while charging. */
public boolean moveWhileCharging = true;
/** If false, this turret can't reload while charging */
public boolean reloadWhileCharging = true;
/** How long warmup is maintained even if this turret isn't shooting. */
public float warmupMaintainTime = 0f;
/** pattern used for bullets */
@@ -157,7 +161,7 @@ public class Turret extends ReloadTurret{
super.setStats();
stats.add(Stat.inaccuracy, (int)inaccuracy, StatUnit.degrees);
stats.add(Stat.reload, 60f / (reload) * shoot.shots, StatUnit.perSecond);
stats.add(Stat.reload, 60f / (reload + (!reloadWhileCharging ? shoot.firstShotDelay : 0f)) * shoot.shots, StatUnit.perSecond);
stats.add(Stat.targetsAir, targetAir);
stats.add(Stat.targetsGround, targetGround);
if(ammoPerShot != 1) stats.add(Stat.ammoUse, ammoPerShot, StatUnit.perShot);
@@ -420,7 +424,10 @@ public class Turret extends ReloadTurret{
}
//turret always reloads regardless of whether it's targeting something
updateReload();
if(reloadWhileCharging || !charging()){
updateReload();
updateCooling();
}
if(state.rules.fog){
float newRange = hasAmmo() ? peekAmmo().rangeChange : 0f;
@@ -476,10 +483,6 @@ public class Turret extends ReloadTurret{
updateShooting();
}
}
if(coolant != null){
updateCooling();
}
}
@Override
@@ -559,14 +562,14 @@ public class Turret extends ReloadTurret{
/** @return whether the turret has ammo. */
public boolean hasAmmo(){
//used for "side-ammo" like gas in some turrets
if(!canConsume()) return false;
//skip first entry if it has less than the required amount of ammo
if(ammo.size >= 2 && ammo.peek().amount < ammoPerShot && ammo.get(ammo.size - 2).amount >= ammoPerShot){
ammo.swap(ammo.size - 1, ammo.size - 2);
}
//used for "side-ammo" like gas in some turrets
if(!canConsume()) return false;
return ammo.size > 0 && (ammo.peek().amount >= ammoPerShot || cheating());
}
@@ -640,7 +643,7 @@ public class Turret extends ReloadTurret{
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(Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y) / type.range, minRange / type.range, range() / type.range) : 1f;
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;
//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);
@@ -23,6 +23,7 @@ public class SeaBush extends Prop{
@Override
public void drawBase(Tile tile){
Draw.z(layer);
rand.setSeed(tile.pos());
float offset = rand.random(180f);
int lobes = rand.random(lobesMin, lobesMax);
@@ -23,9 +23,7 @@ public class LiquidRouter extends LiquidBlock{
public class LiquidRouterBuild extends LiquidBuild{
@Override
public void updateTile(){
if(liquids.currentAmount() > 0.01f){
dumpLiquid(liquids.current());
}
dumpLiquid(liquids.current());
}
@Override
@@ -82,6 +82,11 @@ public class BuildPayload implements Payload{
return build.block.size * tilesize;
}
@Override
public void remove(){
build.stopSound();
}
@Override
public void write(Writes write){
write.b(payloadBlock);
@@ -75,6 +75,8 @@ public interface Payload extends Position{
return y();
}
default void remove(){}
static void write(@Nullable Payload payload, Writes write){
if(payload == null){
write.bool(false);
@@ -6,6 +6,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
@@ -251,6 +252,13 @@ public class PayloadBlock extends Block{
}
}
@Override
public double sense(Content content){
if(payload instanceof UnitPayload up) return up.unit.type == content ? 1 : 0;
if(payload instanceof BuildPayload bp) return bp.build.block == content ? 1 : 0;
return super.sense(content);
}
@Override
public void write(Writes write){
super.write(write);
@@ -5,6 +5,7 @@ import arc.math.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -201,6 +202,13 @@ public class PayloadDeconstructor extends PayloadBlock{
}
}
@Override
public double sense(Content content){
if(deconstructing instanceof UnitPayload up) return up.unit.type == content ? 1 : 0;
if(deconstructing instanceof BuildPayload bp) return bp.build.block == content ? 1 : 0;
return super.sense(content);
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return progress;
@@ -118,7 +118,7 @@ public class UnitPayload implements Payload{
}
//cannot dump when there's a lot of overlap going on
if(!unit.type.flying && Units.count(unit.x, unit.y, unit.physicSize(), o -> o.isGrounded() && (o.type.allowLegStep == unit.type.allowLegStep)) > 0){
if(!unit.type.flying && Units.count(unit.x, unit.y, unit.physicSize() * 1.05f, o -> o.isGrounded() && (o.type.allowLegStep == unit.type.allowLegStep)) > 0){
return false;
}
@@ -3,6 +3,7 @@ package mindustry.world.blocks.power;
import arc.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -21,6 +22,7 @@ public class ConsumeGenerator extends PowerGenerator{
public float effectChance = 0.01f;
public Effect generateEffect = Fx.none, consumeEffect = Fx.none;
public float generateEffectRange = 3f;
public float baseLightRadius = 65f;
public @Nullable LiquidStack outputLiquid;
/** If true, this block explodes when outputLiquid exceeds capacity. */
@@ -28,6 +30,8 @@ public class ConsumeGenerator extends PowerGenerator{
public @Nullable ConsumeItemFilter filterItem;
public @Nullable ConsumeLiquidFilter filterLiquid;
/** Multiplies the itemDuration for a given item. */
public ObjectFloatMap<Item> itemDurationMultipliers = new ObjectFloatMap<>();
public ConsumeGenerator(String name){
super(name);
@@ -47,6 +51,11 @@ public class ConsumeGenerator extends PowerGenerator{
filterItem = findConsumer(c -> c instanceof ConsumeItemFilter);
filterLiquid = findConsumer(c -> c instanceof ConsumeLiquidFilter);
//pass along the duration multipliers to the consumer, so it can display them properly
if(filterItem instanceof ConsumeItemEfficiency eff){
eff.itemDurationMultipliers = itemDurationMultipliers;
}
if(outputLiquid != null){
outputsLiquid = true;
hasLiquids = true;
@@ -56,14 +65,14 @@ public class ConsumeGenerator extends PowerGenerator{
explosionPuddleLiquid = outputLiquid.liquid;
}
//TODO hardcoded
emitLight = true;
lightRadius = 65f * size;
lightRadius = baseLightRadius * size;
super.init();
}
@Override
public void setStats(){
stats.timePeriod = itemDuration;
super.setStats();
if(hasItems){
@@ -76,16 +85,18 @@ public class ConsumeGenerator extends PowerGenerator{
}
public class ConsumeGeneratorBuild extends GeneratorBuild{
public float warmup, totalTime, efficiencyMultiplier = 1f;
public float warmup, totalTime, efficiencyMultiplier = 1f, itemDurationMultiplier = 1;
@Override
public void updateEfficiencyMultiplier(){
efficiencyMultiplier = 1f;
if(filterItem != null){
float m = filterItem.efficiencyMultiplier(this);
if(m > 0) efficiencyMultiplier = m;
}else if(filterLiquid != null){
if(m > 0) efficiencyMultiplier *= m;
}
if(filterLiquid != null){
float m = filterLiquid.efficiencyMultiplier(this);
if(m > 0) efficiencyMultiplier = m;
if(m > 0) efficiencyMultiplier *= m;
}
}
@@ -103,6 +114,11 @@ public class ConsumeGenerator extends PowerGenerator{
generateEffect.at(x + Mathf.range(generateEffectRange), y + Mathf.range(generateEffectRange));
}
//make sure the multiplier doesn't change when there is nothing to consume while it's still running
if(filterItem != null && valid && itemDurationMultipliers.size > 0 && filterItem.getConsumed(this) != null){
itemDurationMultiplier = itemDurationMultipliers.get(filterItem.getConsumed(this), 1);
}
//take in items periodically
if(hasItems && valid && generateTime <= 0f){
consume();
@@ -122,7 +138,7 @@ public class ConsumeGenerator extends PowerGenerator{
}
//generation time always goes down, but only at the end so consumeTriggerValid doesn't assume fake items
generateTime -= delta() / itemDuration;
generateTime -= delta() / (itemDuration * itemDurationMultiplier);
}
@Override
@@ -62,20 +62,23 @@ public class PowerDiode extends Block{
PowerGraph frontGraph = front().power.graph;
if(backGraph == frontGraph) return;
// 0f - 1f of battery capacity in use
float backStored = backGraph.getBatteryStored() / backGraph.getTotalBatteryCapacity();
float frontStored = frontGraph.getBatteryStored() / frontGraph.getTotalBatteryCapacity();
float backStored = backGraph.getBatteryStored();
float backCapacity = backGraph.getTotalBatteryCapacity();
float frontStored = frontGraph.getBatteryStored();
float frontCapacity = frontGraph.getTotalBatteryCapacity();
// try to send if the back side has more % capacity stored than the front side
if(backStored > frontStored){
// send half of the difference
float amount = backGraph.getBatteryStored() * (backStored - frontStored) / 2;
// prevent sending more than the front can handle
amount = Mathf.clamp(amount, 0, frontGraph.getTotalBatteryCapacity() * (1 - frontStored));
if(backStored/backCapacity <= frontStored/frontCapacity) return;
backGraph.transferPower(-amount);
frontGraph.transferPower(amount);
}
float targetPercentage = (frontStored + backStored) / (frontCapacity + backCapacity);
// send half of the difference
float amount = (targetPercentage * frontCapacity - frontStored) / 2;
// prevent sending more than the front can handle
amount = Mathf.clamp(amount, 0, frontCapacity - frontStored);
backGraph.transferPower(-amount);
frontGraph.transferPower(amount);
}
}
}
}
@@ -45,6 +45,10 @@ public class BeamDrill extends Block{
/** Multipliers of drill speed for each item. Defaults to 1. */
public ObjectFloatMap<Item> drillMultipliers = new ObjectFloatMap<>();
/** Special exemption item that this drill can't mine. */
public @Nullable Item blockedItem;
/** Special exemption items that this drill can't mine. */
public @Nullable Seq<Item> blockedItems;
public Color sparkColor = Color.valueOf("fd9e81"), glowColor = Color.white;
public float glowIntensity = 0.2f, pulseIntensity = 0.07f;
@@ -76,6 +80,9 @@ public class BeamDrill extends Block{
public void init(){
updateClipRadius((range + 2) * tilesize);
super.init();
if(blockedItems == null && blockedItem != null){
blockedItems = Seq.with(blockedItem);
}
}
@Override
@@ -111,7 +118,10 @@ public class BeamDrill extends Block{
public void setStats(){
super.setStats();
stats.add(Stat.drillTier, StatValues.drillables(drillTime, 0f, size, drillMultipliers, b -> (b instanceof Floor f && f.wallOre && f.itemDrop != null && f.itemDrop.hardness <= tier) || (b instanceof StaticWall w && w.itemDrop != null && w.itemDrop.hardness <= tier)));
stats.add(Stat.drillTier, StatValues.drillables(drillTime, 0f, size, drillMultipliers, b ->
(b instanceof Floor f && f.wallOre && f.itemDrop != null && f.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(f.itemDrop))) ||
(b instanceof StaticWall w && w.itemDrop != null && w.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(w.itemDrop)))
));
stats.add(Stat.drillSpeed, 60f / drillTime * size, StatUnit.itemsSecond);
@@ -142,7 +152,7 @@ public class BeamDrill extends Block{
if(other != null && other.solid()){
Item drop = other.wallDrop();
if(drop != null){
if(drop.hardness <= tier){
if(drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
found = drop;
count++;
}else{
@@ -193,7 +203,7 @@ public class BeamDrill extends Block{
Tile other = world.tile(Tmp.p1.x + Geometry.d4x(rotation)*j, Tmp.p1.y + Geometry.d4y(rotation)*j);
if(other != null && other.solid()){
Item drop = other.wallDrop();
if(drop != null && drop.hardness <= tier){
if(drop != null && drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
return true;
}
break;
@@ -379,7 +389,7 @@ public class BeamDrill extends Block{
if(other != null){
if(other.solid()){
Item drop = other.wallDrop();
if(drop != null && drop.hardness <= tier){
if(drop != null && drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
facingAmount ++;
if(lastItem != drop && lastItem != null){
multiple = true;
@@ -40,6 +40,8 @@ public class Drill extends Block{
public float warmupSpeed = 0.015f;
/** Special exemption item that this drill can't mine. */
public @Nullable Item blockedItem;
/** Special exemption items that this drill can't mine. */
public @Nullable Seq<Item> blockedItems;
//return variables for countOre
protected @Nullable Item returnItem;
@@ -52,7 +54,7 @@ public class Drill extends Block{
/** Drill effect randomness. Block size by default. */
public float drillEffectRnd = -1f;
/** Chance of displaying the effect. Useful for extremely fast drills. */
public float drillEffectChance = 1f;
public float drillEffectChance = 0.02f;
/** Speed the drill bit rotates at. */
public float rotateSpeed = 2f;
/** Effect randomly played while drilling. */
@@ -89,6 +91,9 @@ public class Drill extends Block{
@Override
public void init(){
super.init();
if(blockedItems == null && blockedItem != null){
blockedItems = Seq.with(blockedItem);
}
if(drillEffectRnd < 0) drillEffectRnd = size;
}
@@ -155,7 +160,7 @@ public class Drill extends Block{
Draw.color();
}
}else{
Tile to = tile.getLinkedTilesAs(this, tempTiles).find(t -> t.drop() != null && (t.drop().hardness > tier || t.drop() == blockedItem));
Tile to = tile.getLinkedTilesAs(this, tempTiles).find(t -> t.drop() != null && (t.drop().hardness > tier || (blockedItems != null && blockedItems.contains(t.drop()))));
Item item = to == null ? null : to.drop();
if(item != null){
drawPlaceText(Core.bundle.get("bar.drilltierreq"), x, y, valid);
@@ -172,7 +177,7 @@ public class Drill extends Block{
super.setStats();
stats.add(Stat.drillTier, StatValues.drillables(drillTime, hardnessDrillMultiplier, size * size, drillMultipliers, b -> b instanceof Floor f && !f.wallOre && f.itemDrop != null &&
f.itemDrop.hardness <= tier && f.itemDrop != blockedItem && (indexer.isBlockPresent(f) || state.isMenu())));
f.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(f.itemDrop)) && (indexer.isBlockPresent(f) || state.isMenu())));
stats.add(Stat.drillSpeed, 60f / drillTime * size * size, StatUnit.itemsSecond);
@@ -227,7 +232,7 @@ public class Drill extends Block{
public boolean canMine(Tile tile){
if(tile == null || tile.block().isStatic()) return false;
Item drops = tile.drop();
return drops != null && drops.hardness <= tier && drops != blockedItem;
return drops != null && drops.hardness <= tier && (blockedItems == null || !blockedItems.contains(drops));
}
public class DrillBuild extends Building{
@@ -315,11 +320,14 @@ public class Drill extends Block{
}
if(dominantItems > 0 && progress >= delay && items.total() < itemCapacity){
offload(dominantItem);
int amount = (int)(progress / delay);
for(int i = 0; i < amount; i++){
offload(dominantItem);
}
progress %= delay;
if(wasVisible && Mathf.chanceDelta(updateEffectChance * warmup)) drillEffect.at(x + Mathf.range(drillEffectRnd), y + Mathf.range(drillEffectRnd), dominantItem.color);
if(wasVisible && Mathf.chanceDelta(drillEffectChance * warmup)) drillEffect.at(x + Mathf.range(drillEffectRnd), y + Mathf.range(drillEffectRnd), dominantItem.color);
}
}
@@ -40,6 +40,7 @@ public class GenericCrafter extends Block{
public Effect craftEffect = Fx.none;
public Effect updateEffect = Fx.none;
public float updateEffectChance = 0.04f;
public float updateEffectSpread = 4f;
public float warmupSpeed = 0.019f;
/** Only used for legacy cultivator blocks. */
public boolean legacyReadWarmup = false;
@@ -233,7 +234,7 @@ public class GenericCrafter extends Block{
}
if(wasVisible && Mathf.chanceDelta(updateEffectChance)){
updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4));
updateEffect.at(x + Mathf.range(size * updateEffectSpread), y + Mathf.range(size * updateEffectSpread));
}
}else{
warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed);
@@ -85,7 +85,7 @@ public class WallCrafter extends Block{
if(consItems && itemConsumer instanceof ConsumeItems coni){
stats.remove(Stat.booster);
stats.add(Stat.booster, StatValues.itemBoosters("{0}" + StatUnit.timesSpeed.localized(), stats.timePeriod, itemBoostIntensity, 0f, coni.items, i -> Structs.contains(coni.items, s -> s.item == i)));
stats.add(Stat.booster, StatValues.itemBoosters("{0}" + StatUnit.timesSpeed.localized(), stats.timePeriod, itemBoostIntensity, 0f, coni.items));
}
if(liquidBoostIntensity != 1 && findConsumer(f -> f instanceof ConsumeLiquidBase && f.booster) instanceof ConsumeLiquidBase consBase){
@@ -88,6 +88,7 @@ public class ItemSource extends Block{
while(counter >= limit){
items.set(outputItem, 1);
dump(outputItem);
produced(outputItem);
items.set(outputItem, 0);
counter -= limit;
}
@@ -27,18 +27,19 @@ import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import mindustry.world.modules.*;
import static mindustry.Vars.*;
public class CoreBlock extends StorageBlock{
protected static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f, cloudAlpha = 0.81f;
protected static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
public static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f, cloudAlpha = 0.81f;
public static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
//hacky way to pass item modules between methods
private static ItemModule nextItems;
protected static final float[] thrusterSizes = {0f, 0f, 0f, 0f, 0.3f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f};
public static final float[] thrusterSizes = {0f, 0f, 0f, 0f, 0.3f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f};
public @Load(value = "@-thruster1", fallback = "clear-effect") TextureRegion thruster1; //top right
public @Load(value = "@-thruster2", fallback = "clear-effect") TextureRegion thruster2; //bot left
@@ -230,113 +231,14 @@ public class CoreBlock extends StorageBlock{
}
}
public void drawLanding(CoreBuild build, float x, float y){
float fin = renderer.getLandTimeIn();
float fout = 1f - fin;
float scl = Scl.scl(4f) / renderer.getDisplayScale();
float shake = 0f;
float s = region.width * region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
float rotation = Interp.pow2In.apply(fout) * 135f;
x += Mathf.range(shake);
y += Mathf.range(shake);
float thrustOpen = 0.25f;
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
float thrusterSize = Mathf.sample(thrusterSizes, fin);
//when launching, thrusters stay out the entire time.
if(renderer.isLaunching()){
Interp i = Interp.pow2Out;
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
thrusterSize = i.apply(Mathf.clamp(fout*9f));
}
Draw.color(Pal.lightTrail);
//TODO spikier heat
Draw.rect("circle-shadow", x, y, s, s);
Draw.scl(scl);
//draw thruster flame
float strength = (1f + (size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
float offset = (size - 3) * 3f * scl;
for(int i = 0; i < 4; i++){
Tmp.v1.trns(i * 90 + rotation, 1f);
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*2f + offset);
Draw.color(build.team.color);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
Draw.color(Color.white);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
}
drawLandingThrusters(x, y, rotation, thrusterFrame);
Drawf.spinSprite(region, x, y, rotation);
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
drawLandingThrusters(x, y, rotation, thrusterFrame);
Draw.alpha(1f);
if(teamRegions[build.team.id] == teamRegion) Draw.color(build.team.color);
Drawf.spinSprite(teamRegions[build.team.id], x, y, rotation);
Draw.color();
Draw.scl();
Draw.reset();
}
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
float length = thrusterLength * (frame - 1f) - 1f/4f;
float alpha = Draw.getColorAlpha();
//two passes for consistent lighting
for(int j = 0; j < 2; j++){
for(int i = 0; i < 4; i++){
var reg = i >= 2 ? thruster2 : thruster1;
float rot = (i * 90) + rotation % 90f;
Tmp.v1.trns(rot, length * Draw.xscl);
//second pass applies extra layer of shading
if(j == 1){
Tmp.v1.rotate(-90f);
Draw.alpha((rotation % 90f) / 90f * alpha);
rot -= 90f;
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}else{
Draw.alpha(alpha);
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}
}
}
Draw.alpha(1f);
}
public class CoreBuild extends Building{
public class CoreBuild extends Building implements LaunchAnimator{
public int storageCapacity;
public boolean noEffect = false;
public Team lastDamage = Team.derelict;
public float iframes = -1f;
public float thrusterTime = 0f;
protected float cloudSeed;
//utility methods for less Block-to-CoreBlock casts. also allows for more customization
public float landDuration(){
return landDuration;
}
public Music landMusic(){
return landMusic;
}
public Music launchMusic(){
return launchMusic;
}
protected float cloudSeed, landParticleTimer;
@Override
public void draw(){
@@ -357,11 +259,26 @@ public class CoreBlock extends StorageBlock{
}
}
// `launchType` is null if it's landing instead of launching.
public void beginLaunch(@Nullable CoreBlock launchType){
@Override
public float launchDuration(){
return landDuration;
}
@Override
public Music landMusic(){
return landMusic;
}
@Override
public Music launchMusic(){
return launchMusic;
}
@Override
public void beginLaunch(boolean launching){
cloudSeed = Mathf.random(1f);
if(launchType != null){
Fx.coreLaunchConstruct.at(x, y, launchType.size);
if(launching){
Fx.coreLaunchConstruct.at(x, y, size);
}
if(!headless){
@@ -373,7 +290,7 @@ public class CoreBlock extends StorageBlock{
image.color.a = 0f;
image.touchable = Touchable.disabled;
image.setFillParent(true);
image.actions(Actions.delay((landDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
image.actions(Actions.delay((launchDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
image.update(() -> {
image.toFront();
ui.loadfrag.toFront();
@@ -397,7 +314,7 @@ public class CoreBlock extends StorageBlock{
});
Core.scene.add(image);
Time.run(landDuration(), () -> {
Time.run(launchDuration(), () -> {
launchEffect.at(this);
Effect.shake(5f, 5f, this);
thrusterTime = 1f;
@@ -412,9 +329,11 @@ public class CoreBlock extends StorageBlock{
}
}
@Override
public void endLaunch(){}
public void drawLanding(CoreBlock block){
@Override
public void drawLaunch(){
var clouds = Core.assets.get("sprites/clouds.png", Texture.class);
float fin = renderer.getLandTimeIn();
@@ -432,7 +351,7 @@ public class CoreBlock extends StorageBlock{
});
Draw.color();
block.drawLanding(this, x, y);
drawLanding(x, y);
Draw.color();
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
@@ -466,6 +385,92 @@ public class CoreBlock extends StorageBlock{
}
}
public void drawLanding(float x, float y){
float fin = renderer.getLandTimeIn();
float fout = 1f - fin;
float scl = Scl.scl(4f) / renderer.getDisplayScale();
float shake = 0f;
float s = region.width * region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
float rotation = Interp.pow2In.apply(fout) * 135f;
x += Mathf.range(shake);
y += Mathf.range(shake);
float thrustOpen = 0.25f;
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
float thrusterSize = Mathf.sample(thrusterSizes, fin);
//when launching, thrusters stay out the entire time.
if(renderer.isLaunching()){
Interp i = Interp.pow2Out;
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
thrusterSize = i.apply(Mathf.clamp(fout*9f));
}
Draw.color(Pal.lightTrail);
//TODO spikier heat
Draw.rect("circle-shadow", x, y, s, s);
Draw.scl(scl);
//draw thruster flame
float strength = (1f + (size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
float offset = (size - 3) * 3f * scl;
for(int i = 0; i < 4; i++){
Tmp.v1.trns(i * 90 + rotation, 1f);
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*2f + offset);
Draw.color(team.color);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
Draw.color(Color.white);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
}
drawLandingThrusters(x, y, rotation, thrusterFrame);
Drawf.spinSprite(region, x, y, rotation);
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
drawLandingThrusters(x, y, rotation, thrusterFrame);
Draw.alpha(1f);
if(teamRegions[team.id] == teamRegion) Draw.color(team.color);
Drawf.spinSprite(teamRegions[team.id], x, y, rotation);
Draw.color();
Draw.scl();
Draw.reset();
}
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
float length = thrusterLength * (frame - 1f) - 1f/4f;
float alpha = Draw.getColorAlpha();
//two passes for consistent lighting
for(int j = 0; j < 2; j++){
for(int i = 0; i < 4; i++){
var reg = i >= 2 ? thruster2 : thruster1;
float rot = (i * 90) + rotation % 90f;
Tmp.v1.trns(rot, length * Draw.xscl);
//second pass applies extra layer of shading
if(j == 1){
Tmp.v1.rotate(-90f);
Draw.alpha((rotation % 90f) / 90f * alpha);
rot -= 90f;
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}else{
Draw.alpha(alpha);
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}
}
}
Draw.alpha(1f);
}
public void drawThrusters(float frame){
float length = thrusterLength * (frame - 1f) - 1f/4f;
for(int i = 0; i < 4; i++){
@@ -545,28 +550,26 @@ public class CoreBlock extends StorageBlock{
}
/** @return Camera zoom while landing or launching. May optionally do other things such as setting camera position to itself. */
public float zoomLaunching(){
@Override
public float zoomLaunch(){
Core.camera.position.set(this);
return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), renderer.getLandTimeIn());
}
public void updateLaunching(){
updateLandParticles();
}
@Override
public void updateLaunch(){
float in = renderer.getLandTimeIn() * launchDuration();
float tsize = Mathf.sample(thrusterSizes, (in + 35f) / launchDuration());
public void updateLandParticles(){
float in = renderer.getLandTimeIn() * landDuration();
float tsize = Mathf.sample(thrusterSizes, (in + 35f) / landDuration());
renderer.setLandPTimer(renderer.getLandPTimer() + tsize * Time.delta);
if(renderer.getLandTime() >= 1f){
landParticleTimer += tsize * Time.delta;
if(landParticleTimer >= 1f){
tile.getLinkedTiles(t -> {
if(Mathf.chance(0.4f)){
Fx.coreLandDust.at(t.worldx(), t.worldy(), angleTo(t.worldx(), t.worldy()) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f)));
}
});
renderer.setLandPTimer(0f);
landParticleTimer = 0f;
}
}
@@ -1,9 +1,9 @@
package mindustry.world.consumers;
import mindustry.gen.*;
import mindustry.type.*;
/** For mods. I don't use this (yet). */
public class ConsumeItemCharged extends ConsumeItemFilter{
public class ConsumeItemCharged extends ConsumeItemEfficiency{
public float minCharge;
public ConsumeItemCharged(float minCharge){
@@ -16,8 +16,7 @@ public class ConsumeItemCharged extends ConsumeItemFilter{
}
@Override
public float efficiencyMultiplier(Building build){
var item = getConsumed(build);
return item == null ? 0f : item.charge;
public float itemEfficiencyMultiplier(Item item){
return item.charge;
}
}
@@ -0,0 +1,24 @@
package mindustry.world.consumers;
import arc.func.*;
import arc.struct.*;
import arc.util.*;
import mindustry.type.*;
import mindustry.world.meta.*;
public class ConsumeItemEfficiency extends ConsumeItemFilter{
/** This has no effect on the consumer itself, but is used for stat display. */
public @Nullable ObjectFloatMap<Item> itemDurationMultipliers;
public ConsumeItemEfficiency(Boolf<Item> item){
super(item);
}
public ConsumeItemEfficiency(){
}
@Override
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, StatValues.itemEffMultiplier(this::itemEfficiencyMultiplier, stats.timePeriod, filter, itemDurationMultipliers));
}
}
@@ -1,8 +1,8 @@
package mindustry.world.consumers;
import mindustry.gen.*;
import mindustry.type.*;
public class ConsumeItemExplosive extends ConsumeItemFilter{
public class ConsumeItemExplosive extends ConsumeItemEfficiency{
public float minExplosiveness;
public ConsumeItemExplosive(float minCharge){
@@ -15,8 +15,7 @@ public class ConsumeItemExplosive extends ConsumeItemFilter{
}
@Override
public float efficiencyMultiplier(Building build){
var item = getConsumed(build);
return item == null ? 0f : item.explosiveness;
public float itemEfficiencyMultiplier(Item item){
return item.explosiveness;
}
}
@@ -66,6 +66,16 @@ public class ConsumeItemFilter extends Consume{
@Override
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, stats.timePeriod < 0 ? StatValues.items(filter) : StatValues.items(stats.timePeriod, filter));
stats.add(booster ? Stat.booster : Stat.input, StatValues.items(stats.timePeriod, filter));
}
@Override
public float efficiencyMultiplier(Building build){
var item = getConsumed(build);
return item == null ? 0f : itemEfficiencyMultiplier(item);
}
public float itemEfficiencyMultiplier(Item item){
return 1f;
}
}
@@ -1,8 +1,8 @@
package mindustry.world.consumers;
import mindustry.gen.*;
import mindustry.type.*;
public class ConsumeItemFlammable extends ConsumeItemFilter{
public class ConsumeItemFlammable extends ConsumeItemEfficiency{
public float minFlammability;
public ConsumeItemFlammable(float minFlammability){
@@ -15,8 +15,7 @@ public class ConsumeItemFlammable extends ConsumeItemFilter{
}
@Override
public float efficiencyMultiplier(Building build){
var item = getConsumed(build);
return item == null ? 0f : item.flammability;
public float itemEfficiencyMultiplier(Item item){
return item.flammability;
}
}
@@ -1,8 +1,8 @@
package mindustry.world.consumers;
import mindustry.gen.*;
import mindustry.type.*;
public class ConsumeItemRadioactive extends ConsumeItemFilter{
public class ConsumeItemRadioactive extends ConsumeItemEfficiency{
public float minRadioactivity;
public ConsumeItemRadioactive(float minRadioactivity){
@@ -15,8 +15,7 @@ public class ConsumeItemRadioactive extends ConsumeItemFilter{
}
@Override
public float efficiencyMultiplier(Building build){
var item = getConsumed(build);
return item == null ? 0f : item.radioactivity;
public float itemEfficiencyMultiplier(Item item){
return item.radioactivity;
}
}
@@ -22,6 +22,7 @@ public class ConsumeLiquid extends ConsumeLiquidBase{
this(null, 0f);
}
@Override
public void apply(Block block){
super.apply(block);
@@ -54,6 +54,12 @@ public class ConsumeLiquidFilter extends ConsumeLiquidBase{
return liq != null ? Math.min(build.liquids.get(liq) / (amount * ed * multiplier.get(build)), 1f) : 0f;
}
@Override
public float efficiencyMultiplier(Building build){
var liq = getConsumed(build);
return liq == null ? 0 : liquidEfficiencyMultiplier(liq);
}
public @Nullable Liquid getConsumed(Building build){
if(filter.get(build.liquids.current()) && build.liquids.currentAmount() > 0){
return build.liquids.current();
@@ -79,4 +85,8 @@ public class ConsumeLiquidFilter extends ConsumeLiquidBase{
public boolean consumes(Liquid liquid){
return filter.get(liquid);
}
public float liquidEfficiencyMultiplier(Liquid liquid){
return 1f;
}
}
@@ -1,6 +1,7 @@
package mindustry.world.consumers;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.meta.*;
public class ConsumeLiquidFlammable extends ConsumeLiquidFilter{
public float minFlammability;
@@ -20,8 +21,12 @@ public class ConsumeLiquidFlammable extends ConsumeLiquidFilter{
}
@Override
public float efficiencyMultiplier(Building build){
var liq = getConsumed(build);
return liq == null ? 0f : liq.flammability;
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, StatValues.liquidEffMultiplier(l -> l.flammability, amount * 60f, filter));
}
@Override
public float liquidEfficiencyMultiplier(Liquid liquid){
return liquid.flammability;
}
}
@@ -1,12 +1,12 @@
package mindustry.world.consumers;
import arc.func.Func;
import arc.scene.ui.layout.Table;
import mindustry.Vars;
import mindustry.gen.Building;
import mindustry.type.LiquidStack;
import mindustry.ui.ReqImage;
import mindustry.world.Block;
import arc.func.*;
import arc.scene.ui.layout.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
public class ConsumeLiquidsDynamic extends Consume{
public final Func<Building, LiquidStack[]> liquids;
@@ -43,7 +43,7 @@ public class ConsumePower extends Consume{
public void display(Stats stats){
if(buffered){
stats.add(Stat.powerCapacity, capacity, StatUnit.none);
}else{
}else if(usage > 0f){
stats.add(Stat.powerUse, usage * 60f, StatUnit.powerSecond);
}
}
@@ -9,7 +9,7 @@ import mindustry.gen.*;
public class DrawArcSmelt extends DrawBlock{
public Color flameColor = Color.valueOf("f58349"), midColor = Color.valueOf("f2d585");
public float flameRad = 1f, circleSpace = 2f, flameRadiusScl = 3f, flameRadiusMag = 0.3f, circleStroke = 1.5f;
public float x = 0, y = 0;
public float alpha = 0.68f;
public int particles = 25;
public float particleLife = 40f, particleRad = 7f, particleStroke = 1.1f, particleLen = 3f;
@@ -26,10 +26,10 @@ public class DrawArcSmelt extends DrawBlock{
Draw.blend(blending);
Draw.color(midColor, a);
if(drawCenter) Fill.circle(build.x, build.y, flameRad + si);
if(drawCenter) Fill.circle(build.x + x, build.y + y, flameRad + si);
Draw.color(flameColor, a);
if(drawCenter) Lines.circle(build.x, build.y, (flameRad + circleSpace + si) * build.warmup());
if(drawCenter) Lines.circle(build.x + x, build.y + y, (flameRad + circleSpace + si) * build.warmup());
Lines.stroke(particleStroke * build.warmup());
@@ -39,7 +39,7 @@ public class DrawArcSmelt extends DrawBlock{
float fin = (rand.random(1f) + base) % 1f, fout = 1f - fin;
float angle = rand.random(360f);
float len = particleRad * Interp.pow2Out.apply(fin);
Lines.lineAngle(build.x + Angles.trnsx(angle, len), build.y + Angles.trnsy(angle, len), angle, particleLen * fout * build.warmup());
Lines.lineAngle(build.x + Angles.trnsx(angle, len) + x, build.y + Angles.trnsy(angle, len) + y, angle, particleLen * fout * build.warmup());
}
Draw.blend();
@@ -9,8 +9,7 @@ import mindustry.gen.*;
public class DrawCrucibleFlame extends DrawBlock{
public Color flameColor = Color.valueOf("f58349"), midColor = Color.valueOf("f2d585");
public float flameRad = 1f, circleSpace = 2f, flameRadiusScl = 10f, flameRadiusMag = 0.6f, circleStroke = 1.5f;
public float flameRad = 1f, circleSpace = 2f, flameRadiusScl = 10f, flameRadiusMag = 0.6f, circleStroke = 1.5f, x = 0, y = 0;
public float alpha = 0.5f;
public int particles = 30;
public float particleLife = 70f, particleRad = 7f, particleSize = 3f, fadeMargin = 0.4f, rotateScl = 1.5f;
@@ -27,10 +26,10 @@ public class DrawCrucibleFlame extends DrawBlock{
Draw.blend(Blending.additive);
Draw.color(midColor, a);
Fill.circle(build.x, build.y, flameRad + si);
Fill.circle(build.x + x, build.y + y, flameRad + si);
Draw.color(flameColor, a);
Lines.circle(build.x, build.y, (flameRad + circleSpace + si) * build.warmup());
Lines.circle(build.x + x, build.y + y, (flameRad + circleSpace + si) * build.warmup());
float base = (Time.time / particleLife);
rand.setSeed(build.id);
@@ -40,8 +39,8 @@ public class DrawCrucibleFlame extends DrawBlock{
float len = particleRad * particleInterp.apply(fout);
Draw.alpha(a * (1f - Mathf.curve(fin, 1f - fadeMargin)));
Fill.circle(
build.x + Angles.trnsx(angle, len),
build.y + Angles.trnsy(angle, len),
build.x + Angles.trnsx(angle, len) + x,
build.y + Angles.trnsy(angle, len) + y,
particleSize * fin * build.warmup()
);
}
@@ -10,24 +10,25 @@ import mindustry.gen.*;
public class DrawParticles extends DrawBlock{
public Color color = Color.valueOf("f2d585");
public int sides = 12;
public float x = 0, y = 0;
public float alpha = 0.5f;
public int particles = 30;
public float particleLife = 70f, particleRad = 7f, particleSize = 3f, fadeMargin = 0.4f, rotateScl = 3f;
public boolean reverse = false;
public float particleRotation = 0, particleLife = 70f, particleRad = 7f, particleSize = 3f, fadeMargin = 0.4f, rotateScl = 3f;
public boolean reverse = false, poly = false;
public Interp particleInterp = new PowIn(1.5f);
public Interp particleSizeInterp = Interp.slope;
public Blending blending = Blending.normal;
@Override
public void draw(Building build){
if(build.warmup() > 0f){
float a = alpha * build.warmup();
Draw.blend(blending);
Draw.color(color);
float base = (Time.time / particleLife);
float base = Time.time / particleLife;
rand.setSeed(build.id);
for(int i = 0; i < particles; i++){
float fin = (rand.random(2f) + base) % 1f;
@@ -35,12 +36,23 @@ public class DrawParticles extends DrawBlock{
float fout = 1f - fin;
float angle = rand.random(360f) + (Time.time / rotateScl) % 360f;
float len = particleRad * particleInterp.apply(fout);
Draw.alpha(a * (1f - Mathf.curve(fin, 1f - fadeMargin)));
Fill.circle(
build.x + Angles.trnsx(angle, len),
build.y + Angles.trnsy(angle, len),
particleSize * particleSizeInterp.apply(fin) * build.warmup()
);
if(poly){
Fill.poly(
build.x + x + Angles.trnsx(angle, len),
build.y + y + Angles.trnsy(angle, len),
sides,
particleSize * particleSizeInterp.apply(fin) * build.warmup(),
particleRotation
);
}else{
Fill.circle(
build.x + x + Angles.trnsx(angle, len),
build.y + y + Angles.trnsy(angle, len),
particleSize * particleSizeInterp.apply(fin) * build.warmup()
);
}
}
Draw.blend();
@@ -51,7 +51,11 @@ public class DrawRegion extends DrawBlock{
@Override
public void drawPlan(Block block, BuildPlan plan, Eachable<BuildPlan> list){
if(!drawPlan) return;
Draw.rect(region, plan.drawx(), plan.drawy(), (buildingRotate ? plan.rotation * 90f : 0));
if(spinSprite){
Drawf.spinSprite(region, plan.drawx() + x, plan.drawy() + y, (buildingRotate ? plan.rotation * 90f : 0 + rotation));
}else{
Draw.rect(region, plan.drawx()+ x, plan.drawy() + y, (buildingRotate ? plan.rotation * 90f : 0 + rotation));
}
}
@Override
@@ -13,7 +13,7 @@ public class DrawSoftParticles extends DrawBlock{
public TextureRegion region;
public Color color = Color.valueOf("e3ae6f"), color2 = Color.valueOf("d04d46");
public float x = 0, y = 0;
public float alpha = 0.5f;
public int particles = 30;
public float particleLife = 70f, particleRad = 7f, particleSize = 3f, fadeMargin = 0.4f, rotateScl = 1.5f;
@@ -43,8 +43,8 @@ public class DrawSoftParticles extends DrawBlock{
float r = particleSize * fin * build.warmup()*2f;
Draw.rect(
region,
build.x + Angles.trnsx(angle, len),
build.y + Angles.trnsy(angle, len),
build.x + x + Angles.trnsx(angle, len),
build.y + y + Angles.trnsy(angle, len),
r, r
);
}
@@ -15,6 +15,8 @@ public class BuildVisibility{
worldProcessorOnly = new BuildVisibility(() -> Vars.state.rules.editor || Vars.state.rules.allowEditWorldProcessors),
sandboxOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.infiniteResources),
campaignOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.isCampaign()),
legacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && Vars.state.getPlanet().campaignRules.legacyLaunchPads) && Blocks.advancedLaunchPad != null && Blocks.advancedLaunchPad.unlocked()),
notLegacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && !Vars.state.getPlanet().campaignRules.legacyLaunchPads)),
lightingOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()),
ammoOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.unitAmmo),
fogOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.fog || Vars.state.rules.editor);
@@ -29,6 +29,7 @@ public class StatUnit{
perMinute = new StatUnit("perMinute", false),
perShot = new StatUnit("perShot", false),
timesSpeed = new StatUnit("timesSpeed", false),
multiplier = new StatUnit("multiplier", false),
percent = new StatUnit("percent", false),
shieldHealth = new StatUnit("shieldHealth"),
none = new StatUnit("none"),
+112 -27
View File
@@ -69,8 +69,50 @@ public class StatValues{
return number(value, unit, false);
}
public static StatValue multiplierModifier(float value, StatUnit unit, boolean merge){
return table -> {
String l1 = (unit.icon == null ? "" : unit.icon + " ") + multStat(value), l2 = (unit.space ? " " : "") + unit.localized();
if(merge){
table.add(l1 + l2).left();
}else{
table.add(l1).left();
table.add(l2).left();
}
};
}
public static StatValue multiplierModifier(float value, StatUnit unit){
return multiplierModifier(value, unit, true);
}
public static StatValue multiplierModifier(float value){
return multiplierModifier(value, StatUnit.multiplier);
}
public static StatValue percentModifier(float value, StatUnit unit, boolean merge){
return table -> {
String l1 = (unit.icon == null ? "" : unit.icon + " ") + ammoStat((value - 1) * 100), l2 = (unit.space ? " " : "") + unit.localized();
if(merge){
table.add(l1 + l2).left();
}else{
table.add(l1).left();
table.add(l2).left();
}
};
}
public static StatValue percentModifier(float value, StatUnit unit){
return percentModifier(value, unit, true);
}
public static StatValue percentModifier(float value){
return percentModifier(value, StatUnit.percent);
}
public static StatValue liquid(Liquid liquid, float amount, boolean perSecond){
return table -> table.add(displayLiquid(liquid, amount, perSecond));
return table -> table.add(displayLiquid(liquid, amount, perSecond)).left();
}
public static StatValue liquids(Boolf<Liquid> filter, float amount, boolean perSecond){
@@ -150,7 +192,7 @@ public class StatValues{
t.add(Strings.autoFixed(amount, 2)).style(Styles.outlineLabel);
add(t);
}
}}).size(iconMed).padRight(3 + (amount != 0 && Strings.autoFixed(amount, 2).length() > 2 ? 8 : 0)).with(s -> withTooltip(s, liquid, false));
}}).size(iconMed).padRight(3 + (amount != 0 ? (Strings.autoFixed(amount, 2).length() - 1) * 10 : 0)).with(s -> withTooltip(s, liquid, false));
if(perSecond && amount != 0){
t.add(StatUnit.perSecond.localized()).padLeft(2).padRight(5).color(Color.lightGray).style(Styles.outlineLabel);
@@ -417,6 +459,44 @@ public class StatValues{
};
}
public static StatValue itemEffMultiplier(Floatf<Item> efficiency, float timePeriod, Boolf<Item> filter){
return itemEffMultiplier(efficiency, timePeriod, filter, null);
}
public static StatValue itemEffMultiplier(Floatf<Item> efficiency, float timePeriod, Boolf<Item> filter, @Nullable ObjectFloatMap<Item> itemDurationMultipliers){
return table -> {
table.getCells().peek().growX(); //Expand the spacer on the row above to push everything to the left
table.row();
table.table(c -> {
for(Item item : content.items().select(i -> filter.get(i) && i.unlockedNow() && !i.isHidden())){
float timeMultiplier = itemDurationMultipliers == null ? 1f : itemDurationMultipliers.get(item, 1f);
float time = 1f / (timePeriod * timeMultiplier / 60f);
c.table(Styles.grayPanel, b -> {
b.image(item.uiIcon).size(40).pad(10f).left().scaling(Scaling.fit);
b.add(item.localizedName + (timePeriod > 0 ? "\n[lightgray]" + (time < 0.01f ? Strings.fixed(time, 3) : Strings.autoFixed(time, 2)) + StatUnit.perSecond.localized() : "")).left().grow();
b.add(Core.bundle.format("stat.efficiency", fixValue(efficiency.get(item) * 100f))).right().pad(10f).padRight(15f);
}).growX().pad(5).row();
}
}).growX().colspan(table.getColumns()).row();
};
}
public static StatValue liquidEffMultiplier(Floatf<Liquid> efficiency, float amount, Boolf<Liquid> filter){
return table -> {
table.getCells().peek().growX(); //Expand the spacer on the row above to push everything to the left
table.row();
table.table(c -> {
for(Liquid liquid : content.liquids().select(l -> filter.get(l) && l.unlockedNow() && !l.isHidden())){
c.table(Styles.grayPanel, b -> {
b.add(displayLiquid(liquid, amount, true)).pad(10f).left().grow();
b.add(Core.bundle.format("stat.efficiency", fixValue(efficiency.get(liquid) * 100f))).right().pad(10f).padRight(15f);
}).growX().pad(5).row();
}
}).growX().colspan(table.getColumns()).row();
};
}
public static StatValue speedBoosters(String unit, float amount, float speed, boolean strength, Boolf<Liquid> filter){
return table -> {
table.row();
@@ -442,30 +522,28 @@ public class StatValues{
};
}
public static StatValue itemBoosters(String unit, float timePeriod, float speedBoost, float rangeBoost, ItemStack[] items, Boolf<Item> filter){
public static StatValue itemBoosters(String unit, float timePeriod, float speedBoost, float rangeBoost, ItemStack[] items){
return table -> {
table.row();
table.table(c -> {
for(Item item : content.items()){
if(!filter.get(item)) continue;
c.table(Styles.grayPanel, b -> {
c.table(Styles.grayPanel, b -> {
b.table(it -> {
for(ItemStack stack : items){
if(timePeriod < 0){
b.add(displayItem(stack.item, stack.amount, true)).pad(20f).left();
it.add(displayItem(stack.item, stack.amount, true)).pad(10f).padLeft(15f).left();
}else{
b.add(displayItem(stack.item, stack.amount, timePeriod, true)).pad(20f).left();
it.add(displayItem(stack.item, stack.amount, timePeriod, true)).pad(10f).padLeft(15f).left();
}
if(items.length > 1) b.row();
it.row();
}
}).left();
b.table(bt -> {
bt.right().defaults().padRight(3).left();
if(rangeBoost != 0) bt.add("[lightgray]+[stat]" + Strings.autoFixed(rangeBoost / tilesize, 2) + "[lightgray] " + StatUnit.blocks.localized()).row();
if(speedBoost != 0) bt.add("[lightgray]" + unit.replace("{0}", "[stat]" + Strings.autoFixed(speedBoost, 2) + "[lightgray]"));
}).right().grow().pad(10f).padRight(15f);
}).growX().pad(5).padBottom(-5).row();
}
b.table(bt -> {
bt.right().defaults().padRight(3).left();
if(rangeBoost != 0) bt.add("[lightgray]+[stat]" + Strings.autoFixed(rangeBoost / tilesize, 2) + "[lightgray] " + StatUnit.blocks.localized()).row();
if(speedBoost != 0) bt.add("[lightgray]" + unit.replace("{0}", "[stat]" + Strings.autoFixed(speedBoost, 2) + "[lightgray]"));
}).right().top().grow().pad(10f).padRight(15f);
}).growX().pad(5).padBottom(-5).row();
}).growX().colspan(table.getColumns());
table.row();
};
@@ -520,14 +598,14 @@ public class StatValues{
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map){
return ammo(map, 0, false);
return ammo(map, false, false);
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map, boolean showUnit){
return ammo(map, 0, showUnit);
return ammo(map, false, showUnit);
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map, int indent, boolean showUnit){
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map, boolean nested, boolean showUnit){
return table -> {
table.row();
@@ -536,12 +614,12 @@ public class StatValues{
orderedKeys.sort();
for(T t : orderedKeys){
boolean compact = t instanceof UnitType && !showUnit || indent > 0;
boolean compact = t instanceof UnitType && !showUnit || nested;
BulletType type = map.get(t);
if(type.spawnUnit != null && type.spawnUnit.weapons.size > 0){
ammo(ObjectMap.of(t, type.spawnUnit.weapons.first().bullet), indent, false).display(table);
ammo(ObjectMap.of(t, type.spawnUnit.weapons.first().bullet), nested, false).display(table);
continue;
}
@@ -565,14 +643,17 @@ public class StatValues{
}
if(type.buildingDamageMultiplier != 1){
int val = (int)(type.buildingDamageMultiplier * 100 - 100);
sep(bt, Core.bundle.format("bullet.buildingdamage", ammoStat(val)));
sep(bt, Core.bundle.format("bullet.buildingdamage", ammoStat((int)(type.buildingDamageMultiplier * 100 - 100))));
}
if(type.rangeChange != 0 && !compact){
sep(bt, Core.bundle.format("bullet.range", ammoStat(type.rangeChange / tilesize)));
}
if(type.shieldDamageMultiplier != 1){
sep(bt, Core.bundle.format("bullet.shielddamage", ammoStat((int)(type.shieldDamageMultiplier * 100 - 100))));
}
if(type.splashDamage > 0){
sep(bt, Core.bundle.format("bullet.splashdamage", (int)type.splashDamage, Strings.fixed(type.splashDamageRadius / tilesize, 1)));
}
@@ -643,7 +724,7 @@ public class StatValues{
bt.row();
Table ic = new Table();
ammo(ObjectMap.of(t, type.intervalBullet), indent + 1, false).display(ic);
ammo(ObjectMap.of(t, type.intervalBullet), true, false).display(ic);
Collapser coll = new Collapser(ic, true);
coll.setDuration(0.1f);
@@ -661,7 +742,7 @@ public class StatValues{
bt.row();
Table fc = new Table();
ammo(ObjectMap.of(t, type.fragBullet), indent + 1, false).display(fc);
ammo(ObjectMap.of(t, type.fragBullet), true, false).display(fc);
Collapser coll = new Collapser(fc, true);
coll.setDuration(0.1f);
@@ -674,7 +755,7 @@ public class StatValues{
bt.row();
bt.add(coll);
}
}).padLeft(indent * 5).padTop(5).padBottom(compact ? 0 : 5).growX().margin(compact ? 0 : 10);
}).padLeft(5).padTop(5).padBottom(compact ? 0 : 5).growX().margin(compact ? 0 : 10);
table.row();
}
};
@@ -691,6 +772,10 @@ public class StatValues{
return (val > 0 ? "[stat]+" : "[negstat]") + Strings.autoFixed(val, 1);
}
private static String multStat(float val){
return (val >= 1 ? "[stat]" : "[negstat]") + Strings.autoFixed(val, 2);
}
private static TextureRegion icon(UnlockableContent t){
return t.uiIcon;
}
+10
View File
@@ -32,6 +32,16 @@ public class Stats{
add(stat, StatValues.number((int)(value * 100), StatUnit.percent));
}
/** Adds a multiplicative modifier stat value. Value is assumed to be in the 0-1 range. */
public void addMultModifier(Stat stat, float value){
add(stat, StatValues.multiplierModifier(value));
}
/** Adds an percent modifier stat value. Value is assumed to be in the 0-1 range. */
public void addPercentModifier(Stat stat, float value){
add(stat, StatValues.percentModifier(value));
}
/** Adds a single y/n boolean value. */
public void add(Stat stat, boolean value){
add(stat, StatValues.bool(value));