diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index e771db3bf0..264a3e1f3b 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -776,6 +776,7 @@ sectors.stored = Stored: sectors.resume = Resume sectors.launch = Launch sectors.select = Select +sectors.launchselect = Select Launch Destination sectors.nonelaunch = [lightgray]none (sun) sectors.rename = Rename Sector sectors.enemybase = [scarlet]Enemy Base @@ -1090,6 +1091,7 @@ ability.stat.buildtime = [stat]{0} sec[lightgray] build time bar.onlycoredeposit = Only Core Depositing Allowed bar.drilltierreq = Better Drill Required +bar.nobatterypower = Insufficieny Battery Power bar.noresources = Missing Resources bar.corereq = Core Base Required bar.corefloor = Core Zone Tile Required @@ -1098,6 +1100,7 @@ bar.drillspeed = Drill Speed: {0}/s bar.pumpspeed = Pump Speed: {0}/s bar.efficiency = Efficiency: {0}% bar.boost = Boost: +{0}% +bar.powerbuffer = Battery Power: {0}/{1} bar.powerbalance = Power: {0}/s bar.powerstored = Stored: {0}/{1} bar.poweramount = Power: {0} diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 8317a8c7cb..4a5de1909e 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -6362,8 +6362,9 @@ public class Blocks{ }}; interplanetaryAccelerator = new Accelerator("interplanetary-accelerator"){{ - requirements(Category.effect, BuildVisibility.hidden, with(Items.copper, 16000, Items.silicon, 11000, Items.thorium, 13000, Items.titanium, 12000, Items.surgeAlloy, 6000, Items.phaseFabric, 5000)); + requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 16000, Items.silicon, 11000, Items.thorium, 13000, Items.titanium, 12000, Items.surgeAlloy, 6000, Items.phaseFabric, 5000)); researchCostMultiplier = 0.1f; + powerBufferRequirement = 1_000_000f; size = 7; hasPower = true; consumePower(10f); diff --git a/core/src/mindustry/content/Planets.java b/core/src/mindustry/content/Planets.java index 338e25cadc..d23ab25f47 100644 --- a/core/src/mindustry/content/Planets.java +++ b/core/src/mindustry/content/Planets.java @@ -153,6 +153,7 @@ public class Planets{ atmosphereRadOut = 0.3f; startSector = 15; alwaysUnlocked = true; + allowSelfSectorLaunch = true; landCloudColor = Pal.spore.cpy().a(0.5f); }}; diff --git a/core/src/mindustry/content/SerpuloTechTree.java b/core/src/mindustry/content/SerpuloTechTree.java index 6da887240c..f158069c08 100644 --- a/core/src/mindustry/content/SerpuloTechTree.java +++ b/core/src/mindustry/content/SerpuloTechTree.java @@ -20,10 +20,9 @@ public class SerpuloTechTree{ node(junction, () -> { node(router, () -> { node(launchPad, Seq.with(new SectorComplete(extractionOutpost)), () -> { - //no longer necessary to beat the campaign - //node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> { + node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> { - //}); + }); }); node(distributor); diff --git a/core/src/mindustry/core/Renderer.java b/core/src/mindustry/core/Renderer.java index c544e8bba2..b1f0787749 100644 --- a/core/src/mindustry/core/Renderer.java +++ b/core/src/mindustry/core/Renderer.java @@ -20,8 +20,7 @@ import mindustry.graphics.*; import mindustry.graphics.g3d.*; import mindustry.maps.*; import mindustry.type.*; -import mindustry.world.blocks.storage.*; -import mindustry.world.blocks.storage.CoreBlock.*; +import mindustry.world.blocks.*; import static arc.Core.*; import static mindustry.Vars.*; @@ -51,8 +50,7 @@ public class Renderer implements ApplicationListener{ 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 @Nullable LaunchAnimator landCore; private Color clearColor = new Color(0f, 0f, 0f, 1f); private float //target camera scale that is lerp-ed to @@ -379,7 +377,7 @@ public class Renderer implements ApplicationListener{ if(state.rules.fog) Draw.draw(Layer.fogOfWar, fog::drawFog); Draw.draw(Layer.space, () -> { if(landCore == null || landTime <= 0f) return; - landCore.drawLanding(launching && launchCoreType != null ? launchCoreType : (CoreBlock)landCore.block); + landCore.drawLanding(); }); Events.fire(Trigger.drawOver); @@ -504,10 +502,6 @@ public class Renderer implements ApplicationListener{ return launching; } - public CoreBlock getLaunchCoreType(){ - return launchCoreType; - } - public float getLandTime(){ return landTime; } @@ -527,28 +521,16 @@ public class Renderer implements ApplicationListener{ this.landPTimer = landPTimer; } - @Deprecated - public void showLanding(){ - var core = player.bestCore(); - if(core != null) showLanding(core); - } - - public void showLanding(CoreBuild landCore){ + public void showLanding(LaunchAnimator landCore){ this.landCore = landCore; launching = false; landTime = landCore.landDuration(); - landCore.beginLaunch(null); + landCore.beginLaunch(false); camerascale = landCore.zoomLaunching(); } - @Deprecated - public void showLaunch(CoreBlock coreType){ - var core = player.team().core(); - if(core != null) showLaunch(core, coreType); - } - - public void showLaunch(CoreBuild landCore, CoreBlock coreType){ + public void showLaunch(LaunchAnimator landCore){ control.input.config.hideConfig(); control.input.planConfig.hide(); control.input.inv.hide(); @@ -556,14 +538,13 @@ public class Renderer implements ApplicationListener{ this.landCore = landCore; launching = true; landTime = landCore.landDuration(); - launchCoreType = coreType; Music music = landCore.launchMusic(); music.stop(); music.play(); music.setVolume(settings.getInt("musicvol") / 100f); - landCore.beginLaunch(coreType); + landCore.beginLaunch(true); } public void takeMapScreenshot(){ diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index a22d073f9d..5026101451 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -6,6 +6,7 @@ import arc.struct.*; import arc.util.*; import mindustry.content.*; import mindustry.game.EventType.*; +import mindustry.game.Schematic.*; import mindustry.game.SectorInfo.*; import mindustry.gen.*; import mindustry.maps.*; @@ -115,6 +116,11 @@ public class Universe{ Core.settings.putJson("launch-resources-seq", lastLaunchResources); } + /** Updates selected loadout for future deployment. Creates an empty schematic with a single core block. */ + public void updateLoadout(CoreBlock block){ + updateLoadout(block, new Schematic(Seq.with(new Stile(block, 0, 0, null, (byte)0)), new StringMap(), block.size, block.size)); + } + /** Updates selected loadout for future deployment. */ public void updateLoadout(CoreBlock block, Schematic schem){ Core.settings.put("lastloadout-" + block.name, schem.file == null ? "" : schem.file.nameWithoutExtension()); diff --git a/core/src/mindustry/maps/generators/PlanetGenerator.java b/core/src/mindustry/maps/generators/PlanetGenerator.java index 69524884d7..a093a27df4 100644 --- a/core/src/mindustry/maps/generators/PlanetGenerator.java +++ b/core/src/mindustry/maps/generators/PlanetGenerator.java @@ -39,7 +39,7 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe if(sector.planet.getSector(other).id == sector.planet.startSector){ return; } - + if(sector.planet.getSector(other).generateEnemyBase){ any = false; break; @@ -57,6 +57,11 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe return sector.planet.allowLaunchToNumbered && (sector.hasBase() || sector.near().contains(Sector::hasBase)); } + /** @return whether to allow landing on the specified procedural sector */ + public boolean allowAcceleratorLanding(Sector sector){ + return sector.planet.allowLaunchToNumbered; + } + public void addWeather(Sector sector, Rules rules){ //apply weather based on terrain diff --git a/core/src/mindustry/type/Planet.java b/core/src/mindustry/type/Planet.java index f99fba5fe6..7ef88ed9e4 100644 --- a/core/src/mindustry/type/Planet.java +++ b/core/src/mindustry/type/Planet.java @@ -144,8 +144,10 @@ public class Planet extends UnlockableContent{ public Seq children = new Seq<>(); /** Default root node shown when the tech tree is opened here. */ public @Nullable TechNode techTree; - /** TODO remove? Planets that can be launched to from this one. Made mutual in init(). */ + /** Planets that can be launched to from this one. */ public Seq launchCandidates = new Seq<>(); + /** Whether interplanetary accelerators can launch to 'any' procedural sector on this planet's surface. */ + public boolean allowSelfSectorLaunch; /** If true, all content in this planet's tech tree will be assigned this planet in their shownPlanets. */ public boolean autoAssignPlanet = true; /** Content (usually planet-specific) that is unlocked upon landing here. */ @@ -383,18 +385,6 @@ public class Planet extends UnlockableContent{ updateBaseCoverage(); } - //make planet launch candidates mutual. - var candidates = launchCandidates.copy(); - - for(Planet planet : content.planets()){ - if(planet.launchCandidates.contains(this)){ - candidates.addUnique(planet); - } - } - - //TODO currently, mutual launch candidates are simply a nuisance. - //launchCandidates = candidates; - clipRadius = Math.max(clipRadius, radius + atmosphereRadOut + 0.5f); } diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 1a040bc00a..e28459c31c 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -54,6 +54,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ public PlanetParams state = new PlanetParams(); public float zoom = 1f; public @Nullable Sector selected, hovered, launchSector; + /** Must not be null in planet launch mode. */ + public @Nullable Seq launchCandidates; public Mode mode = look; public boolean launching; public Cons listener = s -> {}; @@ -294,7 +296,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } void addTech(){ - buttons.button("@techtree", Icon.tree, () -> ui.research.show()).size(200f, 54f).pad(2).bottom(); + buttons.button("@techtree", Icon.tree, () -> ui.research.show()).size(200f, 54f).visible(() -> mode == look).pad(2).bottom(); } public void showOverview(){ @@ -312,16 +314,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } //TODO not fully implemented, cutscene needed - public void showPlanetLaunch(Sector sector, Cons listener){ + public void showPlanetLaunch(Sector sector, Seq launchCandidates, Cons listener){ selected = null; hovered = null; launching = false; this.listener = listener; + this.launchCandidates = (launchCandidates == null ? sector.planet.launchCandidates : launchCandidates); launchSector = sector; //automatically select next planets; - if(sector.planet.launchCandidates.size == 1){ - state.planet = sector.planet.launchCandidates.first(); + if(this.launchCandidates.size == 1){ + state.planet = this.launchCandidates.first(); state.otherCamPos = sector.planet.position; state.otherCamAlpha = 0f; @@ -332,8 +335,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ preset.unlock(); } selected = destSec; - updateSelected(); - rebuildExpand(); } //TODO pan over to correct planet @@ -345,6 +346,13 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ mode = planetLaunch; + updateSelected(); + rebuildExpand(); + + if(sectorTop != null){ + sectorTop.color.a = 0f; + } + super.show(); } @@ -382,8 +390,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ boolean canSelect(Sector sector){ if(mode == select) return sector.hasBase() && launchSector != null && sector.planet == launchSector.planet; - //cannot launch to existing sector w/ accelerator TODO test - if(mode == planetLaunch) return sector.id == sector.planet.startSector; + + if(mode == planetLaunch && sector.hasBase()){ + return false; + } + if(sector.hasBase() || sector.id == sector.planet.startSector) return true; //preset sectors can only be selected once unlocked if(sector.preset != null){ @@ -393,11 +404,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ return sector.planet.generator != null ? //use planet impl when possible - sector.planet.generator.allowLanding(sector) : - sector.hasBase() || sector.near().contains(Sector::hasBase); //near an occupied sector + (mode == planetLaunch ? sector.planet.generator.allowAcceleratorLanding(sector) : sector.planet.generator.allowLanding(sector)) : + mode == planetLaunch || sector.hasBase() || sector.near().contains(Sector::hasBase); //near an occupied sector } Sector findLauncher(Sector to){ + if(mode == planetLaunch){ + return launchSector; + } + Sector launchSector = this.launchSector != null && this.launchSector.planet == to.planet && this.launchSector.hasBase() ? this.launchSector : null; //directly nearby. if(to.near().contains(launchSector)) return launchSector; @@ -472,6 +487,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } } + if(mode == planetLaunch && launchSector != null && selected != null && hovered == null){ + planets.drawArc(planet, launchSector.tile.v, selected.tile.v); + } + if(state.uiAlpha > 0.001f){ for(Sector sec : planet.sectors){ if(sec.hasBase()){ @@ -548,7 +567,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //TODO what if any sector is selectable? //TODO launch criteria - which planets can be launched to? Where should this be defined? Should planets even be selectable? if(mode == select) return planet == state.planet; - if(mode == planetLaunch) return launchSector != null && planet != launchSector.planet && launchSector.planet.launchCandidates.contains(planet); + if(mode == planetLaunch) return launchSector != null && (launchCandidates.contains(planet) || (planet == launchSector.planet && planet.allowSelfSectorLaunch)); return (planet.alwaysUnlocked && planet.isLandable()) || planet.sectors.contains(Sector::hasBase) || debugSelect; } @@ -604,7 +623,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ new Table(t -> { t.touchable = Touchable.disabled; t.top(); - t.label(() -> mode == select ? "@sectors.select" : "").style(Styles.outlineLabel).color(Pal.accent); + t.label(() -> + mode == select ? "@sectors.select" : + mode == planetLaunch ? "@sectors.launchselect" : + "" + ).style(Styles.outlineLabel).color(Pal.accent); }), buttons, @@ -615,7 +638,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ t.add(pane).colspan(2).row(); t.button("@campaign.difficulty", Icon.bookSmall, () -> { campaignRules.show(state.planet); - }).margin(12f).size(208f, 40f).padTop(12f).visible(() -> state.planet.allowCampaignRules).row(); + }).margin(12f).size(208f, 40f).padTop(12f).visible(() -> state.planet.allowCampaignRules && mode != planetLaunch).row(); t.add().height(64f); //padding for close button Table starsTable = new Table(Styles.black); pane.setWidget(starsTable); @@ -634,7 +657,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(planet.solarSystem == star && selectable(planet)){ Button planetButton = planetTable.button(planet.localizedName, Icon.icons.get(planet.icon + "Small", Icon.icons.get(planet.icon, Icon.commandRallySmall)), Styles.flatTogglet, () -> { selected = null; - launchSector = null; if(state.planet != planet){ newPresets.clear(); state.planet = planet; @@ -660,7 +682,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ void rebuildExpand(){ Table c = expandTable; c.clear(); - c.visible(() -> !(graphics.isPortrait() && mobile)); + c.visible(() -> !(graphics.isPortrait() && mobile) && mode != planetLaunch); if(state.planet.sectors.contains(Sector::hasBase)){ int attacked = state.planet.sectors.count(Sector::isAttacked); @@ -783,7 +805,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(Mathf.equal(state.otherCamAlpha, 1f, 0.01f)){ //TODO change zoom too - state.camPos.set(Tmp.v31.set(state.otherCamPos).lerp(state.planet.position, state.otherCamAlpha).add(state.camPos).sub(state.planet.position)); + state.camPos.set(Tmp.v31.set(state.otherCamPos).slerp(state.planet.position, state.otherCamAlpha).add(state.camPos).sub(state.planet.position)); state.otherCamPos = null; //announce new sector @@ -792,6 +814,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } } + //fade in sector dialog after panning + if(sectorTop != null && state.otherCamPos == null){ + sectorTop.color.a = Mathf.lerpDelta(sectorTop.color.a, 1f, 0.1f); + } + if(hovered != null && !mobile && state.planet.hasGrid()){ addChild(hoverLabel); hoverLabel.toFront(); @@ -1258,7 +1285,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //allow planet dialog to finish hiding before actually launching Time.runTask(5f, () -> { Runnable doLaunch = () -> { - renderer.showLaunch(core, schemCore); + renderer.showLaunch(core); //run with less delay, as the loading animation is delayed by several frames Time.runTask(core.landDuration() - 8f, () -> control.playSector(from, sector)); }; @@ -1275,15 +1302,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } }); } - }else if(mode == select){ + }else if(mode == select || mode == planetLaunch){ 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); - //unlock right before launch - sector.planet.unlockedOnLand.each(UnlockableContent::unlock); - control.playSector(sector); }else{ //sector should have base here control.playSector(sector); diff --git a/core/src/mindustry/world/blocks/LaunchAnimator.java b/core/src/mindustry/world/blocks/LaunchAnimator.java new file mode 100644 index 0000000000..4bba63a5f4 --- /dev/null +++ b/core/src/mindustry/world/blocks/LaunchAnimator.java @@ -0,0 +1,22 @@ +package mindustry.world.blocks; + +import arc.audio.*; + +public interface LaunchAnimator{ + + void drawLanding(); + + void beginLaunch(boolean launching); + + void endLaunch(); + + void updateLaunching(); + + float landDuration(); + + Music landMusic(); + + Music launchMusic(); + + float zoomLaunching(); +} diff --git a/core/src/mindustry/world/blocks/campaign/Accelerator.java b/core/src/mindustry/world/blocks/campaign/Accelerator.java index e1dde597c7..9968fdc874 100644 --- a/core/src/mindustry/world/blocks/campaign/Accelerator.java +++ b/core/src/mindustry/world/blocks/campaign/Accelerator.java @@ -3,32 +3,56 @@ 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.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.*; +import arc.util.io.*; +import mindustry.*; 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; - //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 launchCandidates; + + public Music launchMusic = Musics.coreLaunch; + public float launchDuration = 160f; + public float buildDuration = 120f; + + protected int[] capacities = {}; public Accelerator(String name){ super(name); update = true; solid = true; hasItems = true; + hasPower = true; itemCapacity = 8000; configurable = true; } @@ -37,27 +61,56 @@ public class Accelerator extends Block{ 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; + + protected float cloudSeed; @Override public void updateTile(){ super.updateTile(); heat = Mathf.lerpDelta(heat, efficiency, 0.05f); statusLerp = Mathf.lerpDelta(statusLerp, power.status, 0.05f); + + time += Time.delta * efficiency; + + if(efficiency >= 0f){ + progress += Time.delta * efficiency / buildDuration; + progress = Math.min(progress, 1f); + } + } + + @Override + public float progress(){ + return progress; } @Override @@ -74,6 +127,32 @@ public class Accelerator extends Block{ } } + { + 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(); + }); + + //TODO: build line? + //Draw.z(Layer.blockBuilding + 1); + //Draw.color(Pal.accent, heat); + + //Lines.lineAngleCenter(x + Mathf.sin(time, 10f, Vars.tilesize / 2f * recipe.size + 1f), y, 90, recipe.size * Vars.tilesize + 1f); + + Draw.reset(); + } + if(heat < 0.0001f) return; float rad = size * tilesize / 2f * 0.74f; @@ -99,28 +178,52 @@ public class Accelerator extends Block{ Draw.reset(); } + public boolean canLaunch(){ + return isValid() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f; + } + @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){ + 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()){ + //TODO: animation! - if(false) - ui.planet.showPlanetLaunch(state.rules.sector, sector -> { - //TODO cutscene, etc... + consume(); + power.graph.useBatteries(powerBufferRequirement); + progress = 0f; - //TODO should consume resources based on destination schem - consume(); + var core = team.core(); - universe.clearLoadoutInfo(); - universe.updateLoadout(sector.planet.generator.defaultLoadout.findCore(), sector.planet.generator.defaultLoadout); + renderer.showLaunch(this); + + Time.runTask(core.landDuration() - 8f, () -> { + //unlock right before launch + sector.planet.unlockedOnLand.each(UnlockableContent::unlock); + + universe.clearLoadoutInfo(); + universe.updateLoadout((CoreBlock)launchBlock); + + control.playSector(sector); + }); + } }); Events.fire(Trigger.acceleratorUse); @@ -135,5 +238,275 @@ 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 zoomLaunching(){ + CoreBlock core = (CoreBlock)launchBlock; + Core.camera.position.set(this); + return core.landZoomInterp.apply(Scl.scl(core.landZoomFrom), Scl.scl(core.landZoomTo), renderer.getLandTimeIn()); + } + + @Override + public void updateLaunching(){ + float in = renderer.getLandTimeIn() * landDuration(); + float tsize = Mathf.sample(CoreBlock.thrusterSizes, (in + 35f) / landDuration()); + + renderer.setLandPTimer(renderer.getLandPTimer() + tsize * Time.delta); + if(renderer.getLandTime() >= 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); + } + } + + @Override + public float landDuration(){ + return launchDuration; + } + + @Override + public Music landMusic(){ + //unused + return launchMusic; + } + + @Override + public Music launchMusic(){ + return launchMusic; + } + + @Override + public void beginLaunch(boolean launching){ + cloudSeed = Mathf.random(1f); + if(launching){ + Fx.coreLaunchConstruct.at(x, y, launchBlock.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(), () -> { + CoreBlock core = (CoreBlock)launchBlock; + core.launchEffect.at(this); + Effect.shake(5f, 5f, this); + + 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); + } + }); + } + } + } + + @Override + public void endLaunch(){} + + @Override + public void drawLanding(){ + 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(); + + drawLanding(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(); + } + } + + 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 = 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); + } + + public void drawThrusters(float frame){ + CoreBlock core = (CoreBlock)launchBlock; + float length = core.thrusterLength * (frame - 1f) - 1f/4f; + for(int i = 0; i < 4; i++){ + var reg = i >= 2 ? core.thruster2 : core.thruster1; + float dx = Geometry.d4x[i] * length, dy = Geometry.d4y[i] * length; + Draw.rect(reg, x + dx, y + dy, i * 90); + } + } } } diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index 11f7b8c893..3dd29fc6b1 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -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,93 +231,7 @@ 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; @@ -325,19 +240,6 @@ public class CoreBlock extends StorageBlock{ 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; - } - @Override public void draw(){ //draw thrusters when just landed @@ -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 landDuration(){ + 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){ @@ -412,9 +329,11 @@ public class CoreBlock extends StorageBlock{ } } + @Override public void endLaunch(){} - public void drawLanding(CoreBlock block){ + @Override + public void drawLanding(){ 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,16 +550,14 @@ public class CoreBlock extends StorageBlock{ } /** @return Camera zoom while landing or launching. May optionally do other things such as setting camera position to itself. */ + @Override public float zoomLaunching(){ Core.camera.position.set(this); return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), renderer.getLandTimeIn()); } + @Override public void updateLaunching(){ - updateLandParticles(); - } - - public void updateLandParticles(){ float in = renderer.getLandTimeIn() * landDuration(); float tsize = Mathf.sample(thrusterSizes, (in + 35f) / landDuration()); diff --git a/core/src/mindustry/world/consumers/ConsumePower.java b/core/src/mindustry/world/consumers/ConsumePower.java index 4ceb202d87..74a96b1c5f 100644 --- a/core/src/mindustry/world/consumers/ConsumePower.java +++ b/core/src/mindustry/world/consumers/ConsumePower.java @@ -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); } }