Launch pad rework progress, base mechanics done

This commit is contained in:
Anuken
2025-02-01 20:46:08 -05:00
parent ab65c9d29a
commit 18b78f4bf6
12 changed files with 226 additions and 59 deletions

View File

@@ -6379,9 +6379,8 @@ public class Blocks{
itemCapacity = 100;
liquidCapacity = 2000;
addLiquidBar(Liquids.water);
consumeLiquid(Liquids.water, liquidCapacity).trigger(true).update(false);
liquidCapacity = 4000f;
consumeLiquidAmount = 2000f;
}};
interplanetaryAccelerator = new Accelerator("interplanetary-accelerator"){{

View File

@@ -215,6 +215,10 @@ public class Control implements ApplicationListener, Loadable{
}
if(state.isCampaign()){
if(state.rules.sector.info.importRateCache != null){
state.rules.sector.info.refreshImportRates(state.rules.sector.planet);
}
//don't run when hosting, that doesn't really work.
if(state.rules.sector.planet.prebuildBase){
toBePlaced.clear();

View File

@@ -429,6 +429,8 @@ public class Logic implements ApplicationListener{
}
if(!state.isPaused()){
Events.fire(Trigger.beforeGameUpdate);
float delta = Core.graphics.getDeltaTime();
state.tick += Float.isNaN(delta) || Float.isInfinite(delta) ? 0f : delta * 60f;
state.updateId ++;
@@ -488,6 +490,8 @@ public class Logic implements ApplicationListener{
Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity));
Groups.update();
Events.fire(Trigger.afterGameUpdate);
}
if(runStateCheck){

View File

@@ -38,6 +38,8 @@ public class EventType{
teamCoreDamage,
socketConfigChanged,
update,
beforeGameUpdate,
afterGameUpdate,
unitCommandChange,
unitCommandPosition,
unitCommandAttack,

View File

@@ -86,6 +86,9 @@ public class SectorInfo{
/** Wave where first boss shows up. */
public int bossWave = -1;
public ObjectFloatMap<Item> importCooldownTimers = new ObjectFloatMap<>();
public @Nullable transient float[] importRateCache;
/** Counter refresh state. */
private transient Interval time = new Interval();
/** Core item storage input/output deltas. */
@@ -105,12 +108,6 @@ public class SectorInfo{
productionDeltas[item.id] += amount;
}
/** @return the real location items go when launched on this sector */
public Sector getRealDestination(){
//on multiplayer the destination is, by default, the first captured sector (basically random)
return !net.client() || destination != null ? destination : state.rules.sector.planet.sectors.find(Sector::hasBase);
}
/** Updates export statistics. */
public void handleItemExport(ItemStack stack){
handleItemExport(stack.item, stack.amount);
@@ -125,6 +122,29 @@ public class SectorInfo{
return export.get(item, ExportStat::new).mean;
}
public void refreshImportRates(Planet planet){
if(importRateCache == null || importRateCache.length != content.items().size){
importRateCache = new float[content.items().size];
}else{
Arrays.fill(importRateCache, 0f);
}
eachImport(planet, sector -> sector.info.export.each((item, stat) -> {
importRateCache[item.id] += stat.mean;
}));
}
public float[] getImportRates(Planet planet){
if(importRateCache == null){
refreshImportRates(planet);
}
return importRateCache;
}
/** @return the import rate of an item as item/second. */
public float getImportRate(Planet planet, Item item){
return getImportRates(planet)[item.id];
}
/** Write contents of meta into main storage. */
public void write(){
//enable attack mode when there's a core.
@@ -291,7 +311,7 @@ public class SectorInfo{
/** Iterates through every sector this one imports from. */
public void eachImport(Planet planet, Cons<Sector> cons){
for(Sector sector : planet.sectors){
Sector dest = sector.info.getRealDestination();
Sector dest = sector.info.destination;
if(sector.hasBase() && sector.info != this && dest != null && dest.info == this && sector.info.anyExports()){
cons.get(sector);
}

View File

@@ -163,9 +163,17 @@ public class Universe{
continue;
}
//don't simulate the planet if there is an in-progress mission on that planet
if(!planet.allowWaveSimulation && planet.sectors.contains(s -> s.hasBase() && !s.isBeingPlayed() && s.isAttacked())){
continue;
}
//third pass: everything else
for(Sector sector : planet.sectors){
if(sector.hasBase()){
if(sector.info.importRateCache != null){
sector.info.refreshImportRates(planet);
}
//if it is being attacked, capture time is 0; otherwise, increment the timer
if(sector.isAttacked()){

View File

@@ -504,11 +504,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
if(selected != null && selected != sec && selected.hasBase()){
//imports
if(sec.info.getRealDestination() == selected && sec.info.anyExports()){
if(sec.info.destination == selected && sec.info.anyExports()){
planets.drawArc(planet, sec.tile.v, selected.tile.v, Color.gray.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25);
}
//exports
if(selected.info.getRealDestination() == sec && selected.info.anyExports()){
if(selected.info.destination == sec && selected.info.anyExports()){
planets.drawArc(planet, selected.tile.v, sec.tile.v, Pal.place.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25);
}
}
@@ -1219,24 +1219,24 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
return;
}
//make sure there are no under-attack sectors (other than this one)
for(Planet planet : content.planets()){
if(!planet.allowWaveSimulation && !debugSelect && planet.allowWaveSimulation == sector.planet.allowWaveSimulation){
//if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock
Sector attacked = planet.sectors.find(s -> s.isAttacked() && s != sector);
if(attacked != null && planet.sectors.count(s -> s.isAttacked()) < 2){
BaseDialog dialog = new BaseDialog("@sector.noswitch.title");
dialog.cont.add(bundle.format("sector.noswitch", attacked.name(), attacked.planet.localizedName)).width(400f).labelAlign(Align.center).center().wrap();
dialog.addCloseButton();
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
dialog.hide();
lookAt(attacked);
selectSector(attacked);
});
dialog.show();
Planet planet = sector.planet;
return;
}
//make sure there are no under-attack sectors (other than this one)
if(!planet.allowWaveSimulation && !debugSelect){
//if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock
Sector attacked = planet.sectors.find(s -> s.isAttacked() && s != sector);
if(attacked != null && planet.sectors.count(s -> s.isAttacked()) < 2){
BaseDialog dialog = new BaseDialog("@sector.noswitch.title");
dialog.cont.add(bundle.format("sector.noswitch", attacked.name(), attacked.planet.localizedName)).width(400f).labelAlign(Align.center).center().wrap();
dialog.addCloseButton();
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
dialog.hide();
lookAt(attacked);
selectSector(attacked);
});
dialog.show();
return;
}
}

View File

@@ -1,18 +1,46 @@
package mindustry.world.blocks.campaign;
import arc.*;
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.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.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 float cooldownTime = 12f;
public float consumeLiquidAmount = 100f;
public Liquid consumeLiquid = Liquids.water;
public LandingPad(String name){
super(name);
hasItems = true;
hasLiquids = true;
solid = true;
update = true;
configurable = true;
@@ -22,19 +50,127 @@ public class LandingPad extends Block{
configClear((LandingPadBuild build) -> 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("heat", (LandingPadBuild entity) -> new Bar("bar.heat", 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;
public void handleLanding(){
if(!state.isCampaign() || config == null) return;
//TODO animation, etc
cooldown = 1f;
items.set(config, itemCapacity);
liquids.remove(consumeLiquid, consumeLiquidAmount);
for(int i = 0; i < 10; i++){
Fx.steam.at(this);
}
//TODO this is a temporary effect
Fx.shockwave.at(this);
state.rules.sector.info.importCooldownTimers.put(config, 0f);
}
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);
}
}
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 updateTile(){
updateTimers();
if(items.total() > 0){
dumpAccumulate(config == null || items.get(config) != items.total() ? null : config);
}
if(config != null && state.isCampaign()){
cooldown -= delta() / cooldownTime;
if(cooldown <= 0f && efficiency > 0f && items.total() == 0 && state.rules.sector.info.importCooldownTimers.get(config, 0f) >= 1f){
//queue landing for next frame
waiting.get(config, Seq::new).add(this);
}
}
}
@Override
@@ -45,6 +181,11 @@ public class LandingPad extends Block{
return true;
}
@Override
public void buildConfiguration(Table table){
ItemSelection.buildTable(LandingPad.this, table, content.items(), () -> config, this::configure, selectionRows, selectionColumns);
}
@Override
public boolean acceptItem(Building source, Item item){
return false;
@@ -59,12 +200,16 @@ public class LandingPad extends Block{
public void read(Reads read, byte revision){
super.read(read, revision);
config = TypeIO.readItem(read);
priority = read.i();
cooldown = read.f();
}
@Override
public void write(Writes write){
super.write(write);
TypeIO.writeItem(write, config);
write.i(priority);
write.f(cooldown);
}
}
}

View File

@@ -150,7 +150,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") :
@@ -161,7 +161,7 @@ public class LaunchPad extends Block{
@Override
public void buildConfiguration(Table table){
//TODO: this UI should be on landing pads
if(!state.isCampaign() || net.client() || true){
if(!state.isCampaign() || net.client()){
deselect();
return;
}
@@ -262,27 +262,23 @@ 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));
}
if(!net.client()){
destsec.addItems(dest);
}
//update export statistics
state.rules.sector.info.handleItemExport(stack);
Events.fire(new LaunchItemEvent(stack));
}
destsec.addItems(dest);
}
}
}

View File

@@ -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;
}

View File

@@ -12,7 +12,6 @@ import static mindustry.Vars.*;
//TODO replace with ConsumeLiquids?
public class ConsumeLiquid extends ConsumeLiquidBase{
public final Liquid liquid;
public boolean trigger;
public ConsumeLiquid(Liquid liquid, float amount){
super(amount);
@@ -23,10 +22,6 @@ public class ConsumeLiquid extends ConsumeLiquidBase{
this(null, 0f);
}
public ConsumeLiquid trigger(boolean trigger){
this.trigger = trigger;
return this;
}
@Override
public void apply(Block block){
@@ -44,13 +39,6 @@ public class ConsumeLiquid extends ConsumeLiquidBase{
build.liquids.remove(liquid, amount * build.edelta() * multiplier.get(build));
}
@Override
public void trigger(Building build){
if(trigger){
build.liquids.remove(liquid, amount);
}
}
@Override
public float efficiency(Building build){
float ed = build.edelta() * build.efficiencyScale();