diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index 0dc1cf9e3f..552f0ac54f 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -28,6 +28,7 @@ import mindustry.net.*; import mindustry.service.*; import mindustry.ui.dialogs.*; import mindustry.world.*; +import mindustry.world.blocks.storage.*; import mindustry.world.meta.*; import java.io.*; @@ -105,8 +106,8 @@ public class Vars implements Loadable{ public static final float invasionGracePeriod = 20; /** min armor fraction damage; e.g. 0.05 = at least 5% damage */ public static final float minArmorDamage = 0.1f; - /** land/launch animation duration */ - public static final float coreLandDuration = 160f; + /** @deprecated see {@link CoreBlock#landDuration} instead! */ + public static final @Deprecated float coreLandDuration = 160f; /** size of tiles in units */ public static final int tilesize = 8; /** size of one tile payload (^2) */ diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java index d39b338b9b..bc730c44c4 100644 --- a/core/src/mindustry/core/Control.java +++ b/core/src/mindustry/core/Control.java @@ -30,6 +30,7 @@ import mindustry.net.*; import mindustry.type.*; import mindustry.ui.dialogs.*; import mindustry.world.*; +import mindustry.world.blocks.storage.*; import mindustry.world.blocks.storage.CoreBlock.*; import java.io.*; @@ -191,43 +192,29 @@ public class Control implements ApplicationListener, Loadable{ Events.run(Trigger.newGame, () -> { var core = player.bestCore(); - if(core == null) return; camera.position.set(core); player.set(core); float coreDelay = 0f; - if(!settings.getBool("skipcoreanimation") && !state.rules.pvp){ - coreDelay = coreLandDuration; + coreDelay = core.landDuration(); //delay player respawn so animation can play. - player.deathTimer = Player.deathDelay - coreLandDuration; + player.deathTimer = Player.deathDelay - core.landDuration(); //TODO this sounds pretty bad due to conflict if(settings.getInt("musicvol") > 0){ - Musics.land.stop(); - Musics.land.play(); - Musics.land.setVolume(settings.getInt("musicvol") / 100f); + //TODO what to do if another core with different music is already playing? + Music music = core.landMusic(); + music.stop(); + music.play(); + music.setVolume(settings.getInt("musicvol") / 100f); } - app.post(() -> ui.hudfrag.showLand()); - renderer.showLanding(); - - Time.run(coreLandDuration, () -> { - Fx.launch.at(core); - Effect.shake(5f, 5f, core); - core.thrusterTime = 1f; - - if(state.isCampaign() && Vars.showSectorLandInfo && (state.rules.sector.preset == null || state.rules.sector.preset.showSectorLandInfo)){ - ui.announce("[accent]" + state.rules.sector.name() + "\n" + - (state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " + - state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5); - } - }); + renderer.showLanding(core); } if(state.isCampaign()){ - //don't run when hosting, that doesn't really work. if(state.rules.sector.planet.prebuildBase){ toBePlaced.clear(); diff --git a/core/src/mindustry/core/Renderer.java b/core/src/mindustry/core/Renderer.java index 7b7d1dc155..3eb3b169b0 100644 --- a/core/src/mindustry/core/Renderer.java +++ b/core/src/mindustry/core/Renderer.java @@ -13,7 +13,6 @@ import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import mindustry.*; -import mindustry.content.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.graphics.*; @@ -30,11 +29,6 @@ public class Renderer implements ApplicationListener{ /** These are global variables, for headless access. Cached. */ public static float laserOpacity = 0.5f, bridgeOpacity = 0.75f; - private static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f; - private static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f}; - private static final float cloudAlpha = 0.81f; - private static final Interp landInterp = Interp.pow3; - public final BlockRenderer blocks = new BlockRenderer(); public final FogRenderer fog = new FogRenderer(); public final MinimapRenderer minimap = new MinimapRenderer(); @@ -55,18 +49,15 @@ public class Renderer implements ApplicationListener{ public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12]; public TextureRegion[][] fluidFrames; + //currently landing core, null if there are no cores or it has finished landing. private @Nullable CoreBuild landCore; private @Nullable CoreBlock launchCoreType; private Color clearColor = new Color(0f, 0f, 0f, 1f); private float - //seed for cloud visuals, 0-1 - cloudSeed = 0f, //target camera scale that is lerp-ed to targetscale = Scl.scl(4), //current actual camera scale camerascale = targetscale, - //minimum camera zoom value for landing/launching; constant TODO make larger? - minZoomScl = Scl.scl(0.02f), //starts at coreLandDuration, ends at 0. if positive, core is landing. landTime, //timer for core landing particles @@ -113,10 +104,6 @@ public class Renderer implements ApplicationListener{ setupBloom(); } - Events.run(Trigger.newGame, () -> { - landCore = player.bestCore(); - }); - EnvRenderers.init(); for(int i = 0; i < bubbles.length; i++) bubbles[i] = atlas.find("bubble-" + i); for(int i = 0; i < splashes.length; i++) splashes[i] = atlas.find("splash-" + i); @@ -181,32 +168,26 @@ public class Renderer implements ApplicationListener{ enableEffects = settings.getBool("effects"); drawDisplays = !settings.getBool("hidedisplays"); drawLight = settings.getBool("drawlight", true); - pixelate = Core.settings.getBool("pixelate"); + pixelate = settings.getBool("pixelate"); + //don't bother drawing landing animation if core is null + if(landCore == null) landTime = 0f; if(landTime > 0){ - if(!state.isPaused()){ - CoreBuild build = landCore == null ? player.bestCore() : landCore; - if(build != null){ - build.updateLandParticles(); - } - } + if(!state.isPaused()) landCore.updateLaunching(); - if(!state.isPaused()){ - landTime -= Time.delta; - } - float fin = landTime / coreLandDuration; - if(!launching) fin = 1f - fin; - camerascale = landInterp.apply(minZoomScl, Scl.scl(4f), fin); weatherAlpha = 0f; + camerascale = landCore.zoomLaunching(); - //snap camera to cutscene core regardless of player input - if(landCore != null){ - camera.position.set(landCore); - } + if(!state.isPaused()) landTime -= Time.delta; }else{ weatherAlpha = Mathf.lerpDelta(weatherAlpha, 1f, 0.08f); } + if(landCore != null && landTime <= 0f){ + landCore.endLaunch(); + landCore = null; + } + camera.width = graphics.getWidth() / camerascale; camera.height = graphics.getHeight() / camerascale; @@ -304,7 +285,7 @@ public class Renderer implements ApplicationListener{ graphics.clear(clearColor); Draw.reset(); - if(Core.settings.getBool("animatedwater") || animateShields){ + if(settings.getBool("animatedwater") || animateShields){ effectBuffer.resize(graphics.getWidth(), graphics.getHeight()); } @@ -393,7 +374,10 @@ public class Renderer implements ApplicationListener{ Draw.draw(Layer.overlayUI, overlays::drawTop); if(state.rules.fog) Draw.draw(Layer.fogOfWar, fog::drawFog); - Draw.draw(Layer.space, this::drawLanding); + Draw.draw(Layer.space, () -> { + if(landCore == null || landTime <= 0f) return; + landCore.drawLanding(launching && launchCoreType != null ? launchCoreType : (CoreBlock)landCore.block); + }); Events.fire(Trigger.drawOver); blocks.drawBlocks(); @@ -481,61 +465,6 @@ public class Renderer implements ApplicationListener{ if(state.rules.customBackgroundCallback != null && customBackgrounds.containsKey(state.rules.customBackgroundCallback)){ customBackgrounds.get(state.rules.customBackgroundCallback).run(); } - - } - - void drawLanding(){ - CoreBuild build = landCore == null ? player.bestCore() : landCore; - var clouds = assets.get("sprites/clouds.png", Texture.class); - if(landTime > 0 && build != null){ - float fout = landTime / coreLandDuration; - if(launching) fout = 1f - fout; - float fin = 1f - fout; - float scl = Scl.scl(4f) / camerascale; - 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(build.x + ax, build.y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl); - }); - Draw.color(); - - CoreBlock block = launching && launchCoreType != null ? launchCoreType : (CoreBlock)build.block; - block.drawLanding(build, build.x, build.y); - - Draw.color(); - Draw.mixcol(Color.white, Interp.pow5In.apply(fout)); - - //accent tint indicating that the core was just constructed - if(launching){ - 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 = cloudScaling; - float sscl = Math.max(1f + Mathf.clamp(fin + cfinOffset)* cfinScl, 0f) * camerascale; - - Tmp.tr1.set(clouds); - Tmp.tr1.set( - (camera.position.x - camera.width/2f * sscl) / scaling, - (camera.position.y - camera.height/2f * sscl) / scaling, - (camera.position.x + camera.width/2f * sscl) / scaling, - (camera.position.y + camera.height/2f * sscl) / scaling); - - Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed); - - Draw.alpha(Mathf.sample(cloudAlphas, fin + calphaFinOffset) * cloudAlpha); - Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a); - Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height); - Draw.reset(); - } - } } public void scaleCamera(float amount){ @@ -580,6 +509,13 @@ public class Renderer implements ApplicationListener{ return landTime; } + public float getLandTimeIn(){ + if(landCore == null) return 0f; + float fin = landTime / landCore.landDuration(); + if(!launching) fin = 1f - fin; + return fin; + } + public float getLandPTimer(){ return landPTimer; } @@ -588,25 +524,37 @@ public class Renderer implements ApplicationListener{ this.landPTimer = landPTimer; } + @Deprecated public void showLanding(){ - launching = false; - camerascale = minZoomScl; - landTime = coreLandDuration; - cloudSeed = Mathf.random(1f); + var core = player.bestCore(); + if(core != null) showLanding(core); } + public void showLanding(CoreBuild landCore){ + this.landCore = landCore; + launching = false; + landTime = landCore.landDuration(); + + landCore.beginLaunch(null); + camerascale = landCore.zoomLaunching(); + } + + @Deprecated public void showLaunch(CoreBlock coreType){ - Vars.ui.hudfrag.showLaunch(); - Vars.control.input.config.hideConfig(); - Vars.control.input.inv.hide(); - launchCoreType = coreType; + var core = player.team().core(); + if(core != null) showLaunch(core, coreType); + } + + public void showLaunch(CoreBuild landCore, CoreBlock coreType){ + control.input.config.hideConfig(); + control.input.inv.hide(); + + this.landCore = landCore; launching = true; - landCore = player.team().core(); - cloudSeed = Mathf.random(1f); - landTime = coreLandDuration; - if(landCore != null){ - Fx.coreLaunchConstruct.at(landCore.x, landCore.y, coreType.size); - } + landTime = landCore.landDuration(); + launchCoreType = coreType; + + landCore.beginLaunch(coreType); } public void takeMapScreenshot(){ @@ -648,7 +596,7 @@ public class Renderer implements ApplicationListener{ Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png"); PixmapIO.writePng(file, fullPixmap); fullPixmap.dispose(); - app.post(() -> ui.showInfoFade(Core.bundle.format("screenshot", file.toString()))); + app.post(() -> ui.showInfoFade(bundle.format("screenshot", file.toString()))); }); } diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 9a230d0fc6..0345b04775 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -35,6 +35,7 @@ import mindustry.maps.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.blocks.storage.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static arc.Core.*; import static mindustry.Vars.*; @@ -1244,7 +1245,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ Events.fire(new SectorLaunchLoadoutEvent(sector, from, loadout)); - if(settings.getBool("skipcoreanimation")){ + CoreBuild core = player.team().core(); + if(core == null || settings.getBool("skipcoreanimation")){ //just... go there control.playSector(from, sector); //hide only after load screen is shown @@ -1256,9 +1258,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //allow planet dialog to finish hiding before actually launching Time.runTask(5f, () -> { Runnable doLaunch = () -> { - renderer.showLaunch(schemCore); + renderer.showLaunch(core, schemCore); //run with less delay, as the loading animation is delayed by several frames - Time.runTask(coreLandDuration - 8f, () -> control.playSector(from, sector)); + Time.runTask(core.landDuration() - 8f, () -> control.playSector(from, sector)); }; //load launchFrom sector right before launching so animation is correct @@ -1276,7 +1278,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ }else if(mode == select){ listener.get(sector); }else if(mode == planetLaunch){ //TODO make sure it doesn't have a base already. - //TODO animation //schematic selection and cost handled by listener listener.get(sector); diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index cb92cb77ad..c882edc714 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -27,6 +27,8 @@ import mindustry.input.*; import mindustry.net.Packets.*; import mindustry.type.*; import mindustry.ui.*; +import mindustry.world.blocks.storage.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; import static mindustry.gen.Tex.*; @@ -584,6 +586,8 @@ public class HudFragment{ } } + /** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */ + @Deprecated public void showLaunch(){ float margin = 30f; @@ -602,6 +606,8 @@ public class HudFragment{ Core.scene.add(image); } + /** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */ + @Deprecated public void showLand(){ Image image = new Image(); image.color.a = 1f; diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index ff24181b79..184523e3be 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -1,11 +1,15 @@ package mindustry.world.blocks.storage; import arc.*; +import arc.audio.*; import arc.func.*; import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.scene.actions.*; +import arc.scene.event.*; +import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; @@ -29,6 +33,9 @@ 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}; + //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}; @@ -42,6 +49,12 @@ public class CoreBlock extends StorageBlock{ public boolean incinerateNonBuildable = false; public UnitType unitType = UnitTypes.alpha; + public float landDuration = 160f; + public Music landMusic = Musics.land; + public Effect launchEffect = Fx.launch; + + public Interp landZoomInterp = Interp.pow3; + public float landZoomFrom = 0.02f, landZoomTo = 4f; public float captureInvicibility = 60f * 15f; @@ -217,11 +230,8 @@ public class CoreBlock extends StorageBlock{ } public void drawLanding(CoreBuild build, float x, float y){ - float fout = renderer.getLandTime() / coreLandDuration; - - if(renderer.isLaunching()) fout = 1f - fout; - - float fin = 1f - fout; + float fin = renderer.getLandTimeIn(); + float fout = 1f - fin; float scl = Scl.scl(4f) / renderer.getDisplayScale(); float shake = 0f; @@ -312,6 +322,17 @@ public class CoreBlock extends StorageBlock{ 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; + } + @Override public void draw(){ //draw thrusters when just landed @@ -331,6 +352,115 @@ public class CoreBlock extends StorageBlock{ } } + // `launchType` is null if it's landing instead of launching. + public void beginLaunch(@Nullable CoreBlock launchType){ + cloudSeed = Mathf.random(1f); + if(launchType != null){ + Fx.coreLaunchConstruct.at(x, y, launchType.size); + } + + if(!headless){ + // Add fade-in and fade-out foreground when landing or launching. + if(renderer.isLaunching()){ + float margin = 30f; + + Image image = new Image(); + 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.update(() -> { + image.toFront(); + ui.loadfrag.toFront(); + if(state.isMenu()){ + image.remove(); + } + }); + Core.scene.add(image); + }else{ + Image image = new Image(); + image.color.a = 1f; + image.touchable = Touchable.disabled; + image.setFillParent(true); + image.actions(Actions.fadeOut(35f / 60f), Actions.remove()); + image.update(() -> { + image.toFront(); + ui.loadfrag.toFront(); + if(state.isMenu()){ + image.remove(); + } + }); + Core.scene.add(image); + + Time.run(landDuration(), () -> { + launchEffect.at(this); + Effect.shake(5f, 5f, this); + thrusterTime = 1f; + + if(state.isCampaign() && Vars.showSectorLandInfo && (state.rules.sector.preset == null || state.rules.sector.preset.showSectorLandInfo)){ + ui.announce("[accent]" + state.rules.sector.name() + "\n" + + (state.rules.sector.info.resources.any() ? "[lightgray]" + Core.bundle.get("sectors.resources") + "[white] " + + state.rules.sector.info.resources.toString(" ", UnlockableContent::emoji) : ""), 5); + } + }); + } + } + } + + public void endLaunch(){} + + public void drawLanding(CoreBlock block){ + var clouds = Core.assets.get("sprites/clouds.png", Texture.class); + + float fin = renderer.getLandTimeIn(); + 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(); + + block.drawLanding(this, 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 = cloudScaling; + float sscl = Math.max(1f + Mathf.clamp(fin + cfinOffset) * 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(cloudAlphas, fin + calphaFinOffset) * 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(); + } + } + public void drawThrusters(float frame){ float length = thrusterLength * (frame - 1f) - 1f/4f; for(int i = 0; i < 4; i++){ @@ -409,9 +539,19 @@ public class CoreBlock extends StorageBlock{ thrusterTime -= Time.delta/90f; } + /** @return Camera zoom while landing or launching. May optionally do other things such as setting camera position to itself. */ + public float zoomLaunching(){ + Core.camera.position.set(this); + return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), renderer.getLandTimeIn()); + } + + public void updateLaunching(){ + updateLandParticles(); + } + public void updateLandParticles(){ - float time = renderer.isLaunching() ? coreLandDuration - renderer.getLandTime() : renderer.getLandTime(); - float tsize = Mathf.sample(thrusterSizes, (time + 35f) / coreLandDuration); + float in = renderer.getLandTimeIn() * landDuration(); + float tsize = Mathf.sample(thrusterSizes, (in + 35f) / landDuration()); renderer.setLandPTimer(renderer.getLandPTimer() + tsize * Time.delta); if(renderer.getLandTime() >= 1f){