From a89aa53b46908e7f5cd94184c512e6c035c1879d Mon Sep 17 00:00:00 2001 From: QmelZ <59574967+QmelZ@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:43:48 +0300 Subject: [PATCH 01/24] rebase --- src/meld/core/Melting.java | 78 +++++++++++++++++++++++++++++ src/meld/world/blocks/MeldNode.java | 11 ++-- 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/meld/core/Melting.java diff --git a/src/meld/core/Melting.java b/src/meld/core/Melting.java new file mode 100644 index 0000000..e2de6d4 --- /dev/null +++ b/src/meld/core/Melting.java @@ -0,0 +1,78 @@ +package meld.core; + +import java.util.*; +import arc.*; +import arc.math.geom.*; +import arc.struct.*; +import arc.util.*; +import meld.content.*; +import mindustry.*; +import mindustry.content.*; +import mindustry.game.*; +import mindustry.world.*; +import mindustry.world.blocks.environment.*; + +// TODO: fix melting stopping when leaving world +// TODO: make a way to add more melting patterns +public class Melting implements Runnable{ + private static final Floor stable = MeldEnvironment.meldCrystal; + private static final Floor unstable = MeldEnvironment.meldCrystalScattered; + private static final Floor melted = MeldEnvironment.meldSwampland; + + private boolean running = false; + private final ObjectSet toMelt = new ObjectSet<>(); + private final ObjectSet toSpread = new ObjectSet<>(); + + public Melting(){ + Events.on(EventType.ResetEvent.class, _it -> running = false); + } + + public void start(Tile start){ + start.getLinkedTiles(toSpread::add); + + if(!running){ + running = true; + run(); + } + } + + @Override + public void run(){ + toMelt + // TODO: this check might not be necessary + .select(it -> it.floor() != melted) + .each(it -> { + it.setFloor(melted); + Fx.smoke.at(it); + + var build = it.build; + if(build != null) build.kill(); + }) + ; + + toMelt.clear(); + toMelt.addAll(toSpread); + toSpread.clear(); + + toMelt.each(it -> + toSpread.addAll( + nearby(it).select(n -> isValid(it, n)) + ) + ); + + if(toMelt.isEmpty() && toSpread.isEmpty()) running = false; + if(running) Time.run(5, this); + } + + private Seq nearby(Tile tile){ + return Seq.with(0, 1, 2, 3) + .map(tile::nearby) + .select(Objects::nonNull) + ; + } + + private boolean isValid(Tile current, Tile nearby){ + if(nearby.floor() == unstable) return true; + return nearby.floor() == stable && current.floor() == stable; + } +} diff --git a/src/meld/world/blocks/MeldNode.java b/src/meld/world/blocks/MeldNode.java index a96e769..de904dc 100644 --- a/src/meld/world/blocks/MeldNode.java +++ b/src/meld/world/blocks/MeldNode.java @@ -1,5 +1,8 @@ package meld.world.blocks; +import arc.func.*; +import meld.*; +import meld.core.*; import mindustry.content.Blocks; import mindustry.world.blocks.storage.CoreBlock; @@ -12,11 +15,11 @@ public MeldNode(String name) { public class MeldNodeBuild extends CoreBuild{ @Override public void killed() { + /// start melting before killing + /// otherwise {@link mindustry.world.Tile#getLinkedTiles(Cons)} won't work + Meld.melting.start(tileOn()); + super.killed(); - //Spawn melting overlays on all tiles touching the edges of this block - eachEdge(t -> { - t.setOverlay(Blocks.pebbles); - }); } } } From 44b731b6288434d1cf6a8ccea5844b3c8248aaf7 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 11:16:57 +0200 Subject: [PATCH 02/24] merge? --- src/meld/content/MeldBlocks.java | 84 +++++++++++++++- .../world/blocks/crafting/ModularCrafter.java | 88 ++++++++++++++--- .../modules/StupidConsumeHighestModule.java | 34 +++++++ .../modules/StupidConsumeItemModule.java | 54 ++++++++++ .../modules/StupidConsumeLiquidModule.java | 50 ++++++++++ .../modules/StupidConsumePayloadModule.java | 81 +++++++++++++++ .../modules/StupidCraftingModule.java | 26 +++++ .../modules/StupidProduceItemModule.java | 59 +++++++++++ .../modules/StupidProduceLiquidModule.java | 54 ++++++++++ .../modules/StupidProducePayloadModule.java | 98 +++++++++++++++++++ 10 files changed, 612 insertions(+), 16 deletions(-) create mode 100644 src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidCraftingModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java create mode 100644 src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 1c9a9c4..998ddba 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -1,5 +1,6 @@ package meld.content; +import arc.*; import arc.graphics.Color; import arc.math.Interp; import meld.*; @@ -10,6 +11,7 @@ import meld.world.blocks.*; import meld.world.blocks.crafting.ModularCrafter; import meld.world.blocks.crafting.RecipeCrafter; +import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.recipe.ItemRecipe; import meld.world.blocks.crafting.recipe.SpoolRecipe; import meld.world.blocks.crafting.modules.*; @@ -23,8 +25,7 @@ import meld.world.blocks.production.SingleBeamDrill; import meld.world.meta.*; import mindustry.Vars; -import mindustry.content.Fx; -import mindustry.content.StatusEffects; +import mindustry.content.*; import mindustry.entities.bullet.*; import mindustry.entities.effect.MultiEffect; import mindustry.entities.effect.ParticleEffect; @@ -663,6 +664,85 @@ public static void load(){ consume(new ConsumeAspects(outletRate/2, MeldLiquids.outletEfficiencies, MeldLiquids.outletDensities)); }}; + new ModularCrafter("stupid-crafter"){{ + requirements(Category.crafting, with(MeldItems.debris, 40)); + size = 3; + + modules.addAll( + new StupidCraftingModule(0){{ + requestPins = new int[]{1, 2, 3, 4, 5, 6}; + }}, + + new StupidConsumeItemModule(1, -1){{ + items = ItemStack.with(Items.copper, 2); + time = 60f; + }}, + new StupidConsumeLiquidModule(2){{ + liquids = LiquidStack.with(Liquids.water, 1f); + }}, + new StupidConsumePayloadModule(3, -3){{ + payloads = PayloadStack.with(Blocks.plastaniumWallLarge, 1); + time = 180f; + }}, + + new StupidProduceItemModule(4, 0, -4){{ + items = ItemStack.with(Items.titanium, 2); + time = 60f; + }}, + new StupidProduceLiquidModule(5, 0){{ + liquids = LiquidStack.with(Liquids.cryofluid, 1f); + }}, + new StupidProducePayloadModule(6, 0, -6){{ + payloads = PayloadStack.with(Blocks.plastaniumWall, 2); + time = 60f; + }} + ); + }}; + + new ModularCrafter("stupid-silicon-smelter"){ + @Override + public void load(){ + super.load(); + region = Core.atlas.find("silicon-smelter"); + fullIcon = Core.atlas.find("silicon-smelter"); + uiIcon = Core.atlas.find("silicon-smelter"); + } + + { + requirements(Category.crafting, with(MeldItems.debris, 40)); + size = 2; + + modules.addAll( + new StupidCraftingModule(0){{ + requestPins = new int[]{1, 2, 3}; + }}, + new StupidProduceItemModule(1, 0, -1){{ + items = ItemStack.with(Items.silicon, 1); + time = 60f; + }}, + + new StupidConsumeItemModule(2, -2){{ + items = ItemStack.with(Items.sand, 1); + time = 20f; + }}, + + new StupidConsumeHighestModule(3){{ + requestPins = new int[]{4, 5}; + }}, + new StupidConsumeItemModule(4, -4){{ + items = ItemStack.with(Items.coal, 1); + time = 40f; + }}, + new StupidConsumeItemModule(5, -5){{ + items = ItemStack.with(Items.pyratite, 1); + time = 60f; + + efficiencyIncrease = 2.5f; + }} + ); + } + }; + earthboundInfuser = new ModularCrafter("earthbound-infuser"){{ requirements(Category.crafting, with( MeldItems.debris, 40, diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index a56528b..48230a5 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -1,44 +1,59 @@ package meld.world.blocks.crafting; +import arc.math.*; +import arc.math.geom.*; +import arc.scene.ui.layout.*; import arc.struct.*; -import arc.util.Log; -import arc.util.Time; +import arc.struct.IntFloatMap.*; +import arc.util.*; import arc.util.io.Reads; import arc.util.io.Writes; +import mindustry.ctype.*; import mindustry.game.Team; import mindustry.gen.Building; -import mindustry.type.Item; -import mindustry.type.ItemStack; -import mindustry.type.Liquid; +import mindustry.type.*; import mindustry.world.Block; +import mindustry.world.blocks.payloads.*; import mindustry.world.blocks.production.GenericCrafter; -import mindustry.world.meta.Attribute; +import mindustry.world.blocks.units.UnitAssemblerModule.*; +import mindustry.world.meta.*; -import java.util.HashMap; +import java.util.*; -public class ModularCrafter extends Block { +import static mindustry.Vars.*; +import static mindustry.Vars.tilesize; + +public class ModularCrafter extends PayloadBlock{ //Modules updates every update, listeners are for modules with specific events public Seq modules = new Seq(); public ObjectMap> listeners = new ObjectMap>(); - //list of items/liquids which this block accepts + //Lists of stuff which this block accepts + //note: could be a single HashSet. public Seq acceptedLiquids = new Seq<>(); public Seq acceptedItems = new Seq<>(); + public Seq acceptedPayloads = new Seq<>(); - //List of items/liquids which get dumped + //Lists of stuff which gets dumped public Seq dumpedLiquids = new Seq<>(); public Seq dumpedItems = new Seq<>(); + public Seq dumpedPayloads = new Seq<>(); public boolean replaceBars = true; + public int payloadCapacity = 3; - //Default float data array + /// Default float data array public IntFloatMap defaultData = new IntFloatMap(); + /// When true, shows a table with the data map in the block's display. + public boolean debugTable = true; + public ModularCrafter(String name) { super(name); update = true; solid = true; + sync = true; } @Override @@ -57,6 +72,12 @@ public void setBars() { } } + @Override + public void init(){ + super.init(); + modules.each(m -> m.setup(this)); + } + public static void trigger(ModularCrafter block, ModularCrafterBuild build, Object event){ Seq events = block.listeners.get(event); if(events != null) events.each(c -> c.update(build)); @@ -91,6 +112,10 @@ public static abstract class CrafterModule{ public void update(ModularCrafterBuild build){ } + + public void setup(ModularCrafter block){ + + } } public static abstract class CraftingModule extends CrafterModule{ @@ -166,10 +191,40 @@ public static class ModIN{ public static final float ON = 1, OFF = 0; - public class ModularCrafterBuild extends Building { - + public class ModularCrafterBuild extends PayloadBlockBuild{ public ModularCrafter modular; public IntFloatMap data = new IntFloatMap(); + public PayloadSeq payloads = new PayloadSeq(); + + @Override + public void draw(){ + super.draw(); + + drawPayload(); + } + + @Override + public void display(Table table){ + super.display(table); + + if(debugTable){ + table.row(); + table.table(d -> { + Runnable rebuild = () -> { + d.clearChildren(); + d.table(debug -> { + data.iterator().forEachRemaining(entry -> { + debug.add(entry.key + ": " + StatValues.fixValue(entry.value)).left(); + debug.row(); + }); + }).left(); + d.table().growX(); //ugh + }; + d.update(rebuild); + rebuild.run(); + }).growX(); //It doesn't actually grow for some fucking reason. See ugh above. + } + } @Override public void created() { @@ -190,7 +245,7 @@ public void dumpOutputs(){ public void setPin(int pin, float value){ data.put(pin, value); - }; + } public float getPin(int pin){ return data.get(pin); @@ -206,6 +261,11 @@ public boolean acceptItem(Building source, Item item) { return (this.block.consumesItem(item) || acceptedItems.contains(item)) && this.items.get(item) < this.getMaximumAccepted(item); } + @Override + public boolean acceptPayload(Building source, Payload payload){ + return this.payload == null && acceptedPayloads.contains(payload.content()) && payloads.get(payload.content()) < payloadCapacity; + } + @Override public Building create(Block block, Team team) { modular = (ModularCrafter) block; diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java new file mode 100644 index 0000000..b591b39 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java @@ -0,0 +1,34 @@ +package meld.world.blocks.crafting.modules; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public class StupidConsumeHighestModule extends CrafterModule{ + public int[] requestPins; + /// Pin to provide efficiency on. + public int providePin; + + public StupidConsumeHighestModule(int providePin){ + this.providePin = providePin; + } + + public void update(ModularCrafterBuild build){ + float provide = build.getPin(providePin); + + //Get highest + float max = 0f; + int maxPin = 0; + for(int i : requestPins){ + if(build.getPin(i) > max){ + max = build.getPin(i); + maxPin = i; + } + } + + if(max > provide){ + //Just swap pin contents + build.setPin(maxPin, provide); + build.setPin(providePin, max); + } + + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java new file mode 100644 index 0000000..6b25e88 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java @@ -0,0 +1,54 @@ +package meld.world.blocks.crafting.modules; + +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.type.*; + +public class StupidConsumeItemModule extends CrafterModule{ + public ItemStack[] items; + /// Pin to provide efficiency on. + public int providePin; + /// Pin to store consumption progress. + public int progressPin; + public float time = 60f; + + public float baseEfficiency = 0f; + public float efficiencyIncrease = 1f; + + public StupidConsumeItemModule(int providePin, int progressPin){ + this.providePin = providePin; + this.progressPin = progressPin; + } + + @Override + public void update(ModularCrafterBuild build){ + float provide = build.getPin(providePin); + float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float progress = build.getPin(progressPin); + + if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + + //if efficiency has been used + if(consumed > 0f && build.items.has(items)){ + build.setPin(providePin, baseEfficiency + efficiencyIncrease); + progress += consumed * Time.delta; + + //consumption + while(progress > time && build.items.has(items)){ + + build.items.remove(items); + progress -= time; + } + build.setPin(progressPin, progress); + } + } + + @Override + public void setup(ModularCrafter block){ + block.hasItems = true; + for(ItemStack stack : items){ + block.acceptedItems.add(stack.item); + } + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java new file mode 100644 index 0000000..462a9b5 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java @@ -0,0 +1,50 @@ +package meld.world.blocks.crafting.modules; + +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.type.*; + +public class StupidConsumeLiquidModule extends CrafterModule{ + public LiquidStack[] liquids; + public float baseEfficiency = 0; + public float efficiencyIncrease = 1; + /// Pin to provide efficiency on. + public int providePin; + + public StupidConsumeLiquidModule(int providePin){ + this.providePin = providePin; + } + + @Override + public void update(ModularCrafterBuild build){ + float provide = build.getPin(providePin); + //Get the amount of efficiency that was eaten + float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + + if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + + //if efficiency has been used + if(consumed > 0f){ + float min = 10000f; + for(LiquidStack stack : liquids){ + min = Math.min(build.liquids.get(stack.liquid) / (stack.amount * consumed * Time.delta), min); + } + + if(min > 0.00001f){ + for(LiquidStack stack : liquids){ + build.liquids.remove(stack.liquid, stack.amount * min); + } + build.setPin(providePin, baseEfficiency + efficiencyIncrease * min); + } + } + } + + @Override + public void setup(ModularCrafter block){ + block.hasLiquids = true; + for(LiquidStack stack : liquids){ + block.acceptedLiquids.add(stack.liquid); + } + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java new file mode 100644 index 0000000..a34dd90 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java @@ -0,0 +1,81 @@ +package meld.world.blocks.crafting.modules; + +import arc.math.*; +import arc.math.geom.*; +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.ai.types.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.gen.*; +import mindustry.type.*; +import mindustry.world.blocks.payloads.*; + +import java.util.*; + +public class StupidConsumePayloadModule extends CrafterModule{ + public PayloadStack[] payloads; + /// Pin to store consumption progress. + public int progressPin; + /// Pin to provide efficiency on. + public int providePin; + public float time = 60f; + + public float baseEfficiency = 0f; + public float efficiencyIncrease = 1f; + + public StupidConsumePayloadModule(int providePin, int progressPin){ + this.providePin = providePin; + this.progressPin = progressPin; + } + + @Override + public void update(ModularCrafterBuild build){ + float provide = build.getPin(providePin); + float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float progress = build.getPin(progressPin); + + if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + + if(build.payload != null && contains(payloads, build.payload) && build.moveInPayload()){ + build.payloads.add(build.payload.content()); + build.payload = null; + } + + //if efficiency has been used + if(consumed > 0f && has(build, payloads)){ + build.setPin(providePin, baseEfficiency + efficiencyIncrease); + progress += consumed * Time.delta; + + //consumption + while(progress > time && has(build, payloads)){ + for(PayloadStack stack : payloads){ + build.payloads.remove(stack.item, stack.amount); + } + progress -= time; + } + build.setPin(progressPin, progress); + } + } + + public static boolean contains(PayloadStack[] payloads, Payload pay){ + for(PayloadStack stack : payloads){ + if(stack.item == pay.content()) return true; + } + return false; + } + + public static boolean has(ModularCrafterBuild build, PayloadStack[] payloadStacks){ + return Arrays.stream(payloadStacks).allMatch(stack -> build.payloads.contains(stack)); + } + + @Override + public void setup(ModularCrafter block){ + block.acceptsPayload = true; + block.acceptsUnitPayloads = true; + for(PayloadStack stack : payloads){ + block.acceptedPayloads.add(stack.item); + } + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java b/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java new file mode 100644 index 0000000..6e02711 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java @@ -0,0 +1,26 @@ +package meld.world.blocks.crafting.modules; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public class StupidCraftingModule extends CrafterModule{ + public int[] requestPins; + public int outputPin; + + public StupidCraftingModule(int outputPin){ + this.outputPin = outputPin; + } + + public void update(ModularCrafterBuild build){ + float efficiency = build.timeScale(); + //Sum up the efficiency + for(int i : requestPins) efficiency *= build.getPin(i); + + if(efficiency > 0){ + //Subtract the usable efficiency + for(int i : requestPins) build.setPin(i, build.getPin(i) - efficiency); + } + + build.setPin(outputPin, efficiency); + } +} + diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java new file mode 100644 index 0000000..1f0ea72 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java @@ -0,0 +1,59 @@ +package meld.world.blocks.crafting.modules; + +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.type.*; + +import java.util.*; + +import static java.lang.Math.min; + +public class StupidProduceItemModule extends CrafterModule{ + public ItemStack[] items; + /// Pin used to read production efficiency. + public int efficiencyPin; + /// Pin used to store crafting progress. + public int progressPin; + /// Pin used to store whether this module can output. Assigned every tick. + public int workingPin; + public float time = 60f; + + public StupidProduceItemModule(int workingPin, int efficiencyPin, int progressPin){ + this.efficiencyPin = efficiencyPin; + this.workingPin = workingPin; + this.progressPin = progressPin; + } + + @Override + public void update(ModularCrafterBuild build){ + if(!fits(build, items)){ + build.setPin(workingPin, 0f); + return; + } + build.setPin(workingPin, 1f); + + float progress = build.getPin(progressPin); + progress += build.getPin(efficiencyPin) * Time.delta; + + while(progress > time && fits(build, items)){ + for(ItemStack stack : items){ + build.items.add(stack.item, stack.amount); + } + progress -= time; + } + build.setPin(progressPin, progress); + } + + public static boolean fits(ModularCrafterBuild build, ItemStack[] itemStacks){ + return Arrays.stream(itemStacks).allMatch(stack -> build.items.get(stack.item) + stack.amount <= build.block.itemCapacity); + } + + @Override + public void setup(ModularCrafter block){ + block.hasItems = true; + for(ItemStack stack : items){ + block.dumpedItems.add(stack.item); + } + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java new file mode 100644 index 0000000..6a7db33 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java @@ -0,0 +1,54 @@ +package meld.world.blocks.crafting.modules; + +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.type.*; + +public class StupidProduceLiquidModule extends CrafterModule{ + public LiquidStack[] liquids; + /// Pin used to read production efficiency. + public int efficiencyPin; + /// Pin used to store whether this module can output. Assigned every tick. + public int workingPin; + + public boolean dumpExtraLiquid = false; + + public StupidProduceLiquidModule(int workingPin, int efficiencyPin){ + this.efficiencyPin = efficiencyPin; + this.workingPin = workingPin; + } + + //Entirely GenericCrafter copy-paste, probably Sucks Ass. + @Override + public void update(ModularCrafterBuild build){ + //check full + for(LiquidStack stack : liquids){ + if(build.liquids.get(stack.liquid) < build.block.liquidCapacity - 0.001f){ + //if there's still space left, it's not full for all liquids + build.setPin(workingPin, 1f); + }else if(!dumpExtraLiquid){ + build.setPin(workingPin, 0f); + return; + } + } + + //continuously output based on efficiency + for(LiquidStack stack : liquids){ + build.handleLiquid(build, stack.liquid, + Math.min( + stack.amount * build.getPin(efficiencyPin) * Time.delta, + build.block.liquidCapacity - build.liquids.get(stack.liquid) + ) + ); + } + } + + @Override + public void setup(ModularCrafter block){ + block.hasLiquids = true; + for(LiquidStack stack : liquids){ + block.dumpedLiquids.add(stack.liquid); + } + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java new file mode 100644 index 0000000..67fc73e --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java @@ -0,0 +1,98 @@ +package meld.world.blocks.crafting.modules; + +import arc.*; +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.game.EventType.*; +import mindustry.gen.*; +import mindustry.type.*; +import mindustry.world.*; +import mindustry.world.blocks.payloads.*; + +import java.util.*; + +public class StupidProducePayloadModule extends CrafterModule{ + public PayloadStack[] payloads; + /// Pin used to read production efficiency. + public int efficiencyPin; + /// Pin used to store crafting progress. + public int progressPin; + /// Pin used to store whether this module can output. Assigned every tick. + public int workingPin; + public float time = 60f; + + public StupidProducePayloadModule(int workingPin, int efficiencyPin, int progressPin){ + this.efficiencyPin = efficiencyPin; + this.workingPin = workingPin; + this.progressPin = progressPin; + } + + @Override + public void update(ModularCrafterBuild build){ + if(outputFits(build)){ + //consumption + float progress = build.getPin(progressPin); + progress += build.getPin(efficiencyPin) * Time.delta; + + while(progress > time && outputFits(build)){ + for(PayloadStack stack : payloads){ + build.payloads.add(stack.item, stack.amount); + } + progress -= time; + } + build.setPin(progressPin, progress); + //whether other modules should work + build.setPin(workingPin, 1f); + }else{ + build.setPin(workingPin, 0f); + } + + //output + if(build.payload == null && build.payloads.any()){ + for(PayloadStack stack : payloads){ + if(build.payloads.contains(stack.item)){ + build.payloads.remove(stack.item); + + if(stack.item instanceof UnitType type){ + build.payload = new UnitPayload(type.create(build.team)); + Unit p = ((UnitPayload)build.payload).unit; + /*if(commandPos != null && p.isCommandable()){ + p.command().commandPosition(commandPos); + }*/ + Events.fire(new UnitCreateEvent(p, build)); + }else if(stack.item instanceof Block block){ + build.payload = new BuildPayload(block, build.team); + } + build.payVector.setZero(); + build.payRotation = build.rotdeg(); + break; + } + } + } + + if(build.payload != null && outputs(build.payload)){ + build.moveOutPayload(); + } + } + + public boolean outputs(Payload pay){ + for(PayloadStack stack : payloads){ + if(stack.item == pay.content()) return true; + } + return false; + } + + public boolean outputFits(ModularCrafterBuild build){ + return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.block.itemCapacity); + } + + @Override + public void setup(ModularCrafter block){ + block.rotate = true; + block.outputsPayload = true; + for(PayloadStack stack : payloads){ + block.dumpedPayloads.add(stack.item); + } + } +} From 1fa1c72f8bcaabef84c67307e084667511138628 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:20:35 +0200 Subject: [PATCH 03/24] merge? --- src/meld/world/blocks/crafting/modules/StupidCraftingModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java b/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java index 6e02711..8b7a339 100644 --- a/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java @@ -2,6 +2,7 @@ import meld.world.blocks.crafting.ModularCrafter.*; +//TODO remake this to be the same as the consumption modules public class StupidCraftingModule extends CrafterModule{ public int[] requestPins; public int outputPin; From e0511a38beca32b21455245cb29a62d275eb4d61 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:22:01 +0200 Subject: [PATCH 04/24] merge? --- .../world/blocks/crafting/modules/StupidProduceItemModule.java | 3 +-- .../blocks/crafting/modules/StupidProduceLiquidModule.java | 1 + .../blocks/crafting/modules/StupidProducePayloadModule.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java index 1f0ea72..292ef5f 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java @@ -7,8 +7,7 @@ import java.util.*; -import static java.lang.Math.min; - +//TODO remake this to be the same as the consumption modules public class StupidProduceItemModule extends CrafterModule{ public ItemStack[] items; /// Pin used to read production efficiency. diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java index 6a7db33..a1ea39f 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java @@ -5,6 +5,7 @@ import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; +//TODO remake this to be the same as the consumption modules public class StupidProduceLiquidModule extends CrafterModule{ public LiquidStack[] liquids; /// Pin used to read production efficiency. diff --git a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java index 67fc73e..4fd4b37 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java @@ -12,6 +12,7 @@ import java.util.*; +//TODO remake this to be the same as the consumption modules public class StupidProducePayloadModule extends CrafterModule{ public PayloadStack[] payloads; /// Pin used to read production efficiency. From de4db910edf67938f4967b3b3218b953c0155fe5 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:35:19 +0200 Subject: [PATCH 05/24] make red go away --- src/meld/world/blocks/MeldNode.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/meld/world/blocks/MeldNode.java b/src/meld/world/blocks/MeldNode.java index de904dc..0d9df8c 100644 --- a/src/meld/world/blocks/MeldNode.java +++ b/src/meld/world/blocks/MeldNode.java @@ -17,7 +17,10 @@ public class MeldNodeBuild extends CoreBuild{ public void killed() { /// start melting before killing /// otherwise {@link mindustry.world.Tile#getLinkedTiles(Cons)} won't work - Meld.melting.start(tileOn()); + //Spawn melting overlays on all tiles touching the edges of this block + eachEdge(t -> { + t.setOverlay(Blocks.pebbles); + }); super.killed(); } From 62abe44013745eb029ac4f33c9cc544e22bc70c0 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:36:45 +0200 Subject: [PATCH 06/24] make red go away --- src/meld/world/blocks/MeldNode.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/meld/world/blocks/MeldNode.java b/src/meld/world/blocks/MeldNode.java index 0d9df8c..36a7b4d 100644 --- a/src/meld/world/blocks/MeldNode.java +++ b/src/meld/world/blocks/MeldNode.java @@ -15,14 +15,13 @@ public MeldNode(String name) { public class MeldNodeBuild extends CoreBuild{ @Override public void killed() { + super.killed(); /// start melting before killing /// otherwise {@link mindustry.world.Tile#getLinkedTiles(Cons)} won't work //Spawn melting overlays on all tiles touching the edges of this block eachEdge(t -> { t.setOverlay(Blocks.pebbles); }); - - super.killed(); } } } From e05f6da44a395fe9591c5665efca7779051ed040 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:37:55 +0200 Subject: [PATCH 07/24] make diff go away --- src/meld/world/blocks/MeldNode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/meld/world/blocks/MeldNode.java b/src/meld/world/blocks/MeldNode.java index 36a7b4d..2a3981c 100644 --- a/src/meld/world/blocks/MeldNode.java +++ b/src/meld/world/blocks/MeldNode.java @@ -16,8 +16,6 @@ public class MeldNodeBuild extends CoreBuild{ @Override public void killed() { super.killed(); - /// start melting before killing - /// otherwise {@link mindustry.world.Tile#getLinkedTiles(Cons)} won't work //Spawn melting overlays on all tiles touching the edges of this block eachEdge(t -> { t.setOverlay(Blocks.pebbles); From 00c80d02197cf6d6cc3e57a2f13324517c2cd345 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 14:42:38 +0200 Subject: [PATCH 08/24] make diff go away --- src/meld/core/Melting.java | 78 ----------------------------- src/meld/world/blocks/MeldNode.java | 3 -- 2 files changed, 81 deletions(-) delete mode 100644 src/meld/core/Melting.java diff --git a/src/meld/core/Melting.java b/src/meld/core/Melting.java deleted file mode 100644 index e2de6d4..0000000 --- a/src/meld/core/Melting.java +++ /dev/null @@ -1,78 +0,0 @@ -package meld.core; - -import java.util.*; -import arc.*; -import arc.math.geom.*; -import arc.struct.*; -import arc.util.*; -import meld.content.*; -import mindustry.*; -import mindustry.content.*; -import mindustry.game.*; -import mindustry.world.*; -import mindustry.world.blocks.environment.*; - -// TODO: fix melting stopping when leaving world -// TODO: make a way to add more melting patterns -public class Melting implements Runnable{ - private static final Floor stable = MeldEnvironment.meldCrystal; - private static final Floor unstable = MeldEnvironment.meldCrystalScattered; - private static final Floor melted = MeldEnvironment.meldSwampland; - - private boolean running = false; - private final ObjectSet toMelt = new ObjectSet<>(); - private final ObjectSet toSpread = new ObjectSet<>(); - - public Melting(){ - Events.on(EventType.ResetEvent.class, _it -> running = false); - } - - public void start(Tile start){ - start.getLinkedTiles(toSpread::add); - - if(!running){ - running = true; - run(); - } - } - - @Override - public void run(){ - toMelt - // TODO: this check might not be necessary - .select(it -> it.floor() != melted) - .each(it -> { - it.setFloor(melted); - Fx.smoke.at(it); - - var build = it.build; - if(build != null) build.kill(); - }) - ; - - toMelt.clear(); - toMelt.addAll(toSpread); - toSpread.clear(); - - toMelt.each(it -> - toSpread.addAll( - nearby(it).select(n -> isValid(it, n)) - ) - ); - - if(toMelt.isEmpty() && toSpread.isEmpty()) running = false; - if(running) Time.run(5, this); - } - - private Seq nearby(Tile tile){ - return Seq.with(0, 1, 2, 3) - .map(tile::nearby) - .select(Objects::nonNull) - ; - } - - private boolean isValid(Tile current, Tile nearby){ - if(nearby.floor() == unstable) return true; - return nearby.floor() == stable && current.floor() == stable; - } -} diff --git a/src/meld/world/blocks/MeldNode.java b/src/meld/world/blocks/MeldNode.java index 2a3981c..a96e769 100644 --- a/src/meld/world/blocks/MeldNode.java +++ b/src/meld/world/blocks/MeldNode.java @@ -1,8 +1,5 @@ package meld.world.blocks; -import arc.func.*; -import meld.*; -import meld.core.*; import mindustry.content.Blocks; import mindustry.world.blocks.storage.CoreBlock; From 16644dd886f9092a93dec5365d805fb0f8dbdf63 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 16:59:55 +0200 Subject: [PATCH 09/24] refactor producers to be consistent with other StupidModules --- src/meld/content/MeldBlocks.java | 53 +++++++++---------- .../modules/StupidConsumeAllModule.java | 31 +++++++++++ .../modules/StupidConsumeHighestModule.java | 18 +++---- .../modules/StupidConsumeItemModule.java | 15 +++--- .../modules/StupidConsumeLiquidModule.java | 17 +++--- .../modules/StupidConsumePayloadModule.java | 17 +++--- .../modules/StupidCraftingModule.java | 27 ---------- .../modules/StupidProduceItemModule.java | 22 +++----- .../modules/StupidProduceLiquidModule.java | 29 +++++----- .../modules/StupidProducePayloadModule.java | 20 +++---- 10 files changed, 119 insertions(+), 130 deletions(-) create mode 100644 src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/StupidCraftingModule.java diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 998ddba..5d52c5a 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -11,7 +11,6 @@ import meld.world.blocks.*; import meld.world.blocks.crafting.ModularCrafter; import meld.world.blocks.crafting.RecipeCrafter; -import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.recipe.ItemRecipe; import meld.world.blocks.crafting.recipe.SpoolRecipe; import meld.world.blocks.crafting.modules.*; @@ -669,8 +668,8 @@ public static void load(){ size = 3; modules.addAll( - new StupidCraftingModule(0){{ - requestPins = new int[]{1, 2, 3, 4, 5, 6}; + new StupidConsumeAllModule(0){{ + inputPins = new int[]{1, 2, 3, 4, 5, 6}; }}, new StupidConsumeItemModule(1, -1){{ @@ -685,14 +684,14 @@ public static void load(){ time = 180f; }}, - new StupidProduceItemModule(4, 0, -4){{ + new StupidProduceItemModule(0, -4){{ items = ItemStack.with(Items.titanium, 2); time = 60f; }}, - new StupidProduceLiquidModule(5, 0){{ + new StupidProduceLiquidModule(0){{ liquids = LiquidStack.with(Liquids.cryofluid, 1f); }}, - new StupidProducePayloadModule(6, 0, -6){{ + new StupidProducePayloadModule(0, -6){{ payloads = PayloadStack.with(Blocks.plastaniumWall, 2); time = 60f; }} @@ -713,32 +712,30 @@ public void load(){ size = 2; modules.addAll( - new StupidCraftingModule(0){{ - requestPins = new int[]{1, 2, 3}; - }}, - new StupidProduceItemModule(1, 0, -1){{ + new StupidProduceItemModule( 0, -1){{ items = ItemStack.with(Items.silicon, 1); time = 60f; }}, + new StupidConsumeAllModule(0){{ + inputPins = new int[]{2, 3}; + }}, + new StupidConsumeItemModule(2, -2){{ + items = ItemStack.with(Items.sand, 1); + time = 20f; + }}, + new StupidConsumeHighestModule(3){{ + inputPins = new int[]{4, 5}; + }}, + new StupidConsumeItemModule(4, -4){{ + items = ItemStack.with(Items.coal, 1); + time = 40f; + }}, + new StupidConsumeItemModule(5, -5){{ + items = ItemStack.with(Items.pyratite, 1); + time = 60f; - new StupidConsumeItemModule(2, -2){{ - items = ItemStack.with(Items.sand, 1); - time = 20f; - }}, - - new StupidConsumeHighestModule(3){{ - requestPins = new int[]{4, 5}; - }}, - new StupidConsumeItemModule(4, -4){{ - items = ItemStack.with(Items.coal, 1); - time = 40f; - }}, - new StupidConsumeItemModule(5, -5){{ - items = ItemStack.with(Items.pyratite, 1); - time = 60f; - - efficiencyIncrease = 2.5f; - }} + efficiencyIncrease = 2.5f; + }} ); } }; diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java new file mode 100644 index 0000000..56262d1 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java @@ -0,0 +1,31 @@ +package meld.world.blocks.crafting.modules; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public class StupidConsumeAllModule extends CrafterModule{ + public int[] inputPins; + /// Pin to provide efficiency on. + public int[] outputPins; + + public StupidConsumeAllModule(int... outputPins){ + this.outputPins = outputPins; + } + + public void update(ModularCrafterBuild build){ + float input = 1f; + for(int i : inputPins) input *= build.getPin(i); + + //Find the least consumed efficiency, it signals inactivity + float output = 0f; + for(int o : outputPins) output = Math.max(output, build.getPin(o)); + + //If consuming would give us more efficiency, do so + if(input > output){ + //Subtract the usable efficiency + for(int i : inputPins) build.setPin(i, build.getPin(i) - input); + for(int o : outputPins) build.setPin(o, input); + } + + } +} + diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java index b591b39..377c9f9 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java @@ -3,31 +3,31 @@ import meld.world.blocks.crafting.ModularCrafter.*; public class StupidConsumeHighestModule extends CrafterModule{ - public int[] requestPins; + public int[] inputPins; /// Pin to provide efficiency on. - public int providePin; + public int outputPin; - public StupidConsumeHighestModule(int providePin){ - this.providePin = providePin; + public StupidConsumeHighestModule(int outputPin){ + this.outputPin = outputPin; } public void update(ModularCrafterBuild build){ - float provide = build.getPin(providePin); + float output = build.getPin(outputPin); //Get highest float max = 0f; int maxPin = 0; - for(int i : requestPins){ + for(int i : inputPins){ if(build.getPin(i) > max){ max = build.getPin(i); maxPin = i; } } - if(max > provide){ + if(max > output){ //Just swap pin contents - build.setPin(maxPin, provide); - build.setPin(providePin, max); + build.setPin(maxPin, output); + build.setPin(outputPin, max); } } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java index 6b25e88..6f5c971 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java @@ -8,7 +8,7 @@ public class StupidConsumeItemModule extends CrafterModule{ public ItemStack[] items; /// Pin to provide efficiency on. - public int providePin; + public int outputPin; /// Pin to store consumption progress. public int progressPin; public float time = 60f; @@ -16,22 +16,23 @@ public class StupidConsumeItemModule extends CrafterModule{ public float baseEfficiency = 0f; public float efficiencyIncrease = 1f; - public StupidConsumeItemModule(int providePin, int progressPin){ - this.providePin = providePin; + public StupidConsumeItemModule(int outputPin, int progressPin){ + this.outputPin = outputPin; this.progressPin = progressPin; } @Override public void update(ModularCrafterBuild build){ - float provide = build.getPin(providePin); - float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float output = build.getPin(outputPin); float progress = build.getPin(progressPin); + //Get the amount of efficiency that was eaten + float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); //if efficiency has been used if(consumed > 0f && build.items.has(items)){ - build.setPin(providePin, baseEfficiency + efficiencyIncrease); + build.setPin(outputPin, baseEfficiency + efficiencyIncrease); progress += consumed * Time.delta; //consumption diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java index 462a9b5..c74de96 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java @@ -7,22 +7,23 @@ public class StupidConsumeLiquidModule extends CrafterModule{ public LiquidStack[] liquids; + /// Pin to provide efficiency on. + public int outputPin; + public float baseEfficiency = 0; public float efficiencyIncrease = 1; - /// Pin to provide efficiency on. - public int providePin; - public StupidConsumeLiquidModule(int providePin){ - this.providePin = providePin; + public StupidConsumeLiquidModule(int outputPin){ + this.outputPin = outputPin; } @Override public void update(ModularCrafterBuild build){ - float provide = build.getPin(providePin); + float output = build.getPin(outputPin); //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); //if efficiency has been used if(consumed > 0f){ @@ -35,7 +36,7 @@ public void update(ModularCrafterBuild build){ for(LiquidStack stack : liquids){ build.liquids.remove(stack.liquid, stack.amount * min); } - build.setPin(providePin, baseEfficiency + efficiencyIncrease * min); + build.setPin(outputPin, baseEfficiency + efficiencyIncrease * min); } } } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java index a34dd90..7834ce8 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java @@ -16,27 +16,28 @@ public class StupidConsumePayloadModule extends CrafterModule{ public PayloadStack[] payloads; + /// Pin to provide efficiency on. + public int outputPin; /// Pin to store consumption progress. public int progressPin; - /// Pin to provide efficiency on. - public int providePin; public float time = 60f; public float baseEfficiency = 0f; public float efficiencyIncrease = 1f; - public StupidConsumePayloadModule(int providePin, int progressPin){ - this.providePin = providePin; + public StupidConsumePayloadModule(int outputPin, int progressPin){ + this.outputPin = outputPin; this.progressPin = progressPin; } @Override public void update(ModularCrafterBuild build){ - float provide = build.getPin(providePin); - float consumed = (efficiencyIncrease - provide - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float output = build.getPin(outputPin); + //Get the amount of efficiency that was eaten + float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); float progress = build.getPin(progressPin); - if(provide < baseEfficiency) build.setPin(providePin, baseEfficiency); + if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); if(build.payload != null && contains(payloads, build.payload) && build.moveInPayload()){ build.payloads.add(build.payload.content()); @@ -45,7 +46,7 @@ public void update(ModularCrafterBuild build){ //if efficiency has been used if(consumed > 0f && has(build, payloads)){ - build.setPin(providePin, baseEfficiency + efficiencyIncrease); + build.setPin(outputPin, baseEfficiency + efficiencyIncrease); progress += consumed * Time.delta; //consumption diff --git a/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java b/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java deleted file mode 100644 index 8b7a339..0000000 --- a/src/meld/world/blocks/crafting/modules/StupidCraftingModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package meld.world.blocks.crafting.modules; - -import meld.world.blocks.crafting.ModularCrafter.*; - -//TODO remake this to be the same as the consumption modules -public class StupidCraftingModule extends CrafterModule{ - public int[] requestPins; - public int outputPin; - - public StupidCraftingModule(int outputPin){ - this.outputPin = outputPin; - } - - public void update(ModularCrafterBuild build){ - float efficiency = build.timeScale(); - //Sum up the efficiency - for(int i : requestPins) efficiency *= build.getPin(i); - - if(efficiency > 0){ - //Subtract the usable efficiency - for(int i : requestPins) build.setPin(i, build.getPin(i) - efficiency); - } - - build.setPin(outputPin, efficiency); - } -} - diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java index 292ef5f..b7ff12e 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java @@ -4,36 +4,28 @@ import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; - import java.util.*; -//TODO remake this to be the same as the consumption modules public class StupidProduceItemModule extends CrafterModule{ public ItemStack[] items; - /// Pin used to read production efficiency. - public int efficiencyPin; + /// Pin to consume efficiency from. + public int inputPin; /// Pin used to store crafting progress. public int progressPin; - /// Pin used to store whether this module can output. Assigned every tick. - public int workingPin; public float time = 60f; - public StupidProduceItemModule(int workingPin, int efficiencyPin, int progressPin){ - this.efficiencyPin = efficiencyPin; - this.workingPin = workingPin; + public StupidProduceItemModule(int inputPin, int progressPin){ + this.inputPin = inputPin; this.progressPin = progressPin; } @Override public void update(ModularCrafterBuild build){ - if(!fits(build, items)){ - build.setPin(workingPin, 0f); - return; - } - build.setPin(workingPin, 1f); + if(!fits(build, items)) return; float progress = build.getPin(progressPin); - progress += build.getPin(efficiencyPin) * Time.delta; + progress += build.getPin(inputPin) * build.timeScale() * Time.delta; + build.setPin(inputPin, 0f); while(progress > time && fits(build, items)){ for(ItemStack stack : items){ diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java index a1ea39f..d06d5aa 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java @@ -5,44 +5,45 @@ import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; -//TODO remake this to be the same as the consumption modules public class StupidProduceLiquidModule extends CrafterModule{ public LiquidStack[] liquids; - /// Pin used to read production efficiency. - public int efficiencyPin; - /// Pin used to store whether this module can output. Assigned every tick. - public int workingPin; + /// Pin to consume efficiency from. + public int inputPin; public boolean dumpExtraLiquid = false; - public StupidProduceLiquidModule(int workingPin, int efficiencyPin){ - this.efficiencyPin = efficiencyPin; - this.workingPin = workingPin; + public StupidProduceLiquidModule(int inputPin){ + this.inputPin = inputPin; } //Entirely GenericCrafter copy-paste, probably Sucks Ass. @Override public void update(ModularCrafterBuild build){ //check full + boolean allFull = true; for(LiquidStack stack : liquids){ - if(build.liquids.get(stack.liquid) < build.block.liquidCapacity - 0.001f){ + if(build.liquids.get(stack.liquid) >= build.block.liquidCapacity - 0.001f){ + if(!dumpExtraLiquid) return; + }else{ //if there's still space left, it's not full for all liquids - build.setPin(workingPin, 1f); - }else if(!dumpExtraLiquid){ - build.setPin(workingPin, 0f); - return; + allFull = false; } } + //if there is no space left for any liquid, it can't produce + if(allFull) return; + //continuously output based on efficiency for(LiquidStack stack : liquids){ build.handleLiquid(build, stack.liquid, Math.min( - stack.amount * build.getPin(efficiencyPin) * Time.delta, + stack.amount * build.getPin(inputPin) * build.timeScale() * Time.delta, build.block.liquidCapacity - build.liquids.get(stack.liquid) ) ); } + + build.setPin(inputPin, 0f); } @Override diff --git a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java index 4fd4b37..46c997f 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java @@ -12,29 +12,25 @@ import java.util.*; -//TODO remake this to be the same as the consumption modules public class StupidProducePayloadModule extends CrafterModule{ public PayloadStack[] payloads; - /// Pin used to read production efficiency. - public int efficiencyPin; + /// Pin to consume efficiency from. + public int inputPin; /// Pin used to store crafting progress. public int progressPin; - /// Pin used to store whether this module can output. Assigned every tick. - public int workingPin; public float time = 60f; - public StupidProducePayloadModule(int workingPin, int efficiencyPin, int progressPin){ - this.efficiencyPin = efficiencyPin; - this.workingPin = workingPin; + public StupidProducePayloadModule(int inputPin, int progressPin){ + this.inputPin = inputPin; this.progressPin = progressPin; } @Override public void update(ModularCrafterBuild build){ if(outputFits(build)){ - //consumption float progress = build.getPin(progressPin); - progress += build.getPin(efficiencyPin) * Time.delta; + progress += build.getPin(inputPin) * build.timeScale() * Time.delta; + build.setPin(inputPin, 0f); while(progress > time && outputFits(build)){ for(PayloadStack stack : payloads){ @@ -43,10 +39,6 @@ public void update(ModularCrafterBuild build){ progress -= time; } build.setPin(progressPin, progress); - //whether other modules should work - build.setPin(workingPin, 1f); - }else{ - build.setPin(workingPin, 0f); } //output From 3c1769f62e5ff3edf9ceb712cecca8ff7790bddd Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Thu, 2 Apr 2026 23:06:01 +0200 Subject: [PATCH 10/24] some fixes after testing --- src/meld/content/MeldBlocks.java | 29 ++++++++---------- .../world/blocks/crafting/ModularCrafter.java | 9 ++++-- .../modules/StupidConsumeHighestModule.java | 30 ++++++++++--------- .../modules/StupidProduceLiquidModule.java | 1 + .../modules/StupidProducePayloadModule.java | 2 +- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 5d52c5a..d9c6575 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -668,30 +668,27 @@ public static void load(){ size = 3; modules.addAll( - new StupidConsumeAllModule(0){{ - inputPins = new int[]{1, 2, 3, 4, 5, 6}; - }}, - new StupidConsumeItemModule(1, -1){{ items = ItemStack.with(Items.copper, 2); time = 60f; }}, - new StupidConsumeLiquidModule(2){{ + new StupidProduceItemModule(1, -2){{ + items = ItemStack.with(Items.titanium, 2); + time = 60f; + }}, + /* + new StupidConsumeLiquidModule(2){{ TODO does not work properly liquids = LiquidStack.with(Liquids.water, 1f); }}, + new StupidProduceLiquidModule(5){{ + liquids = LiquidStack.with(Liquids.cryofluid, 1f); + }}, + */ new StupidConsumePayloadModule(3, -3){{ payloads = PayloadStack.with(Blocks.plastaniumWallLarge, 1); time = 180f; }}, - - new StupidProduceItemModule(0, -4){{ - items = ItemStack.with(Items.titanium, 2); - time = 60f; - }}, - new StupidProduceLiquidModule(0){{ - liquids = LiquidStack.with(Liquids.cryofluid, 1f); - }}, - new StupidProducePayloadModule(0, -6){{ + new StupidProducePayloadModule(3, -4){{ payloads = PayloadStack.with(Blocks.plastaniumWall, 2); time = 60f; }} @@ -712,11 +709,11 @@ public void load(){ size = 2; modules.addAll( - new StupidProduceItemModule( 0, -1){{ + new StupidProduceItemModule( 1, -1){{ items = ItemStack.with(Items.silicon, 1); time = 60f; }}, - new StupidConsumeAllModule(0){{ + new StupidConsumeAllModule(1){{ inputPins = new int[]{2, 3}; }}, new StupidConsumeItemModule(2, -2){{ diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index 48230a5..a565e3d 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -26,8 +26,8 @@ public class ModularCrafter extends PayloadBlock{ //Modules updates every update, listeners are for modules with specific events - public Seq modules = new Seq(); - public ObjectMap> listeners = new ObjectMap>(); + public Seq modules = new Seq<>(); + public ObjectMap> listeners = new ObjectMap<>(); //Lists of stuff which this block accepts //note: could be a single HashSet. @@ -108,6 +108,11 @@ public enum Defaults{ } } + @Override + public boolean outputsItems(){ + return !dumpedItems.isEmpty(); + } + public static abstract class CrafterModule{ public void update(ModularCrafterBuild build){ diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java index 377c9f9..c9df4ee 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java @@ -5,29 +5,31 @@ public class StupidConsumeHighestModule extends CrafterModule{ public int[] inputPins; /// Pin to provide efficiency on. - public int outputPin; + public int[] outputPins; - public StupidConsumeHighestModule(int outputPin){ - this.outputPin = outputPin; + public StupidConsumeHighestModule(int... outputPins){ + this.outputPins = outputPins; } public void update(ModularCrafterBuild build){ - float output = build.getPin(outputPin); - - //Get highest - float max = 0f; - int maxPin = 0; + //Get the highest input pin + float input = 0f; + int inputPin = 0; for(int i : inputPins){ - if(build.getPin(i) > max){ - max = build.getPin(i); - maxPin = i; + if(build.getPin(i) > input){ + input = build.getPin(i); + inputPin = i; } } - if(max > output){ + //Find the least consumed output, it signals inactivity + float output = 0f; + for(int o : outputPins) output = Math.max(output, build.getPin(o)); + + if(input > output){ //Just swap pin contents - build.setPin(maxPin, output); - build.setPin(outputPin, max); + build.setPin(inputPin, output); + for(int o : outputPins) build.setPin(o, input); } } diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java index d06d5aa..6978c72 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java @@ -49,6 +49,7 @@ public void update(ModularCrafterBuild build){ @Override public void setup(ModularCrafter block){ block.hasLiquids = true; + block.outputsLiquid = true; for(LiquidStack stack : liquids){ block.dumpedLiquids.add(stack.liquid); } diff --git a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java index 46c997f..334ad8c 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java @@ -77,7 +77,7 @@ public boolean outputs(Payload pay){ } public boolean outputFits(ModularCrafterBuild build){ - return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.block.itemCapacity); + return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.modular.payloadCapacity); } @Override From 6e2b56bd702336e04c393fa0a822349f9d5188e5 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Fri, 3 Apr 2026 09:24:00 +0200 Subject: [PATCH 11/24] attributes, renames --- src/meld/content/MeldBlocks.java | 38 +++++++++------ .../world/blocks/crafting/ModularCrafter.java | 15 ++---- .../crafting/modules/AttributeModule.java | 27 ----------- .../modules/rework/AttributeModule.java | 46 +++++++++++++++++++ .../ConsumeAllModule.java} | 6 +-- .../ConsumeHighestModule.java} | 6 +-- .../ConsumeItemModule.java} | 6 +-- .../ConsumePayloadModule.java} | 12 ++--- .../rework/EfficiencySourceModule.java | 18 ++++++++ .../ProduceItemModule.java} | 6 +-- .../ProducePayloadModule.java} | 6 +-- .../StupidConsumeLiquidModule.java | 2 +- .../StupidProduceLiquidModule.java | 2 +- 13 files changed, 112 insertions(+), 78 deletions(-) delete mode 100644 src/meld/world/blocks/crafting/modules/AttributeModule.java create mode 100644 src/meld/world/blocks/crafting/modules/rework/AttributeModule.java rename src/meld/world/blocks/crafting/modules/{StupidConsumeAllModule.java => rework/ConsumeAllModule.java} (83%) rename src/meld/world/blocks/crafting/modules/{StupidConsumeHighestModule.java => rework/ConsumeHighestModule.java} (83%) rename src/meld/world/blocks/crafting/modules/{StupidConsumeItemModule.java => rework/ConsumeItemModule.java} (90%) rename src/meld/world/blocks/crafting/modules/{StupidConsumePayloadModule.java => rework/ConsumePayloadModule.java} (87%) create mode 100644 src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java rename src/meld/world/blocks/crafting/modules/{StupidProduceItemModule.java => rework/ProduceItemModule.java} (88%) rename src/meld/world/blocks/crafting/modules/{StupidProducePayloadModule.java => rework/ProducePayloadModule.java} (94%) rename src/meld/world/blocks/crafting/modules/{ => rework}/StupidConsumeLiquidModule.java (96%) rename src/meld/world/blocks/crafting/modules/{ => rework}/StupidProduceLiquidModule.java (97%) diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index d9c6575..25e7ecf 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -11,6 +11,7 @@ import meld.world.blocks.*; import meld.world.blocks.crafting.ModularCrafter; import meld.world.blocks.crafting.RecipeCrafter; +import meld.world.blocks.crafting.modules.rework.*; import meld.world.blocks.crafting.recipe.ItemRecipe; import meld.world.blocks.crafting.recipe.SpoolRecipe; import meld.world.blocks.crafting.modules.*; @@ -593,7 +594,7 @@ public static void load(){ dumpedLiquids.addAll(MeldLiquids.pollutantMixture, MeldLiquids.fumes); //Should make all the modules trigger in order - hookAll(BlockEvent.Defaults.proximityUpdate, + /*hookAll(BlockEvent.Defaults.proximityUpdate, new AttributeModule(){{ attribute = MeldAttributes.aetherAttr; baseEfficiency = 0; @@ -610,7 +611,7 @@ public static void load(){ efficiencyPin = 1; }} - ); + );*/ modules.addAll( new ProduceLiquidModule(new LiquidStack(MeldLiquids.pollutantMixture, 1), 0), @@ -668,11 +669,11 @@ public static void load(){ size = 3; modules.addAll( - new StupidConsumeItemModule(1, -1){{ + new ConsumeItemModule(1, -1){{ items = ItemStack.with(Items.copper, 2); time = 60f; }}, - new StupidProduceItemModule(1, -2){{ + new ProduceItemModule(1, -2){{ items = ItemStack.with(Items.titanium, 2); time = 60f; }}, @@ -684,11 +685,11 @@ public static void load(){ liquids = LiquidStack.with(Liquids.cryofluid, 1f); }}, */ - new StupidConsumePayloadModule(3, -3){{ + new ConsumePayloadModule(3, -3){{ payloads = PayloadStack.with(Blocks.plastaniumWallLarge, 1); time = 180f; }}, - new StupidProducePayloadModule(3, -4){{ + new ProducePayloadModule(3, -4){{ payloads = PayloadStack.with(Blocks.plastaniumWall, 2); time = 60f; }} @@ -709,27 +710,34 @@ public void load(){ size = 2; modules.addAll( - new StupidProduceItemModule( 1, -1){{ + new ProduceItemModule( 1, -1){{ items = ItemStack.with(Items.silicon, 1); time = 60f; }}, - new StupidConsumeAllModule(1){{ + new ConsumeAllModule(1){{ inputPins = new int[]{2, 3}; }}, - new StupidConsumeItemModule(2, -2){{ + new ConsumeItemModule(2, -2){{ items = ItemStack.with(Items.sand, 1); time = 20f; }}, - new StupidConsumeHighestModule(3){{ + new ConsumeHighestModule(3){{ inputPins = new int[]{4, 5}; }}, - new StupidConsumeItemModule(4, -4){{ + new ConsumeItemModule(4, -4){{ items = ItemStack.with(Items.coal, 1); time = 40f; }}, - new StupidConsumeItemModule(5, -5){{ + new AttributeModule(5, -5){{ + attribute = Attribute.heat; + baseEfficiency = 0f; + maxBoost = 2f; + }}, + //This is assigned to the same pin as the attribute, and updates after, which means + //it will only "top off" the efficiency that the attributes don't reach. + new ConsumeItemModule(5, -6){{ items = ItemStack.with(Items.pyratite, 1); - time = 60f; + time = 20f; efficiencyIncrease = 2.5f; }} @@ -753,7 +761,7 @@ public void load(){ dumpedItems.add(MeldItems.carbolith); - //Should make all the modules trigger in order + /*//Should make all the modules trigger in order hookAll(BlockEvent.Defaults.proximityUpdate, new AttributeModule(){{ attribute = Attribute.steam; @@ -763,7 +771,7 @@ public void load(){ efficiencyPin = 0; }} - ); + );*/ ItemRecipe carbolith = new ItemRecipe(with(MeldItems.debris, 1), with(MeldItems.carbolith, 1)); diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index a565e3d..7bd643a 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -80,7 +80,7 @@ public void init(){ public static void trigger(ModularCrafter block, ModularCrafterBuild build, Object event){ Seq events = block.listeners.get(event); - if(events != null) events.each(c -> c.update(build)); + if(events != null) events.each(c -> c.on_event(build)); } public void hook(Object event, CrafterModule module){ @@ -92,15 +92,6 @@ public void hook(Object event, CrafterModule module){ events.add(module); } - public void hookAll(Object event, CrafterModule... module){ - Seq events = listeners.get(event); - if(events == null) { - listeners.put(event, Seq.with(module)); - return; - } - events.add(module); - } - public static class BlockEvent{ public enum Defaults{ update, @@ -121,6 +112,10 @@ public void update(ModularCrafterBuild build){ public void setup(ModularCrafter block){ } + + public void on_event(ModularCrafterBuild build){ + + } } public static abstract class CraftingModule extends CrafterModule{ diff --git a/src/meld/world/blocks/crafting/modules/AttributeModule.java b/src/meld/world/blocks/crafting/modules/AttributeModule.java deleted file mode 100644 index 600085e..0000000 --- a/src/meld/world/blocks/crafting/modules/AttributeModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package meld.world.blocks.crafting.modules; - -import arc.util.Log; -import meld.world.blocks.crafting.ModularCrafter; -import mindustry.world.meta.Attribute; - -public class AttributeModule extends ModularCrafter.CrafterModule { - public int efficiencyPin; - - public Attribute attribute; - public float baseEfficiency = 1; - public float boostScale = 1; - public float maxBoost = 1; - public float minEfficiency = -1; - - @Override - public void update(ModularCrafter.ModularCrafterBuild build) { - float efficiency = baseEfficiency + Math.min(maxBoost, boostScale * build.block.sumAttribute(attribute, build.tileX(), build.tileY()) + attribute.env()); - - if(minEfficiency != -1 && efficiency < minEfficiency) { - build.setPin(efficiencyPin, 0); - return; - } - - build.data.put(efficiencyPin, efficiency); - } -} \ No newline at end of file diff --git a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java new file mode 100644 index 0000000..716e1a8 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java @@ -0,0 +1,46 @@ +package meld.world.blocks.crafting.modules.rework; + +import meld.world.blocks.crafting.ModularCrafter; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.world.meta.Attribute; + +public class AttributeModule extends CrafterModule { + /// Pin to provide efficiency on. + public int outputPin; + /// Pin to store the efficiency that should be output. Don't let other modules touch it. + public int storagePin; + + public Attribute attribute; + public float baseEfficiency = 1; + public float boostScale = 1; + public float maxBoost = 1; + /// The minimum attribute efficiency needed for it to affect output. + public float minEfficiency = -1; + + public AttributeModule(int outputPin, int storagePin){ + this.outputPin = outputPin; + this.storagePin = storagePin; + } + + @Override + public void update(ModularCrafter.ModularCrafterBuild build) { + build.setPin(outputPin, build.getPin(storagePin)); + } + + @Override + public void setup(ModularCrafter block){ + block.hook(BlockEvent.Defaults.proximityUpdate, this); + } + + @Override + public void on_event(ModularCrafterBuild build){ + float efficiency = baseEfficiency + Math.min(maxBoost, boostScale * build.block.sumAttribute(attribute, build.tileX(), build.tileY()) + attribute.env()); + + if(efficiency < minEfficiency) { + build.setPin(storagePin, 0); + return; + } + + build.setPin(storagePin, efficiency); + } +} \ No newline at end of file diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java similarity index 83% rename from src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java rename to src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java index 56262d1..282caed 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeAllModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java @@ -1,13 +1,13 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import meld.world.blocks.crafting.ModularCrafter.*; -public class StupidConsumeAllModule extends CrafterModule{ +public class ConsumeAllModule extends CrafterModule{ public int[] inputPins; /// Pin to provide efficiency on. public int[] outputPins; - public StupidConsumeAllModule(int... outputPins){ + public ConsumeAllModule(int... outputPins){ this.outputPins = outputPins; } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java similarity index 83% rename from src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java rename to src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java index c9df4ee..dff687c 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeHighestModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java @@ -1,13 +1,13 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import meld.world.blocks.crafting.ModularCrafter.*; -public class StupidConsumeHighestModule extends CrafterModule{ +public class ConsumeHighestModule extends CrafterModule{ public int[] inputPins; /// Pin to provide efficiency on. public int[] outputPins; - public StupidConsumeHighestModule(int... outputPins){ + public ConsumeHighestModule(int... outputPins){ this.outputPins = outputPins; } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java similarity index 90% rename from src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java rename to src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java index 6f5c971..5d9cd40 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java @@ -1,11 +1,11 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; -public class StupidConsumeItemModule extends CrafterModule{ +public class ConsumeItemModule extends CrafterModule{ public ItemStack[] items; /// Pin to provide efficiency on. public int outputPin; @@ -16,7 +16,7 @@ public class StupidConsumeItemModule extends CrafterModule{ public float baseEfficiency = 0f; public float efficiencyIncrease = 1f; - public StupidConsumeItemModule(int outputPin, int progressPin){ + public ConsumeItemModule(int outputPin, int progressPin){ this.outputPin = outputPin; this.progressPin = progressPin; } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java similarity index 87% rename from src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java rename to src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java index 7834ce8..99036d9 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java @@ -1,20 +1,14 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; -import arc.math.*; -import arc.math.geom.*; import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; -import mindustry.ai.types.*; -import mindustry.content.*; -import mindustry.entities.*; -import mindustry.gen.*; import mindustry.type.*; import mindustry.world.blocks.payloads.*; import java.util.*; -public class StupidConsumePayloadModule extends CrafterModule{ +public class ConsumePayloadModule extends CrafterModule{ public PayloadStack[] payloads; /// Pin to provide efficiency on. public int outputPin; @@ -25,7 +19,7 @@ public class StupidConsumePayloadModule extends CrafterModule{ public float baseEfficiency = 0f; public float efficiencyIncrease = 1f; - public StupidConsumePayloadModule(int outputPin, int progressPin){ + public ConsumePayloadModule(int outputPin, int progressPin){ this.outputPin = outputPin; this.progressPin = progressPin; } diff --git a/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java b/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java new file mode 100644 index 0000000..d631ecf --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java @@ -0,0 +1,18 @@ +package meld.world.blocks.crafting.modules.rework; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public class EfficiencySourceModule extends CrafterModule{ + /// Pin to provide efficiency on. + public int outputPin; + public float baseEfficiency = 1f; + + public EfficiencySourceModule(int outputPin){ + this.outputPin = outputPin; + } + + @Override + public void update(ModularCrafterBuild build){ + build.setPin(outputPin, baseEfficiency); + } +} diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java similarity index 88% rename from src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java rename to src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java index b7ff12e..c48bac2 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import arc.util.*; import meld.world.blocks.crafting.*; @@ -6,7 +6,7 @@ import mindustry.type.*; import java.util.*; -public class StupidProduceItemModule extends CrafterModule{ +public class ProduceItemModule extends CrafterModule{ public ItemStack[] items; /// Pin to consume efficiency from. public int inputPin; @@ -14,7 +14,7 @@ public class StupidProduceItemModule extends CrafterModule{ public int progressPin; public float time = 60f; - public StupidProduceItemModule(int inputPin, int progressPin){ + public ProduceItemModule(int inputPin, int progressPin){ this.inputPin = inputPin; this.progressPin = progressPin; } diff --git a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java similarity index 94% rename from src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java rename to src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java index 334ad8c..565bdd8 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import arc.*; import arc.util.*; @@ -12,7 +12,7 @@ import java.util.*; -public class StupidProducePayloadModule extends CrafterModule{ +public class ProducePayloadModule extends CrafterModule{ public PayloadStack[] payloads; /// Pin to consume efficiency from. public int inputPin; @@ -20,7 +20,7 @@ public class StupidProducePayloadModule extends CrafterModule{ public int progressPin; public float time = 60f; - public StupidProducePayloadModule(int inputPin, int progressPin){ + public ProducePayloadModule(int inputPin, int progressPin){ this.inputPin = inputPin; this.progressPin = progressPin; } diff --git a/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java similarity index 96% rename from src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java rename to src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java index c74de96..4fcb145 100644 --- a/src/meld/world/blocks/crafting/modules/StupidConsumeLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import arc.util.*; import meld.world.blocks.crafting.*; diff --git a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java similarity index 97% rename from src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java rename to src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java index 6978c72..7e8dab9 100644 --- a/src/meld/world/blocks/crafting/modules/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules; +package meld.world.blocks.crafting.modules.rework; import arc.util.*; import meld.world.blocks.crafting.*; From 980b19fa8926ca9938dca5eb103ae569fc998154 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Fri, 3 Apr 2026 14:09:18 +0200 Subject: [PATCH 12/24] more pins everywhere, I can delete ConsumeAllModule --- src/meld/content/MeldBlocks.java | 70 +++++++++++-------- .../world/blocks/crafting/ModularCrafter.java | 6 ++ .../modules/rework/AttributeModule.java | 13 ++-- .../modules/rework/ConsumeAllModule.java | 31 -------- .../modules/rework/ConsumeDiscreteModule.java | 45 ++++++++++++ .../modules/rework/ConsumeHighestModule.java | 16 ++--- .../modules/rework/ConsumeItemModule.java | 42 +++-------- .../modules/rework/ConsumeModule.java | 33 +++++++++ .../modules/rework/ConsumePayloadModule.java | 52 ++++---------- .../rework/EfficiencySourceModule.java | 13 ++-- .../modules/rework/EfficiencyVoidModule.java | 22 ++++++ .../modules/rework/ProduceDiscreteModule.java | 44 ++++++++++++ .../modules/rework/ProduceItemModule.java | 35 +++------- .../modules/rework/ProducePayloadModule.java | 55 ++++++--------- .../rework/StupidConsumeLiquidModule.java | 20 +++--- .../rework/StupidProduceLiquidModule.java | 28 +++----- 16 files changed, 287 insertions(+), 238 deletions(-) delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java create mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java create mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java create mode 100644 src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java create mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 25e7ecf..f2a1967 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -669,13 +669,17 @@ public static void load(){ size = 3; modules.addAll( - new ConsumeItemModule(1, -1){{ + new ConsumeItemModule(1){{ items = ItemStack.with(Items.copper, 2); time = 60f; + + progressPin = -1; }}, - new ProduceItemModule(1, -2){{ + new ProduceItemModule(1){{ items = ItemStack.with(Items.titanium, 2); time = 60f; + + progressPin = -2; }}, /* new StupidConsumeLiquidModule(2){{ TODO does not work properly @@ -685,13 +689,17 @@ public static void load(){ liquids = LiquidStack.with(Liquids.cryofluid, 1f); }}, */ - new ConsumePayloadModule(3, -3){{ + new ConsumePayloadModule(3){{ payloads = PayloadStack.with(Blocks.plastaniumWallLarge, 1); time = 180f; + + progressPin = -3; }}, - new ProducePayloadModule(3, -4){{ + new ProducePayloadModule(3){{ payloads = PayloadStack.with(Blocks.plastaniumWall, 2); time = 60f; + + progressPin = -4; }} ); }}; @@ -710,37 +718,37 @@ public void load(){ size = 2; modules.addAll( - new ProduceItemModule( 1, -1){{ + new ProduceItemModule( 1, 2){{ items = ItemStack.with(Items.silicon, 1); time = 60f; + progressPin = 0; }}, - new ConsumeAllModule(1){{ - inputPins = new int[]{2, 3}; + //TODO sand consumption does not scale. + new ConsumeItemModule(1){{ + items = ItemStack.with(Items.sand, 1); + time = 20f; + progressPin = -1; }}, - new ConsumeItemModule(2, -2){{ - items = ItemStack.with(Items.sand, 1); - time = 20f; - }}, - new ConsumeHighestModule(3){{ - inputPins = new int[]{4, 5}; - }}, - new ConsumeItemModule(4, -4){{ - items = ItemStack.with(Items.coal, 1); - time = 40f; - }}, - new AttributeModule(5, -5){{ - attribute = Attribute.heat; - baseEfficiency = 0f; - maxBoost = 2f; - }}, - //This is assigned to the same pin as the attribute, and updates after, which means - //it will only "top off" the efficiency that the attributes don't reach. - new ConsumeItemModule(5, -6){{ - items = ItemStack.with(Items.pyratite, 1); - time = 20f; - - efficiencyIncrease = 2.5f; - }} + new AttributeModule(2){{ + attribute = Attribute.heat; + baseEfficiency = 0f; + maxBoost = 2f; + storagePin = -2; + }}, + //This is assigned to the same pin as the attribute, and updates after, + //which means it will only "top off" the efficiency that the attributes don't reach. + new ConsumeItemModule(2){{ + items = ItemStack.with(Items.pyratite, 1); + efficiencyIncrease = 2.5f; + time = 20f; + progressPin = -3; + }}, + //Same as above, but will also cover for the pyratite consumer. + new ConsumeItemModule(2){{ + items = ItemStack.with(Items.coal, 1); + time = 40f; + progressPin = -4; + }} ); } }; diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index 7bd643a..5aa1e8d 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -116,6 +116,8 @@ public void setup(ModularCrafter block){ public void on_event(ModularCrafterBuild build){ } + + } public static abstract class CraftingModule extends CrafterModule{ @@ -247,6 +249,10 @@ public void setPin(int pin, float value){ data.put(pin, value); } + public void setPins(int[] pins, float value){ + for(int i : pins) setPin(i, value); + } + public float getPin(int pin){ return data.get(pin); } diff --git a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java index 716e1a8..f88d444 100644 --- a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java @@ -5,8 +5,8 @@ import mindustry.world.meta.Attribute; public class AttributeModule extends CrafterModule { - /// Pin to provide efficiency on. - public int outputPin; + /// Pins to provide efficiency on. + public int[] outputPins; /// Pin to store the efficiency that should be output. Don't let other modules touch it. public int storagePin; @@ -17,14 +17,13 @@ public class AttributeModule extends CrafterModule { /// The minimum attribute efficiency needed for it to affect output. public float minEfficiency = -1; - public AttributeModule(int outputPin, int storagePin){ - this.outputPin = outputPin; - this.storagePin = storagePin; + public AttributeModule(int... outputPins){ + this.outputPins = outputPins; } @Override - public void update(ModularCrafter.ModularCrafterBuild build) { - build.setPin(outputPin, build.getPin(storagePin)); + public void update(ModularCrafterBuild build){ + for(int o : outputPins) build.setPin(o, build.getPin(storagePin)); } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java deleted file mode 100644 index 282caed..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeAllModule.java +++ /dev/null @@ -1,31 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.ModularCrafter.*; - -public class ConsumeAllModule extends CrafterModule{ - public int[] inputPins; - /// Pin to provide efficiency on. - public int[] outputPins; - - public ConsumeAllModule(int... outputPins){ - this.outputPins = outputPins; - } - - public void update(ModularCrafterBuild build){ - float input = 1f; - for(int i : inputPins) input *= build.getPin(i); - - //Find the least consumed efficiency, it signals inactivity - float output = 0f; - for(int o : outputPins) output = Math.max(output, build.getPin(o)); - - //If consuming would give us more efficiency, do so - if(input > output){ - //Subtract the usable efficiency - for(int i : inputPins) build.setPin(i, build.getPin(i) - input); - for(int o : outputPins) build.setPin(o, input); - } - - } -} - diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java new file mode 100644 index 0000000..f903be3 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java @@ -0,0 +1,45 @@ +package meld.world.blocks.crafting.modules.rework; + +import arc.util.*; +import meld.world.blocks.crafting.ModularCrafter.*; + +public abstract class ConsumeDiscreteModule extends ConsumeModule{ + /// Pin to store consumption progress. + public int progressPin; + public float time = 60f; + + public float baseEfficiency = 0f; + public float efficiencyIncrease = 1f; + + public ConsumeDiscreteModule(int... outputPins){ + super(outputPins); + } + + public abstract boolean canConsume(ModularCrafterBuild build); + + public abstract void consume(ModularCrafterBuild build); + + @Override + public void update(ModularCrafterBuild build){ + float current = getCurrent(build); + //Get the amount of efficiency that was eaten + float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float output = Math.max(baseEfficiency, current); + + //if efficiency has been used + if(consumed > 0f && canConsume(build)){ + float progress = build.getPin(progressPin); + progress += consumed * Time.delta; + output = baseEfficiency + efficiencyIncrease; + + //consumption + while(progress > time && canConsume(build)){ + consume(build); + progress -= time; + } + build.setPin(progressPin, progress); + } + + build.setPins(outputPins, output); + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java index dff687c..06ec172 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java @@ -2,13 +2,12 @@ import meld.world.blocks.crafting.ModularCrafter.*; -public class ConsumeHighestModule extends CrafterModule{ +/// Will only consume from the pin with the highest efficiency. +public class ConsumeHighestModule extends ConsumeModule{ public int[] inputPins; - /// Pin to provide efficiency on. - public int[] outputPins; public ConsumeHighestModule(int... outputPins){ - this.outputPins = outputPins; + super(outputPins); } public void update(ModularCrafterBuild build){ @@ -23,13 +22,12 @@ public void update(ModularCrafterBuild build){ } //Find the least consumed output, it signals inactivity - float output = 0f; - for(int o : outputPins) output = Math.max(output, build.getPin(o)); + float current = getCurrent(build); - if(input > output){ + if(input > current){ //Just swap pin contents - build.setPin(inputPin, output); - for(int o : outputPins) build.setPin(o, input); + build.setPin(inputPin, current); + build.setPins(outputPins, input); } } diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java index 5d9cd40..608fbf3 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java @@ -1,48 +1,24 @@ package meld.world.blocks.crafting.modules.rework; -import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; -public class ConsumeItemModule extends CrafterModule{ +public class ConsumeItemModule extends ConsumeDiscreteModule{ public ItemStack[] items; - /// Pin to provide efficiency on. - public int outputPin; - /// Pin to store consumption progress. - public int progressPin; - public float time = 60f; - public float baseEfficiency = 0f; - public float efficiencyIncrease = 1f; - - public ConsumeItemModule(int outputPin, int progressPin){ - this.outputPin = outputPin; - this.progressPin = progressPin; + public ConsumeItemModule(int... outputPins){ + super(outputPins); } @Override - public void update(ModularCrafterBuild build){ - float output = build.getPin(outputPin); - float progress = build.getPin(progressPin); - //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - - if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); - - //if efficiency has been used - if(consumed > 0f && build.items.has(items)){ - build.setPin(outputPin, baseEfficiency + efficiencyIncrease); - progress += consumed * Time.delta; - - //consumption - while(progress > time && build.items.has(items)){ + public boolean canConsume(ModularCrafterBuild build){ + return build.items.has(items); + } - build.items.remove(items); - progress -= time; - } - build.setPin(progressPin, progress); - } + @Override + public void consume(ModularCrafterBuild build){ + build.items.remove(items); } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java new file mode 100644 index 0000000..7aeeedd --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java @@ -0,0 +1,33 @@ +package meld.world.blocks.crafting.modules.rework; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public abstract class ConsumeModule extends CrafterModule{ + /// Pins to provide efficiency on. + public int[] outputPins; + /// Makes the module ignore other pins that are full until no more can be output. + public boolean dumpExtra = false; + + public ConsumeModule(int... outputPins){ + this.outputPins = outputPins; + } + + public float getCurrent(ModularCrafterBuild build){ + if(!dumpExtra){ + //Find the least consumed output, it signals inactivity + float max = 0f; + for(int i : outputPins){ + max = Math.max(max, build.getPin(i)); + } + return max; + }else{ + //If we don't care about a few full inputs, pick the minimum instead + float min = 0f; + for(int i : outputPins){ + min = Math.min(min, build.getPin(i)); + } + return min; + } + + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java index 99036d9..eab1869 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java @@ -1,6 +1,5 @@ package meld.world.blocks.crafting.modules.rework; -import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; @@ -8,63 +7,42 @@ import java.util.*; -public class ConsumePayloadModule extends CrafterModule{ +public class ConsumePayloadModule extends ConsumeDiscreteModule{ public PayloadStack[] payloads; - /// Pin to provide efficiency on. - public int outputPin; - /// Pin to store consumption progress. - public int progressPin; - public float time = 60f; - public float baseEfficiency = 0f; - public float efficiencyIncrease = 1f; - - public ConsumePayloadModule(int outputPin, int progressPin){ - this.outputPin = outputPin; - this.progressPin = progressPin; + public ConsumePayloadModule(int... outputPins){ + super(outputPins); } @Override public void update(ModularCrafterBuild build){ - float output = build.getPin(outputPin); - //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - float progress = build.getPin(progressPin); - - if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); + super.update(build); - if(build.payload != null && contains(payloads, build.payload) && build.moveInPayload()){ + if(build.payload != null && wants(build.payload) && build.moveInPayload()){ build.payloads.add(build.payload.content()); build.payload = null; } + } - //if efficiency has been used - if(consumed > 0f && has(build, payloads)){ - build.setPin(outputPin, baseEfficiency + efficiencyIncrease); - progress += consumed * Time.delta; + @Override + public boolean canConsume(ModularCrafterBuild build){ + return Arrays.stream(payloads).allMatch(stack -> build.payloads.contains(stack)); + } - //consumption - while(progress > time && has(build, payloads)){ - for(PayloadStack stack : payloads){ - build.payloads.remove(stack.item, stack.amount); - } - progress -= time; - } - build.setPin(progressPin, progress); + @Override + public void consume(ModularCrafterBuild build){ + for(PayloadStack stack : payloads){ + build.payloads.remove(stack.item, stack.amount); } } - public static boolean contains(PayloadStack[] payloads, Payload pay){ + public boolean wants(Payload pay){ for(PayloadStack stack : payloads){ if(stack.item == pay.content()) return true; } return false; } - public static boolean has(ModularCrafterBuild build, PayloadStack[] payloadStacks){ - return Arrays.stream(payloadStacks).allMatch(stack -> build.payloads.contains(stack)); - } - @Override public void setup(ModularCrafter block){ block.acceptsPayload = true; diff --git a/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java b/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java index d631ecf..b73b9f2 100644 --- a/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java @@ -2,17 +2,20 @@ import meld.world.blocks.crafting.ModularCrafter.*; +/// When you have multiple modules draw from the same pin, the first module in +/// the update order to craft something will block the rest from working. +/// This module is made for when you need this behavior, but without a shared input. public class EfficiencySourceModule extends CrafterModule{ - /// Pin to provide efficiency on. - public int outputPin; + /// Pins to provide efficiency on. + public int[] outputPins; public float baseEfficiency = 1f; - public EfficiencySourceModule(int outputPin){ - this.outputPin = outputPin; + public EfficiencySourceModule(int... outputPins){ + this.outputPins = outputPins; } @Override public void update(ModularCrafterBuild build){ - build.setPin(outputPin, baseEfficiency); + for(int o : outputPins) build.setPin(o, baseEfficiency); } } diff --git a/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java b/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java new file mode 100644 index 0000000..1ca1e57 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java @@ -0,0 +1,22 @@ +package meld.world.blocks.crafting.modules.rework; + +import arc.util.*; +import meld.world.blocks.crafting.ModularCrafter.*; + +/// Can be used to make blocks consume, regardless of whether they're doing something. +/// To do this, you can place it after a producer module, and make it consume from its input pin. +public class EfficiencyVoidModule extends CrafterModule{ + /// Pins to consume efficiency of. + public int[] inputPins; + /// The amount of efficiency consumed. + public float amount = Float.POSITIVE_INFINITY; + + public EfficiencyVoidModule(int... inputPins){ + this.inputPins = inputPins; + } + + @Override + public void update(ModularCrafterBuild build){ + for(int i : inputPins) build.setPin(i, Math.max(0f, build.getPin(i) - amount * build.timeScale() * Time.delta)); + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java new file mode 100644 index 0000000..09c4a80 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java @@ -0,0 +1,44 @@ +package meld.world.blocks.crafting.modules.rework; + +import arc.util.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import mindustry.type.*; + +import java.util.*; + +public abstract class ProduceDiscreteModule extends CrafterModule{ + /// Pins to consume efficiency from. + public int[] inputPins; + /// Pin used to store crafting progress. + public int progressPin; + public float time = 60f; + + public ProduceDiscreteModule(int... inputPins){ + this.inputPins = inputPins; + } + + @Override + public void update(ModularCrafterBuild build){ + if(!canOutput(build)) return; + + float input = 1f; + for(int i : inputPins) input *= build.getPin(i); + + //After summing, subtract the final efficiency + for(int i : inputPins) build.setPin(i, build.getPin(i) - input); + + float progress = build.getPin(progressPin); + progress += input * build.timeScale() * Time.delta; + + while(progress > time && canOutput(build)){ + output(build); + progress -= time; + } + build.setPin(progressPin, progress); + + } + + public abstract boolean canOutput(ModularCrafterBuild build); + + public abstract void output(ModularCrafterBuild build); +} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java index c48bac2..478b7df 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java @@ -6,38 +6,23 @@ import mindustry.type.*; import java.util.*; -public class ProduceItemModule extends CrafterModule{ +public class ProduceItemModule extends ProduceDiscreteModule{ public ItemStack[] items; - /// Pin to consume efficiency from. - public int inputPin; - /// Pin used to store crafting progress. - public int progressPin; - public float time = 60f; - public ProduceItemModule(int inputPin, int progressPin){ - this.inputPin = inputPin; - this.progressPin = progressPin; + public ProduceItemModule(int... inputPins){ + super(inputPins); } @Override - public void update(ModularCrafterBuild build){ - if(!fits(build, items)) return; - - float progress = build.getPin(progressPin); - progress += build.getPin(inputPin) * build.timeScale() * Time.delta; - build.setPin(inputPin, 0f); - - while(progress > time && fits(build, items)){ - for(ItemStack stack : items){ - build.items.add(stack.item, stack.amount); - } - progress -= time; - } - build.setPin(progressPin, progress); + public boolean canOutput(ModularCrafterBuild build){ + return Arrays.stream(items).allMatch(stack -> build.items.get(stack.item) + stack.amount <= build.block.itemCapacity); } - public static boolean fits(ModularCrafterBuild build, ItemStack[] itemStacks){ - return Arrays.stream(itemStacks).allMatch(stack -> build.items.get(stack.item) + stack.amount <= build.block.itemCapacity); + @Override + public void output(ModularCrafterBuild build){ + for(ItemStack stack : items){ + build.items.add(stack.item, stack.amount); + } } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java index 565bdd8..036f9bc 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java @@ -1,7 +1,6 @@ package meld.world.blocks.crafting.modules.rework; import arc.*; -import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.game.EventType.*; @@ -12,36 +11,18 @@ import java.util.*; -public class ProducePayloadModule extends CrafterModule{ +public class ProducePayloadModule extends ProduceDiscreteModule{ public PayloadStack[] payloads; - /// Pin to consume efficiency from. - public int inputPin; - /// Pin used to store crafting progress. - public int progressPin; - public float time = 60f; - public ProducePayloadModule(int inputPin, int progressPin){ - this.inputPin = inputPin; - this.progressPin = progressPin; + public ProducePayloadModule(int... inputPins){ + super(inputPins); } @Override public void update(ModularCrafterBuild build){ - if(outputFits(build)){ - float progress = build.getPin(progressPin); - progress += build.getPin(inputPin) * build.timeScale() * Time.delta; - build.setPin(inputPin, 0f); + super.update(build); - while(progress > time && outputFits(build)){ - for(PayloadStack stack : payloads){ - build.payloads.add(stack.item, stack.amount); - } - progress -= time; - } - build.setPin(progressPin, progress); - } - - //output + //dump payloads if(build.payload == null && build.payloads.any()){ for(PayloadStack stack : payloads){ if(build.payloads.contains(stack.item)){ @@ -50,9 +31,9 @@ public void update(ModularCrafterBuild build){ if(stack.item instanceof UnitType type){ build.payload = new UnitPayload(type.create(build.team)); Unit p = ((UnitPayload)build.payload).unit; - /*if(commandPos != null && p.isCommandable()){ - p.command().commandPosition(commandPos); - }*/ + /*if(commandPos != null && p.isCommandable()){ + p.command().commandPosition(commandPos); + }*/ Events.fire(new UnitCreateEvent(p, build)); }else if(stack.item instanceof Block block){ build.payload = new BuildPayload(block, build.team); @@ -64,22 +45,30 @@ public void update(ModularCrafterBuild build){ } } - if(build.payload != null && outputs(build.payload)){ + if(build.payload != null && shouldDump(build.payload)){ build.moveOutPayload(); } } - public boolean outputs(Payload pay){ + @Override + public boolean canOutput(ModularCrafterBuild build){ + return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.modular.payloadCapacity); + } + + @Override + public void output(ModularCrafterBuild build){ + for(PayloadStack stack : payloads){ + build.payloads.add(stack.item, stack.amount); + } + } + + public boolean shouldDump(Payload pay){ for(PayloadStack stack : payloads){ if(stack.item == pay.content()) return true; } return false; } - public boolean outputFits(ModularCrafterBuild build){ - return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.modular.payloadCapacity); - } - @Override public void setup(ModularCrafter block){ block.rotate = true; diff --git a/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java index 4fcb145..a310506 100644 --- a/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java @@ -5,25 +5,23 @@ import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; -public class StupidConsumeLiquidModule extends CrafterModule{ +public class StupidConsumeLiquidModule extends ConsumeModule{ public LiquidStack[] liquids; - /// Pin to provide efficiency on. - public int outputPin; public float baseEfficiency = 0; public float efficiencyIncrease = 1; - public StupidConsumeLiquidModule(int outputPin){ - this.outputPin = outputPin; + public StupidConsumeLiquidModule(int... outputPins){ + super(outputPins); } @Override public void update(ModularCrafterBuild build){ - float output = build.getPin(outputPin); + //Find the least consumed output, it signals inactivity + float current = getCurrent(build); //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - output - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - - if(output < baseEfficiency) build.setPin(outputPin, baseEfficiency); + float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); + float output = Math.max(baseEfficiency, current); //if efficiency has been used if(consumed > 0f){ @@ -36,9 +34,11 @@ public void update(ModularCrafterBuild build){ for(LiquidStack stack : liquids){ build.liquids.remove(stack.liquid, stack.amount * min); } - build.setPin(outputPin, baseEfficiency + efficiencyIncrease * min); + output = baseEfficiency + efficiencyIncrease * min; } } + + build.setPins(outputPins, output); } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java index 7e8dab9..f439342 100644 --- a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java @@ -8,42 +8,36 @@ public class StupidProduceLiquidModule extends CrafterModule{ public LiquidStack[] liquids; /// Pin to consume efficiency from. - public int inputPin; + public int[] inputPins; - public boolean dumpExtraLiquid = false; - - public StupidProduceLiquidModule(int inputPin){ - this.inputPin = inputPin; + public StupidProduceLiquidModule(int... inputPins){ + this.inputPins = inputPins; } - //Entirely GenericCrafter copy-paste, probably Sucks Ass. @Override public void update(ModularCrafterBuild build){ + float input = 1f; + for(int i : inputPins) input *= build.getPin(i); + + //After summing, subtract the final efficiency + for(int i : inputPins) build.setPin(i, build.getPin(i) - input); + //check full - boolean allFull = true; for(LiquidStack stack : liquids){ if(build.liquids.get(stack.liquid) >= build.block.liquidCapacity - 0.001f){ - if(!dumpExtraLiquid) return; - }else{ - //if there's still space left, it's not full for all liquids - allFull = false; + return; } } - //if there is no space left for any liquid, it can't produce - if(allFull) return; - //continuously output based on efficiency for(LiquidStack stack : liquids){ build.handleLiquid(build, stack.liquid, Math.min( - stack.amount * build.getPin(inputPin) * build.timeScale() * Time.delta, + stack.amount * input * build.timeScale() * Time.delta, build.block.liquidCapacity - build.liquids.get(stack.liquid) ) ); } - - build.setPin(inputPin, 0f); } @Override From 281d80028dff3435a4f993ddd61e15de9396b770 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Fri, 3 Apr 2026 15:43:00 +0200 Subject: [PATCH 13/24] consumption fixes --- src/meld/content/MeldBlocks.java | 36 +++++-------------- .../modules/rework/AttributeModule.java | 2 +- .../modules/rework/ConsumeDiscreteModule.java | 2 ++ .../modules/rework/ConsumeModule.java | 4 +-- .../modules/rework/ProduceDiscreteModule.java | 16 ++++----- .../modules/rework/ProduceModule.java | 23 ++++++++++++ .../rework/StupidProduceLiquidModule.java | 15 +++----- 7 files changed, 48 insertions(+), 50 deletions(-) create mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceModule.java diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index f2a1967..a781400 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -669,37 +669,20 @@ public static void load(){ size = 3; modules.addAll( - new ConsumeItemModule(1){{ - items = ItemStack.with(Items.copper, 2); - time = 60f; - - progressPin = -1; - }}, - new ProduceItemModule(1){{ + new ProduceItemModule(1, 2){{ items = ItemStack.with(Items.titanium, 2); - time = 60f; + time = 30f; - progressPin = -2; - }}, - /* - new StupidConsumeLiquidModule(2){{ TODO does not work properly - liquids = LiquidStack.with(Liquids.water, 1f); + progressPin = 0; }}, - new StupidProduceLiquidModule(5){{ - liquids = LiquidStack.with(Liquids.cryofluid, 1f); - }}, - */ - new ConsumePayloadModule(3){{ - payloads = PayloadStack.with(Blocks.plastaniumWallLarge, 1); - time = 180f; + new ConsumeItemModule(1){{ + items = ItemStack.with(Items.copper, 2); + time = 30f; - progressPin = -3; + progressPin = -1; }}, - new ProducePayloadModule(3){{ - payloads = PayloadStack.with(Blocks.plastaniumWall, 2); - time = 60f; - - progressPin = -4; + new EfficiencySourceModule(2){{ + baseEfficiency = 3f; }} ); }}; @@ -723,7 +706,6 @@ public void load(){ time = 60f; progressPin = 0; }}, - //TODO sand consumption does not scale. new ConsumeItemModule(1){{ items = ItemStack.with(Items.sand, 1); time = 20f; diff --git a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java index f88d444..cb4a4b2 100644 --- a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java @@ -23,7 +23,7 @@ public AttributeModule(int... outputPins){ @Override public void update(ModularCrafterBuild build){ - for(int o : outputPins) build.setPin(o, build.getPin(storagePin)); + for(int i : outputPins) build.setPin(i, Math.max(build.getPin(storagePin), build.getPin(i))); } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java index f903be3..5267596 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java @@ -26,6 +26,8 @@ public void update(ModularCrafterBuild build){ float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); float output = Math.max(baseEfficiency, current); + build.setPin(outputPins[0] + 20, current); + //if efficiency has been used if(consumed > 0f && canConsume(build)){ float progress = build.getPin(progressPin); diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java index 7aeeedd..404ebf3 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java @@ -15,14 +15,14 @@ public ConsumeModule(int... outputPins){ public float getCurrent(ModularCrafterBuild build){ if(!dumpExtra){ //Find the least consumed output, it signals inactivity - float max = 0f; + float max = Float.NEGATIVE_INFINITY; for(int i : outputPins){ max = Math.max(max, build.getPin(i)); } return max; }else{ //If we don't care about a few full inputs, pick the minimum instead - float min = 0f; + float min = Float.POSITIVE_INFINITY; for(int i : outputPins){ min = Math.min(min, build.getPin(i)); } diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java index 09c4a80..6234de6 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java @@ -6,29 +6,25 @@ import java.util.*; -public abstract class ProduceDiscreteModule extends CrafterModule{ - /// Pins to consume efficiency from. - public int[] inputPins; +public abstract class ProduceDiscreteModule extends ProduceModule{ + /// Pin used to store crafting progress. public int progressPin; public float time = 60f; public ProduceDiscreteModule(int... inputPins){ - this.inputPins = inputPins; + super(inputPins); } @Override public void update(ModularCrafterBuild build){ if(!canOutput(build)) return; - float input = 1f; - for(int i : inputPins) input *= build.getPin(i); - - //After summing, subtract the final efficiency - for(int i : inputPins) build.setPin(i, build.getPin(i) - input); + //Sum efficiencies and timescale up + float input = takeEfficiency(build); float progress = build.getPin(progressPin); - progress += input * build.timeScale() * Time.delta; + progress += input * Time.delta; while(progress > time && canOutput(build)){ output(build); diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceModule.java new file mode 100644 index 0000000..4de9e23 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceModule.java @@ -0,0 +1,23 @@ +package meld.world.blocks.crafting.modules.rework; + +import meld.world.blocks.crafting.ModularCrafter.*; + +public abstract class ProduceModule extends CrafterModule{ + /// Pins to consume efficiency from. + public int[] inputPins; + + public ProduceModule(int... inputPins){ + this.inputPins = inputPins; + } + + public float takeEfficiency(ModularCrafterBuild build){ + //Sum efficiencies and timescale up + float input = build.timeScale(); + for(int i : inputPins) input *= build.getPin(i); + + //After summing, subtract the final efficiency + for(int i : inputPins) build.setPin(i, build.getPin(i) - input); + + return input; + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java index f439342..a2e04ee 100644 --- a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java @@ -5,23 +5,15 @@ import meld.world.blocks.crafting.ModularCrafter.*; import mindustry.type.*; -public class StupidProduceLiquidModule extends CrafterModule{ +public class StupidProduceLiquidModule extends ProduceModule{ public LiquidStack[] liquids; - /// Pin to consume efficiency from. - public int[] inputPins; public StupidProduceLiquidModule(int... inputPins){ - this.inputPins = inputPins; + super(inputPins); } @Override public void update(ModularCrafterBuild build){ - float input = 1f; - for(int i : inputPins) input *= build.getPin(i); - - //After summing, subtract the final efficiency - for(int i : inputPins) build.setPin(i, build.getPin(i) - input); - //check full for(LiquidStack stack : liquids){ if(build.liquids.get(stack.liquid) >= build.block.liquidCapacity - 0.001f){ @@ -29,6 +21,9 @@ public void update(ModularCrafterBuild build){ } } + //Sum efficiencies and timescale up + float input = takeEfficiency(build); + //continuously output based on efficiency for(LiquidStack stack : liquids){ build.handleLiquid(build, stack.liquid, From cafe8fc36d1f4588c6f1e4850875df92c04e2f91 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Fri, 3 Apr 2026 22:51:23 +0200 Subject: [PATCH 14/24] kill everything else in the project, also heat production --- src/meld/content/MeldBlocks.java | 44 +++++++++------- .../world/blocks/crafting/ModularCrafter.java | 50 ++++++++++++++----- .../crafting/modules/ConsumeLiquidModule.java | 41 --------------- .../crafting/modules/ProduceLiquidModule.java | 22 -------- .../modules/rework/ConsumeHighestModule.java | 1 + .../modules/rework/ConsumeItemModule.java | 1 + ...idModule.java => ConsumeLiquidModule.java} | 5 +- .../modules/rework/ConsumePayloadModule.java | 1 + .../modules/rework/ProduceHeatModule.java | 28 +++++++++++ .../modules/rework/ProduceItemModule.java | 11 +++- ...idModule.java => ProduceLiquidModule.java} | 13 +++-- .../modules/rework/ProducePayloadModule.java | 1 + .../{ => base}/ConsumeDiscreteModule.java | 4 +- .../rework/{ => base}/ConsumeModule.java | 2 +- .../{ => base}/ProduceDiscreteModule.java | 5 +- .../rework/{ => base}/ProduceModule.java | 2 +- 16 files changed, 122 insertions(+), 109 deletions(-) delete mode 100644 src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java rename src/meld/world/blocks/crafting/modules/rework/{StupidConsumeLiquidModule.java => ConsumeLiquidModule.java} (90%) create mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java rename src/meld/world/blocks/crafting/modules/rework/{StupidProduceLiquidModule.java => ProduceLiquidModule.java} (78%) rename src/meld/world/blocks/crafting/modules/rework/{ => base}/ConsumeDiscreteModule.java (93%) rename src/meld/world/blocks/crafting/modules/rework/{ => base}/ConsumeModule.java (94%) rename src/meld/world/blocks/crafting/modules/rework/{ => base}/ProduceDiscreteModule.java (90%) rename src/meld/world/blocks/crafting/modules/rework/{ => base}/ProduceModule.java (91%) diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index a781400..d4ff185 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -613,10 +613,10 @@ public static void load(){ }} );*/ - modules.addAll( + /*modules.addAll( new ProduceLiquidModule(new LiquidStack(MeldLiquids.pollutantMixture, 1), 0), new ProduceLiquidModule(new LiquidStack(MeldLiquids.fumes, 1), 1) - ); + );*/ }}; elementalBlaster = new BeamDrill("elemental-blaster"){{ @@ -669,20 +669,28 @@ public static void load(){ size = 3; modules.addAll( - new ProduceItemModule(1, 2){{ - items = ItemStack.with(Items.titanium, 2); - time = 30f; - - progressPin = 0; + new ProduceHeatModule(0){{ + heatOutput = 10; }}, - new ConsumeItemModule(1){{ - items = ItemStack.with(Items.copper, 2); - time = 30f; - - progressPin = -1; + new AttributeModule(0){{ + attribute = Attribute.heat; + baseEfficiency = 0f; + maxBoost = 2f; + storagePin = 1; + }}, + //This is assigned to the same pin as the attribute, and updates after, + //which means it will only "top off" the efficiency that the attributes don't reach. + new ConsumeItemModule(0){{ + items = ItemStack.with(Items.pyratite, 1); + efficiencyIncrease = 4f; + time = 20f; + progressPin = 2; }}, - new EfficiencySourceModule(2){{ - baseEfficiency = 3f; + //Same as above, but will also cover for the pyratite consumer. + new ConsumeItemModule(0){{ + items = ItemStack.with(Items.coal, 1); + time = 40f; + progressPin = 3; }} ); }}; @@ -765,7 +773,7 @@ public void load(){ ItemRecipe carbolith = new ItemRecipe(with(MeldItems.debris, 1), with(MeldItems.carbolith, 1)); - modules.addAll( + /*modules.addAll( new ProduceLiquidModule(new LiquidStack(MeldLiquids.fumes, 2f), 0), new GateModule(1, new GateModule.RecipeCondition(carbolith)), new ConsumeLiquidModule(LiquidStack.with(MeldLiquids.fumes, 1, MeldLiquids.aspect, outletRate * 2), 1, 2), @@ -775,7 +783,7 @@ public void load(){ craftTime = 12; recipe = carbolith; }} - ); + );*/ /* @@ -993,7 +1001,7 @@ public void load(){ float produceTime = 30; - modules.addAll( + /*modules.addAll( new GateModule( ModOUT.ZERO, new GateModule.RecipeCondition(aspectPipe1) ), @@ -1024,7 +1032,7 @@ public void load(){ progressPin = ModOUT.FIVE; craftTime = produceTime; }} - ); + );*/ }}; sharkFactory = new UnitFactory("shark-factory"){{ diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index 5aa1e8d..25ec54e 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -13,6 +13,7 @@ import mindustry.gen.Building; import mindustry.type.*; import mindustry.world.Block; +import mindustry.world.blocks.heat.*; import mindustry.world.blocks.payloads.*; import mindustry.world.blocks.production.GenericCrafter; import mindustry.world.blocks.units.UnitAssemblerModule.*; @@ -35,6 +36,9 @@ public class ModularCrafter extends PayloadBlock{ public Seq acceptedItems = new Seq<>(); public Seq acceptedPayloads = new Seq<>(); + public boolean consumesHeat = false; + public boolean producesHeat = false; + //Lists of stuff which gets dumped public Seq dumpedLiquids = new Seq<>(); public Seq dumpedItems = new Seq<>(); @@ -193,11 +197,13 @@ public static class ModIN{ public static final float ON = 1, OFF = 0; - public class ModularCrafterBuild extends PayloadBlockBuild{ + public class ModularCrafterBuild extends PayloadBlockBuild implements HeatConsumer, HeatBlock{ public ModularCrafter modular; public IntFloatMap data = new IntFloatMap(); public PayloadSeq payloads = new PayloadSeq(); + public float heat = 0f; + @Override public void draw(){ super.draw(); @@ -234,17 +240,6 @@ public void created() { data.putAll(defaultData); } - @Override - public void updateTile() { - super.updateTile(); - this.dumpOutputs(); - } - - public void dumpOutputs(){ - dumpedLiquids.each(this::dumpLiquid); - dumpedItems.each(this::dump); - } - public void setPin(int pin, float value){ data.put(pin, value); } @@ -290,9 +285,30 @@ public void update() { modules.each(c -> c.update(this)); } + @Override + public float heat(){ //the useful one + return heat; + } + + @Override + public float heatFrac(){ //only used by DrawHeatOutput + return 0; + } + + @Override + public float[] sideHeat(){ //only used by DrawHeatInput + return new float[0]; + } + + @Override + public float heatRequirement(){ //only used by DrawHeatInput and DrawHeatRegion + return 0; + } + @Override public void write(Writes write) { super.write(write); + write.i(data.size); int[] keys = data.keys().toArray().toArray(); @@ -300,16 +316,26 @@ public void write(Writes write) { write.i(i); write.f(data.get(i)); } + + write.f(heat); } @Override public void read(Reads read, byte revision) { super.read(read, revision); + data = new IntFloatMap(); int len = read.i(); for (int i = 0; i < len; i++){ data.put(read.i(), read.f()); } + + if(revision >= 1) heat = read.f(); + } + + @Override + public byte version(){ + return 1; } } } diff --git a/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java deleted file mode 100644 index cc4df34..0000000 --- a/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java +++ /dev/null @@ -1,41 +0,0 @@ -package meld.world.blocks.crafting.modules; - -import meld.world.blocks.crafting.ModularCrafter; -import mindustry.type.LiquidStack; - -public class ConsumeLiquidModule extends ModularCrafter.CrafterModule { - - public ConsumeLiquidModule(LiquidStack[] consumed, int efficiencyPin, int outputEfficiencyPin){ - this.consumed = consumed; - this.efficiencyPin = efficiencyPin; - this.outputEfficiencyPin = outputEfficiencyPin; - } - - public int efficiencyPin; - public int outputEfficiencyPin; - public LiquidStack[] consumed; - - @Override - public void update(ModularCrafter.ModularCrafterBuild build) { - float efficiency = build.getPin(efficiencyPin); - if(efficiency == 0) return; - - float maxEfficiency = 1; - - for(LiquidStack stack: consumed){ - float target = build.liquids.get(stack.liquid); - float consumeAmount = stack.amount * efficiency; - - maxEfficiency = Math.min(maxEfficiency, target/consumeAmount); - } - - efficiency *= maxEfficiency; - - for (LiquidStack stack: consumed){ - float removed = efficiency * stack.amount; - build.liquids.remove(stack.liquid, removed); - } - - build.setPin(outputEfficiencyPin, efficiency); - } -} diff --git a/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java deleted file mode 100644 index 590998e..0000000 --- a/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package meld.world.blocks.crafting.modules; - -import arc.math.Mathf; -import meld.world.blocks.crafting.ModularCrafter; -import mindustry.type.LiquidStack; - -public class ProduceLiquidModule extends ModularCrafter.CrafterModule { - public LiquidStack liquid; - public int efficiencyPin; - - public ProduceLiquidModule(LiquidStack liquid, int efficiencyPin){ - this.liquid = liquid; - this.efficiencyPin = efficiencyPin; - } - - @Override - public void update(ModularCrafter.ModularCrafterBuild build) { - float efficiency = build.getPin(efficiencyPin); - float addedAmount = Math.min(liquid.amount * efficiency, Mathf.maxZero(build.block.liquidCapacity - build.liquids.get(liquid.liquid))); - build.liquids.add(liquid.liquid, addedAmount); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java index 06ec172..3690bd8 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java @@ -1,6 +1,7 @@ package meld.world.blocks.crafting.modules.rework; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; /// Will only consume from the pin with the highest efficiency. public class ConsumeHighestModule extends ConsumeModule{ diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java index 608fbf3..bd8713a 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java @@ -2,6 +2,7 @@ import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.type.*; public class ConsumeItemModule extends ConsumeDiscreteModule{ diff --git a/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java similarity index 90% rename from src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java rename to src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java index a310506..c799091 100644 --- a/src/meld/world/blocks/crafting/modules/rework/StupidConsumeLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java @@ -3,15 +3,16 @@ import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.type.*; -public class StupidConsumeLiquidModule extends ConsumeModule{ +public class ConsumeLiquidModule extends ConsumeModule{ public LiquidStack[] liquids; public float baseEfficiency = 0; public float efficiencyIncrease = 1; - public StupidConsumeLiquidModule(int... outputPins){ + public ConsumeLiquidModule(int... outputPins){ super(outputPins); } diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java index eab1869..37ecab7 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java @@ -2,6 +2,7 @@ import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.type.*; import mindustry.world.blocks.payloads.*; diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java new file mode 100644 index 0000000..8bee6f2 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java @@ -0,0 +1,28 @@ +package meld.world.blocks.crafting.modules.rework; + +import arc.math.*; +import arc.util.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; + +public class ProduceHeatModule extends ProduceModule{ + public float heatOutput = 10f; + public float warmupRate = 0.15f; + + public ProduceHeatModule(int... inputPins){ + super(inputPins); + } + + @Override + public void update(ModularCrafterBuild build){ + float input = takeEfficiency(build); + + build.heat = Mathf.approachDelta(build.heat, heatOutput * input, warmupRate * Time.delta); + } + + @Override + public void setup(ModularCrafter block){ + block.rotate = true; + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java index 478b7df..c0d6b2e 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java @@ -1,8 +1,8 @@ package meld.world.blocks.crafting.modules.rework; -import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.type.*; import java.util.*; @@ -13,6 +13,15 @@ public ProduceItemModule(int... inputPins){ super(inputPins); } + @Override + public void update(ModularCrafterBuild build){ + super.update(build); + + for(ItemStack stack : items){ + build.dump(stack.item); + } + } + @Override public boolean canOutput(ModularCrafterBuild build){ return Arrays.stream(items).allMatch(stack -> build.items.get(stack.item) + stack.amount <= build.block.itemCapacity); diff --git a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java similarity index 78% rename from src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java rename to src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java index a2e04ee..970f438 100644 --- a/src/meld/world/blocks/crafting/modules/rework/StupidProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java @@ -3,23 +3,28 @@ import arc.util.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.type.*; -public class StupidProduceLiquidModule extends ProduceModule{ +public class ProduceLiquidModule extends ProduceModule{ public LiquidStack[] liquids; - public StupidProduceLiquidModule(int... inputPins){ + public ProduceLiquidModule(int... inputPins){ super(inputPins); } @Override public void update(ModularCrafterBuild build){ - //check full + //dump/check full + boolean full = false; for(LiquidStack stack : liquids){ + build.dumpLiquid(stack.liquid); + if(build.liquids.get(stack.liquid) >= build.block.liquidCapacity - 0.001f){ - return; + full = true; } } + if(full) return; //Sum efficiencies and timescale up float input = takeEfficiency(build); diff --git a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java index 036f9bc..6ce1c90 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java @@ -3,6 +3,7 @@ import arc.*; import meld.world.blocks.crafting.*; import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.type.*; diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java similarity index 93% rename from src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java rename to src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java index 5267596..f78f10e 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeDiscreteModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules.rework; +package meld.world.blocks.crafting.modules.rework.base; import arc.util.*; import meld.world.blocks.crafting.ModularCrafter.*; @@ -26,8 +26,6 @@ public void update(ModularCrafterBuild build){ float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); float output = Math.max(baseEfficiency, current); - build.setPin(outputPins[0] + 20, current); - //if efficiency has been used if(consumed > 0f && canConsume(build)){ float progress = build.getPin(progressPin); diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java similarity index 94% rename from src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java rename to src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java index 404ebf3..07f00b8 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules.rework; +package meld.world.blocks.crafting.modules.rework.base; import meld.world.blocks.crafting.ModularCrafter.*; diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java similarity index 90% rename from src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java rename to src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java index 6234de6..d0fc288 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceDiscreteModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java @@ -1,10 +1,7 @@ -package meld.world.blocks.crafting.modules.rework; +package meld.world.blocks.crafting.modules.rework.base; import arc.util.*; import meld.world.blocks.crafting.ModularCrafter.*; -import mindustry.type.*; - -import java.util.*; public abstract class ProduceDiscreteModule extends ProduceModule{ diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java similarity index 91% rename from src/meld/world/blocks/crafting/modules/rework/ProduceModule.java rename to src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java index 4de9e23..428a3d7 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java @@ -1,4 +1,4 @@ -package meld.world.blocks.crafting.modules.rework; +package meld.world.blocks.crafting.modules.rework.base; import meld.world.blocks.crafting.ModularCrafter.*; From e99414e778fa909c628845a7e675c3ca4fc43052 Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Sat, 4 Apr 2026 09:58:18 +0200 Subject: [PATCH 15/24] heat consumption --- src/meld/content/MeldBlocks.java | 1 + .../world/blocks/crafting/ModularCrafter.java | 15 ++++++---- .../modules/rework/AttributeModule.java | 10 +++---- .../modules/rework/ConsumeHeatModule.java | 30 +++++++++++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index d4ff185..4747fe1 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -719,6 +719,7 @@ public void load(){ time = 20f; progressPin = -1; }}, + new ConsumeHeatModule(2), new AttributeModule(2){{ attribute = Attribute.heat; baseEfficiency = 0f; diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index 25ec54e..bc1d741 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -202,6 +202,7 @@ public class ModularCrafterBuild extends PayloadBlockBuild implements H public IntFloatMap data = new IntFloatMap(); public PayloadSeq payloads = new PayloadSeq(); + public float[] sideHeat = new float[4]; public float heat = 0f; @Override @@ -285,23 +286,27 @@ public void update() { modules.each(c -> c.update(this)); } + //the useful one @Override - public float heat(){ //the useful one + public float heat(){ return heat; } + //only used by DrawHeatOutput @Override - public float heatFrac(){ //only used by DrawHeatOutput + public float heatFrac(){ return 0; } + //only used by DrawHeatInput @Override - public float[] sideHeat(){ //only used by DrawHeatInput - return new float[0]; + public float[] sideHeat(){ + return sideHeat; } + //only used by DrawHeatInput and DrawHeatRegion @Override - public float heatRequirement(){ //only used by DrawHeatInput and DrawHeatRegion + public float heatRequirement(){ return 0; } diff --git a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java index cb4a4b2..dd8bc08 100644 --- a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java @@ -26,11 +26,6 @@ public void update(ModularCrafterBuild build){ for(int i : outputPins) build.setPin(i, Math.max(build.getPin(storagePin), build.getPin(i))); } - @Override - public void setup(ModularCrafter block){ - block.hook(BlockEvent.Defaults.proximityUpdate, this); - } - @Override public void on_event(ModularCrafterBuild build){ float efficiency = baseEfficiency + Math.min(maxBoost, boostScale * build.block.sumAttribute(attribute, build.tileX(), build.tileY()) + attribute.env()); @@ -42,4 +37,9 @@ public void on_event(ModularCrafterBuild build){ build.setPin(storagePin, efficiency); } + + @Override + public void setup(ModularCrafter block){ + block.hook(BlockEvent.Defaults.proximityUpdate, this); + } } \ No newline at end of file diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java new file mode 100644 index 0000000..6ecdde7 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java @@ -0,0 +1,30 @@ +package meld.world.blocks.crafting.modules.rework; + +import arc.math.*; +import meld.world.blocks.crafting.*; +import meld.world.blocks.crafting.ModularCrafter.*; +import meld.world.blocks.crafting.modules.rework.base.*; + +public class ConsumeHeatModule extends ConsumeModule{ + public float baseEfficiency = 0f; + /// Base heat requirement for 100% efficiency. + public float heatRequirement = 10f; + /// After heat meets this requirement, excess heat will be scaled by this number. + public float overheatScale = 1f; + /// Maximum possible efficiency after overheat. + public float maxEfficiency = 4f; + + public ConsumeHeatModule(int... outputPins){ + super(outputPins); + } + + @Override + public void update(ModularCrafterBuild build){ + build.heat = build.calculateHeat(build.sideHeat); + + float over = Math.max(build.heat - heatRequirement, 0f); + float scaled = Math.min(Mathf.clamp(build.heat / heatRequirement) + over / heatRequirement * overheatScale, maxEfficiency); + + for(int i : outputPins) build.setPin(i, Math.max(baseEfficiency + scaled, build.getPin(i))); + } +} From 166a3f41c589e3dbad3a37b8fff82601b4db108c Mon Sep 17 00:00:00 2001 From: stacktrace-error Date: Sat, 4 Apr 2026 12:44:41 +0200 Subject: [PATCH 16/24] support vanilla drawers --- src/meld/content/MeldBlocks.java | 11 ++++ .../world/blocks/crafting/ModularCrafter.java | 50 ++++++++++++------- .../modules/rework/ProduceHeatModule.java | 5 +- .../modules/rework/ProduceLiquidModule.java | 4 +- .../rework/base/ProduceDiscreteModule.java | 1 + 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 4747fe1..eb437ff 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -696,18 +696,29 @@ public static void load(){ }}; new ModularCrafter("stupid-silicon-smelter"){ + //I refuse to add sprites to the files. @Override public void load(){ super.load(); region = Core.atlas.find("silicon-smelter"); fullIcon = Core.atlas.find("silicon-smelter"); uiIcon = Core.atlas.find("silicon-smelter"); + if(drawer instanceof DrawMulti m && m.drawers[1] instanceof DrawHeatInput f) f.heat = Core.atlas.find("phase-heater-heat"); + if(drawer instanceof DrawMulti m && m.drawers[2] instanceof DrawRegion r) r.region = Core.atlas.find("pneumatic-drill-rotator"); + if(drawer instanceof DrawMulti m && m.drawers[3] instanceof DrawFlame f) f.top = Core.atlas.find("silicon-smelter-top"); } { requirements(Category.crafting, with(MeldItems.debris, 40)); size = 2; + drawer = new DrawMulti( + new DrawDefault(), + new DrawHeatInput(), + new DrawRegion("", 3f, true), + new DrawFlame(Color.valueOf("ffef99")) + ); + modules.addAll( new ProduceItemModule( 1, 2){{ items = ItemStack.with(Items.silicon, 1); diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index bc1d741..cb7e48b 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -1,10 +1,7 @@ package meld.world.blocks.crafting; -import arc.math.*; -import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.struct.*; -import arc.struct.IntFloatMap.*; import arc.util.*; import arc.util.io.Reads; import arc.util.io.Writes; @@ -15,17 +12,10 @@ import mindustry.world.Block; import mindustry.world.blocks.heat.*; import mindustry.world.blocks.payloads.*; -import mindustry.world.blocks.production.GenericCrafter; -import mindustry.world.blocks.units.UnitAssemblerModule.*; +import mindustry.world.draw.*; import mindustry.world.meta.*; -import java.util.*; - -import static mindustry.Vars.*; -import static mindustry.Vars.tilesize; - public class ModularCrafter extends PayloadBlock{ - //Modules updates every update, listeners are for modules with specific events public Seq modules = new Seq<>(); public ObjectMap> listeners = new ObjectMap<>(); @@ -36,9 +26,6 @@ public class ModularCrafter extends PayloadBlock{ public Seq acceptedItems = new Seq<>(); public Seq acceptedPayloads = new Seq<>(); - public boolean consumesHeat = false; - public boolean producesHeat = false; - //Lists of stuff which gets dumped public Seq dumpedLiquids = new Seq<>(); public Seq dumpedItems = new Seq<>(); @@ -47,11 +34,16 @@ public class ModularCrafter extends PayloadBlock{ public boolean replaceBars = true; public int payloadCapacity = 3; + /// Used to show heat as a percentage. Does not affect function. + public float fullHeat = 10f; + /// Default float data array public IntFloatMap defaultData = new IntFloatMap(); /// When true, shows a table with the data map in the block's display. public boolean debugTable = true; + public DrawBlock drawer = new DrawDefault(); + public ModularCrafter(String name) { super(name); @@ -82,6 +74,12 @@ public void init(){ modules.each(m -> m.setup(this)); } + @Override + public void load(){ + super.load(); + drawer.load(this); + } + public static void trigger(ModularCrafter block, ModularCrafterBuild build, Object event){ Seq events = block.listeners.get(event); if(events != null) events.each(c -> c.on_event(build)); @@ -205,9 +203,13 @@ public class ModularCrafterBuild extends PayloadBlockBuild implements H public float[] sideHeat = new float[4]; public float heat = 0f; + public float totalProgress = 0f; + public float visualEfficiency = 0f; + + @Override public void draw(){ - super.draw(); + drawer.draw(this); drawPayload(); } @@ -283,7 +285,11 @@ public void onProximityUpdate() { @Override public void update() { super.update(); + visualEfficiency = 0f; + modules.each(c -> c.update(this)); + + totalProgress += visualEfficiency * Time.delta; } //the useful one @@ -295,7 +301,7 @@ public float heat(){ //only used by DrawHeatOutput @Override public float heatFrac(){ - return 0; + return heat / fullHeat; } //only used by DrawHeatInput @@ -307,7 +313,17 @@ public float[] sideHeat(){ //only used by DrawHeatInput and DrawHeatRegion @Override public float heatRequirement(){ - return 0; + return fullHeat; + } + + @Override + public float warmup(){ + return visualEfficiency; + } + + @Override + public float totalProgress(){ + return totalProgress; } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java index 8bee6f2..936b884 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java @@ -19,10 +19,7 @@ public void update(ModularCrafterBuild build){ float input = takeEfficiency(build); build.heat = Mathf.approachDelta(build.heat, heatOutput * input, warmupRate * Time.delta); - } - @Override - public void setup(ModularCrafter block){ - block.rotate = true; + build.visualEfficiency = Math.max(input, build.visualEfficiency); } } diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java index 970f438..bd82e2e 100644 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java @@ -33,11 +33,13 @@ public void update(ModularCrafterBuild build){ for(LiquidStack stack : liquids){ build.handleLiquid(build, stack.liquid, Math.min( - stack.amount * input * build.timeScale() * Time.delta, + stack.amount * input * Time.delta, build.block.liquidCapacity - build.liquids.get(stack.liquid) ) ); } + + build.visualEfficiency = Math.max(input, build.visualEfficiency); } @Override diff --git a/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java index d0fc288..d0b2213 100644 --- a/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java +++ b/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java @@ -29,6 +29,7 @@ public void update(ModularCrafterBuild build){ } build.setPin(progressPin, progress); + build.visualEfficiency = Math.max(input, build.visualEfficiency); } public abstract boolean canOutput(ModularCrafterBuild build); From 444244249247644f8f469330b38e85dded5f4396 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Mon, 6 Apr 2026 13:25:28 +1000 Subject: [PATCH 17/24] Core linking, Vivisection, Channel director patch -vents have a scaling factor to how much fluid they vent -Bulbhead can now link up with nearby raft cores -Add build time stat unit to items -Database categories for aspects --- assets/bundles/bundle.properties | 18 + assets/maps/gameplay-testmap-2.msav | Bin 28734 -> 30825 bytes .../sprites/blocks/fluid/channel-director.png | Bin 1121 -> 1156 bytes .../turrets/viv/sawblade-large-glow.png | Bin 0 -> 706 bytes .../blocks/turrets/viv/sawblade-large-l.png | Bin 0 -> 1046 bytes .../blocks/turrets/viv/sawblade-large-r.png | Bin 0 -> 1055 bytes .../blocks/turrets/viv/sawblade-large.png | Bin 0 -> 1607 bytes .../blocks/turrets/viv/vivisection-base.png | Bin 0 -> 2256 bytes .../turrets/viv/vivisection-body-heat.png | Bin 0 -> 2214 bytes .../blocks/turrets/viv/vivisection-body.png | Bin 0 -> 1250 bytes .../blocks/turrets/viv/vivisection-center.png | Bin 0 -> 169 bytes .../blocks/turrets/viv/vivisection-jaw-l.png | Bin 0 -> 654 bytes .../blocks/turrets/viv/vivisection-jaw-r.png | Bin 0 -> 654 bytes .../turrets/viv/vivisection-plate-l.png | Bin 0 -> 929 bytes .../turrets/viv/vivisection-plate-r.png | Bin 0 -> 917 bytes .../sprites/bullet/crystalline-smog.aseprite | Bin 0 -> 40191 bytes assets/sprites/bullet/crystalline-smog.png | Bin 0 -> 15183 bytes assets/sprites/bullet/dual-spike.aseprite | Bin 0 -> 614 bytes assets/sprites/bullet/dual-spike.png | Bin 0 -> 205 bytes assets/sprites/bullet/thorn-back.png | Bin 0 -> 254 bytes assets/sprites/bullet/thorn.aseprite | Bin 0 -> 586 bytes assets/sprites/bullet/thorn.png | Bin 0 -> 197 bytes assets/sprites/effect/sparkle.aseprite | Bin 465 -> 0 bytes assets/sprites/effects/chain.aseprite | Bin 4902 -> 4902 bytes assets/sprites/effects/chain.png | Bin 794 -> 311 bytes assets/sprites/liquids/storming-aspect.png | Bin 0 -> 369 bytes assets/sprites/liquids/thundering-aether.png | Bin 0 -> 366 bytes src/meld/Meld.java | 6 +- src/meld/content/MeldBlocks.java | 349 ++++++++++++++++- src/meld/content/MeldEnvironment.java | 4 +- src/meld/content/MeldItems.java | 4 + src/meld/content/MeldLiquids.java | 60 ++- src/meld/content/MeldStatusEffects.java | 8 + src/meld/content/MeldUnits.java | 53 ++- src/meld/entities/unit/BulbheadEntity.java | 350 ++++++++++++++++++ src/meld/entities/unit/UnitEdgeWaterMove.java | 40 -- src/meld/fluid/Aspect.java | 12 + src/meld/graphics/Draww.java | 77 +++- src/meld/graphics/MeldInterps.java | 13 + src/meld/graphics/MeldPal.java | 4 +- src/meld/graphics/MeldRegions.java | 14 + src/meld/meta/MeldStatUnit.java | 9 + src/meld/world/blocks/CoreRaft.java | 37 +- src/meld/world/blocks/fluid/AspectPipe.java | 11 +- .../world/blocks/fluid/ChannelDirector.java | 14 +- src/meld/world/blocks/fluid/ChannelVent.java | 7 +- 46 files changed, 1011 insertions(+), 79 deletions(-) create mode 100644 assets/sprites/blocks/turrets/viv/sawblade-large-glow.png create mode 100644 assets/sprites/blocks/turrets/viv/sawblade-large-l.png create mode 100644 assets/sprites/blocks/turrets/viv/sawblade-large-r.png create mode 100644 assets/sprites/blocks/turrets/viv/sawblade-large.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-base.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-body-heat.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-body.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-center.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-jaw-l.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-jaw-r.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-plate-l.png create mode 100644 assets/sprites/blocks/turrets/viv/vivisection-plate-r.png create mode 100644 assets/sprites/bullet/crystalline-smog.aseprite create mode 100644 assets/sprites/bullet/crystalline-smog.png create mode 100644 assets/sprites/bullet/dual-spike.aseprite create mode 100644 assets/sprites/bullet/dual-spike.png create mode 100644 assets/sprites/bullet/thorn-back.png create mode 100644 assets/sprites/bullet/thorn.aseprite create mode 100644 assets/sprites/bullet/thorn.png delete mode 100644 assets/sprites/effect/sparkle.aseprite create mode 100644 assets/sprites/liquids/storming-aspect.png create mode 100644 assets/sprites/liquids/thundering-aether.png create mode 100644 src/meld/entities/unit/BulbheadEntity.java delete mode 100644 src/meld/entities/unit/UnitEdgeWaterMove.java create mode 100644 src/meld/fluid/Aspect.java create mode 100644 src/meld/graphics/MeldInterps.java create mode 100644 src/meld/graphics/MeldRegions.java create mode 100644 src/meld/meta/MeldStatUnit.java diff --git a/assets/bundles/bundle.properties b/assets/bundles/bundle.properties index 34732a3..d9d1bd3 100644 --- a/assets/bundles/bundle.properties +++ b/assets/bundles/bundle.properties @@ -743,10 +743,18 @@ liquid.meld-pollutant-mixture.name = [#cb8650][#6b4e4e]Pollutant Mix[] liquid.meld-pollutant-mixture.description = A mix of common aspects in the atmosphere of Ikaru. Emitted by [accent]aether vents[].\n\nCan be used in place of [#cb8650]Aether[] liquid.meld-pollutant-mixture.details = A mixture of secondary aspects. +liquid.meld-thundering-aether.name = [#e98b66]Thundering Aether[] +liquid.meld-thundering-aether.description = Cackles and crackles, an energy inbetween.\n\nCan be used in place of [#cb8650]Aether[] +liquid.meld-thundering-aether.details = Crickling and crackling, cackling and cack- auegh... dammit\n\nwait, is this thing still on?\n\nFith... fith- sixed? nono... has t-[red][] + liquid.meld-bound-aspect.name = [#a451c8]Bound Aspect[] liquid.meld-bound-aspect.description = A highly energy dense, stable fuel.\n\nCan be used in place of [#cbdbfc]Aspect[] liquid.meld-bound-aspect.details = Confusing mixture of primordial aspects +liquid.meld-storming-aspect.name = [#ffffff]Storming Aspect[] +liquid.meld-storming-aspect.description = Sparks and flashes, dancing on the monitor.\n\nCan be used in place of [#cbdbfc]Aspect[] +liquid.meld-storming-aspect.details = Hexing dance of primordial aspects + liquid.meld-nectar.name = [#b89f47]Nectar liquid.meld-nectar.description = Used as a potent coolant. Can be refined into [#89828c]Mercury[] over high heat with the right reagents. Encourages a slight mending effect on units. liquid.meld-nectar.details = Blood of the swamp. @@ -1387,3 +1395,13 @@ sectors.underattack.nodamage = [accent]In Progress... status.disarmed.name = Disarmed +database-category.aspect = Aspecti +database-tag.aspect-powergen = Potential/Power family +database-tag.aspect-omnipotence = Progress +database-tag.aspect-vita = Vita + +database-tag.heavy-industry = Heavy Industry + +unit.ticks = ticks + +bar.spool = Spool \ No newline at end of file diff --git a/assets/maps/gameplay-testmap-2.msav b/assets/maps/gameplay-testmap-2.msav index 541753e78fee7ac3199e80b527589d3859428802..21ce5bc181504e4d318ec571d08c03b4fe82eb68 100644 GIT binary patch literal 30825 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 z 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 WMmJXjlpO_l=EjH6Nq+~0d(SSML*};t literal 28734 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�MUaQeFq5&!9XamGYPSN!Tb-SX;- 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 diff --git a/assets/sprites/blocks/fluid/channel-director.png b/assets/sprites/blocks/fluid/channel-director.png index 35b1b9668c2e0cb3aa478270e704fb6bbc1aa94c..4962d0539dc79baf99c8c6fbcc4851b60f021113 100644 GIT binary patch delta 1122 zcmV-o1fBcg2!sicF@HWuL_t(oh0T{;h*eb-$A4?>bM7>AXBOq7cqU=+qBG61q zWX9|v5hTOD@F6UzAZDhN@guDs1jV2a&=(;P!iX%&AWiZ`>Me>v2ntF|Qka+SIcM+P z!#?-Sojd2Axm08e4*Q(F*INJoUVELr*Ov7gHhS7*_r{_2u75(dzkjad*x#|$N^k#x zM6DkymfZxv$;l}mx^bY=u&LXWufF-AQvc-SRAJMqTSTDU;mOIVuE9I?b@J7!Te$>f zB&vYQ#;PU)Am1O~@8?o%U5+Zf&p!ST=VAX#kI$*w^jQ$-)}WKlsn1U!B6ts{zWou8 z2k+TA_FUzJW`7Wvi^LlT-$PVcv|tcbgE5MVvg-E5od4+pqJh8{=g#9jcs$;Fc8+c> z1*a?rI%WTN=g#ox{kNhbs3@v}iXZ~>2kHzhy#W;^GKwIG2>BZmuOI5@yEF;g6?g1C z$ozpm2K)QivhH30ybrVUkdM`QVCd%1CxC#LKm?g4(SH?zRubFy9>4>G{e8vr%-Xwg z&S9OyTFbn8FN6KPJU#I?M^Bs~Uw$B8-MqQ)seRoO0C@Ga1HssP2K(zp6z0|Y7=7$P zk~AerQwHk2^!Hs;*!_O#SMUz+JkC2jE)epwc75c9O(R}ah>Q{$Lu3qbY>1hO%twM`h{8?Q;Xw} z*=btXUAyo)5JfZ`TtXhDlFgzcf#$Kidw+Zz0whU;BuPk;gvQk6JQ88=NYc>1V&y6} zjVuRY=Cb6?*~4SAa7qcZ*95!9pQq6XAirI_5PaszCs0IYaXkxtdDJC8G4Y;>mYSfF zK&OJ_*(Ww|rv36gr ox>4Cc)eW~Bz)mFom$&1;0Ep@rBT)J(!vFvP07*qoM6N<$f;j~+tN;K2 delta 1087 zcmV-F1i<@*3E>EkF@GCLL_t(oh0Rx6h*eb>{?`AmeNJ=EjN@ZOL?u>4ggFR3l!(+M zW0!|SI1uHe2u%()QrZkDrJ$)WWj16NGa?9t5P~i+L6p6T^wNz98*C<%dYqT`Ubi0B z-uvux&ffbRD$)guwfFun-}nD(t-bywHf?S=w6z0`HSM3$wtx4-bA_^hVoRmo{)Z;B zFKdTx0|3mLxx@0=*g(=^UJX8!qxsPZ?Dkx65;E-pmJif1(YOavN-s}z;_=}0JB7Y;7h${Np;t~~`9KYW{WXj| zdN(9{#~g!sSsi;v*Mm7|%43qu5--2{Zf;2$h1;29eT`ebov)0R}UrjuWtI5TIJ|pw&u%N=!~N zBaj4U1_=QnIy3n2&LjBr^+_-jn0yyPfLVBOCdf)4kz|0GTL^65|5n)y_0&do9KvTa zlkuD1o|y@NXl#1Kkq{svFcXLvQcApe>Vtm`o_}pqyLKQ_3Q8#`t)aDnG5X3M$71!+ z8YkimBqI@lm~r;2FaHye4I>Z17!6|#j4`mrz*q}w48*FthMfPcX23HfI2{?pZTsEyXJ)~4U(>6e};Ox^JKj6ZyYz;!oV3${Lbp9PDBi@!ra;Kmi#ci`W;@=n^4E?Ag%wJeY=Tyb3J83Id+!4!kOV>ts zU=$Kaj^#Z&M`4Uc6orVQ2vHPaVg5H5t$(~@s}U*xeeJsSxcR2rQ!ryRjBY-EF*Dv9 zf%ckUa$+mO(1ToByxij4Xq{%}*ZIXXW7(*U(RluuCvyYsAW&40jBVMBKQ8|Xt(%U0 zSBRnzix)3JYwhKUPCQQS+E(cM>M6;tSW%i>&5FwH_Ufoxncb^?aycp8hIuty**Fa- zPuA^*l@e$-E;GynDkIkJE2}H>1Z6keUI2?oT$Q)uzX6?DPvuLT;i~`u002ovPDHLk FV1f}m2F3sY diff --git a/assets/sprites/blocks/turrets/viv/sawblade-large-glow.png b/assets/sprites/blocks/turrets/viv/sawblade-large-glow.png new file mode 100644 index 0000000000000000000000000000000000000000..e691eb311ee7dfe7243f077851a3c7fb198391af GIT binary patch literal 706 zcmV;z0zLhSP)Px%d`Uz>RCt{2oZF7WAPhudwg3Oi-Iqo}QAsg)%$S|?SP28C)ZvzvB7_h^2qAyg zKJ|)=Y9CxI0&f`zx#`5R{{k*jiA7J-Jmb5FeKmN*>(zmZji_f??7blf0T~w*OJXBB zoKb2i3t~@|>NT(ZohgKv?s!hr(aY5l%p;b4y2Bu~6r))7=?;t3QVgjea=J4k=v$fS z%^J!Df^|-elsa3UQmiMtHfxR$+w1hs%^JAas0T{K3eXaFY94E))>=%I0zgJ>&k#47X|aKJ|_(`da$UBu;$@NL6gv_j}MVJxspefrE3 zh*&16Pax%*&%Clo8wGXef$AD3iXH797MoS-MRfaz#AcUz5!sLJ3xwJbQD2nYA+e}V z-{YPncX-6Qp9yv1opNBg<&^dmc~6*g?DEdK70BSx|7tN(choO{@+OQ|=kIlA oO)HdG{{}?}A%qY@2qE100|qLPbq@PXQ~&?~07*qoM6N<$f~UkzG5`Po literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/sawblade-large-l.png b/assets/sprites/blocks/turrets/viv/sawblade-large-l.png new file mode 100644 index 0000000000000000000000000000000000000000..d43b2679e1e49439dc178a6d71b364689b71858b GIT binary patch literal 1046 zcmV+x1nK*UP)Px&&`Cr=RCt{2oV{)nK@fn)h)1|oX^<$K7qE~hQuqa^BGK>wR0zd|dm0K{q{=Hq zvQqE@DUd)y_X4Jfl*X0s9Za=iv+U2z{+xIA=4*Y*oxjY@&g@PCAP9mW2!bF8f^aHs z9`BAfk9WsYKSyb1W8TaZ`swxiQSUz{qkS<~l(s5&lrI%}^;?wtqVx@>kWXzC7dur% zY#~H!A+m^7wW%$i-mW(}Zo!UZv8GIy+^Gpp5=&k2rzAI8?8Vh0s*IY7+$gawsSiUB z4?|D>c`equWU)4>u|joPlS>k7O3fiurzN>)vAWb@e}G|suwJ+97D*4(irkl!i(N7m zsZtfXT&^25=!o@6U3LqSR7z#QHG-2HgxDx;U2c+CjMUw>cfQIk%O&Mlo$mY$eeG`B z&s?>yk~?2r0zkezeQMAVtBZ6=^_5?~$W>BXf|Lrn^E0p15H`7{)ZDf4smQWzND-mS zZ81_6Nn&r;8>`fJzps-Q8t##sBzD*z;OFOi>t{Z>^VKCfTDc0&k_sqtW22dy+-R|8 zCx($5yNAmWv{-%7akaNTxk~DvZ$G^Ep|;1hLW-BH048#|62EvMA|lzg~OqGdZyU&*j?0*{TVOU`|p2G7ld68URXb9OEIx}h2T3YYn394f!Zvwt?DVSIHuF91@eQMCjg-ypCVU9wd-?E zVo9mMcB~RQGO=}0p|qe<2j}#GAK37$?lE%F_hl*>C00qrY+z$CMR5}s0+q8cwP=i( zVSjj*H$#VwzLvQ*P!Wq+G-6hJ^XG=daXRJ24<2V;kzEpZ+kWO7=HMciR278eVxt!G zN^_T6|2h5pt|=_bb#Qy9POGmkMyg#mkh6 zt${g61;`{e*XgNEt{$~qqyl8F3aI~V2RpVEu3+g6n*d%xtyf}6fU=7Ut{i41wjx5K@bE%5QH)K3x6m1r+M{b Q6#xJL07*qoM6N<$f;zA9y8r+H literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/sawblade-large-r.png b/assets/sprites/blocks/turrets/viv/sawblade-large-r.png new file mode 100644 index 0000000000000000000000000000000000000000..49b055f6ce9453ec295495bd8d0093b7d851bb29 GIT binary patch literal 1055 zcmV+)1mOFLP)Px&*-1n}RCt{2oIPqAQ4q)f3ORyYsWGH*E?^7}xX1-kgg}}cAXN}{p*~FtFD8|) z5HKoy0Tlupf-Ybc0XM3&cW?^x?8npl{#ft4onIwv?OOV2H1por2=L_kGHA(f2TA*Cyl8xA0 z5jQ?PEE2gSBUYCBaeS-&aM zrMieYzSm-b^sjg98aVbmg5HY-;v+J5&o$(HB*X&Ya^HVB4wkBmS~d6j%B`k&0Z(t* zwK>prwPZFE{qaW^XF;!pOvD1_$}Lc(`s~1b5$E#F`J}`GPjX#?O>8b#&OU*yr5I93 z$%z#R!J$zMxSKFKxuM(S-t1I-@Vz4I+b$FWb6=JLDitf^l3c1`Ln;--TqXGa>)iOe zl3g%TVu5^eOScwDu49+&`nf3CBGN<`{aR8vR^Iif)!s+2i1sepi=@N?F9pn2luVLs zX(YF7ayeFDi%^qv+nP6b!-j0c%9upzmNu_3!IQ<0Ka5=R$j&OSqmT(ZT>gzpBzr==J>Ee=#D)`prM ze3~_}a>(8vviWZMwPBjEmeJ<`n|%x6vTfrva$tVc ztJIL47^!uvj4U_D{ja%%3do9;Y409JDRNCxRiT3z<5bBxF{oR9h0YyRBNj-PYg*4% zcb``bF>-AigxCq0>cr}ZU3R669SuS(5H2@0tqevi0ONL7m6)Rtk{Q?t!Ds1J;=Ti! zK}Bqru-HPd*g_0aZ0Y<8R9d_Ma{=o@Dp0XlU|()mQU}5K^zgCNzcOVQhG7_nVHk$- Z;y=kr?Lui039|qI002ovPDHLkV1l%Q@vZ;> literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/sawblade-large.png b/assets/sprites/blocks/turrets/viv/sawblade-large.png new file mode 100644 index 0000000000000000000000000000000000000000..b81142e50f5248d0fc9b00126d6fcbdf035eaf8c GIT binary patch literal 1607 zcmV-N2Dtf&P)Px*0ZBwbRCt{2oIOq)OAyCH+KZKZM<$nO|2|Rae(+2u)2*O-)Tr zO-)TrO|PZXhqLkY;cR^E`>3tilo!i^e*5sn<@)KktD$Fo=knu8)n|WKbMRCpEA_Bk z?HIea%c`qp5{soCUTe9~xLm6Zp`PVdx7b?#)XUH&RD=py=1#X@jdQ(! z5kjZ~_m2acTdEdrgc{`y_r2#}wFNEz)zB9~?sSXr>+s~o--oB?;`rUx`Z(M?y7*id zi;cNy$)A=UOe4ytN&*%pFUvK9&0#`tEr7X!Du?SesT!HUN+Z7JLAB8zyH+<}(+nw+ z8tmrY!u^4BX`rr#-ejp>K2{aHAgEeDclvNPdTnn*2;uI$pLDBUlGtKPP*+254jx_` ziP|*)u0&vSs2)I5S>e7Fs38bkK?*hIfg6R-?R1N9X}BS+TcDs){u(xhI8o*eg7(2Z;X)7XU2~O; zX9CNCilsg^;cD{ilGWzd<-%1LVI;ekyYqeqJ!pixaj+diur2-F+vRwFe1(1{uWIGG zpO?QM?~g0C#ik%3s95@iSuXdcDd;&=3bI6TEN&1Jxq@!KhC-!Of}2C7a${+rOm%;J zMXod~NJ}yUm$IQ`laRudF0WJ(u+)k}pxsyru5_Es{xIYtAvoCV#<4hHHP*=uiV>h! z;U$#`Y`n2gKR*}01Hsj#LABDlS_K;yR{%xY5 zTl$(5O$*nxjT1|}A%NIs!@Kt?cMqKLwLW)}b9l~yZ4+ zufvmYe|!bz#Oaom3)j*eSW&3@vnAonv=k}Z0<;y>^bzlu#(Os}ZQRi1xiM^06aze~=WnputIM%Q7n@HA(Y z1Rbwn$#2*|qPqqz`?>_=Qsz*q6L8={P&K^_EU*zO)t)0!4$3RIVaGlYTr90iflA=A z`~@c$)qokEo_{MnQiqL_WeT0Wh49i(LeKa8@1qxgf%o2_CpZb?_Td; zym&CIt{TF>fF+G>+O*5kc2wOJVB0|Q4Hibxnky+>t6O)VGfGs%zbbn~?$$l>X^m{-1~SLQi#bp^TQQb#A; zCXPQ1Y6um4Y=p}_kD}ndg`XkOs7mni^0~F3)_AWgSJewh-jd0ezxCmvO~MpVL#Ui? zX)Vs<;Y(vo1@(WSZfkw4^;4!zO-)TrO-)TrO-)TK{R8vRsXn93aYFzA002ovPDHLk FV1iJw3=seT literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/vivisection-base.png b/assets/sprites/blocks/turrets/viv/vivisection-base.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd090e147862f25331c06421ebac8d17090496c GIT binary patch literal 2256 zcmV;>2ru`EP)Px-ib+I4RCt{2UA<}@M-)E31ocJ;j4C&`u~ax7Tjxy)@xMJCJu0ICS4i&^+f6~Qzyi$7EmOb@f>7gYq)!VhgkDJ4sK5OpL||@^FEs`6b#Ak?;=1z?nuydV(PonFf_Z90)UJnDA{3Ll-j3fzx{sj zweLss4WD#?byBGzb%^$(1OUb&;EaxvoCpK%LuU$zVU-TJp|PZpI^;f}1po#jkUF3Q z2M)att<%6RG^dD7LFAq~jvdb7J=<$*;dmBDPQ%0D*{#ukRT3OM+dtA0I;<= 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/vivisection-body.png b/assets/sprites/blocks/turrets/viv/vivisection-body.png new file mode 100644 index 0000000000000000000000000000000000000000..a418d438d317aa2c9f399b829790d62d476e20e7 GIT binary patch literal 1250 zcmb7^ZB&wX0LA|xq=2H4iE&)MMTV8w$|=1lnwCN{AV8)m&5qO5vLMR}^1-mObW#$h zjvlqdd`qV`D>D={H#g0pj$US@Y;Ki>J*^UwwAr_P?VfY*@AEzPoLj((-RguTVF3V6 zG0~Bnc|xD;uwXuWLci^w2SUu*8UdOHJ)Q%ADvOB>=jN1|jbRzCY*#eAXm@*xoa^(D z?C+irc|~Z41^#knLH5gtRZbM8`g;|jnHLuBBBwVGKS>IMjPZSmCsXF`T#~*r9vdqc z24$*@_sXwljSZA;13sT6PIKifl0$oBB%ONvN>^nIFTUB?7B_J$JIaPuyO5515>x$# zJV)~W#43Cw>}1RtfBLU*)E@GRtAN%`BwwgJjht{4Dmh3CTJIoKvXBqZdPiXnRW^zg zI0$nBWc^41R+zJTo~(^i*!>3%eTm;BDIK}_hFRjfgBXXd#a~x3VDkr2tGN-i56iNA zk#QMifb(kzI}OJX+rO$PMvV7AEiP)<+u|q47`2s{4IRPDN5jH2#nT>s9SMJF4yP7% zjp%ebcYt?x=EW`HZ3URzqaFS!))9Wi)LlvaPv_{}6;JM0@}b+>nymgMozaPTx3xjc zN)B{&`>}VaB-2?gISm)zfO>?}yLG}xS=C?n-DOpwzQMT=<{DC8=!)Zl?Xv!z?P0@G z+qMO^mj$b4=L|)2Cy`^@Mfi z_-u6OGRe^5oE?~xQ?2P`9#^J~j}OY5GuG73{4+>sx)n$BO{<;so@jzU?rg{nZCrce z_d|BUD&mQKrOSQ83*p`8&UsCgbhWp-9!v=EM=RQ1r;mG^a7@$LP;p-Rcxz)eUOpqM zEc&}Q$@Eb(7h-C-bZ_wW9#|S2r{adakI(?A055z?k)iit$Gs({I#d95oFXw*qb76R zhsjaatg$=gnPcrzajT}w^RTF0EIDJL3L~Wh54l^Q8(kOcrWn=u+&_lc)`F_b^}gYw zN09Lkyt#?$JsWuIdz+FHt%im4yGt?9^c$xjYDC?7`!o_pls~qtH(GzNjBdlKCrh-? z32CROA|-u;M5}Mh`>ks(TD0ui$`7}Hfa+a^_8^c{wC#>%(wENI9QuBQQ;1TzCP40{ zv*($tOvDODT;NjyMEe;zKwFE>c?u5(5T=AP57%5iM8&c>{#>1nKihZ)SYiB;m-^)GfJ)4N=KkUH$!h3 yUgu4Qmxi*U5zsWjQ4_#Uz%U#tPXA9>i}TU<<+BMbo7MBD1!AINBby?2<^2bJ?EPI>Z+cN>>@qt^f%ESEl@u>jH9bn?`UeCn%_|DZtkV;Qi KKbLh*2~7Yi+aK)! literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/vivisection-jaw-l.png b/assets/sprites/blocks/turrets/viv/vivisection-jaw-l.png new file mode 100644 index 0000000000000000000000000000000000000000..d8c40a2b1210651b5d184d34fa623a18f1abd8e2 GIT binary patch literal 654 zcmV;90&)F`P)Px%NJ&INRCt{2n@vu`Kn#Tq;$9VOPJfSb#7Wp8HXu$`R*^zFnfUkFrSrCJ z(=V>aGx2kYhK5+WvwZk)Je|4keS1BbzH2G<#oj-^&qPFDA3vhcdEJ2OA`|qa4ETHN z1&D~A_B+x0B`LNh@trTDJr^?aE)k!EjJzv20pZ=j2?QSkP5}5Ya0-SG1=olR9||rl zj}nD}Tc9~nFPAD@iNRlt6Gsafg_+NE#cMR zNa&VIFV3EB3;b|Aog;fD9Eri_izE|X1l}r3S@3M|-Y%8_&jRPP#Cq@%H?|2bY5leU zqkfGpxW*}%gGQ#%!`cfLV+oP-&C{WW59zJt3Go8_8lY(jel5_1Ft03ax1RG1u?scu zTXs3jpq0RLJbtJp&VlC+Ccw|*J$T79MaqLj3tl<`lJ*2Nhu6%ltZw-TJ?(dDL*TZa zIV&g?+~OPk0rwti?8$0&T|^uK@8Qs`UO;}@cfmf~uTBhyBs?&6ffM~`~UaU{HZs-WOQavTAlJ`}8lFENI#f&agC+;T22 zITXC55@XmEpN7~Gm%>LISfW&^1TW6Hq)$ literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/vivisection-jaw-r.png b/assets/sprites/blocks/turrets/viv/vivisection-jaw-r.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8b04ef966f724332b2016d6e195d2bad20739a GIT binary patch literal 654 zcmV;90&)F`P)Px%NJ&INRCt{2n@vswF${$th|C;1BNx4*0^mf=fh&cLnE` zM~>XUHPDo(mrE6@*x*0k8`5saU6@>nB3=ProGRJysvOURS1-;ic$2D<0dH0gsqj|1 zpE_Pe13X#-oFdC(ZWuThdhKXiI#qh`CYwgDNi<25CauPiI^q;)ty+;+fk!}3TEdIL zBcWR=y*PWiFYxXDc=qg>P{anGFOp1n7I>>HWx5ULhZ(dSc#6j#YKc?ese=jO=kXpqXPP4ABGH29j)0`yfadU$xs}wb+hKPS58K?= zowI^c!8N|oA8_lT#-6lh*G0r5;4K`ws}GQ#6nh(trKNZ^$0O55qVD44!h4T;%w<+p zBB6m%$Ky4?qcy-Ovh@BEjvj6}907*qoM6N<$g8n}V2<{5aSW-L z^Y*TNmb9Zt!^e%DA2@mR?yQ{lD(2A2%TFZMxf~D(`6-bX<2~JD?vTZ{ zyAR5*IXDv3Rz7r}CMa-#r>#vs(zPJ(qr#8$y>aR7@?6Rj9T?iY+T|l%4yYaQDF1Q4 z{?DEI|NlNE)&KtX(y}uC>{&+bhQ@vS|GfJU9Pd6$n~_mNTej}q?zN|D4}n zpZY#~t7QUfz$NoP>*eo>-@Tuus=&ay^>DwAk^)0w(Bb|q$_fmQmk#%v0O?eq_!gkJ zD^T17NUL5te14`BBcnyZ?=N?L@2c<71UmD~uIBm5OiTh>Zsxe{^`0)Zqm+e3VU0=C zLdWla=ex+Xb2vEaHMOsP{pP%d%(i16n5~;j_P%*{Pv>a-zP~r`zmpSJSX;7OP`hE# z=h?gew_T8}<4{~Pjr(={OI{{}B8xvB%1ws5dpHj`-2b3_lH-`1{s+G|6DLlQt_u-z zd}Vn!?{|e*{5N0Ah!zH^O;LJP$}@f$-()dy`(??@XyReS#FQ~5gvExvAmv5x!Yc>D zPrKg8eJwXx^g!pG-Az&NrGy%omma>_(ZG;qR<`1jpg_Z>jmJ5rOz3G~;Hl_qVCbpn z1+p?-0=gNb3hOI3{=a^-Q&C-^T#?g{C-caSY!=>$T|iYHeD;%oW=MrgGcw93`tevQ z)uyvNpW^1g(B~@)bSN;yLaqO3h%++Iv0yTI^fC8=1H-l;!7FmN!>cc8Fj`FLT9C3q zD4j>RLAYIT&9v@=mrpG96IW+&Phz<%5hUmlrww$))HnPdxqQ0>ZDL}(=Sdu@WOU0a zXPX)mn!**vzsB;=FeG8!RtY$?kh*+M+c5y`LzLm+uP_0-Wt6`~6=-e@<@3Qc%5^$>@{Ojj1u>7B|9%(Dcq)J@Vj z&&ZP@%cu6yaMK!K;tOlKw7+iUc>y(sWM{z$etUrfHbH`5qw1#2IjRgYc^{AK0>c`N dG|#}W_7}JCr^FbWp9QZ$vYxJfF6*2UngB~)bV~pL literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/viv/vivisection-plate-r.png b/assets/sprites/blocks/turrets/viv/vivisection-plate-r.png new file mode 100644 index 0000000000000000000000000000000000000000..436240ab7f3c04edc15b71ca3954d904190090c4 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>VD|TPaSW-L z^Y*TxpR}V$!$U=`Q=COivrIhCstVmI(X?Qm>e{;Plwd$t$;!#z0k?al=Qju|a0T^l zHr#Bz+4}w7%IyF1TT&bvmVV!yy*~ZABG7yc(4cCWUCZuwH*0HE=l0#}jOVj6np}~u zOOa({G?^q-mm&$I4b1+W&}uRlt7rLN^Y4?X^`8KVP8KGE5UW2Yl$gpG-F_YZ|K(%S z|F64GfByUScKh=u=d<4N-C&ZC_;vn0zx~(BB|B9W7*dxWK3^NF&BPR7Qu<@(`)n5n z2CufZ|9}7PUNnDuR|A8{6+Zh-9SsaEp1k&#`WhHmJo)V}^)xU{xyomM2_z2^2g*!j=bgKZka2Le2%}`;$8! z3ovS&a4jg^fA(MB;lykn`wemq*T4O`z1sKV*~gdvmDR^KY`J90lEWxc`{U#1{fb}O z<()WQpFQ_WZ?6-B8(+J8ri(!;t01FVB$t*=ar4#E(gR8blwSc zT)Vi9<$=S?0#hZ0hGWb4Lal&dV^-Shzvbcrw;M!y^+IL^tj{bXOG= zXb9eVxW7nH;6SG`gR&eyGdt6Usa*@oBE#Q$E(W?lt7+0fuQ`T341O|h8}7Yv^H-j3 z6+4GPrc>}svWKfe zN&Lv?56rt6Vz(Z?zNanV$)^`r3_lqCxwDn|2CGGi=^qD%e-V840uB1t4)e1-aPNM| z&SaritS5GKGlOgJe9kL5tk2>!dl;rfx&#!@v|ut=AnEv4+WWJ_fre{E2A$=i>D&n` zQoI9x-Piv*?GsBvjtXo literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/crystalline-smog.aseprite b/assets/sprites/bullet/crystalline-smog.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..6d9a5d602e2e2af3a0e092b7fe731d6515dceb67 GIT binary patch literal 40191 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/crystalline-smog.png b/assets/sprites/bullet/crystalline-smog.png new file mode 100644 index 0000000000000000000000000000000000000000..f8278c7c87b8cd941ef901ed048f560c26013279 GIT binary patch literal 15183 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 JPDHLkV1l5+o1_2$ literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/dual-spike.aseprite b/assets/sprites/bullet/dual-spike.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..dfbf661a7a19e6b17e2192ddc4c07b4c23da7dac GIT binary patch literal 614 zcmYdeVqkc%l#xM$L4-kpfsuiMAq9vL04N3$WDo#CT7YLj(|>(r1KY|5v_K4q*%iQ6 zB3YLOWV3)=rvPLqDVVrw*rZt+_J>*DFI2m^Ano1ZS-(G-&3qb=wa>tON}hM7evr3{ zk&UIjeThLrQ*_?LRtWQ zy}I%ZWI#44Fat>#_zz^TFeD`=XUl?E7;>x(X+`;YC7XbftO^VaObkE%{|5#t1gJ2m zFjUNWd(E5cfC5iTV*j;R|69dTEnG@l?TiZ3F3NA3${*;#@$HfD0}T(ac@bZ$Z;5U5 zJb8K32A`~Xn;u&wOD$8JuA3A5HeqK~U+lWb_UWfzIqba97dm}=@|CT(FBWZ-eYLpA zU+cZc)A_wtdtz7fZQ*{pVb>m+&FfI_UUo&6jJop z-f>*D_ev4_(rZreG%TQI%0`Km0lsQi;HPnHbKI4~OI8TXIB~;#TZnj~XST~Z=`^2YRwqN_#Yf`!gx=m*_2SE; z9IbTM%iUq;Bf?55jQ)P$?`oTTBgs!e;4NQRx8=mX6|et9@i<=GzA>7o(xv~dlHh+& zL#1j@ft%@U6Khup-dxW&QCCdhW)b&Ft%#K&Q~rnt6@;+dKIP;HbP|K7tDnm{r-UW| DR4Gj} literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/thorn-back.png b/assets/sprites/bullet/thorn-back.png new file mode 100644 index 0000000000000000000000000000000000000000..57cdc89a6f1c60c684bb093baa1452ff69b47a95 GIT binary patch literal 254 zcmVPx#xJg7oR9J=W*Gmq7FbD)tFy8;lxUrL_QOopW8j+og4)QQ2RUymFEPqy2WzKJG z!``CauAB}b#FVd3xGw1(CxV=;KdFe+IT7Yy{Yl-N2&}LMCuKPi`oJ2Pw3QRVH>}?$ zU1Nv6`$PD}9s`n&;zWajH5ln=PBh6_1Ch?+M5B{+ZPKe>Rnz^@!}%2KX_|V_qE&^c2zrb0P?FmY*^nQg#Z8m07*qoM6N<$ Ef?t+ys{jB1 literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/thorn.aseprite b/assets/sprites/bullet/thorn.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..1152fb70eaeeff63af5e013f0482492efbde239b GIT binary patch literal 586 zcmeZbVqkc%l#xM(L4!eofsuiMAq9vL04N3$WDo#CT7XkP(|>(r1KY|5v_K4q*%iQ6 zB3YLOWV3)=rvPLqDVVrw*rZt+_J>*DFI2m^Ano1ZS-(G-&3qb=wa>tON}hM7evr3{ zk&UIjeThLrQ*_?LRtWQ zy}I%Z#_zz^TGo+-ZC6?xt$N)K*(ku)~iOJbHKsiqbEQ<_g|)zc>i$yt_`Q!j3vGiP03 zq~EzK3DYJomdg11Vvp4A+;{Ff8NWpfuTBmTo4w!0aq-m2K*L#~!Gy326p0mc-d-}~ zI{>unz@Pt@U&@IlHg{^Rp4XWCAu8sZVA0%jzVEjz^G(;^w9F@6fAf`Po74@ZUL=SN9p( W23BZ!XD*s{C`I>34a3PD;Tiy0=cs7_ literal 0 HcmV?d00001 diff --git a/assets/sprites/bullet/thorn.png b/assets/sprites/bullet/thorn.png new file mode 100644 index 0000000000000000000000000000000000000000..04f4391e2b95b7fa6bfbb30a85ee1538dbb84a19 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^GC-`s!3HGXKAWWvq!^2X+?^QKos)S9TMGvLe*$?3(q$iOf+;OCr>ntfR!kPrY(^(R;0Rs@>Jj2W{8>Yp=a{_~t*w wKa2WJwM?qtPhIxE{iU(*uNbWsfk(}6-l-dCZx8;s0_YS5Pgg&ebxsLQ0F)I{Gynhq literal 0 HcmV?d00001 diff --git a/assets/sprites/effect/sparkle.aseprite b/assets/sprites/effect/sparkle.aseprite deleted file mode 100644 index d2245d8c24fe713451dd39c1dc7cf268a00a239f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmcb}$iVPmDI{ z7Ld&Xa-9N@p`>8qs$r96Y1kiTeZNrc=7O|$hiCo%WH$3@K-NA3^C@}WnfgKADn>Sz z_Vy(P2~E*?50hvAk3ak5^zA?Y|No!8c6&=*duCQvWLQv{Q`Z_JqnqK$*Q%4q@KV6M1;Zxwhc!Zn zepT?PRq2YDtmEsnZ#^tcwblvW+ANpqKgD{{pPAPM-`*-+5vz54rS>VuuWM!)>A3VC fpOUFqm9~tF`{L)cRa*pXl_#Yx`4ZzlrQ#I;xZ8e_ diff --git a/assets/sprites/effects/chain.aseprite b/assets/sprites/effects/chain.aseprite index 6abb0bea853b259b1d755e84f2b6f5b6b7ab2fce..570ec24f6e1ddd4b5e49dd4eb967302525926d07 100644 GIT binary patch delta 20 bcmZ3cwoGkQ z63oonJ5mr4NzeOC<10d>+r#UE)_WMh1A&OQ8UhVq_6tBjh7VQ=BOtdMQQ}V!r;BLu z5k#J^;H};t!ao;sIPiAw4{;)b9YB{>>$@H4y`@1*4l}drfPY8alKc526aj9l7E)Y{ zIe8tJ8GB1xW(UO5>VS3)!LY149m_ih3P6g{^6f2XW&laZ1Mvv3vP4U5<(>mqa>NyD zPHqR<s@6ofxF4F(hAU;=Uyl&}nI@3PJ|ci;dy z07u{eIRJOy4%vAPOAdx_71KJO{0BJGkb?^V9!cG zn%O&)fhVILcoCUQCKESug1Mzk5ClQofN|y~op0g>^M65{wJ!>oHzo>*W)LT%q-z$s z0$*mx^K7id6_`{~pN93?f^kH?Z_7gWz<&YHV%#S-?q_;x0h5S)L`?iM8rRt`ktAKS z*gX(c9;d`f*DQ7gn4-Jmt~J2LJVeA?b76EsnN`4~bE^h82P$!c&kz;h+)6Px$D@jB_R9J=OmfaPCFbstoVHDf|_a6W^z)|qj7pgHN%^xow@0%8Kza~veDZi|` zD-+|AG0zyM0M0TG14xRL#0_9E@Ibf#xWE7ao){^Bo_X|Km;tDPwsR{`Cv(wITPhVmGN)=VUI4KK-l4Lr8~dylg9V7TlmyX}T0<-;{Z#Wb zd<;A#;OcEPjp~aI16YjlPU-V)&76UZIrkfOX>r(DP62SgFY8vR85Vw*n-f%Q7hnS# z#1a%sYv)l~n5Q8<^RaQQwQ}*^8St1%XC%@w-OGp@EbTvPCUt?P?jYhW9m}-i%%uz; z&Hw;_bZE4TN!oRPx$C`m*?R9J=Wma!4TAPhwzmmR!DsuZcRLMm^N4Ln6Q@CK>8f-4uUSR+#;m3%@7 zAro+l_*Xta;z`0FV|M+!Y-+*tc7DTmJIu-TMFH^UwIHI-=;k$|j{G zNC&vOgI_%Z~)iKN#zWH)_?{R)VQQL5O1L7<}rxP4tu+ z$4fyh>5xu8h&zlJj?PPSPzrSkIOnO%V4K+5;2zK@w(P-QvPvL1jFkb~;6MWaZ5F^Gw_bvv@Tv@?qM{J(Fk9&X!WWVHHykG?rk=}0)Acs> zzNk7PEDl1`7hn*)UfmOdAktFJ$*R8qr@85RvCdkl+wz)?>8_vZ1KQAw3|;vmp8x;= M07*qoM6N<$f`sy&Z2$lO literal 0 HcmV?d00001 diff --git a/src/meld/Meld.java b/src/meld/Meld.java index 112f64a..52b0dda 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -9,6 +9,7 @@ import arc.util.Time; import arc.util.Tmp; import meld.content.*; +import meld.graphics.MeldRegions; import meld.core.*; import mindustry.Vars; import mindustry.content.Blocks; @@ -26,8 +27,11 @@ public class Meld extends Mod{ public static NativeJavaPackage p = null; public static Melting melting; - + public Meld(){ + Events.on(EventType.ClientLoadEvent.class, e -> { + MeldRegions.load(); + }); } public static String prefix(String in){ diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index d31db7d..3f8f0e9 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -1,7 +1,13 @@ package meld.content; +import arc.graphics.Blending; import arc.graphics.Color; +import arc.math.Angles; import arc.math.Interp; +import arc.math.Mathf; +import arc.math.geom.Vec2; +import arc.util.Log; +import arc.util.Tmp; import meld.*; import meld.entities.bullet.OutflowBulletType; import meld.entities.bullet.TransitionBulletType; @@ -26,6 +32,7 @@ import mindustry.entities.bullet.*; import mindustry.entities.effect.MultiEffect; import mindustry.entities.effect.ParticleEffect; +import mindustry.entities.part.DrawPart; import mindustry.entities.part.HaloPart; import mindustry.entities.part.RegionPart; import mindustry.entities.part.ShapePart; @@ -169,6 +176,13 @@ public static void load(){ float density = MeldLiquids.aetherDensities.get(MeldLiquids.pollutantMixture, 1); inputLiquids = LiquidStack.with(MeldLiquids.pollutantMixture, outletRate/density); outputLiquids = LiquidStack.with(MeldLiquids.boundAspect, outletRate * multi * 10); + }}, + new TimedRecipe(){{ + craftTime = 10; + float multi = MeldLiquids.aetherEfficiencies.get(MeldLiquids.thunderingAether, 1); + float density = MeldLiquids.aetherDensities.get(MeldLiquids.thunderingAether, 1); + inputLiquids = LiquidStack.with(MeldLiquids.thunderingAether, outletRate/density); + outputLiquids = LiquidStack.with(MeldLiquids.stormingAspect, outletRate * multi * 10); }} ); }}; @@ -568,13 +582,346 @@ public static void load(){ molBullet ); - consume(new ConsumeAspects(outletRate, MeldLiquids.aspectEfficiencies, MeldLiquids.aspectDensities)); + consume( + new ConsumeAspects(outletRate, MeldLiquids.aspectEfficiencies, MeldLiquids.aspectDensities) + ); + }}; + + vivisection = new ItemTurret("vivisection"){{ + requirements(Category.turret, with(MeldItems.debris, 200, MeldItems.silver, 320, MeldItems.resonarum, 60)); + size = 4; + health = 2640; + range = 252; + + liquidCapacity = outletRate * 60 * 4; + fogRadiusMultiplier = 0.25f; + reload = 120; + + shootEffect = Fx.shootBig; + shootWarmupSpeed = 0.09f; + minWarmup = 0.7f; + + velocityRnd = 0.2f; + recoil = 1.5f; + inaccuracy = 2; + shootCone = 5; + + ammoPerShot = 12; + shootY = 8; + + rotate = quickRotate = false; + + shoot = new ShootSpread(){{ + shots = 1; + firstShotDelay = 60; + }}; + + consume(new ConsumeAspects(outletRate * 3, MeldLiquids.aspectEfficiencies, MeldLiquids.aspectDensities)); + + drawer = new DrawTurret(){{ + + parts.addAll( + new RegionPart("-jaw"){{ + mirror = true; + x = 4.5f; + y = 2; + moveRot = 180; + progress = PartProgress.charge.compress(0, 0.5f); + moves.add( + new PartMove(PartProgress.charge.compress(0.5f, 1).curve(Interp.pow2In),-0.75f, 0.5f, 0)); + }} + ); + + //Define the centers for the center of rotation and starting point + Vec2 center = new Vec2(4.5f, 2); + + //Start offset should be endpoint mirrored across the center + Vec2 startOffset = new Vec2(4, -20).scl(0.25f); + Vec2 endOffset = new Vec2(-18, 20).scl(0.25f); + float toMove = Angles.backwardDistance(startOffset.angle(), endOffset.angle()); + + float startRad = startOffset.len(); + float endRad = endOffset.len(); + + //Trust me the constant has a reason for it + DrawPart.PartProgress base = DrawPart.PartProgress.charge.compress(0, 0.5f); + DrawPart.PartMove SPINNNNNNNNBABYYYSPINSPINBABYYEEEEEEEEEE = + new DrawPart.PartMove(){{ + rot = 360 * 5; + progress = DrawPart.PartProgress.charge.compress(0.6f, 1).curve(Interp.pow5In); + }}; + for(int i = 0; i < 2; i++){ + int sign = i == 0 ? 1 : -1; + parts.add( + new RegionPart(){{ + mirror = false; + name = Meld.prefix("sawblade-large" + (sign == 1 ? "-r" : "-l")); + clampProgress = false; + + //Starting pos for the sawblades + x = center.x * sign; + y = center.y; + + moveX = moveY = 0; + growX = growY = 1; + xScl = yScl = 0; + growProgress = PartProgress.charge.compress(0, 0.5f).curve(Interp.pow2In); + + moves.addAll( + new PartMove(){{ + x = sign; + progress = p -> { + //Go from a clockwise starting position to a counterclockwise position + Tmp.v1.trns(Angles.moveToward(startOffset.angle(), endOffset.angle(), -toMove * base.get(p)), Mathf.lerp(startRad, endRad, base.get(p))); + return Tmp.v1.x; + }; + }}, + new PartMove(){{ + y = 1; + progress = p -> { + //counterclockwise rotation from start till end + Tmp.v1.trns(Angles.moveToward(startOffset.angle(), endOffset.angle(), -toMove * base.get(p)), Mathf.lerp(startRad, endRad, base.get(p))); + return Tmp.v1.y; + }; + }}, + new PartMove(){{ + rot = -180 * sign; + progress = base.inv(); + }}, + SPINNNNNNNNBABYYYSPINSPINBABYYEEEEEEEEEE + ); + }} + ); + } + + parts.addAll( + new RegionPart(){{ + name = Meld.prefix("sawblade-large-glow"); + x = 0; + y = 9; + blending = Blending.additive; + color = Color.white.cpy().a(0); + colorTo = Color.valueOf("6ed88e"); + progress = PartProgress.charge.compress(0.5f, 1).curve(Interp.pow2Out).slope(); + outline = false; + moves.addAll( + SPINNNNNNNNBABYYYSPINSPINBABYYEEEEEEEEEE + ); + }}, + new RegionPart("-plate"){{ + mirror = true; + moveX = 0.5f; + moveY = -0.5f; + moveRot = 10; + progress = PartProgress.warmup; + + moves.addAll( + new PartMove(PartProgress.recoil,1.25f, -1.25f, 15), + new PartMove(PartProgress.charge.compress(0.5f, 1).curve(Interp.pow2In),-0.75f, 0, 25) + ); + }}, + new RegionPart("-body"){{ + progress = PartProgress.charge.compress(0.75f, 1).curve(Interp.pow5In); + moveY = 0.75f; + children.addAll( + new RegionPart("-body-glow"){{ + blending = Blending.additive; + outline = false; + color = Pal.turretHeat; + colorTo = Color.valueOf("6ed88e"); + progress = PartProgress.charge.compress(0.5f, 1).curve(Interp.pow5In); + }} + ); + }} + ); + }}; + + ammoTypes.putAll( + MeldItems.resonarum, + new BasicBulletType(5, 160, Meld.prefix("sawblade-large")){{ + frontColor = Color.white; + backColor = Color.clear; + pierce = true; + pierceCap = 5; + pierceDamageFactor = 0.05f; + lifetime = 52; + knockback = 50; + impact = true; + spin = 300; + width = height = 24 * 2; + hitShake = 10.75f; + shrinkInterp = Interp.pow10In; + shrinkX = shrinkY = 0; + ammoMultiplier = 1; + + hitEffect = new ParticleEffect(){{ + lifetime = 15; + line = true; + lenTo = 8; + strokeFrom = 2; + strokeTo = 0.5f; + interp = Interp.pow5Out; + sizeInterp = Interp.pow5Out; + baseRotation = 25; + cone = 35; + colorFrom = Color.valueOf("85c799"); + colorTo = Color.valueOf("4bb66b"); + }}; + despawnEffect = Fx.none; + intervalBullet = new ExplosionBulletType(){{ + killShooter = false; + fragBullets = 2; + fragRandomSpread = 15; + fragSpread = 35; + fragAngle = 180; + fragVelocityMin = 0.5f; + fragVelocityMax = 1; + fragBullet = new BulletType(){{ + spawnBullets.addAll( + new TransitionBulletType(){{ + fragVelocityMin = 1; + fragBullets = 1; + fragBullet = new BulletType(2.5f, 5) {{ + + drag = 0.2f; + hitEffect = despawnEffect = Fx.none; + lifetime = 30; + bulletInterval = 5; + intervalBullets = 1; + lightRadius = 0; + parts.addAll( + new RegionPart() {{ + name = Meld.prefix("crystalline-smog"); + outline = false; + color = Color.white.cpy().a(0f); + colorTo = Color.white.cpy().a(0.1f); + progress = PartProgress.life.curve(Interp.slope); + moveRot = 360; + blending = Blending.additive; + layer = Layer.flyingUnitLow; + + xScl = yScl = 0.5f; + growX = growY = 0.5f; + growProgress = PartProgress.life.compress(0, 1).curve(Interp.pow2In); + }} + ); + + intervalBullet = new TransitionBulletType() {{ + fragBullets = 2; + fragVelocityMin = 0.5f; + fragVelocityMax = 1; + + fragBullet = new BasicBulletType(4.2f, 2, Meld.prefix("dual-spike")) {{ + width = 6; + height = 2; + frontColor = MeldPal.resoShardFront; + backColor = MeldPal.resoShardBack; + drag = 0.15f; + lifetime = 30; + + splashDamage = 5; + splashDamageRadius = 8; + hittable = false; + spin = 15; + hitEffect = despawnEffect = Fx.none; + status = MeldStatusEffects.lacerated; + statusDuration = 20; + sticky = true; + stickyExtraLifetime = 60; + + lightRadius = 0; + + float xScale = width / 20f; + float yScale = height / 20; + + parts.addAll( + new RegionPart() {{ + name = Meld.prefix("dual-spike"); + outline = false; + rotation = 60; + xScl = xScale; + yScl = yScale; + moveRot = 15 * 60; + progress = PartProgress.life; + + growX = -xScale; + growY = -yScale; + growProgress = PartProgress.life.curve(Interp.pow2In); + }}, + new RegionPart() {{ + name = Meld.prefix("dual-spike"); + outline = false; + rotation = 120; + xScl = xScale; + yScl = yScale; + moveRot = 7.5f * 60; + progress = PartProgress.life; + color = MeldPal.resoShardBack; + + growX = -xScale; + growY = -yScale; + growProgress = PartProgress.life.curve(Interp.pow5In); + }} + ); + }}; + }}; + }}; + }} + ); + damage = 5; + speed = 1.5f; + drag = 0.15f; + lifetime = 120; + homingPower = 0.05f; + frontColor = Color.valueOf("85c799"); + pierce = true; + status = MeldStatusEffects.lacerated; + statusDuration = 15; + hitEffect = Fx.none; + despawnEffect = Fx.none; + absorbable = hittable = reflectable = false; + parts.addAll( + new RegionPart(){{ + name = Meld.prefix("crystalline-smog"); + outline = false; + color = Color.clear; + colorTo = Color.white.cpy().a(1f); + progress = PartProgress.life.compress(0, 0.65f).curve(Interp.pow5Out).curve(Interp.slope); + moveRot = 360; + blending = Blending.additive; + + growX = growY = 2; + growProgress = PartProgress.life.compress(0, 0.65f).curve(Interp.pow2In); + }}, + //less glowey layer + new RegionPart(){{ + name = Meld.prefix("crystalline-smog"); + outline = false; + color = Color.clear; + colorTo = Color.white.cpy().a(0.6f); + progress = PartProgress.life.curve(Interp.pow5Out).curve(Interp.slope); + moveRot = 180 * 1.5f; + layer = Layer.flyingUnitLow; + + growX = growY = 4; + growProgress = PartProgress.life.compress(0, 0.8f).curve(Interp.pow2Out); + }} + ); + }}; + }}; + bulletInterval = 4; + intervalBullets = 1; + intervalSpread = 120; + intervalRandomSpread = 15; + }} + ); }}; shadesteelShingles = new Wall("shadesteel-shingles"){{ requirements(Category.defense, with(MeldItems.shadesteel, 64)); size = 2; health = 2400; + buildCostMultiplier = 2f; }}; silverHusk = new RegenProjector("silver-husk"){{ diff --git a/src/meld/content/MeldEnvironment.java b/src/meld/content/MeldEnvironment.java index 2b878d1..82b4d99 100644 --- a/src/meld/content/MeldEnvironment.java +++ b/src/meld/content/MeldEnvironment.java @@ -162,13 +162,13 @@ public static void load(){ }}; //Barrens + literallyCarbonStoneFromMindustryButSlightlyDifferent = new Floor("carbonic-stone", 4); + bedrock = new Floor("bedrock", 3); slate = new Floor("slate", 0); earthenStone = new Floor("earthen-stone", 4); carbonicPlates = new Floor("carbonic-plates", 3); - literallyCarbonStoneFromMindustryButSlightlyDifferent = new Floor("carbonic-stone", 4); - slate.blendGroup = bedrock; mixtureStone = new Floor("mixture-stone", 3); diff --git a/src/meld/content/MeldItems.java b/src/meld/content/MeldItems.java index b0d36ba..1939f0c 100644 --- a/src/meld/content/MeldItems.java +++ b/src/meld/content/MeldItems.java @@ -1,5 +1,6 @@ package meld.content; +import arc.struct.Seq; import mindustry.type.Item; public class MeldItems { @@ -66,5 +67,8 @@ public static void load(){ cruciblePlating = new Item("crucible-plating"){{ }}; + + Seq heavyIndustry = Seq.with(tenbris, shadesteel, elnarDust, annealedSilver, glassMallows, cruciblePlating, aspectPipe); + heavyIndustry.each(i -> i.databaseTag = "heavy-industry"); }; } diff --git a/src/meld/content/MeldLiquids.java b/src/meld/content/MeldLiquids.java index dc6e5d6..1f8b6d6 100644 --- a/src/meld/content/MeldLiquids.java +++ b/src/meld/content/MeldLiquids.java @@ -4,12 +4,14 @@ import arc.struct.ObjectFloatMap; import arc.struct.ObjectMap; import arc.struct.Seq; +import meld.fluid.Aspect; import mindustry.type.Liquid; public class MeldLiquids { - public static Liquid aether, aspect, meld, fumes, - pollutantMixture, boundAspect; + public static Aspect aether, aspect, meld, fumes, + pollutantMixture, boundAspect, + thunderingAether, stormingAspect; public static ObjectFloatMap aetherEfficiencies = new ObjectFloatMap<>(); public static ObjectFloatMap aetherDensities = new ObjectFloatMap<>(); @@ -24,68 +26,92 @@ public class MeldLiquids { public float outletRatio = 10; public static void load(){ - aether = new Liquid("aether"){{ + aether = new Aspect("aether"){{ gas = true; color = Color.valueOf("cb8650"); temperature = 0.6f; }}; - aspect = new Liquid("aspect"){{ + pollutantMixture = new Aspect("pollutant-mixture"){{ gas = true; - flammability = 1; - explosiveness = 2; - color = Color.valueOf("cbdbfc"); + color = Color.valueOf("6a634d"); temperature = 0.6f; }}; - meld = new Liquid("meld"){{ + thunderingAether = new Aspect("thundering-aether"){{ gas = true; - color = Color.valueOf("e4aad5"); + color = Color.valueOf("f99b76"); temperature = 0.6f; + explosiveness = 0.1f; }}; - fumes = new Liquid("fumes"){{ + aspect = new Aspect("aspect"){{ gas = true; - color = Color.valueOf("5b4739"); + flammability = 0.35f; + explosiveness = 1; + color = Color.valueOf("cbdbfc"); temperature = 0.6f; }}; - pollutantMixture = new Liquid("pollutant-mixture"){{ + stormingAspect = new Aspect("storming-aspect"){{ gas = true; - color = Color.valueOf("6a634d"); + color = Color.valueOf("aaadfd"); temperature = 0.6f; + flammability = 1; + explosiveness = 2; + }}; - boundAspect = new Liquid("bound-aspect"){{ + boundAspect = new Aspect("bound-aspect"){{ gas = true; color = Color.valueOf("d7a9ef"); temperature = 0.6f; }}; + meld = new Aspect("meld"){{ + gas = true; + color = Color.valueOf("e4aad5"); + temperature = 0.6f; + }}; + + fumes = new Aspect("fumes"){{ + gas = true; + color = Color.valueOf("5b4739"); + temperature = 0.6f; + }}; aetherEfficiencies.put(aether, 1); aetherEfficiencies.put(pollutantMixture, 1); + aetherEfficiencies.put(thunderingAether, 2); aetherDensities.put(aether, 1); aetherDensities.put(pollutantMixture, 1/5f); + aetherDensities.put(thunderingAether, 0.5f); aspectEfficiencies.put(aspect, 1); aspectEfficiencies.put(boundAspect, 1); + aspectEfficiencies.put(stormingAspect, 2); aspectDensities.put(aspect, 1); aspectDensities.put(boundAspect, 2.5f); + aspectDensities.put(stormingAspect, 1f); + + aether.databaseTag = pollutantMixture.databaseTag = thunderingAether.databaseTag = + aspect.databaseTag = boundAspect.databaseTag = stormingAspect.databaseTag = "aspect-powergen"; + meld.databaseTag = "aspect-omnipotence"; + fumes.databaseTag = "aspect-vita"; outletMapping.putAll( aether, aspect, - pollutantMixture, boundAspect + pollutantMixture, boundAspect, + thunderingAether, stormingAspect ); - Seq outletLiquids = Seq.with(aether, pollutantMixture); + Seq outletLiquids = Seq.with(aether, pollutantMixture, thunderingAether); outletLiquids.each(liquid -> { Liquid aspecti = outletMapping.get(liquid); outletEfficiencies.put(liquid, aetherEfficiencies.get(liquid, 1) * aspectEfficiencies.get(aspecti, 1)); outletDensities.put(liquid, aetherDensities.get(liquid, 1) * aspectDensities.get(aspecti, 1)); }); - } } diff --git a/src/meld/content/MeldStatusEffects.java b/src/meld/content/MeldStatusEffects.java index dbad377..0b89170 100644 --- a/src/meld/content/MeldStatusEffects.java +++ b/src/meld/content/MeldStatusEffects.java @@ -8,6 +8,7 @@ public class MeldStatusEffects { public static StatusEffect amplified, rally, anchored, aspectBurn, sentry, spurting, newborn, interference, drenched, stunned; + public static StatusEffect lacerated; public static void load(){ @@ -166,5 +167,12 @@ public void update(Unit unit, StatusEntry entry) { speedMultiplier = 0.5f; }}; + + lacerated = new StatusEffect("lacerated"){{ + damage = -0.2f; + speedMultiplier = 0.45f; + reloadMultiplier = 0.5f; + buildSpeedMultiplier = 0.5f; + }}; } } diff --git a/src/meld/content/MeldUnits.java b/src/meld/content/MeldUnits.java index 3f134b2..64f49d1 100644 --- a/src/meld/content/MeldUnits.java +++ b/src/meld/content/MeldUnits.java @@ -1,5 +1,6 @@ package meld.content; +import arc.func.Prov; import arc.graphics.Blending; import arc.graphics.Color; import arc.graphics.g2d.Draw; @@ -8,6 +9,8 @@ import arc.math.Angles; import arc.math.Interp; import arc.math.Mathf; +import arc.struct.ObjectIntMap; +import arc.util.Log; import arc.util.Tmp; import meld.*; import meld.entities.bullet.TransitionBulletType; @@ -39,8 +42,52 @@ import mindustry.type.UnitType; import mindustry.type.Weapon; import mindustry.type.weapons.RepairBeamWeapon; +import arc.struct.ObjectMap.Entry; public class MeldUnits { + + private static Entry, Prov>[] types = new Entry[]{ + prov(BulbheadEntity.class, BulbheadEntity::new) + }; + + private static ObjectIntMap> idMap = new ObjectIntMap<>(); + + /** + * Internal function to flatmap {@code Class -> Prov} into an {@link Entry}. + * @author GlennFolker + */ + + private static Entry, Prov> prov(Class type, Prov prov){ + Entry, Prov> entry = new Entry<>(); + entry.key = type; + entry.value = prov; + return entry; + } + + /** + * Setups all entity IDs and maps them into {@link EntityMapping}. + * Find all free ids to map to, then put the Entry(s) from types into the idMap. Starts searching after the last known index of a vanilla Entry + */ + + private static void setupID(){ + int start = 33; + int[] free = new int[types.length]; + for (int i = start, j = 0; i < EntityMapping.idMap.length; i++) { + if(EntityMapping.idMap[i] == null) free[j++] = i; + if(j > free.length - 1) break; + } + + Log.info("setting up map"); + for (int i = 0; i < free.length; i++) { + idMap.put(types[i].key, free[i]); + EntityMapping.idMap[free[i]] = types[i].value; + } + } + + public static int classID(Class type){ + return idMap.get(type, -1); + } + //player units public static UnitType bulbhead, shark, @@ -53,6 +100,8 @@ public class MeldUnits { jilla, kathid; public static void load(){ + setupID(); + bulbheadOverseer = new UnitType("bulbhead-overseer"){{ float IR = 80; @@ -206,7 +255,7 @@ public static void load(){ }}; bulbhead = new UnitType("bulbhead"){{ - float IR = 120; + float IR = 140; health = 290; @@ -376,7 +425,7 @@ public static void load(){ color = Pal.accent; }} ); - constructor = UnitEdgeWaterMove::new; + constructor = BulbheadEntity::new; abilities.addAll( new RegenAbility(){{ diff --git a/src/meld/entities/unit/BulbheadEntity.java b/src/meld/entities/unit/BulbheadEntity.java new file mode 100644 index 0000000..014a11f --- /dev/null +++ b/src/meld/entities/unit/BulbheadEntity.java @@ -0,0 +1,350 @@ +package meld.entities.unit; + +import arc.Events; +import arc.graphics.Blending; +import arc.graphics.Color; +import arc.graphics.g2d.Draw; +import arc.graphics.g2d.Fill; +import arc.graphics.g2d.Lines; +import arc.math.Angles; +import arc.math.Interp; +import arc.math.Mathf; +import arc.math.geom.Position; +import arc.util.*; +import meld.content.MeldUnits; +import meld.graphics.Draww; +import meld.graphics.MeldRegions; +import meld.world.blocks.CoreRaft; +import mindustry.Vars; +import mindustry.content.Blocks; +import mindustry.content.Fx; +import mindustry.entities.EntityCollisions; +import mindustry.entities.Units; +import mindustry.entities.units.BuildPlan; +import mindustry.game.EventType; +import mindustry.game.Team; +import mindustry.game.Teams; +import mindustry.gen.Building; +import mindustry.gen.Call; +import mindustry.gen.Sounds; +import mindustry.gen.UnitWaterMove; +import mindustry.graphics.Drawf; +import mindustry.graphics.Layer; +import mindustry.graphics.Pal; +import mindustry.world.Build; +import mindustry.world.Tile; +import mindustry.world.blocks.ConstructBlock; +import mindustry.world.blocks.storage.CoreBlock; + +//Bandaid fix for multiplayer units dying randomly because I legit have no idea how to fix it otherwise +public class BulbheadEntity extends UnitWaterMove { + + @Nullable + public CoreRaft.CoreRaftBuild nearbyRaft; + + //Actually counts upwards... kinda counter intuitively named + public float corelinkGrace = 0; + + //skip all the logic involved and just give rotation + @Override + public float prefRotation() { + return this.moving() && this.type.omniMovement ? this.vel().angle() : this.rotation; + } + + @Override + public boolean canShoot() { + return super.canShoot(); + } + + @Override + public void update() { + getNearbyLink(); + + Tile tile = this.tileOn(); + + if (tile != null && !this.canPassOn()) { + for(int i = 0; i < 4; i++){ + Tile other = tile.nearby(i); + if(!EntityCollisions.waterSolid(other.x, other.y)){ + x = other.worldx(); + y = other.worldy(); + + Log.info("SAVED"); + } + } + } + super.update(); + + } + + public void getNearbyLink(){ + nearbyRaft = CoreRaft.rafts.sort(raft -> raft.dst(this)).first(); + if(!within(nearbyRaft, type.fogRadius * Vars.tilesize)) nearbyRaft = null; + } + + //Copying a lot from vanilla's source code... yeah... + //Reuseable code I guess + @Override + public boolean activelyBuilding() { + if (this.isBuilding()) { + BuildPlan plan = this.buildPlan(); + if (!Vars.state.isEditor() && plan != null && !withinBuildRange(plan)) { + return false; + } + } + + return this.isBuilding() && this.updateBuilding; + } + + public boolean withinBuildRange(Position p){ + return Vars.state.rules.infiniteResources || within(p, type.buildRange) || nearbyRaft != null && nearbyRaft.within(p, nearbyRaft.fogRadius() * Vars.tilesize); + } + + public float buildDistance(Position p){ + return nearbyRaft != null ? Math.min(dst(p), nearbyRaft.dst(p)) : dst(p); + } + + //God this is a lot of reused code isn't it + public void updateBuildLogic() { + if (!(this.type.buildSpeed <= 0.0F)) { + if (!Vars.headless) { + if (this.lastActive != null && this.buildAlpha <= 0.01F) { + this.lastActive = null; + } + + this.buildAlpha = Mathf.lerpDelta(this.buildAlpha, this.activelyBuilding() ? 1.0F : 0.0F, 0.15F); + } + + this.validatePlans(); + if (this.updateBuilding && this.canBuild()) { + boolean infinite = Vars.state.rules.infiniteResources || this.team().rules().infiniteResources; + this.buildCounter += Time.delta; + if (Float.isNaN(this.buildCounter) || Float.isInfinite(this.buildCounter)) { + this.buildCounter = 0.0F; + } + + this.buildCounter = Math.min(this.buildCounter, 10.0F); + boolean instant = Vars.state.rules.instantBuild && Vars.state.rules.infiniteResources; + int maxPerFrame = instant ? this.plans.size : 10; + int count = 0; + CoreBlock.CoreBuild core = this.core(); + if (core != null || infinite) { + while((this.buildCounter >= 1.0F || instant) && count++ < maxPerFrame && this.plans.size > 0) { + --this.buildCounter; + boolean hasAll; + if (this.plans.size > 1) { + int total = 0; + int size = this.plans.size; + float bestDst = Float.MAX_VALUE; + hasAll = false; + + int bestIndex; + for(bestIndex = -1; total < size; ++total) { + BuildPlan plan = this.buildPlan(); + + //Tweakead so anything within the nearest raft core's build radius gets validated + float dst = plan.dst2(this); + boolean within = withinBuildRange(plan); + if (within && !this.shouldSkip(plan, core)) { + hasAll = true; + break; + } + + if (within && dst < bestDst) { + bestIndex = total; + bestDst = dst; + } + + this.plans.removeFirst(); + this.plans.addLast(plan); + } + + if (!hasAll && bestIndex > 0 && withinBuildRange(buildPlan())) { + for(int i = 0; i < bestIndex; ++i) { + this.plans.addLast(this.plans.removeFirst()); + } + } + } + + BuildPlan current = this.buildPlan(); + Tile tile = current.tile(); + this.lastActive = current; + this.buildAlpha = 1.0F; + if (current.breaking) { + this.lastSize = tile.block().size; + } + + if (withinBuildRange(tile)) { + if (!Vars.headless) { + Vars.control.sound.loop(Sounds.loopBuild, tile, 1.3F); + } + + Building var18 = tile.build; + ConstructBlock.ConstructBuild entity; + if (var18 instanceof ConstructBlock.ConstructBuild) { + entity = (ConstructBlock.ConstructBuild)var18; + if (tile.team() != this.team && tile.team() != Team.derelict || !current.breaking && (entity.current != current.block || entity.tile != current.tile())) { + this.plans.removeFirst(); + continue; + } + } else if (!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, this.team, current.x, current.y, current.rotation, true, true)) { + if (!Build.checkNoUnitOverlap(current.block, current.x, current.y)) { + this.plans.removeFirst(); + this.plans.addLast(current); + continue; + } + + hasAll = infinite || current.isRotation(this.team) || tile.team() == Team.derelict && tile.block() == current.block && tile.build != null && tile.block().allowDerelictRepair && Vars.state.rules.derelictRepair || !Structs.contains(current.block.requirements, (ix) -> { + return !core.items.has(ix.item, Math.min(Mathf.round((float)ix.amount * Vars.state.rules.buildCostMultiplier), 1)); + }); + if (hasAll) { + Call.beginPlace(this, current.block, this.team, current.x, current.y, current.rotation, current.block.instantBuild ? current.config : null); + if (!Vars.net.client() && current.block.instantBuild) { + if (this.plans.size > 0) { + this.plans.removeFirst(); + } + continue; + } + } else { + current.stuck = true; + } + } else { + if (current.initialized || !current.breaking || !Build.validBreak(this.team, current.x, current.y)) { + this.plans.removeFirst(); + continue; + } + + Call.beginBreak(this, this.team, current.x, current.y); + } + + if (tile.build instanceof ConstructBlock.ConstructBuild && !current.initialized) { + Events.fire(new EventType.BuildSelectEvent(tile, this.team, this, current.breaking)); + current.initialized = true; + } + + var18 = tile.build; + if (var18 instanceof ConstructBlock.ConstructBuild) { + entity = (ConstructBlock.ConstructBuild)var18; + float bs = 1.0F / entity.buildCost * this.type.buildSpeed * this.buildSpeedMultiplier * Vars.state.rules.buildSpeed(this.team); + if (current.breaking) { + entity.deconstruct(this, core, bs); + } else if (entity.current != null && (Vars.state.isEditor() || Vars.state.rules.waves && this.team == Vars.state.rules.waveTeam && entity.current.isVisible() || entity.current.unlockedNowHost() && entity.current.environmentBuildable() && entity.current.isPlaceable())) { + entity.construct(this, core, bs, current.config); + } + + current.stuck = Mathf.equal(current.progress, entity.progress); + current.progress = entity.progress; + } + } + } + + } + } + } + } + + public void draw(){ + super.draw(); + + //Draww.drawChain(chain, x, y, Core.input.mouseWorldX(), Core.input.mouseWorldY(), 0); + + if(nearbyRaft != null){ + float bsize = nearbyRaft.block.size/2f * Vars.tilesize; + Draw.z(Layer.blockBuilding - 1); + Draw.quad(nearbyRaft.block.fullIcon, + nearbyRaft.x - bsize, nearbyRaft.y - bsize, Tmp.c1.set(Color.red).shiftHue(Time.time).toFloatBits(), + nearbyRaft.x - bsize, nearbyRaft.y + bsize, Tmp.c1.set(Color.green).shiftHue(Time.time).toFloatBits(), + nearbyRaft.x + bsize, nearbyRaft.y + bsize, Tmp.c1.set(Color.blue).shiftHue(Time.time).toFloatBits(), + nearbyRaft.x + bsize, nearbyRaft.y - bsize, Tmp.c1.set(Color.yellow).shiftHue(Time.time).toFloatBits()); + + Draw.blend(Blending.additive); + Draw.z(Layer.effect); + Draw.color(Pal.accent); + //How close/far the unit is away from the core on a scale of 0-1 + float dist = dst(nearbyRaft); + float stretch = 1 - dist/(type.fogRadius * Vars.tilesize); + //Draww.drawChainAlpha(MeldRegions.chain, nearbyRaft.x, nearbyRaft.y, x, y, 1 - 0.5f * stretch, 1 + 2 * stretch, 0,a -> Interp.slope.apply(1 - a)); + Draww.drawChainAlpha(MeldRegions.chain, nearbyRaft.x, nearbyRaft.y, x, y, 1 - 0.5f * Interp.pow5.apply(1-stretch), 0.5f + 0.5f * stretch, 0, f -> Interp.slope.apply((f + dist)/(type.fogRadius * Vars.tilesize) % 1 + Time.time/60)); + Draw.blend(Blending.normal); + } + } + + //Attempt to draw the build beam from the unit itself. If that fails, draw it to the core instead, then draw the build beam from the core + public void drawBuilding() { + boolean active = this.activelyBuilding(); + if (plans.size > 0 && (active || this.lastActive != null)) { + Draw.z(115.0F); + BuildPlan plan = active ? this.buildPlan() : this.lastActive; + Tile tile = plan.tile(); + CoreBlock.CoreBuild core = this.team.core(); + + Position origin = within(plan, type.buildRange) ? this : nearbyRaft; + if (tile != null && origin != null) { + if (core != null && active && !this.isLocal() && !(tile.block() instanceof ConstructBlock)) { + Draw.z(84.0F); + this.drawPlan(plan, 0.5F); + this.drawPlanTop(plan, 0.5F); + Draw.z(115.0F); + } + + if (type.drawBuildBeam) { + float focusLen = type.buildBeamOffset + Mathf.absin(Time.time, 3.0F, 0.6F); + float px = origin.getX(); + float py = origin.getY(); + if(origin == this){ + px += Angles.trnsx(rotation, focusLen); + py += Angles.trnsy(rotation, focusLen); + } + + drawBuildingBeam(px, py); + + if(origin != this){ + Draw.z(122); + Drawf.buildBeam(origin.getX(), origin.getY(), x, y, 4); + Lines.stroke(2); + + px = x + Angles.trnsx(rotation, focusLen); + py = y + Angles.trnsy(rotation, focusLen); + Lines.dashLine(px, py, plan.drawx(), plan.drawy(), 5); + } + } + } + } + } + + @Override + public void controlWeapons(boolean rotateShoot) { + super.controlWeapons(rotateShoot); + } + + public void drawBuildingBeam(float px, float py) { + + boolean active = this.activelyBuilding(); + if (active || this.lastActive != null) { + Draw.z(115.0F); + BuildPlan plan = active ? this.buildPlan() : this.lastActive; + Tile tile = Vars.world.tile(plan.x, plan.y); + if (tile != null) { + int size = plan.breaking ? (active ? tile.block().size : this.lastSize) : plan.block.size; + float tx = plan.drawx(); + float ty = plan.drawy(); + Lines.stroke(1.0F, plan.breaking ? Pal.remove : Pal.accent); + Draw.z(122.0F); + Draw.alpha(this.buildAlpha); + if (!active && !(tile.build instanceof ConstructBlock.ConstructBuild)) { + Fill.square(plan.drawx(), plan.drawy(), (float)(size * 8) / 2.0F); + } + + Drawf.buildBeam(px, py, tx, ty, (float)(8 * size) / 2.0F); + Fill.square(px, py, 1.8F + Mathf.absin(Time.time, 2.2F, 1.1F), this.rotation + 45.0F); + Draw.reset(); + Draw.z(115.0F); + } + } + } + + @Override + public int classId() { + return MeldUnits.classID(this.getClass()); + } +} diff --git a/src/meld/entities/unit/UnitEdgeWaterMove.java b/src/meld/entities/unit/UnitEdgeWaterMove.java deleted file mode 100644 index 939dd6a..0000000 --- a/src/meld/entities/unit/UnitEdgeWaterMove.java +++ /dev/null @@ -1,40 +0,0 @@ -package meld.entities.unit; - -import arc.util.Log; -import mindustry.entities.EntityCollisions; -import mindustry.gen.UnitWaterMove; -import mindustry.world.Tile; - -//Bandaid fix for multiplayer units dying randomly because I legit have no idea how to fix it otherwise -public class UnitEdgeWaterMove extends UnitWaterMove { - - //skip all the logic involved and just give rotation - @Override - public float prefRotation() { - return this.moving() && this.type.omniMovement ? this.vel().angle() : this.rotation; - } - - @Override - public boolean canShoot() { - return super.canShoot(); - } - - @Override - public void update() { - Tile tile = this.tileOn(); - - if (tile != null && !this.canPassOn()) { - for(int i = 0; i < 4; i++){ - Tile other = tile.nearby(i); - if(!EntityCollisions.waterSolid(other.x, other.y)){ - x = other.worldx(); - y = other.worldy(); - - Log.info("SAVED"); - } - } - } - super.update(); - - } -} diff --git a/src/meld/fluid/Aspect.java b/src/meld/fluid/Aspect.java new file mode 100644 index 0000000..e806c15 --- /dev/null +++ b/src/meld/fluid/Aspect.java @@ -0,0 +1,12 @@ +package meld.fluid; + +import arc.graphics.Color; +import mindustry.ctype.ContentType; +import mindustry.type.Liquid; + +public class Aspect extends Liquid { + public Aspect(String name) { + super(name, Color.white); + databaseCategory = "aspect"; + } +} diff --git a/src/meld/graphics/Draww.java b/src/meld/graphics/Draww.java index 33c691a..8957d7d 100644 --- a/src/meld/graphics/Draww.java +++ b/src/meld/graphics/Draww.java @@ -1,13 +1,20 @@ package meld.graphics; +import arc.graphics.Color; import arc.graphics.g2d.Draw; +import arc.graphics.g2d.Lines; import arc.graphics.g2d.TextureRegion; +import arc.math.Interp; import arc.math.Mathf; import arc.math.geom.Vec2; +import arc.util.Tmp; import mindustry.Vars; +import mindustry.content.Blocks; +import mindustry.graphics.Pal; +import mindustry.world.Block; public class Draww { - public static Vec2 tv1 = new Vec2(); + public static Vec2 tv1 = new Vec2(), tv2 = new Vec2(), tv3 = new Vec2(); //draws a chain of sprites public static void drawChain(TextureRegion region, float x, float y, float endx, float endy, float drawRotation){ drawChain(region, x, y, endx, endy, 1, 1, drawRotation); @@ -17,21 +24,75 @@ public static void drawChain(TextureRegion region, float x, float y, float endx, float distance = Mathf.dst(x, y, endx, endy); //This is in WU - float height = region.height/4 * hScale; + float height = region.height/4f * hScale; float width = region.width/4f * wScale; //Distance converted back to WU - float remainder = ((distance * 4) % region.height * hScale)/8; + float remainder = distance % height; - for (int i = 0; i < Math.floor((distance)/region.height * 4); i++) { - tv1.trns(angleToEnd, distance - i * height - region.height/8 * hScale).add(x ,y); + for (int i = 0; i < Math.floor((distance)/(height)); i++) { + tv1.trns(angleToEnd, distance - i * height - height/2).add(x ,y); Draw.rect(region, tv1.x, tv1.y, width, height, drawRotation + angleToEnd - 90); } - tv1.trns(drawRotation + angleToEnd, remainder); + tv1.trns(angleToEnd, remainder/2); - //Lines.line(x, y, x + tv1.x, y + tv1.y); - Draw.rect(region, x + tv1.x, y + tv1.y, width, remainder * 2, drawRotation + angleToEnd - 90); + Draw.rect(region, x + tv1.x, y + tv1.y, width, remainder, drawRotation + angleToEnd - 90); + } + + public interface AlphaProv{ + float get(float in); + } + + public static void drawChainAlpha(TextureRegion region, float x, float y, float endx, float endy, float wScale, float hScale, float drawRotation, Interp alphaProv){ + float angleToEnd = Mathf.angle(endx - x, endy - y); + float distance = Mathf.dst(x, y, endx, endy); + + //This is in WU + float height = region.height/4f * hScale; + float width = region.width/4f * wScale; + + //Distance converted back to WU + float remainder = distance % height; + + + + Tmp.c1.set(Draw.getColor()); + Tmp.c2.set(Draw.getColor()); + + tv3.trns(angleToEnd + 90, width/2); + + for (int i = 0; i < Math.floor((distance)/(height)); i++) { + tv1.trns(angleToEnd, distance - i * height - height); + tv2.trns(angleToEnd, distance - i * height); + + float baseAlpha = alphaProv.apply(tv1.len()); + float endAlpha = alphaProv.apply(tv2.len()); + + tv1.add(x, y); + tv2.add(x, y); + + Tmp.c1.a(baseAlpha); + Tmp.c2.a(endAlpha); + Draw.quad(region, + tv2.x + tv3.x, tv2.y + tv3.y, Tmp.c2.toFloatBits(), + tv1.x + tv3.x, tv1.y + tv3.y, Tmp.c1.toFloatBits(), + tv1.x - tv3.x, tv1.y - tv3.y, Tmp.c1.toFloatBits(), + tv2.x - tv3.x, tv2.y - tv3.y, Tmp.c2.toFloatBits() + ); + + //Draw.rect(region, tv1.x, tv1.y, width, height, drawRotation + angleToEnd - 90); + //Draw.quad(region, tv1.x, tv1.y, width, height, drawRotation + angleToEnd - 90); + } + + tv1.trns(angleToEnd, remainder/2f); + + Draw.alpha(alphaProv.apply(1 - tv1.len()/distance)); + Draw.rect(region, x + tv1.x, y + tv1.y, width, remainder, drawRotation + angleToEnd - 90); + Draw.alpha(1); + + Draw.color(); + Lines.stroke(Vars.tilesize); } public static void drawShine(TextureRegion region, float x, float y, float rotation, float alpha){ diff --git a/src/meld/graphics/MeldInterps.java b/src/meld/graphics/MeldInterps.java new file mode 100644 index 0000000..a507273 --- /dev/null +++ b/src/meld/graphics/MeldInterps.java @@ -0,0 +1,13 @@ +package meld.graphics; + +import arc.math.Interp; +import arc.math.Mathf; + +public class MeldInterps { + public static Interp + trueSin = Mathf::sin, + trueCos = Mathf::cos, + compressedSin = f -> Mathf.sin(f * Mathf.PI2), + compressedCos = f -> Mathf.cos(f * Mathf.PI2) + ; +} diff --git a/src/meld/graphics/MeldPal.java b/src/meld/graphics/MeldPal.java index 52d238a..daf59e0 100644 --- a/src/meld/graphics/MeldPal.java +++ b/src/meld/graphics/MeldPal.java @@ -14,5 +14,7 @@ public class MeldPal { flamePink = Color.valueOf("feafea"), flamePinkDark = Color.valueOf("d26ab1"), shockwaveGray = Color.valueOf("7f7f7f"), - sparkOrange = Color.valueOf("f6e096"); + sparkOrange = Color.valueOf("f6e096"), + resoShardFront = Color.valueOf("a3efba"), + resoShardBack = Color.valueOf("40a547"); } diff --git a/src/meld/graphics/MeldRegions.java b/src/meld/graphics/MeldRegions.java new file mode 100644 index 0000000..2f56de7 --- /dev/null +++ b/src/meld/graphics/MeldRegions.java @@ -0,0 +1,14 @@ +package meld.graphics; + +import arc.Core; +import arc.graphics.g2d.TextureRegion; +import meld.Meld; + +public class MeldRegions { + + public static TextureRegion chain; + + public static void load(){ + chain = Core.atlas.find(Meld.prefix("chain")); + } +} diff --git a/src/meld/meta/MeldStatUnit.java b/src/meld/meta/MeldStatUnit.java new file mode 100644 index 0000000..baaa480 --- /dev/null +++ b/src/meld/meta/MeldStatUnit.java @@ -0,0 +1,9 @@ +package meld.meta; + +import mindustry.world.meta.StatUnit; + +public class MeldStatUnit { + public static final StatUnit + ticks = new StatUnit("ticks") + ; +} diff --git a/src/meld/world/blocks/CoreRaft.java b/src/meld/world/blocks/CoreRaft.java index 00d9c47..5134e19 100644 --- a/src/meld/world/blocks/CoreRaft.java +++ b/src/meld/world/blocks/CoreRaft.java @@ -1,8 +1,10 @@ package meld.world.blocks; +import arc.Events; import arc.graphics.g2d.Draw; import arc.graphics.g2d.Lines; import arc.math.Interp; +import arc.struct.Seq; import arc.util.Nullable; import arc.util.Time; import meld.content.MeldStatusEffects; @@ -10,6 +12,8 @@ import mindustry.content.Fx; import mindustry.entities.Units; import mindustry.entities.units.WeaponMount; +import mindustry.game.EventType; +import mindustry.game.Team; import mindustry.gen.Building; import mindustry.gen.Player; import mindustry.gen.Unit; @@ -22,9 +26,11 @@ import static mindustry.Vars.tilesize; -//Rallies units nearby, +//Rallies units nearby, provides an extended build radius for core units public class CoreRaft extends CoreBlock { + public static Seq rafts = new Seq<>(); + private float nearestLen; public int spawnRange; @@ -35,7 +41,13 @@ public class CoreRaft extends CoreBlock { public CoreRaft(String name) { super(name); - fogRadius = 40; + fogRadius = 50; + Events.on(EventType.WorldLoadEvent.class, e -> { + rafts.clear(); + Team.sharded.cores().each(c -> { + if(c instanceof CoreRaftBuild raft) rafts.add(raft); + }); + }); } @Override @@ -89,15 +101,35 @@ public static void playerSpawn(Tile tile, Player player) { public class CoreRaftBuild extends CoreBuild { public @Nullable Tile targetTile; + public boolean building = false, lastBuilding = false; + + @Override + public void onRemoved() { + super.onRemoved(); + rafts.remove(this); + } + + @Override + public Building init(Tile tile, Team team, boolean shouldAdd, int rotation) { + rafts.add(this); + return super.init(tile, team, shouldAdd, rotation); + } @Override public void updateTile() { super.updateTile(); + lastBuilding = building; + building = false; + if(timer.get(rallyTimer, 5)){ Units.nearby(team, x, y, fogRadius() * tilesize, (other) -> { other.apply(MeldStatusEffects.rally, rallyDuration); }); } + + if(lastBuilding){ + + } } @Override @@ -115,7 +147,6 @@ public void draw() { } Draw.z(layer); - } /* diff --git a/src/meld/world/blocks/fluid/AspectPipe.java b/src/meld/world/blocks/fluid/AspectPipe.java index 5ca01aa..e9adab8 100644 --- a/src/meld/world/blocks/fluid/AspectPipe.java +++ b/src/meld/world/blocks/fluid/AspectPipe.java @@ -2,19 +2,26 @@ import arc.math.Mathf; import arc.math.geom.Geometry; +import arc.struct.Seq; import arc.util.Log; +import meld.content.MeldLiquids; import meld.world.blocks.crafting.RecipeCrafter; import mindustry.Vars; +import mindustry.game.Team; import mindustry.gen.Building; import mindustry.type.Liquid; import mindustry.world.Block; import mindustry.world.Tile; +import mindustry.world.blocks.distribution.DuctBridge; +import mindustry.world.blocks.liquid.ArmoredConduit; import mindustry.world.blocks.liquid.Conduit; import mindustry.world.blocks.liquid.LiquidRouter; import static meld.content.MeldLiquids.*; -public class AspectPipe extends Conduit { +public class AspectPipe extends ArmoredConduit { + + Seq liquidSeq = new Seq<>(); static final float rotatePad = 6, hpad = rotatePad / 2f / 4f; static final float[][] rotateOffsets = {{hpad, hpad}, {-hpad, hpad}, {-hpad, -hpad}, {hpad, -hpad}}; @@ -24,7 +31,7 @@ public AspectPipe(String name) { @Override public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock) { - return super.blends(tile, rotation, otherx, othery, otherrot, otherblock) || (otherblock.hasLiquids && !(otherblock instanceof Conduit || otherblock instanceof LiquidRouter)); + return super.blends(tile, rotation, otherx, othery, otherrot, otherblock) || (otherblock.hasLiquids && !(otherblock instanceof Conduit || otherblock instanceof LiquidRouter) && outletMapping.keys().toSeq().find(l -> otherblock.consumesLiquid(l)) != null); } public class AspectPipeBuild extends ConduitBuild{ diff --git a/src/meld/world/blocks/fluid/ChannelDirector.java b/src/meld/world/blocks/fluid/ChannelDirector.java index 1cdceb4..9367148 100644 --- a/src/meld/world/blocks/fluid/ChannelDirector.java +++ b/src/meld/world/blocks/fluid/ChannelDirector.java @@ -2,9 +2,13 @@ import arc.Core; import arc.graphics.g2d.Draw; +import arc.math.geom.Geometry; +import arc.math.geom.Point2; +import arc.math.geom.Position; import arc.util.Log; import meld.world.WorldUtil; import mindustry.gen.Building; +import mindustry.type.Liquid; public class ChannelDirector extends FlexibleSizeJunction{ @@ -20,9 +24,17 @@ public ChannelDirector(String name) { public class DirectorBuild extends FlexibleBuild{ + @Override + public Building getLiquidDestination(Building source, Liquid liquid, Position otherOffset) { + int dir = WorldUtil.relativeTo(source.tile.x, source.tile.y, tile.x, tile.y); + if(!acceptDirection(source, this, dir)) return this; + + return super.getLiquidDestination(source, liquid, otherOffset); + } + @Override public boolean acceptDirection(Building source, Building junction, int dir) { - return dir == rotation; + return dir != (rotation + 2) % 4; } @Override diff --git a/src/meld/world/blocks/fluid/ChannelVent.java b/src/meld/world/blocks/fluid/ChannelVent.java index 1499aca..16df53d 100644 --- a/src/meld/world/blocks/fluid/ChannelVent.java +++ b/src/meld/world/blocks/fluid/ChannelVent.java @@ -1,6 +1,7 @@ package meld.world.blocks.fluid; import arc.graphics.g2d.Draw; +import arc.math.Interp; import arc.math.Mathf; import mindustry.entities.Puddles; import mindustry.type.Liquid; @@ -15,11 +16,15 @@ public class ChannelVent extends LiquidRouter { int ventTimer = timers++; + public Interp ventScaling = Interp.pow2In; + public ChannelVent(String name) { super(name); } public class ChannelVentBuild extends LiquidRouterBuild{ + + @Override public void updateTile() { super.updateTile(); @@ -27,7 +32,7 @@ public void updateTile() { Liquid liquid = liquids.current(); if(timer.get(ventTimer, ventDelay) && liquid.gas || liquid.boilPoint <= Attribute.heat.env()){ - float ventEfficiency = (liquids.get(liquid)/liquidCapacity - minPressure)/(1 - minPressure); + float ventEfficiency = ventScaling.apply((liquids.get(liquid)/liquidCapacity - minPressure)/(1 - minPressure)); if(ventEfficiency > 0){ From 82d37d4e9a33f07a89816afbcd7e6bb5121c2aa2 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Mon, 6 Apr 2026 13:27:17 +1000 Subject: [PATCH 18/24] yknow those commits where you manually resolve errors and then realise "oh I manually resolved errors" and then you're like "oh. I meanually resolved errors" yeah well HERES AN INSTANCE OF THAT --- src/meld/Meld.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/meld/Meld.java b/src/meld/Meld.java index 52b0dda..543c071 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -14,6 +14,7 @@ import mindustry.Vars; import mindustry.content.Blocks; import mindustry.content.Fx; +import mindustry.game.EventType; import mindustry.game.EventType.*; import mindustry.mod.*; import mindustry.world.Tile; @@ -95,6 +96,10 @@ public void loadContent(){ MeldLiquids.load(); MeldBlocks.load(); MeldEnvironment.load(); + + Vars.content.items().each(c -> { + c.stats.add(Stat.buildCost, c.cost, MeldStatUnit.ticks); + }); Vars.content.blocks().each(b -> { if(b.minfo.mod != null && b.minfo.mod.name.equals("meld")){ From 28b1f852a624aeae9966eef4cb1710ef6a7d2c84 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Mon, 6 Apr 2026 13:38:54 +1000 Subject: [PATCH 19/24] HAAAAAAAAAAAAAAAAAAA --- src/meld/Meld.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meld/Meld.java b/src/meld/Meld.java index 543c071..12bf21c 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -10,6 +10,7 @@ import arc.util.Tmp; import meld.content.*; import meld.graphics.MeldRegions; +import meld.meta.MeldStatUnit; import meld.core.*; import mindustry.Vars; import mindustry.content.Blocks; From 5da8024435bae5ae7291e78a0c9d35c6bc291576 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Mon, 6 Apr 2026 13:41:01 +1000 Subject: [PATCH 20/24] when you realise you can just --- src/meld/Meld.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/meld/Meld.java b/src/meld/Meld.java index 12bf21c..144e3d4 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -11,18 +11,21 @@ import meld.content.*; import meld.graphics.MeldRegions; import meld.meta.MeldStatUnit; -import meld.core.*; import mindustry.Vars; import mindustry.content.Blocks; import mindustry.content.Fx; +import mindustry.ctype.UnlockableContent; import mindustry.game.EventType; import mindustry.game.EventType.*; import mindustry.mod.*; import mindustry.world.Tile; import mindustry.world.blocks.environment.Floor; +import mindustry.world.meta.Stat; +import mindustry.world.meta.StatUnit; import rhino.ImporterTopLevel; import rhino.NativeJavaPackage; + public class Meld extends Mod{ public static final String name = "meld"; From 58bc56bb457df91462e8827b58ffc08c1d47a74e Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Mon, 6 Apr 2026 13:42:16 +1000 Subject: [PATCH 21/24] and when you just can't --- src/meld/Meld.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meld/Meld.java b/src/meld/Meld.java index 144e3d4..c061249 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -9,6 +9,7 @@ import arc.util.Time; import arc.util.Tmp; import meld.content.*; +import meld.core.*; import meld.graphics.MeldRegions; import meld.meta.MeldStatUnit; import mindustry.Vars; From e5c574d457c5dd629b9f6e1ee2d5c5d19c71d173 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Tue, 7 Apr 2026 21:33:53 +1000 Subject: [PATCH 22/24] Shredstorm, Channel hubs, Pointago --- assets/bundles/bundle.properties | 2 +- .../blocks/fluid/channel-hub-bottom.png | Bin 683 -> 677 bytes assets/sprites/blocks/fluid/channel-hub.png | Bin 3884 -> 12196 bytes .../turrets/shredstorm/shredstorm-barrel.png | Bin 0 -> 429 bytes .../turrets/shredstorm/shredstorm-plate-l.png | Bin 0 -> 306 bytes .../turrets/shredstorm/shredstorm-plate-r.png | Bin 0 -> 305 bytes .../turrets/shredstorm/shredstorm-preview.png | Bin 0 -> 1044 bytes .../blocks/turrets/shredstorm/shredstorm.png | Bin 0 -> 688 bytes .../units/meld/pointago/pointago-foot.png | Bin 0 -> 484 bytes .../units/meld/pointago/pointago-leg-base.png | Bin 0 -> 399 bytes .../units/meld/pointago/pointago-leg.png | Bin 0 -> 328 bytes .../meld/pointago/pointago-sniper-barrel.png | Bin 0 -> 552 bytes .../meld/pointago/pointago-sniper-full.png | Bin 0 -> 982 bytes .../units/meld/pointago/pointago-sniper.png | Bin 0 -> 709 bytes .../sprites/units/meld/pointago/pointago.png | Bin 0 -> 3533 bytes src/meld/Meld.java | 3 + src/meld/content/MeldBlocks.java | 287 +++++++++++++----- src/meld/content/MeldBullets.java | 178 ++++++++++- src/meld/content/MeldStatusEffects.java | 67 ++-- src/meld/content/MeldUnits.java | 81 ++++- .../entities/bullet/OutflowBulletType.java | 9 +- .../entities/bullet/RicochetBulletType.java | 172 +++++++++++ .../entities/bullet/TransitionBulletType.java | 1 + .../unit/abilities/BezerkAbility.java | 116 +++++++ .../unit/abilities/DeathBirthAbility.java | 1 - .../entities/unit/weapons/BaseWeapon.java | 9 + .../unit/weapons/template/PointagoWeapon.java | 64 ++++ src/meld/graphics/MeldPal.java | 4 +- src/meld/world/blocks/CoreRaft.java | 2 +- .../blocks/crafting/recipe/ItemRecipe.java | 8 +- 30 files changed, 860 insertions(+), 144 deletions(-) create mode 100644 assets/sprites/blocks/turrets/shredstorm/shredstorm-barrel.png create mode 100644 assets/sprites/blocks/turrets/shredstorm/shredstorm-plate-l.png create mode 100644 assets/sprites/blocks/turrets/shredstorm/shredstorm-plate-r.png create mode 100644 assets/sprites/blocks/turrets/shredstorm/shredstorm-preview.png create mode 100644 assets/sprites/blocks/turrets/shredstorm/shredstorm.png create mode 100644 assets/sprites/units/meld/pointago/pointago-foot.png create mode 100644 assets/sprites/units/meld/pointago/pointago-leg-base.png create mode 100644 assets/sprites/units/meld/pointago/pointago-leg.png create mode 100644 assets/sprites/units/meld/pointago/pointago-sniper-barrel.png create mode 100644 assets/sprites/units/meld/pointago/pointago-sniper-full.png create mode 100644 assets/sprites/units/meld/pointago/pointago-sniper.png create mode 100644 assets/sprites/units/meld/pointago/pointago.png create mode 100644 src/meld/entities/bullet/RicochetBulletType.java create mode 100644 src/meld/entities/unit/abilities/BezerkAbility.java create mode 100644 src/meld/entities/unit/weapons/BaseWeapon.java create mode 100644 src/meld/entities/unit/weapons/template/PointagoWeapon.java diff --git a/assets/bundles/bundle.properties b/assets/bundles/bundle.properties index d9d1bd3..19898d5 100644 --- a/assets/bundles/bundle.properties +++ b/assets/bundles/bundle.properties @@ -189,7 +189,7 @@ block.meld-compartmentalized-node.description = Acts as a fluid router. Liquid c block.meld-compartmentalized-node.details = Yes, it's dumb that the name goes one character off the UI frag. I'm triggered by this too. block.meld-channel-hub.name = Channel Hub -block.meld-channel-hub.description = Acts as a fluid router. Liquid can be input or output from any side.\n\nStores 100 nodes worth of fluid. +block.meld-channel-hub.description = Acts as a fluid router. Liquid can be input or output from any side.\n\nStores 100 nodes worth of fluid.\n\n[accent]Plated![] block.meld-filtration-unit.name = Filtration Unit block.meld-filtration-unit.description = Acts as a filter. Outputs [#89828c]Mercury[] to any side.\n\nWhen powered with [#cbdbfc]Aspect[] while containing [#89828c]Mercury[], filters out Debris and Starving masses from [#7d312f]Blood[] and [#b89f47]Nectar[], producing some [#89828c]Mercury[] in the process.\n\n[accent]Halts when fully pressurized with [#89828c]Mercury[]![] diff --git a/assets/sprites/blocks/fluid/channel-hub-bottom.png b/assets/sprites/blocks/fluid/channel-hub-bottom.png index 7b94cfe1b37f1e3898f9d795aa5a3550b7dd32e4..a228327aadd45feffbe08bfd3c9229ad24075376 100644 GIT binary patch delta 639 zcmZ3@x|DT-VST)(i(^Q|oVRza`+^+>STC^uc0BUNUcpU=qbZPen$5GD@6JEHYH1i0 zzbemQUfzd71|;wy{?{h!1Csam|NT+??fk!Ae~Vw;*VH`C@sV-9WW`L=A4_x=yR=uQ zK1luYVDhFO5$QK`8>a_0`=2t3N?9SutpA?RPP6_rPhq1Q-<~~IK-HUFT4x%5`1A&- z`izKh%CiHfZyr=novEvIq^ogyJ+GbSX|CJMYh)^Z=6vvs47*wNqpwl@R%BByd-T7x zv8p$URy@7GeKE^v{w;rwhfPm-nK-Tf+$kX)>l2^mZ(haX&A0K-^)(s?uis>!;LA8G zxZ#<|0!!BV3_my<#4HvUcokI6=*oF+=abxoz^3DRf8`cv9IVxiV-Z!|arKoiP>f$U zo^|>=)r`OInY^wGy@~rbF{oj2BB#)_dKJ+J86izmp32vW_Ar?&XE>|rP|S59=8XU1 zSUbikQ~a3(6?f_GSDBJf-LUSo{%-FFM)evFr%x-K-hcI#)s0W@vp*^v`6R#l%VdMo z_RlBvJL&$noL(iC_jCQn_#*~$5hl(-VLsPWxU%#eQvj_27A_ silAwMiw;jsXS1(<;2DXOMi?^Q=<^k;l)Lk75CjQ(y85}Sb4q9e0HN$6VE_OC literal 683 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 H8dO{-3t=$o&cEP7P!KcVAT$k4 zLtHhDv@nYq2(+`3p~q9Auc40TE+ski~oJrVY$Cv#`&b z^Xi>bbxzf(y7zYSfe_z&_tww%)v2meRi`FC|6*a^d01MQ^M5}Y;@-*z`(5s{hw0Z( ze^Q%BD1k#XixlCI0th`2OXv{IAk}J2;s0E@aC+p+)!v?)pFLucKeyW38@~V5<_`HA z_}((zgPuN47l6}eEdYLjMJ6!(3{jt1qCOyAPamj!fV00z&CRxtA8^K6U|13v=QTDhD7RXVf|87H{9mdM}WUarBcTV#YLw~A~^hs1^U4{s@wg>EYYwIt` z&ovf60?GnVxOVv}`;Jr*EI)iievU0H2Pv16+!vD1r5WQ25<-alt|6r?IpjM9mt%uK z{|DO9D3?J^C0LTrjkqPSoC#QzUrIhe!sXQ1C&mTG*xDXM&0&d_|7vqb-4&FmgC@!! zD+eh6Yk%PWF{NMk-;n~12rS6w%CfdDE6cUZSA+Fog-Olm|K7@mXj+3!RW5M=i^`C6 z2PMhZ0@t0^{7Z0eW#i)*Kq3P8&8y8Fu?TPh*4AHUS4{5n5(|(!Z?`BTe#f=TSBsJF zoO2V@YM=yHE}Raw7O2Rr?ZH^3a8|*h<#6+3tA8;7Hx1N4TK;R7uMWoyIhHBZz@x5U z4k1f`Kc@ygHTpJ;6kmX)D_8=mPH^3o%1^QY93uiMqq*L|9OO8HYKs7asfBa!0`tUE1y0-l;On*xB zna>u*c{y?N6q%P)@~LhY`0>|W0DeRE%PHCt=C^hrA5c;R8u9@#5pd&|SpGER1AGyb zARpjc0B&Z1%Q>b&63Z1q3i$v|1e%!(Mhf`=5J5@uohN|Mv=Z|o7J!S0 zpg-ulo7XR~3E?y(BFL$5!1v#Mn}4*@>EjQ7`g3+(QdeCa!Aop~HRe-b4~A$4P5nWC z>~AcP&}g>SB8vra7VpRfu1jjbr`dGl^D^cL(rlf=A^<#l@!B0deoT-FnI*`@{Oplr z!PUs06t6aSM$R6%gxywDqs(3n^f5C%GnP>v73~*D3glGI&Qd4;I?tg6& >Z z@VKBYA^>Pa(59B}JXgH62+RaP%?wgSkUIeU>dUY9sg^4tmio>1)8+lIzWh4N5{PXWw`IJlvQ-2P-0%Ds=RPg}-65oFaF-y>9^8I&~wgfrkJ7q&vxq?Hc zUJA+*Sd#Aw3BVC6ZY3;1F8K~k4<)YPDgO&!1i3BWM=seua$93=+BqVi>VjK9N&$5i zQFS`ln*7`r0r~b}G8%2;D zA3y^O5p>P$bLf%?#*T)SLTsmx)iEl`xdX1nm)Hx)IbdS5gMV7QR1<&;hyassacEK+ z!p=vH&7hKB`ffGTjzpuF?n5qkk4{6<|z(fF=dj?i}d&AQ< zC5XrHMIasQ)!r5kLiql>Z}+MC;$V?aQUFw72M&@-rAV+0;1B|cKs(Z}Edt1&Roe=L?sbHwSTY-;}$~d;ggy{B6HHxa6^4I8T$eEoka!J5JB4`g_gJ-Ov9t+v}rvj8PTP@^v`MZ6E|1*A|q))2=BsDCv@vqG&I1xAyh%d}EZqQ!IXEK$E9h;I+~6VO>E3 z-yZHKK!5!|1+~Tosi+mlFc%tb)VGI+URc{))l93cA?$o;xDnqTuBotCWKrr}wKjzK z`H-4a$aAfTrg%e$pAT)_hQ6Pl9ltZk#1W)XA&$NKx^=Cj2K zye*81OI&*3Y<7LTBtsn8+@(|{5NcK7%C@ltv45*wo;+R|yU)2~cWEYo7(y+5_s1V5 zU}vzuf4#IIq(~J(%C>My1f@VGlp3mxJEJZ=A!~6kB=yP|X)BX~P*ViIcJgD$ z5`RDm@&R4z4!TIb%Mn0H@&R2DL0h7irGT9Ve)#FQ7b@kO=La;>O(vS$V>sE{$64?TsvHi{k@4d+|DZ5E#17CHKNe>qui1aDNw2 zf_y-(2(Dee8oWY2DLz1McM!YFCpTCXCZxnTpceVgIrsejot)PP$@ekR1;jQXa<_%V zEQ9nSr|qZ9`!%`)?UpbU_rjK^aD^LNPoS;%|Zm!)-B1laOyWt9B`n^)HGlGZ|DC~b@sydtBH-M^DFTQcnv}w?T=4h~**G_o5a6EVahg3_>yN;qOx9cL^W@Oni7WCoi&;zWZ$O{T1wWU#*OBdY4~N=Y`v4tw4l`&jSt|2BG}pt?3xR z=cE&mFZ~V1n|E(VNa_#z*)2c!d5I;+4F+jZM*NQ6?t9TTcdaD=J|{KAN`H$p%(YZ4 zEV>Pe1+W-cS#@!^?xZ5nU}<4aA1QQcVNTs0^mgA5O=Qg3VR;M1&|5Nd%|mkb8h)@U4jq7Lh$!iHr&#}oNQY-H6rFjLk&i#O7N#FRN9(!4|@92bp5oWu3(E> zGW>T?^VRYDZS3ypIjHGN-Z2&*03&s}$7LqgdLVtvbPsy^v2+}rLn`LH zow--=kO7Yy?{T#&8#u4LnPjy);ILYNF?YzW$3+`efBGBMtamM2(w-_T!vr*f;lTV& znSUED|NP`vTW9lT^5f@~d9xO06s_y{{dSiXzquXifb8mHbo;c%(_ZW9@D zKk8~souCWS(%GWJIIl~+NOc8kh0eSa6Bh_Q*jeGEz3_*Kge=Demg^Sf(Tj6~zjA%h zQB3cDb&F{dOEvc~xrDh5rUD@1E=xw)i+R$D_gGC1&g)j6wU@gmz-Hk@1tEi=k1Zii zAr5c${}c4Q%dZlCK;Pqmp8E~46^e?>TeDai_LxAi`={gG4<1Kc-)(VEO{kQ_u!jRdKpysePmgU4=y>dYZBcS+>`|CpEH*Dn4 zV)jsSI_tE;tf8c9ftAB4bHOX@9&6c`0;vhdIMkRt4$nU5!?3a^LF>S@8|Nnc(%m@Y r!|oc(`PYh9<{D0BTvF66Q~iahu+1m9?noXe5EwjN{an^LB{Ts5y5)E% literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/shredstorm/shredstorm-plate-r.png b/assets/sprites/blocks/turrets/shredstorm/shredstorm-plate-r.png new file mode 100644 index 0000000000000000000000000000000000000000..acfa703b4b7caf19e7e5c1800d4f1299eb59d61b GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|UVFMYhE&XX zdt)!}Ap-%2hs~iY5`|aXJi4`fVVm!Qv}pmBPABSbh`l)Xe)S@ez;~KOLft@JNZ`$_ zKfl@Btmd8bw%8Y|AG&!a=Lfr*0O5l!4L3eGYBv31*!i$IrseGi>t73JJ@gJ?mvGEw zcz&no#`|6C7aKJc=hc3xR$jsLVWoOgC&MMdgFX#m!7L~C>MiF`n8~5VT=9PPzJz4K z5Vi>}Ez?>9av3B=97`LfFVdQ&MBb@0Em2dbN~PV literal 0 HcmV?d00001 diff --git a/assets/sprites/blocks/turrets/shredstorm/shredstorm-preview.png b/assets/sprites/blocks/turrets/shredstorm/shredstorm-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..efac3d8d12393e0e2e2741db2c915010105ff07f GIT binary patch literal 1044 zcmV+v1nc{WP)Px&&PhZ;RCt{2o4adNK@`T%ih>|wlRrR7om9qvML-a>F^Wyp*eOI1Bmu3mMT%Wg zD7Xj~DeUsHGn*zxK}aE(NQ$JfiHda^5i1LAoXgqCoX5Pfck%wJ`ndIMC1LZ#J7*>!>PR60zD|c9tXM1BC&=m@qv(&6ck* z?}(H@BO+x!7n0DJe5?Uw5O2t30@*}-MA*%EXLnDX9;cPMgI%ts`xvP1Aa(%>pGrvS zCF5e#yNEKtS9}g)O$aui4B`zjCIlH!Ch>+)6TA&5qj*Dz30?-2S-ip5gy>nI*QpHf zNt|Nd&5dt2^=<)WHX&q;I+~>>0PyH>y*PgQqyeC;;Lh$|k*?8b6xZwZI+H7$EMou` z?>g6j#k|3=Z_e`q~otZg76&Y;pWykZpIE63dxDwGgT0lx}xe^Q3*rWkR(zM%~Eqp;NcuK9q7A z5F`Hh_qU;0MOydo1m~qpIIu_v{kRdY632&5k$LUDY>yXQUq5r>UUB^L#hj{MHgUs!NM8{mG zg14HM5cDEskEFI(O1$RDQr*Tj!9EX-V0*6ysW5ZOCr%+nd8zKeai9g{-V5MbL5e|> z!}ai-T>zVa`%$XDueeM|B_7uuk6%>X*&oHtsn>Ri@T+g`{~cBIGJgR%r;6K=&9O58 O0000Px%YDq*vRCt{2nLADcF%X8mh=PtY(5K`fAwi&l5=5v7B@$F9xj{P0tkTgFDk4D% z6ucxP9-`s|+<+4-4PF_?W6zGq2~y^(664J7|F1pX8I%$PK@bE%5ClOG1VIo4K@bFC zQpi#Tet7yU^ym6YjdY!&D9RXFmL<8@BtcE_nyMz4;x#EuU=QdLufdw&60hMlVTv=D zPdpbsb11MGFbeS+P7_#`6Gl8o4zS_a4$%QH;y=z#hQ`{b{Xf@!>34_XEbg^}*N-)U zH3ztGiO2GKp0nPIizov^Pd`SyCS*cvK1jX-{ySs?Mw#_P#H@meUje8Ldfe)i4)^j- zM$P;h`vlOqP67?~M7X@ZD`RYLt_Sr6SAZ$r^}Q;Pc%_tDYPO?}G*i6my9=)Ykl4ZC z!W)z$``d(0gR7f+=Xl73iO&IIfbWmJyyM)Hjm9qdg5AR=xFmf^FvQlznsYpq2!kuY zHX%m5#$`f`c#UlW<`k%$bz_e}o8{Gc^xPE1(R7u`6ik87kMF48nDeR_L?LMsv~M0G z#x@CS8kJ-QKw^C+Kw?vxP}u-XXD7w7#h5^8j>vleahRz?VO8AWKaS% z#jD@ykE1OfZ2(j8L%ibL0-R4Af=jf?$X|t1c7-X7r513RFb?s8AgCWv Wq3|*A8E6v#0000Px$o=HSORA_EH4N3uz0PBeA*{T|44b>b`@7JtAj>F62WM;N`eqEir{{8tQwFk+L zM(yTq4H-q9K#t`Vv=ac7JiE3Ai31TrDktE~Q94E*k=y$RGc)`8I2PBGjuV>IniN3D z=f9af5QK9#O8yk65=>1ADG(uF`!A{l-3Fw5ehwbNQ+P_`^`iz^8b!&oYikHKVz~-Y zLb(!b^vwhcv_w;(gzN|{g=Po52eRkA39>B29)}Y7#j+3+O-1ArLX|jL$jq>6+`STe z{k%so($v+^)QvJqu;gdyMo}etL_R_CN*Qg$YUF2orHn*T^>QyuqYNSCWI{d{O2d literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/pointago/pointago-leg-base.png b/assets/sprites/units/meld/pointago/pointago-leg-base.png new file mode 100644 index 0000000000000000000000000000000000000000..feeb45e83397c98329cf17bd6093be316f0b5600 GIT binary patch literal 399 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/pointago/pointago.png b/assets/sprites/units/meld/pointago/pointago.png new file mode 100644 index 0000000000000000000000000000000000000000..9f1d73296c16531cc88efa55d973126d80809808 GIT binary patch literal 3533 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 literal 0 HcmV?d00001 diff --git a/src/meld/Meld.java b/src/meld/Meld.java index c061249..7c116c2 100644 --- a/src/meld/Meld.java +++ b/src/meld/Meld.java @@ -10,11 +10,14 @@ import arc.util.Tmp; import meld.content.*; import meld.core.*; +import meld.entities.unit.abilities.BezerkAbility; import meld.graphics.MeldRegions; import meld.meta.MeldStatUnit; import mindustry.Vars; import mindustry.content.Blocks; +import mindustry.content.Bullets; import mindustry.content.Fx; +import mindustry.content.UnitTypes; import mindustry.ctype.UnlockableContent; import mindustry.game.EventType; import mindustry.game.EventType.*; diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 3f8f0e9..ef1a023 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -37,8 +37,11 @@ import mindustry.entities.part.RegionPart; import mindustry.entities.part.ShapePart; import mindustry.entities.pattern.ShootAlternate; +import mindustry.entities.pattern.ShootBarrel; import mindustry.entities.pattern.ShootSpread; +import mindustry.gen.Bullet; import mindustry.gen.Sounds; +import mindustry.gen.Statusc; import mindustry.graphics.Layer; import mindustry.graphics.Pal; import mindustry.type.*; @@ -80,9 +83,9 @@ public class MeldBlocks { public static ModularCrafter gasKiln, rotaryKiln, pneumaticExtruder; - public static Block channelNode, channelFace, aspectOutlet, aspectChannel, channelDirector, channelVent, manualValve, intakeValve, valveController, pipebox; + public static Block channelNode, channelHub, channelFace, aspectOutlet, aspectChannel, channelDirector, channelVent, manualValve, intakeValve, valveController, pipebox; - public static Block sunder, molotov, vivisection; + public static Block sunder, shredstorm, molotov, vivisection; public static Block silverHusk, shadesteelShingles; @@ -204,6 +207,15 @@ public static void load(){ junctionReplacement = channelFace; }}; + channelHub = new LiquidRouter("channel-hub"){{ + requirements(Category.liquid, with(MeldItems.debris, 80, MeldItems.shadesteel, 120)); + size = 4; + + health = 120 * 16; + armor = 8; + liquidCapacity = 100 * 100; + }}; + channelDirector = new ChannelDirector("channel-director"){{ requirements(Category.liquid, with( MeldItems.debris, 5 @@ -339,78 +351,164 @@ public static void load(){ shots = 2; }}; - ammoTypes.put( + ammoTypes.putAll( MeldItems.debris, - new FlakBulletType(){{ - collidesGround = true; + MeldBullets.sunderDebris, + MeldItems.glassMallows, + MeldBullets.sunderGlass + ); + }}; - scaleLife = true; + shredstorm = new ItemTurret("shredstorm"){{ + requirements(Category.turret, with( + MeldItems.debris, 45, + MeldItems.silver, 60 + )); + size = 2; + health = 600; + range = 200; - speed = 6; + fogRadiusMultiplier = 0.25f; + reload = 120; + shootEffect = Fx.shootBig; + shootWarmupSpeed = 0.25f; + minWarmup = 0.7f; + + rotateSpeed = 15; + + velocityRnd = 0.3f; + recoil = 1.5f; + shootCone = 25; + + shootSound = Sounds.shootFuse; + + rotate = quickRotate = false; + + shoot = new ShootBarrel(){{ + shotDelay = 5; + shots = 7; + + float[] spreadCone = new float[]{0, 3, 6, 15}; + barrels = new float[]{ + 0, 0, spreadCone[0], + 0, 0, -spreadCone[2], + 0, 0, spreadCone[1], + 0, 0, -spreadCone[1], + 0, 0, spreadCone[3], + 0, 0, spreadCone[2], + 0, 0, -spreadCone[3] + }; + }}; + + inaccuracy = 2; + + shootY = 7; + + ammoPerShot = 4; + maxAmmo = 16; + + drawer = new DrawTurret(){{ + parts.addAll( + new RegionPart("-plate"){{ + progress = PartProgress.warmup; + mirror = true; + under = false; + moveX = 1; + moveY = -2.55f; + moveRot = -15; + }}, + new RegionPart("-plate"){{ + progress = PartProgress.warmup; + mirror = true; + under = false; + moveX = -1; + moveY = -3.55f; + moveRot = 15; + }}, + new RegionPart("-barrel"){{ + progress = PartProgress.warmup; + moveY = 1; + under = true; + + moves.addAll( + new PartMove(PartProgress.recoil, 0, -1.25f, 0) + ); + }} + ); + }}; + + ammoTypes.putAll( + MeldItems.debris, + new BasicBulletType(){{ damage = 2; - lifetime = 30; - width = 3; - height = 8; - splashDamage = 10; - splashDamageRadius = 24; + lifetime = 5; + speed = 12; + sprite = Meld.prefix("clump"); + width = 8; + height = 10; + shrinkY = 0; - knockback = 1; + despawnHit = true; + + fragLifeMin = 0.6f; + ammoMultiplier = 2; + + fragRandomSpread = 5; + fragBullets = 3; + + fragBullet = new BasicBulletType(12, 0.25f, Meld.prefix("clump")){{ + lifetime = 21; + drag = 0.01f; + width = 6; + height = 12; + shrinkX = 0.7f; + shrinkY = 0.2f; + + splashDamage = 1; + splashDamageRadius = 20; + knockback = 0.25f; + impact = true; + + hitEffect = Fx.none; + despawnEffect = Fx.none; + setDefaults = false; + despawnHit = false; + fragOnHit = true; - despawnEffect = Fx.none; - hitEffect = new MultiEffect( - new ParticleEffect(){{ - lifetime = 13; - particles = 3; - length = 15; - sizeFrom = 3; - sizeTo = 0; - sizeInterp = Interp.pow2In; - colorFrom = colorTo = MeldPal.shockwaveGray; - }}, - new ParticleEffect(){{ - lifetime = 9; - line = true; - particles = 2; - length = 24; - lenFrom = 4; - lenTo = 0; - strokeFrom = strokeTo = 2; - - interp = Interp.pow2Out; - sizeInterp = Interp.pow2In; - colorFrom = colorTo = MeldPal.sparkOrange; - }} - ); - - fragOnAbsorb = false; - shrinkY = 0.2f; - fragRandomSpread = 25; - - fragBullets = 5; - fragBullet = new BasicBulletType(){{ - lightRadius = 0; - speed = 8; - lifetime = 20; - - shrinkY = 1; - shrinkX = 1; - shrinkInterp = Interp.pow5In; - - sticky = true; - stickyExtraLifetime = 60; pierce = true; pierceCap = 2; - damage = 3; - splashDamage = 1; - splashDamageRadius = 2; - width = 3; - height = 8; + fragBullets = 3; + fragRandomSpread = 60; + fragAngle = 180; - collidesGround = true; - hitEffect = despawnEffect = Fx.none; + bulletInterval = 2; + fragBullet = new BasicBulletType(9, 2, Meld.prefix("clump")){{ + speed = 9; + damage = 0.5f; + lifetime = 8; + drag = 0.002f; + width = 2; + height = 6; + shrinkY = 0.2f; + shrinkX = 1; + sticky = true; + stickyExtraLifetime = 120; + + hitEffect = Fx.none; + despawnEffect = Fx.none; + despawnHit = false; + + pierce = true; + pierceCap = 2; + status = MeldStatusEffects.impaled; + statusDuration = 5; + }}; }}; - }} + }}, + MeldItems.silver, MeldBullets.shredSilver, + MeldItems.annealedSilver, MeldBullets.shredSilver + ); }}; @@ -441,11 +539,13 @@ public static void load(){ shootCone = 5; BulletType molBullet = new OutflowBulletType(){{ + layer = Layer.bullet + 1; //HHJKGHJGJK. - collidesTiles = false; - collides = false; + collidesTiles = true; + collides = true; collidesAir = false; scaleLife = true; + hitShake = 1.0F; hitSound = Sounds.explosionArtillery; hitEffect = Fx.flakExplosion; @@ -456,9 +556,10 @@ public static void load(){ trailEffect = Fx.artilleryTrail; trailChance = 0.35f; - speed = 7; + trailLength = 8; + speed = 3.5f; damage = 1; - lifetime = 46; + lifetime = 46 * 2; width = 18; height = 24; status = MeldStatusEffects.aspectBurn; @@ -466,10 +567,9 @@ public static void load(){ frontColor = Color.white; backColor = Color.valueOf("cbdbfc"); - splashDamage = 4; - splashDamageRadius = 8; + splashDamage = 40; + splashDamageRadius = 43; - collidesTiles = false; makeFire = true; hitShake = 2.5f; @@ -484,7 +584,7 @@ public static void load(){ fragRandomSpread = 360; - outflowBullet = new ExplosionBulletType(){{ + outflowBullet = new TransitionBulletType(){{ hitEffect = despawnEffect = Fx.none; damage = splashDamage = 0; fragBullets = 3; @@ -745,7 +845,7 @@ public static void load(){ pierceCap = 5; pierceDamageFactor = 0.05f; lifetime = 52; - knockback = 50; + knockback = 120; impact = true; spin = 300; width = height = 24 * 2; @@ -811,7 +911,14 @@ public static void load(){ fragVelocityMin = 0.5f; fragVelocityMax = 1; - fragBullet = new BasicBulletType(4.2f, 2, Meld.prefix("dual-spike")) {{ + fragBullet = new BasicBulletType(4.2f, 2, Meld.prefix("dual-spike")) { + @Override + public void update(Bullet b){ + if(b.stickyTarget instanceof Statusc s){ + s.apply(MeldStatusEffects.lacerated, 60); + } + } + { width = 6; height = 2; frontColor = MeldPal.resoShardFront; @@ -1208,7 +1315,7 @@ public static void load(){ }}; rotaryKiln = new ModularCrafter("rotary-kiln"){{ - requirements(Category.crafting, with(MeldItems.debris, 250, MeldItems.cruciblePlating, 150)); + requirements(Category.crafting, with(MeldItems.debris, 350, MeldItems.cruciblePlating, 150)); size = 5; hasItems = true; @@ -1311,7 +1418,7 @@ public static void load(){ }}; pneumaticExtruder = new ModularCrafter("pneumatic-extruder"){{ - requirements(Category.crafting, with(MeldItems.debris, 60, MeldItems.cruciblePlating, 40)); + requirements(Category.crafting, with(MeldItems.debris, 120)); size = 3; hasItems = true; @@ -1321,12 +1428,13 @@ public static void load(){ itemCapacity = 10; acceptedLiquids.addAll(MeldLiquids.aspect, MeldLiquids.boundAspect); - acceptedItems.addAll(MeldItems.shadesteel, MeldItems.elnarDust, MeldItems.debris, MeldItems.annealedSilver); + acceptedItems.addAll(MeldItems.shadesteel, MeldItems.elnarDust, MeldItems.debris, MeldItems.silver, MeldItems.annealedSilver); dumpedItems.addAll(MeldItems.aspectPipe); ItemRecipe - aspectPipe1 = new ItemRecipe(with(MeldItems.shadesteel, 2, MeldItems.elnarDust, 2), with(MeldItems.aspectPipe, 4)), - aspectPipe2 = new ItemRecipe(with(MeldItems.debris, 1, MeldItems.annealedSilver, 2), with(MeldItems.aspectPipe, 4)); + aspectPipe1 = new ItemRecipe(with(MeldItems.shadesteel, 2, MeldItems.elnarDust, 4), with(MeldItems.aspectPipe, 2)), + aspectPipe2 = new ItemRecipe(with(MeldItems.debris, 1, MeldItems.annealedSilver, 2), with(MeldItems.aspectPipe, 2)), + aspectPipe3 = new ItemRecipe(with(MeldItems.debris, 1, MeldItems.silver, 2), with(MeldItems.aspectPipe, 2));; float produceTime = 30; @@ -1337,29 +1445,38 @@ public static void load(){ new GateModule( ModOUT.ONE, new GateModule.RecipeCondition(aspectPipe2) ), + new GateModule( + ModOUT.TWO, new GateModule.RecipeCondition(aspectPipe3) + ), new LambdaModule(){{ //Set every ping after the first one found as 1 to zero updater = b -> { int a = 0; - for(int i = 0; i < 2; i++){ + for(int i = 0; i < 3; i++){ b.setPin(i, Math.max(b.getPin(i) - a, 0)); a = (int)Math.max(a, b.getPin(i)); } - b.setPin(ModOUT.TWO, a); + b.setPin(ModOUT.THREE, a); }; }}, - new ConsumeLiquidModule(LiquidStack.with(MeldLiquids.aspect, outletRate * 2), ModIN.TWO, ModOUT.THREE), + new ConsumeLiquidModule(LiquidStack.with(MeldLiquids.aspect, outletRate * 2), ModIN.THREE, ModOUT.FOUR), new RecipeCraftingModule(){{ recipe = aspectPipe1; - efficiencyPins = new int[]{ModIN.THREE, ModIN.ZERO}; + efficiencyPins = new int[]{ModIN.FOUR, ModIN.ZERO}; progressPin = ModOUT.FOUR; craftTime = produceTime; }}, new RecipeCraftingModule(){{ recipe = aspectPipe2; - efficiencyPins = new int[]{ModIN.THREE, ModIN.ONE}; + efficiencyPins = new int[]{ModIN.FOUR, ModIN.ONE}; progressPin = ModOUT.FIVE; craftTime = produceTime; + }}, + new RecipeCraftingModule(){{ + recipe = aspectPipe3; + efficiencyPins = new int[]{ModIN.FOUR, ModIN.TWO}; + progressPin = ModOUT.SIX; + craftTime = produceTime; }} ); }}; @@ -1370,7 +1487,9 @@ public static void load(){ health = 2500; consume(new ConsumeLiquid(MeldLiquids.aspect, outletRate * 12)); - plans.addAll(new UnitPlan(MeldUnits.shark, 60 * 5, with(MeldItems.silver, 80, MeldItems.carbolith, 60))); + plans.addAll( + new UnitPlan(MeldUnits.shark, 60 * 5, with(MeldItems.silver, 80, MeldItems.carbolith, 60)) + ); }}; sonarSpire = new SonarSpire("sonar-spire"){{ diff --git a/src/meld/content/MeldBullets.java b/src/meld/content/MeldBullets.java index 84acab3..fc7b02e 100644 --- a/src/meld/content/MeldBullets.java +++ b/src/meld/content/MeldBullets.java @@ -1,19 +1,24 @@ package meld.content; +import arc.graphics.Color; import arc.math.Interp; import meld.Meld; +import meld.entities.bullet.RicochetBulletType; +import meld.entities.bullet.TransitionBulletType; +import meld.graphics.MeldPal; import mindustry.content.Blocks; import mindustry.content.Fx; -import mindustry.entities.bullet.BasicBulletType; -import mindustry.entities.bullet.BulletType; -import mindustry.entities.bullet.ExplosionBulletType; -import mindustry.entities.bullet.RailBulletType; +import mindustry.entities.bullet.*; +import mindustry.entities.effect.MultiEffect; +import mindustry.entities.effect.ParticleEffect; public class MeldBullets { public static BulletType - pulsarBlast, pulsarShrapnel; + pulsarBlast, pulsarShrapnel, + sunderDebris, sunderGlass, + shredSilver; public static void load(){ @@ -61,5 +66,168 @@ public static void load(){ killShooter = false; }}; + sunderDebris = new FlakBulletType(){{ + collidesGround = true; + + scaleLife = true; + + speed = 6; + damage = 2; + lifetime = 30; + width = 6; + height = 12; + splashDamage = 15; + splashDamageRadius = 24; + + knockback = 1; + + despawnEffect = Fx.none; + hitEffect = new MultiEffect( + new ParticleEffect(){{ + lifetime = 13; + particles = 3; + length = 15; + sizeFrom = 3; + sizeTo = 0; + sizeInterp = Interp.pow2In; + colorFrom = colorTo = MeldPal.shockwaveGray; + }}, + new ParticleEffect(){{ + lifetime = 9; + line = true; + particles = 2; + length = 24; + lenFrom = 4; + lenTo = 0; + strokeFrom = strokeTo = 2; + + interp = Interp.pow2Out; + sizeInterp = Interp.pow2In; + colorFrom = colorTo = MeldPal.sparkOrange; + }} + ); + + fragOnAbsorb = false; + shrinkY = 0.2f; + fragRandomSpread = 25; + + fragBullets = 5; + fragBullet = new BasicBulletType(8, 3){{ + lightRadius = 0; + lifetime = 20; + + shrinkY = 1; + shrinkX = 1; + shrinkInterp = Interp.pow5In; + + sticky = true; + stickyExtraLifetime = 60; + pierce = true; + pierceCap = 2; + splashDamage = 1; + splashDamageRadius = 2; + + width = 3; + height = 8; + + collidesGround = true; + hitEffect = despawnEffect = Fx.none; + }}; + }}; + + sunderGlass = new FlakBulletType(){{ + frontColor = MeldPal.glassMallowsFront; + backColor = MeldPal.glassMallowsBack; + collidesGround = true; + scaleLife = true; + + reloadMultiplier = 2; + + speed = 6; + damage = 0.5f; + lifetime = 30; + width = 3; + height = 8; + + knockback = 1; + + despawnEffect = Fx.none; + hitEffect = new MultiEffect( + new ParticleEffect(){{ + lifetime = 9; + line = true; + particles = 2; + length = 24; + lenFrom = 4; + lenTo = 0; + strokeFrom = strokeTo = 2; + + interp = Interp.pow2Out; + sizeInterp = Interp.pow2In; + colorFrom = colorTo = MeldPal.sparkOrange; + }} + ); + + fragOnAbsorb = false; + shrinkY = 0.2f; + fragRandomSpread = 25; + + fragBullets = 5; + + fragBullet = new BasicBulletType(8, 8, Meld.prefix("clump")){{ + frontColor = MeldPal.glassMallowsFront; + backColor = MeldPal.glassMallowsBack; + lightRadius = 0; + speed = 8; + lifetime = 20; + + shrinkY = 1; + shrinkX = 1; + shrinkInterp = Interp.pow5In; + + sticky = true; + stickyExtraLifetime = 60; + splashDamage = 1; + splashDamageRadius = 2; + + width = 8; + height = 12; + + collidesGround = true; + hitEffect = despawnEffect = Fx.none; + }}; + }}; + + shredSilver = new TransitionBulletType(){{ + + fragLifeMin = 0.8f; + ammoMultiplier = 2; + + fragRandomSpread = 5; + fragBullets = 2; + + fragBullet = new RicochetBulletType(8, 6, "shell"){{ + frontColor = Color.white; + backColor = MeldPal.aspect; + + lifetime = 40; + drag = 0.01f; + width = 8; + height = 8; + shrinkX = 0.7f; + shrinkY = 0.7f; + + splashDamage = 5; + splashDamageRadius = 20; + + //For some reason impact + knockback on bouncy bullets doesn't work how I want it to... + knockback = 3; + + bounceEffect = Fx.none; + hitEffect = Fx.none; + despawnEffect = Fx.none; + setDefaults = false; + }}; + }}; } } diff --git a/src/meld/content/MeldStatusEffects.java b/src/meld/content/MeldStatusEffects.java index 0b89170..303aff6 100644 --- a/src/meld/content/MeldStatusEffects.java +++ b/src/meld/content/MeldStatusEffects.java @@ -8,7 +8,7 @@ public class MeldStatusEffects { public static StatusEffect amplified, rally, anchored, aspectBurn, sentry, spurting, newborn, interference, drenched, stunned; - public static StatusEffect lacerated; + public static StatusEffect lacerated, impaled; public static void load(){ @@ -60,41 +60,29 @@ public void update(Unit unit, StatusEntry entry) { anchored = new StatusEffect("anchored"){ - @Override - public void update(Unit unit, StatusEntry entry) { - - unit.speedMultiplier /= speedMultiplier; - //Start the falloff at 150 ticks remaining - unit.speedMultiplier *= Mathf.lerp( - 1, speedMultiplier, + @Override + public void update(Unit unit, StatusEntry entry) { - Interp.pow2.apply( - Mathf.clamp(Math.min(entry.time, 150)/(150)) - ) - ); - } - { + unit.speedMultiplier /= speedMultiplier; + //Start the falloff at 150 ticks remaining + unit.speedMultiplier *= Mathf.lerp( + 1, speedMultiplier, - speedMultiplier = 0.3f; - dragMultiplier = 3; - }}; + Interp.pow2.apply( + Mathf.clamp(Math.min(entry.time, 150)/(150)) + ) + ); + } + { - aspectBurn = new StatusEffect("aspect-burn"){ - @Override - public void update(Unit unit, StatusEntry entry) { - super.update(unit, entry); - if(unit.armor > 0) unit.damageContinuousPierce(unit.armor/60); - } - { - damage = 0.1f; - healthMultiplier = 0.5f; - } - }; + speedMultiplier = 0.3f; + dragMultiplier = 3; + }}; sentry = new StatusEffect("sentry"){{ damage = 0.1f; reloadMultiplier = 2; - speedMultiplier = 0.3f; + speedMultiplier = 0.15f; }}; spurting = new StatusEffect("spurting"){ @@ -168,11 +156,32 @@ public void update(Unit unit, StatusEntry entry) { }}; + //Only affects unarmored lacerated = new StatusEffect("lacerated"){{ damage = -0.2f; speedMultiplier = 0.45f; reloadMultiplier = 0.5f; buildSpeedMultiplier = 0.5f; }}; + + + //Only affects armored + aspectBurn = new StatusEffect("aspect-burn"){ + @Override + public void update(Unit unit, StatusEntry entry) { + super.update(unit, entry); + if(unit.armor > 0) unit.damageContinuousPierce(unit.armor/60); + } + { + damage = 0.1f; + healthMultiplier = 0.5f; + } + }; + + impaled = new StatusEffect("impaled"){{ + damage = 0.2f; + speedMultiplier = 0.45f; + buildSpeedMultiplier = 0.5f; + }}; } } diff --git a/src/meld/content/MeldUnits.java b/src/meld/content/MeldUnits.java index 64f49d1..04f03e7 100644 --- a/src/meld/content/MeldUnits.java +++ b/src/meld/content/MeldUnits.java @@ -14,6 +14,8 @@ import arc.util.Tmp; import meld.*; import meld.entities.bullet.TransitionBulletType; +import meld.entities.unit.abilities.BezerkAbility; +import meld.entities.unit.weapons.template.PointagoWeapon; import meld.graphics.part.*; import meld.entities.unit.*; import meld.entities.unit.type.*; @@ -25,7 +27,9 @@ import meld.entities.unit.weapons.ShadowVisualWeapon; import mindustry.Vars; import mindustry.ai.types.HugAI; +import mindustry.content.Blocks; import mindustry.content.Fx; +import mindustry.content.Items; import mindustry.content.StatusEffects; import mindustry.entities.Effect; import mindustry.entities.abilities.RegenAbility; @@ -43,6 +47,7 @@ import mindustry.type.Weapon; import mindustry.type.weapons.RepairBeamWeapon; import arc.struct.ObjectMap.Entry; +import mindustry.world.blocks.defense.turrets.ItemTurret; public class MeldUnits { @@ -96,7 +101,7 @@ public static int classID(Class type){ //enemy units cannonOverseer, blob, - afraig, craig, braig, globkin, + afraig, craig, braig, pointago, globkin, jilla, kathid; public static void load(){ @@ -255,7 +260,7 @@ public static void load(){ }}; bulbhead = new UnitType("bulbhead"){{ - float IR = 140; + float IR = 120; health = 290; @@ -263,7 +268,7 @@ public static void load(){ drag = 0.15f; accel = 0.8f; - rotateSpeed = 7; + rotateSpeed = 9; trailLength = 18; trailScl = 1.2f; @@ -934,9 +939,7 @@ public static void load(){ constructor = LegsUnit::create; }}; - craig = new UnitType("craig"){{ - outlineColor = Color.clear; - + craig = new MeldUnitType("craig"){{ speed = 0.9f; health = 320; drag = 0.12f; @@ -987,11 +990,11 @@ public static void load(){ constructor = LegsUnit::create; }}; - braig = new UnitType("braig"){{ + braig = new MeldUnitType("braig"){{ outlineColor = Color.clear; speed = 0.8f; - health = 950; + health = 1200; drag = 0.12f; accel = 0.2f; @@ -1115,6 +1118,62 @@ public static void load(){ constructor = LegsUnit::create; }}; + pointago = new MeldUnitType("pointago"){{ + speed = 0.8f; + + health = 1600; + + drag = 0.12f; + accel = 0.2f; + range = 40; + + hitSize = 26; + rotateSpeed = 6; + faceTarget = true; + + drawCell = false; + + legCount = 6; + legLength = 24; + + legBaseOffset = 12; + + lockLegBase = true; + legContinuousMove = true; + + legGroupSize = 2; + legExtension = 4; + legLengthScl = 0.9f; + legPairOffset = 5; + + legMoveSpace = 3; + allowLegStep = true; + legPhysicsLayer = false; + + weapons.addAll( + new PointagoWeapon(30f/4, 2){{ + rotateSpeed = 0.5f; + rotationLimit = 5; + shootCone = 180; + }}, + new PointagoWeapon(-36f/4, -11f/4){{ + rotateSpeed = 0.5f; + rotationLimit = 5; + shootCone = 180; + }} + ); + + abilities.addAll( + new BezerkAbility(){{ + deathBomb = MeldBullets.pulsarBlast; + bezerkTime = 30; + }} + ); + + immunities.addAll(MeldStatusEffects.aspectBurn); + constructor = LegsUnit::create; + }}; + globkin = new UnitType("globkin"){{ outlineColor = Color.clear; speed = 0.7f; @@ -1242,6 +1301,7 @@ public static void load(){ }} ); + immunities.addAll(MeldStatusEffects.lacerated); constructor = LegsUnit::create; }}; @@ -1302,8 +1362,7 @@ public static void load(){ constructor = CrawlUnit::create; }}; - kathid = new UnitType("kathid"){{ - outlineColor = Color.clear; + kathid = new MeldUnitType("kathid"){{ shadowElevation = 0.1f; speed = 0.7f; @@ -1374,7 +1433,7 @@ public static void load(){ }}; }} ); - immunities.addAll(MeldStatusEffects.aspectBurn); + immunities.addAll(MeldStatusEffects.lacerated); constructor = LegsUnit::create; }}; diff --git a/src/meld/entities/bullet/OutflowBulletType.java b/src/meld/entities/bullet/OutflowBulletType.java index 6147eec..5a15d91 100644 --- a/src/meld/entities/bullet/OutflowBulletType.java +++ b/src/meld/entities/bullet/OutflowBulletType.java @@ -23,16 +23,11 @@ public void init() { } @Override - public void despawned(Bullet b) { - super.despawned(b); + public void createSplashDamage(Bullet b, float x, float y) { + super.createSplashDamage(b, x, y); outflow(b, b.x, b.y); } - @Override - public void hit(Bullet b) { - super.hit(b); - } - @Override public void createFrags(Bullet b, float x, float y) { super.createFrags(b, x, y); diff --git a/src/meld/entities/bullet/RicochetBulletType.java b/src/meld/entities/bullet/RicochetBulletType.java new file mode 100644 index 0000000..6e2d6c1 --- /dev/null +++ b/src/meld/entities/bullet/RicochetBulletType.java @@ -0,0 +1,172 @@ +package meld.entities.bullet; + +import arc.math.geom.Geometry; +import arc.math.geom.Intersector; +import arc.math.geom.Vec2; +import arc.util.Tmp; +import mindustry.Vars; +import mindustry.content.Fx; +import mindustry.entities.Effect; +import mindustry.entities.bullet.BasicBulletType; +import mindustry.gen.Building; +import mindustry.gen.Bullet; +import mindustry.gen.Hitboxc; +import mindustry.gen.Unit; + +public class RicochetBulletType extends BasicBulletType { + //how much of its velocity is kept on bounce + public float bounciness = 1; + //Cap for how many times it can bounce. Set to -1 to disable, 0 or null to stop bouncing. + public int bounceCap = -1; + public boolean bounceUnits, bounceBuildings; + //Effect displayed on bounce + public Effect bounceEffect = Fx.plasticExplosion; + + //Whether the bullet can bounce multiple times on the same entity + public boolean bounceSame = true; + + //how many ticks inbetween collisions being cleared. Set around 15/20 or lower to make bullet reliable in low fps. -1 to disable + public int bounceInternal = 2; + + public boolean useRange = true; + + public static class Bounces{ + public int bounceAmount = 0; + } + + public RicochetBulletType(float speed, float damage, String sprite) { + super(speed, damage, sprite); + this.speed = speed; + this.damage = damage; + this.pierceBuilding = this.pierce = this.bounceBuildings = this.bounceUnits = true; + this.trailWidth = width * 0.38f; + this.shrinkX = 0.8f; + } + + @Override + public void update(Bullet b){ + if(bounceSame && bounceInternal != -1 && b.timer.get(1, bounceInternal)){{ + b.collided.clear(); + }} + super.update(b); + RicochetBulletData data = (RicochetBulletData) b.data; + data.lastX = data.curX; + data.curX = b.x; + data.lastY = data.curY; + data.curY = b.y; + } + + @Override + public void init(Bullet b) { + super.init(b); + b.data = new RicochetBulletData(); + } + + @Override + public void hitEntity(Bullet b, Hitboxc entity, float health) { + b.fdata++; + if ((bounceCap == -1 || b.fdata <= bounceCap) && bounceUnits) { + if(b.collided.size >= 1 && bounceInternal == -1) b.collided.pop(); + if((entity instanceof Unit)) { + Unit unit = (Unit) entity; + //Bring bullet back to point of intersection + RicochetBulletData data = (RicochetBulletData) b.data; + entity.hitbox(Tmp.r1); + Tmp.v1.set(b.x - b.lastX, b.y - b.lastY); + Tmp.v1.setLength(Tmp.v1.len() + ((Unit) entity).hitSize).add(b.x, b.y); + + Vec2 collisionPoint = Geometry.raycastRect(b.lastX, b.lastY, Tmp.v1.x, Tmp.v1.y, Tmp.r1); + + if(collisionPoint != null) { + b.set(collisionPoint); + } + + float dx = b.lastX - entity.getX(); + float dy = b.lastY - entity.getY(); + + float angle = 270; + if(Math.abs(dx) > Math.abs(dy)){ + //Angle to the right or left + angle = 0; + if(dx > 0) angle = 180; + } + //If dy is upwards angle faces up + else if(dy > 0) angle = 90; + + float newRotation = (angle - (b.rotation() + 180)) * 2 + b.rotation() + 180; + b.rotation(newRotation); + + b.vel.add(((Unit) entity).vel()); + b.set(b.x + b.vel.x, b.y + b.vel.y); + bounceEffect.at(b.x, b.y, b.vel.angle()); + + } + } + super.hitEntity(b, entity, health); + } + + + @Override + public void hitTile(Bullet b, Building build, float x, float y, float initialHealth, boolean direct) { + b.fdata++; + if(build != null){ + float difX = b.x - build.x, + difY = b.y - build.y; + boolean flipX = false; + boolean flipY = true; + if(Math.abs(difX) > Math.abs(difY)) { + flipX = true; + flipY = false; + } + if ((bounceCap == -1 || b.fdata <= bounceCap) && bounceBuildings) { + //Flip velocity after sliding back. I swear I canot tell how anoying this was to figure out. + + //Offset from the middle to it's sides + float size = build.block.size * Vars.tilesize/2; + + //Corners of side + Tmp.v1.set(size, 0); + + //Set theese two vectors to corners on the right side + Tmp.v2.set(Tmp.v1).add(0, size); + Tmp.v1.sub(0, size); + + //Rotate apropriately + if(flipX && difX < 0){ + Tmp.v2.scl(-1); + Tmp.v1.scl(-1); + } + else if (flipY){ + //Set bottom right corner to top left + Tmp.v1.set(-size, size); + if(difY < 0){ + //Flip the corners across the middle, so that they're on bottom + Tmp.v1.scl(-1); + Tmp.v2.scl(-1); + } + } + Tmp.v1.add(build.x, build.y); + Tmp.v2.add(build.x, build.y); + Tmp.v3.set(b.x, b.y); + Intersector.intersectLines(Tmp.v1, Tmp.v2, Tmp.v3.set(b), Tmp.v4.trns(b.vel.angle() + 180, size * 2).add(b.x, b.y), Tmp.v5); + b.x = Tmp.v3.x; + b.y = Tmp.v3.y; + + if (flipX) { + b.vel.x *= -1 * bounciness; + } + if (flipY) { + b.vel.y *= -1 * bounciness; + } + b.x += b.vel.x; + b.y += b.vel.y; + bounceEffect.at(Tmp.v5.x, Tmp.v5.y, b.vel.angle()); + } + } + super.hitTile(b, build, x, y, initialHealth, direct); + } + + public static class RicochetBulletData{ + public float lastX, lastY, curX, curY; + } +} \ No newline at end of file diff --git a/src/meld/entities/bullet/TransitionBulletType.java b/src/meld/entities/bullet/TransitionBulletType.java index f8d7059..c70822e 100644 --- a/src/meld/entities/bullet/TransitionBulletType.java +++ b/src/meld/entities/bullet/TransitionBulletType.java @@ -6,6 +6,7 @@ public class TransitionBulletType extends BulletType { public TransitionBulletType(){ super(); + speed = 0.00001f; keepVelocity = false; instantDisappear = true; collides = absorbable = reflectable = hittable = false; diff --git a/src/meld/entities/unit/abilities/BezerkAbility.java b/src/meld/entities/unit/abilities/BezerkAbility.java new file mode 100644 index 0000000..ca6448a --- /dev/null +++ b/src/meld/entities/unit/abilities/BezerkAbility.java @@ -0,0 +1,116 @@ +package meld.entities.unit.abilities; + +import arc.math.Mathf; +import arc.struct.Seq; +import arc.util.Log; +import arc.util.Time; +import meld.content.MeldStatusEffects; +import mindustry.Vars; +import mindustry.content.Fx; +import mindustry.entities.Effect; +import mindustry.entities.Units; +import mindustry.entities.abilities.Ability; +import mindustry.entities.bullet.BulletType; +import mindustry.gen.Unit; +import mindustry.type.StatusEffect; +import mindustry.type.UnitType; + +public class BezerkAbility extends Ability { + + /** + Yes this is stupid, @Link{Ability#created} only fires when a unit gets made though + */ + protected boolean setup = false; + + //Time counts up while a unit is shooting, and counts down when the unit isn't/can't shoot + protected float bezerkProgress; + + //Charge up until becoming BEZERK >:D + protected float time; + + //Controls whether the unit can shoot or not, gets set to false once time = 0 + protected boolean bezerk; + + //Bezert under this hp threshold + public float bezerkHealthf = 0.5f, + //Time it takes to drop hp + bezerkTime = 60; + + //Damage/sec based on bezerk% + public float damage = 50/60; + + //Radius/damage for da boom boom prepper + public float boomRadius = Vars.tilesize * 4, boomDamage = 200/60f; + + public float reloadMultiplier = 3, speedMultiplier = 1.2f, bezerkSpeedMulti = 2; + + public BulletType deathBomb; + + public Effect bezerkSmoke = Fx.generate; + public Effect chargeEffect = Fx.lightningCharge; + + public float effectChance = 0.5f; + + + public Seq clear = new Seq<>(); + public Seq sedated = new Seq<>(); + + public BezerkAbility(){ + clear.add(MeldStatusEffects.sentry); + sedated.addAll(MeldStatusEffects.lacerated); + } + + @Override + public void update(Unit unit) { + super.update(unit); + + //Dumb stupid + if(!setup){ + setup = true; + time = unit.healthf() < bezerkHealthf ? bezerkTime : 0; + } + + if(unit.isShooting){ + unit.damageContinuousPierce(damage); + } + + time = Mathf.clamp(time + (unit.healthf() < bezerkHealthf ? Time.delta : -Time.delta), 0, bezerkTime); + + //Let em know when to ZERKIN >:DDDDDD + bezerk = time >= bezerkTime; + + sedated.each(u -> { + //Disable zerk D: + if(unit.hasEffect(u)) time = 0; + }); + + //Get more BEZERK the lower hp you have + bezerkProgress = Mathf.clamp((1-unit.healthf())/bezerkHealthf, 0, 1); + + //AAAnd a bunch of random stuff going nuts + if(bezerk){ + unit.disarmed = true; + clear.each(unit::unapply); + + if(unit.buildOn() != null && unit.buildOn().team != unit.team || Units.nearEnemy(unit.team, unit.x, unit.y, boomRadius, boomRadius)){ + unit.damageContinuousPierce(boomDamage); + } + } + + if(bezerk && Mathf.chance(effectChance)){ + bezerkSmoke.at(unit.x + Mathf.random(-unit.hitSize, unit.hitSize), unit.y + Mathf.random(-unit.hitSize, unit.hitSize)); + } + + unit.reloadMultiplier *= Mathf.lerp(1, reloadMultiplier, bezerkProgress); + unit.speedMultiplier *= Mathf.lerp(1, bezerk ? bezerkSpeedMulti : speedMultiplier, bezerkProgress); + } + + @Override + public void death(Unit unit) { + if(bezerk && deathBomb != null) { + deathBomb.create(null, unit.team, unit.x, unit.y, unit.vel.angle()); + Log.info("boomies!"); + } + super.death(unit); + } +} diff --git a/src/meld/entities/unit/abilities/DeathBirthAbility.java b/src/meld/entities/unit/abilities/DeathBirthAbility.java index 7ecd03a..a606314 100644 --- a/src/meld/entities/unit/abilities/DeathBirthAbility.java +++ b/src/meld/entities/unit/abilities/DeathBirthAbility.java @@ -22,7 +22,6 @@ public void death(Unit unit) { u.apply(MeldStatusEffects.newborn, 60); } } - } } diff --git a/src/meld/entities/unit/weapons/BaseWeapon.java b/src/meld/entities/unit/weapons/BaseWeapon.java new file mode 100644 index 0000000..08a0807 --- /dev/null +++ b/src/meld/entities/unit/weapons/BaseWeapon.java @@ -0,0 +1,9 @@ +package meld.entities.unit.weapons; + +import mindustry.type.Weapon; + +public class BaseWeapon extends Weapon { + public BaseWeapon(String name){ + super(name); + } +} diff --git a/src/meld/entities/unit/weapons/template/PointagoWeapon.java b/src/meld/entities/unit/weapons/template/PointagoWeapon.java new file mode 100644 index 0000000..d3accdc --- /dev/null +++ b/src/meld/entities/unit/weapons/template/PointagoWeapon.java @@ -0,0 +1,64 @@ +package meld.entities.unit.weapons.template; + +import meld.Meld; +import meld.content.MeldStatusEffects; +import meld.entities.unit.weapons.BaseWeapon; +import mindustry.content.Fx; +import mindustry.entities.bullet.BasicBulletType; +import mindustry.entities.part.RegionPart; +import mindustry.entities.pattern.ShootSpread; +import mindustry.graphics.Layer; + +public class PointagoWeapon extends BaseWeapon { + public PointagoWeapon(float wx, float wy){ + super(Meld.prefix("pointago-sniper")); + + x = wx; + y = wy; + + shootY = 8; + rotateSpeed = 2.5f; + reload = 600; + + shootCone = 60; + rotate = true; + mirror = alternate = false; + shootStatus = MeldStatusEffects.sentry; + shootStatusDuration = 200; + + shoot = new ShootSpread(){{ + shots = 3; + shotDelay = 5; + spread = 1; + }}; + + bullet = new BasicBulletType(5, 65, Meld.prefix("clump")){{ + layer = Layer.fogOfWar + 1; + drag = -0.02f; + lifetime = 55; + rangeOverride = 300; + + pierce = true; + pierceCap = 2; + + width = 5; + height = 24; + knockback = 25; + impact = true; + trailEffect = Fx.smoke; + shootEffect = Fx.shootBig; + hitEffect = Fx.explosion; + }}; + + parts.addAll( + new RegionPart("-barrel"){{ + progress = PartProgress.reload; + moveY = -3; + moves.addAll( + new PartMove(PartProgress.reload, 0, -2, 0) + ); + }}, + new RegionPart() + ); + } +} diff --git a/src/meld/graphics/MeldPal.java b/src/meld/graphics/MeldPal.java index daf59e0..3c1cb9d 100644 --- a/src/meld/graphics/MeldPal.java +++ b/src/meld/graphics/MeldPal.java @@ -16,5 +16,7 @@ public class MeldPal { shockwaveGray = Color.valueOf("7f7f7f"), sparkOrange = Color.valueOf("f6e096"), resoShardFront = Color.valueOf("a3efba"), - resoShardBack = Color.valueOf("40a547"); + resoShardBack = Color.valueOf("40a547"), + glassMallowsFront = Color.valueOf("ede0d4"), + glassMallowsBack = Color.valueOf("cd9d70"); } diff --git a/src/meld/world/blocks/CoreRaft.java b/src/meld/world/blocks/CoreRaft.java index 5134e19..a1218f1 100644 --- a/src/meld/world/blocks/CoreRaft.java +++ b/src/meld/world/blocks/CoreRaft.java @@ -41,7 +41,7 @@ public class CoreRaft extends CoreBlock { public CoreRaft(String name) { super(name); - fogRadius = 50; + fogRadius = 40; Events.on(EventType.WorldLoadEvent.class, e -> { rafts.clear(); Team.sharded.cores().each(c -> { diff --git a/src/meld/world/blocks/crafting/recipe/ItemRecipe.java b/src/meld/world/blocks/crafting/recipe/ItemRecipe.java index 97e5644..3cf97ef 100644 --- a/src/meld/world/blocks/crafting/recipe/ItemRecipe.java +++ b/src/meld/world/blocks/crafting/recipe/ItemRecipe.java @@ -68,19 +68,19 @@ public void apply(Block block, Building building) { } //Builder methods - public ItemRecipe input(ItemStack[] items){ + public ItemRecipe input(ItemStack... items){ inputItems = items; return this; } - public ItemRecipe output(ItemStack[] items){ + public ItemRecipe output(ItemStack... items){ outputItems = items; return this; } - public ItemRecipe input(LiquidStack[] liquids){ + public ItemRecipe input(LiquidStack... liquids){ inputLiquids = liquids; return this; } - public ItemRecipe output(LiquidStack[] liquids){ + public ItemRecipe output(LiquidStack... liquids){ outputLiquids = liquids; return this; } From ad6aef03ec1f1a37926054353a894b2cb627d6b8 Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Wed, 15 Apr 2026 00:33:18 +1000 Subject: [PATCH 23/24] AND THE RALLY GOES *PIIIIIIIIIING* (no it doesn't) -Fixed Buisekit not healing blocks which don't update -Added a changed Pointago version -Added Billa... it billa -Formalised AspectGroup into its own class, mostly for meta purposes -Started shifting the unit weapons into their own classes -Revert the module PR, working on another on a dif branch -AspectPipe extends conduit again because I need to fix its blend states --- assets/bundles/bundle.properties | 170 +-------- .../blocks/meld/coffer/billa-coffer.png | Bin 0 -> 2108 bytes .../units/meld/{ => afraig}/afraig-joint.png | Bin .../units/meld/{ => afraig}/afraig-leg.png | Bin .../meld/{ => afraig}/afraig-spurter-cell.png | Bin .../meld/{ => afraig}/afraig-spurter.png | Bin .../units/meld/{ => afraig}/afraig.png | Bin .../units/meld/billa/billa-crystals-glow.png | Bin 0 -> 7429 bytes .../units/meld/billa/billa-crystals.png | Bin 0 -> 1523 bytes .../sprites/units/meld/billa/billa-joint.png | Bin 0 -> 537 bytes .../units/meld/billa/billa-leg-base.png | Bin 0 -> 823 bytes assets/sprites/units/meld/billa/billa-leg.png | Bin 0 -> 936 bytes assets/sprites/units/meld/billa/billa.png | Bin 0 -> 2697 bytes .../meld/{ => braig}/braig-cannon-cell.png | Bin .../units/meld/{ => braig}/braig-cannon.png | Bin .../units/meld/{ => braig}/braig-leg-base.png | Bin .../units/meld/{ => braig}/braig-leg.png | Bin .../sprites/units/meld/{ => braig}/braig.png | Bin .../units/meld/{ => craig}/craig-gun-cell.png | Bin .../units/meld/{ => craig}/craig-gun.png | Bin .../units/meld/{ => craig}/craig-joint.png | Bin .../units/meld/{ => craig}/craig-leg.png | Bin .../sprites/units/meld/{ => craig}/craig.png | Bin .../sprites/units/meld/globkin-gun-blob.png | Bin 643 -> 0 bytes .../units/meld/{ => jilla}/jilla-segment0.png | Bin .../units/meld/{ => jilla}/jilla-segment1.png | Bin .../units/meld/{ => jilla}/jilla-segment2.png | Bin .../sprites/units/meld/{ => jilla}/jilla.png | Bin .../meld/{ => kathid}/kathid-base-joint.png | Bin .../units/meld/{ => kathid}/kathid-cell.png | Bin .../meld/{ => kathid}/kathid-crystal-heat.png | Bin .../meld/{ => kathid}/kathid-crystal.png | Bin .../units/meld/{ => kathid}/kathid-joint.png | Bin .../meld/{ => kathid}/kathid-leg-base.png | Bin .../units/meld/{ => kathid}/kathid-leg.png | Bin .../units/meld/{ => kathid}/kathid.png | Bin .../sprites/units/meld/pointago/pointago.png | Bin 3533 -> 3670 bytes .../scorcher/scorcher-crystal-shine-heat.png | Bin 0 -> 779 bytes .../meld/scorcher/scorcher-crystal-shine.png | Bin 0 -> 543 bytes .../units/meld/scorcher/scorcher-crystal.png | Bin 0 -> 728 bytes .../units/meld/scorcher/scorcher-foot.png | Bin 0 -> 327 bytes .../units/meld/scorcher/scorcher-joint.png | Bin 0 -> 302 bytes .../units/meld/scorcher/scorcher-leg.png | Bin 0 -> 560 bytes .../sprites/units/meld/scorcher/scorcher.png | Bin 0 -> 1540 bytes src/meld/Meld.java | 12 +- src/meld/content/MeldBlocks.java | 298 ++++++---------- src/meld/content/MeldBullets.java | 95 ++++- src/meld/content/MeldEnvironment.java | 4 +- src/meld/content/MeldItems.java | 8 +- src/meld/content/MeldLiquids.java | 41 ++- src/meld/content/MeldStatusEffects.java | 26 +- src/meld/content/MeldUnits.java | 327 ++++++++++++++---- .../entities/bullet/TransitionBulletType.java | 10 + src/meld/entities/unit/BulbheadEntity.java | 1 + .../unit/abilities/DeathBirthAbility.java | 12 + .../unit/abilities/SpawnRushAbility.java | 16 + .../unit/weapons/template/BraigWeapon.java | 48 +++ src/meld/fluid/Aspect.java | 11 + src/meld/fluid/AspectGroup.java | 72 ++++ src/meld/meta/MeldStatUnit.java | 4 +- src/meld/meta/MeldStats.java | 44 +++ src/meld/world/blocks/Bruisekit.java | 6 +- .../consumers/StupidConsumeAspects.java | 102 ++++++ .../world/blocks/crafting/ModularCrafter.java | 189 ++-------- .../crafting/modules/AttributeModule.java | 27 ++ .../crafting/modules/ConsumeLiquidModule.java | 41 +++ .../crafting/modules/ProduceLiquidModule.java | 22 ++ .../modules/rework/AttributeModule.java | 45 --- .../modules/rework/ConsumeHeatModule.java | 30 -- .../modules/rework/ConsumeHighestModule.java | 35 -- .../modules/rework/ConsumeItemModule.java | 32 -- .../modules/rework/ConsumeLiquidModule.java | 52 --- .../modules/rework/ConsumePayloadModule.java | 55 --- .../rework/EfficiencySourceModule.java | 21 -- .../modules/rework/EfficiencyVoidModule.java | 22 -- .../modules/rework/ProduceHeatModule.java | 25 -- .../modules/rework/ProduceItemModule.java | 44 --- .../modules/rework/ProduceLiquidModule.java | 53 --- .../modules/rework/ProducePayloadModule.java | 81 ----- .../rework/base/ConsumeDiscreteModule.java | 45 --- .../modules/rework/base/ConsumeModule.java | 33 -- .../rework/base/ProduceDiscreteModule.java | 38 -- .../modules/rework/base/ProduceModule.java | 23 -- src/meld/world/blocks/fluid/AspectPipe.java | 2 +- 84 files changed, 988 insertions(+), 1234 deletions(-) create mode 100644 assets/sprites/blocks/meld/coffer/billa-coffer.png rename assets/sprites/units/meld/{ => afraig}/afraig-joint.png (100%) rename assets/sprites/units/meld/{ => afraig}/afraig-leg.png (100%) rename assets/sprites/units/meld/{ => afraig}/afraig-spurter-cell.png (100%) rename assets/sprites/units/meld/{ => afraig}/afraig-spurter.png (100%) rename assets/sprites/units/meld/{ => afraig}/afraig.png (100%) create mode 100644 assets/sprites/units/meld/billa/billa-crystals-glow.png create mode 100644 assets/sprites/units/meld/billa/billa-crystals.png create mode 100644 assets/sprites/units/meld/billa/billa-joint.png create mode 100644 assets/sprites/units/meld/billa/billa-leg-base.png create mode 100644 assets/sprites/units/meld/billa/billa-leg.png create mode 100644 assets/sprites/units/meld/billa/billa.png rename assets/sprites/units/meld/{ => braig}/braig-cannon-cell.png (100%) rename assets/sprites/units/meld/{ => braig}/braig-cannon.png (100%) rename assets/sprites/units/meld/{ => braig}/braig-leg-base.png (100%) rename assets/sprites/units/meld/{ => braig}/braig-leg.png (100%) rename assets/sprites/units/meld/{ => braig}/braig.png (100%) rename assets/sprites/units/meld/{ => craig}/craig-gun-cell.png (100%) rename assets/sprites/units/meld/{ => craig}/craig-gun.png (100%) rename assets/sprites/units/meld/{ => craig}/craig-joint.png (100%) rename assets/sprites/units/meld/{ => craig}/craig-leg.png (100%) rename assets/sprites/units/meld/{ => craig}/craig.png (100%) delete mode 100644 assets/sprites/units/meld/globkin-gun-blob.png rename assets/sprites/units/meld/{ => jilla}/jilla-segment0.png (100%) rename assets/sprites/units/meld/{ => jilla}/jilla-segment1.png (100%) rename assets/sprites/units/meld/{ => jilla}/jilla-segment2.png (100%) rename assets/sprites/units/meld/{ => jilla}/jilla.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-base-joint.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-cell.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-crystal-heat.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-crystal.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-joint.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-leg-base.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid-leg.png (100%) rename assets/sprites/units/meld/{ => kathid}/kathid.png (100%) create mode 100644 assets/sprites/units/meld/scorcher/scorcher-crystal-shine-heat.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher-crystal-shine.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher-crystal.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher-foot.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher-joint.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher-leg.png create mode 100644 assets/sprites/units/meld/scorcher/scorcher.png create mode 100644 src/meld/entities/unit/abilities/SpawnRushAbility.java create mode 100644 src/meld/entities/unit/weapons/template/BraigWeapon.java create mode 100644 src/meld/fluid/AspectGroup.java create mode 100644 src/meld/meta/MeldStats.java create mode 100644 src/meld/world/blocks/consumers/StupidConsumeAspects.java create mode 100644 src/meld/world/blocks/crafting/modules/AttributeModule.java create mode 100644 src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java create mode 100644 src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/AttributeModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java delete mode 100644 src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java diff --git a/assets/bundles/bundle.properties b/assets/bundles/bundle.properties index 19898d5..8c8e0f2 100644 --- a/assets/bundles/bundle.properties +++ b/assets/bundles/bundle.properties @@ -2,7 +2,7 @@ block.meld-sunder.name = Sunder block.meld-sunder.description = Fires clumps of Debris flak at enemy targets.\n[accent]Sticks shrapnel in targets![] block.meld-shredstorm.name = Shredstorm -block.meld-shredstorm.description = Sprays shrapnel at enemy targets with slight piercing.\n[accent]Spread is focused towards the aim direction\n\nEffects vary based on ammo type[] +block.meld-shredstorm.description = Sprays debris shrapnel or silver buckshot at enemy targets..\n[accent]Spread is focused towards the aim direction. block.meld-flurry.name = Flurry block.meld-flurry.description = Charges and flings canisters of sticky Carbolith dust which spark on a delay.\n[accent]High damage vs force projectors[]\n\n[red]Warning: Putting hard matterials with sludge in will clog the machinery![]\n\n[accent]Armored![] @@ -444,7 +444,7 @@ block.meld-pointago-coffer.name = Pointago Coffer block.meld-pointago-coffer.description = Bursts open into multiple Pointago upon destruction. Locks onto a target's, then fires a 3 round burst of sharp rocks. block.meld-meld-cannon.name = Meld Cannon -block.meld-meld-cannon.description = Lobs clusters of highly compressed Aether at enemies. +block.meld-meld-cannon.description = Lobs globs of [meld-meld]Meld[] at enemies, suppressing them. block.meld-meld-spewer.name = Meld Spewer block.meld-meld-spewer.description = Sprays focused jets of [#e6c700]Ichor[] toward targets doing huge focused knockback, healing any allied building in the way. @@ -792,18 +792,11 @@ liquid.meld-meld.name = [#ea9ad6]Meld[] liquid.meld-meld.description = Slowly disolves most metals and organics, save for a group of materials. liquid.meld-meld.details = The original lifeform on Ikaru in gaseous state. -liquid.meld-waste.name = Waste -liquid.meld-waste.description = Slurry of unusable gunk - -liquid.meld-cold-waste.name = Cold waste -liquid.meld-cold-waste.description = Cold slurry of unusable gunk - -liquid.meld-valid.name = Valid Placement -liquid.meld-deposit.name = Deposit -liquid.meld-progress.name = Progress -liquid.meld-charge = Charge -liquid.meld-dissonance = Charge - +//Aspect Groups +aspect-group.meld-aether = [meld-aether]Aether +aspect-group.meld-aspect = [meld-aspect]Aspect +aspect-group.meld-fumes = [meld-fumes]Fumes +aspect-group.meld-outlet-fuel = Outlet Fuel //Units @@ -950,7 +943,7 @@ unit.meld-uwubba.details = Yknow theres probably a joke to be made here. unit.meld-ontragga.name = Ontragga unit.meld-ontragga.description = [#ea9ad6]Second form seige worm[]\nConstruct creeps foward shielding numerous wuba inside from harm whilst sporting a heavy aether cannon which fires in three round bursts.\n\nThis construct is a [accent]high priority target[] and [red]must[] be taken out before it breaches a defensive perimeter, [red]DO NOT LET IT LEAK.[]\n\n[red]Plated![]\n\n[orange]Biomechanical![] -unit.meld-uwubba.details = Yknow theres probably a joke to be made here. +unit.meld-ontragga.details = Yknow theres probably a joke to be made here. unit.meld-globkin.name = Globkin unit.meld-globkin.description = [#ea9ad6]Guardian skirmisher nest construct[]\n\nConstruct jolts backwards after launching a blast of shrapnel from its cannons, containing several blobs that detonate if not dealt with.\n\nDistract with moble units or drop overseer pings on it to prevent it from recoiling past defensive lines.\n\n[red]Plated![] @@ -1021,94 +1014,6 @@ unit.meld-soontm.name = Coming Soon! //research categories -//Liquid logistics tree - -status.meld-liquid-logistics.name = [accent]Liquid Logistics[] -status.meld-liquid-logistics.description = Schematics for a basic [accent]liquid distribution network[] and an [#cb8650]Aether[] source.\nThe network is comprised of four components.\n\n\n-[accent]Channel Nodes[] which form the corners and outlets of the network\n-[accent]Channel Faces[] which connect parts of the network.\n-[accent]Channel Directors[] which control the direction and pressure of fluids\n-[accent]Aspect Outlets[] which serve as the endpoint for [#cb8650]Aether[] networks.\n\nChannels can be walked on freely and are stable enough to be placed on deep patches of liquid. The Backdrop will [red]not[] support Channels. - -status.meld-murk-handling.name = [accent]Murk Handling[] -status.meld-murk-handling.description = The Mercury Swamp has proven to be quite hostile in numerous ways, but bessides the outright hostile threats, there have been issues reguarding excess Starving Masses and fluid filtration...\n\nTheese schematics detail a number of ways to handle this, ranging from [accent]Compartmentalized Nodes[], [accent]Filtration Units[], [accent]Neutralization/Purification Chambers[].\n\n[accent]Pressurized Dispersion[] would be helpful to control any accidents. - -status.meld-pressure-control.name = [accent]Pressure Control[] -status.meld-pressure-control.description = Advanced means of [accent]pressure control[] involving [accent]large fluid hubs, pressurized directors, and fluid siphons[]. - -status.meld-pressurized-dispersion.name = [accent]Pressurized Dispersion[] -status.meld-pressurized-dispersion.description = Getting fluids from point A to B was previously only possible with Channels. Any attempts to shoot Fluids out into the world only created puddles.\n\nWith advances in [accent]Pressure Control[] combined with observations about the Mercury Swamp's flora, fluids can now be dispersed into the world with more control over longer distances.\n\n[red]Further advances in technology are required to bridge gaps inside Channel networks or to utalize solid atomized jets.[]\n\n[accent]Murk Handling[] is greatly helpful to filter out fluids for use by Fluid Sprayers. - -status.meld-fluid-extraction.name = [accent]Fluid Extraction[] -status.meld-fluid-extraction.description = Filtration methods inspired off Blossoming Elderflesh give way to means of [accent]directly[] obtaining fluids from [accent]surface deposits[] with varying expirimental methods. -status.meld-fluid-extraction.details = Finally, a direct way to directly harvest [#89828c]Mercury[] pools. A means of extracting massive quantities of liquid [#ea9ad6]Meld[] if I feel like it. - -status.meld-crude-crackers.name = [accent]Crude Crackers[] -status.meld-crude-crackers.description = Schematics taking advantage of the badland's catalytic features.\n\n[#8166f0]More observation may be required to fully defragment this entry's buildings.[] -status.meld-crude-crackers.details = The implications of having to resort to such primitive technology to aquire [#cb8650]Aether[] are not ot be taken lightly. - -liquid-logistics.unlock = [accent]Liquid Logistics Unlocked![]\n[gray] - -//Item logistics tree - -status.meld-item-logistics.name = [accent]Item Logistics[] -status.meld-item-logistics.description = Schematics for a basic [accent]item distribution system[] and an [accent]Item[] source.\n\nThis system is comprised of a system of [accent]Chute[] components which route items from place to place. - -item-logistics.unlock = [accent]Item Logistics Unlocked![]\n[gray] - -status.meld-storage-hubs.name = [accent]Storage Hubs[] -status.meld-storage-hubs.description = Details about [accent]Buffers[] and [accent]Unloaders[], thought to be lost to time.\n\nPlans for massive storage warehouses are [red]encrypted[] for now... - -status.meld-fissure-tapping.name = [accent]Fissure Tapping[] -status.meld-fissure-tapping.description = Schematics for buildings which can utalize Ikaru's various crevices like [accent]Vents[], [accent]Fissures[] and [accent]Growths[].\n\n[#8166f0]More observation may be required to fully defragment this entry's buildings.[] -status.meld-fissure-tapping.details = Ikaruian vents have strange properties. Most vents emit fumes which are key in a number of different processes, but the main two are creating a concrete substitute and synthesizing carbolith in times of deficit.\n\nCertain vents run with [#e6c700]Ichor[] emitting coloured sparkles alongside the regular dull brown smoke swirls. - -fissure-tapping.unlock = [accent]Fissure Tapping Unlocked![]\b[gray] - -status.meld-building-foundations.name = Building Foundations -status.meld-building-foundations.description = For too long structures have been made with Debris and Carbolith as foundations, only using refined materials for moving parts...\n\nWith new data about [#e6c700]Ichor[]'s properties, and observations about how [#ea9ad6]Meld[] constructs form their shells, a new way to industrially cement Carbolith dust into mock Carbon Stone has been prototyped and is ready to deploy.\n\n[accent]Brick Conglomerators and Earthen Crushers[] produce the bricks and motar for building foundations with a variety of materials and deposits, allowing certain buildings to take on some natural [accent]armor[] and be built far quicker.\n\n[accent]Boulders are a good source of Bricks/Gloop and will return some when deconstructed.[] - -status.meld-mercurial-extraction.name = [accent]Mercurial Extraction[] -status.meld-mercurial-extraction.description = A group of schematics, all revolving around one thing: Refining raw material into Mercury over geothermal spots.\n\nIntrestingly, there is an outlier. The [accent]Electrum Amalgamator[]. -mercurial-extraction.unlock = [accent]Mercurial Extraction Unlocked![]\n[gray] - - - -//Utility tree - -status.meld-aspect-channeling.name = [#cbdbfc]Aspect Channeling[] -status.meld-aspect-channeling.description = Details about how to contain and channel Aspect into a variety of uses like [accent]Sonar Pings[], [accent]self regeneration[] and even [red]NULLIFICATION[].\n\nContains some schematics for buildings which use Silver & Resonarum to achieve this, but the rest are scattered through other entries... -status.meld-aspect-channeling.details = Aspect was deemed too dangerous to be converted into power en-mass, but there is rumor of an entry heavly encrypted which allowed for this type of conversion.\n\nThe Temple guarding this secret was to be hidden away from Strata, hidden from you, but there is still hope that it hasn't completely sunken into the backdrop... - -aspect-channeling.unlock = [accent][#cbdbfc]Aspect Channeling[] Unlocked![]\n[gray] - -status.meld-power-transmission.name = [accent]Power Transmission[] -status.meld-power-transmission.description = Schematics for buildings which use Electrum to focus power into lasers, allowing transmission without physical connection. This entry is comprised of three buildings.\n\n-[accent]Connectors[] are a stock standard power node which use Stony Gloop & Compacted Bricks as compared to more abundant materials, allowing them to be built quickly.\n-[accent]Substations[] are able to both connect over longer distances (but to fewer targets) and store small amounts of power.\n-[accent]Continental Links[] are able to bridge large sections of the sector in a cardinal direction, and can withstand a large amount of damage.\n\n[#8166f0]More observation may be required to fully defragment this entry's buildings.[] - -//sector categories - -//Todo: Unify Unlocked text? - -status.meld-ruins.name = Ruins -status.meld-ruins.description = This data point marks the last known location of a Strata instance, and seems to branch out into an almost endless amount of sector groups.\n\n[accent]As the campaign is a WIP right now, no sector groups are accessable yet![] -status.meld-ruins.details = It's time. - -ruins.unlocked = [accent]Ruins Unlocked![]\n[gray] - - -status.meld-lonely-creek.name = Lonely Creek -status.meld-lonely-creek.description = This data point leads toward a secluded creek woven between monolithic rock formations.\n\n[red]There are hundreds, maybe thousands of heat signatures in the distance, none of which are recognisable through the fog.[]\n\nWhat is this place? -status.meld-lonely-creek.details = [lightgray]This is not the way out you were looking for, and you don't have much else to go off.\n\nTime to get yourself together and explore your suroundings.[] - -beginnings.unlocked = [accent]Lonely Creek Unlocked![]\n[gray] - - -status.meld-barrens.name = Carbonic Barrens -status.meld-barrens.description = This data point appears to mark the entrance to a large expanse of carbon stone.\n\nThere is a heavy pink fog in the area lowering visibility dramatically, though there appears to be plentiful [accent]carbolith[] deposits scattered throughout. -status.meld-barrens.details = [lightgray]It's too clouded to recognize anything from a distance.[]\n\nMythical daemons were rumored to inhabit the lands of unseen planets. Though I've never seen one, I'd assume this place may have some answers. -barrens.unlocked = [accent]Carbonic Barrens Unlocked![]\n[gray] - -status.meld-glacier.name = Glacier's Edge -status.meld-glacier.description = This data point leads to a giant formation of crystalline megaliths.\n\n[accent]The fog isn't as thick here, allowing full vission of the terrain.[]\n\nConversely, there seem to be [red]trace amounts of resources available[], though many [accent]friendly[] signals pop up within the datapoint's bounds. -status.meld-glacier.details = They're trapped. As soon as it assumed there were no hostiles around, the spikes grew. Nobody's ever seen this before, and I pray that nobody suffers the same fate. -glacier.unlocked = [accent]Glacier's Edge Unlocked![]\n[gray] - //Status effects status.meld-aspect-burn.name = Aspect Burn @@ -1186,60 +1091,6 @@ status.meld-storm-a1-enabled.description = Increased mobility, damage and reload //Sectors -sector.meld-a-1a-resurgence.name = Resurgence -sector.meld-a-1a-resurgence.description = [accent]Strata has awoken.[]\n\nPlay out its journy. Linger in the confusion. -sector.meld-a-1a-resurgence.details = Nobody left but you.\nOne of two Strata housing facilities. - -sector.meld-creek.name = Creek -sector.meld-creek.description = Incursion point for Ikaru. Practise basic production and ways to push into enemy teritory.\n\n[accent]Objective: Attack\nWeather: Drizzle -sector.meld-creek.details = Something's wrong here... - -sector.meld-scrapyard.name = Scrapyard -sector.meld-scrapyard.description = Filled with Debris and Silver, but [red]lacking[] in Carbolith. Deploy new infusion technology.\n\nThe landing area is [red]resource deprived[], be ready to [accent]attack[]!\n\n[accent]Objective: Survive\nWeather: Hazy\n\n[gray][][accent]\n-Carbolith depleted[]\n\n[#ea9ad6]-1 Meld Amplifier spotted![] -sector.meld-scrapyard.details = The wildlife explodes? - -sector.meld-dead-end.name = Dead End? -sector.meld-dead-end.description = Scrapyard leaves no further routes to explore Ikaru.\nA new data point is encrypted inside a nearby facility, but the keys are in other locations.\n\nCome back when either [accent]Hedge[] or [accent]Shear[] have been completed!\n\n[accent]Objective: Explore\nWeather: None\n\n[gray][][accent]\n-Carbolith depleted[] -sector.meld-dead-end.details = An old checkpoint where Fringe used to rest at between sessions of work. - -sector.meld-hedge.name = Hedge -sector.meld-hedge.description = Scrapyard leaves no further routes to explore Ikaru.\nBacktracking from Creek is the only way fowards.\nThe branching path ahead will [red]further provoke the [#ea9ad6]Meld[].[]\n\n[red]Aerial threats[] will come from the [accent]north[]. Use [accent]Sunder[] turrets to counter them.\n\n[accent]Objective: Survive\nWeather: Foggy\n\n[gray]\n[accent]-Carbolith depleted[][]\n[#ea9ad6]-3 Meld Amplifiers spotted![] -sector.meld-hedge.details = ...why does it sound like missiles are inbound? - -sector.meld-shear.name = Shear -sector.meld-shear.description = Scrapyard leaves no further routes to explore Ikaru.\nBacktracking from Creek is the only way fowards.\nThe branching path ahead will [red]further provoke the [#ea9ad6]Meld[].[]\n\n[#ea9ad6]Meld[] attacks will switch sides after every burst, starting from the west front.\n\n[accent]Friendly signals have been detected... be ready for anything![]\n\n[accent]Objective: ???\nWeather: [green]Australian[]\n\n[gray][]\n[#ea9ad6]-2 Meld Amplifiers spotted![] -sector.meld-shear.details = The tactics never grew, but the hand opened. - -block.meld-hedge-ending.name = Hedge Decryption Key -block.meld-hedge-ending.details = One of two decryption keys for [accent]Dead End[]'s facility. - -block.meld-shear-ending.name = Shear Decryption Key -block.meld-shear-ending.details = One of two decryption keys for [accent]Dead End[]'s facility. - -status.meld-hedge-or-shear.name = Hedge or Shear -status.meld-hedge-and-shear.name = [accent]Both[] Hedge and Shear -status.meld-creek-nullified.name = Creek Completed! -status.meld-creek-nullified.description = What the hell was that??? - -creek-nullified.unlock = [accent]Scrapyard unlocked![]\n[gray]<...maybe it's time to get out of here> - -sector.meld-reminant.name = Remenant -sector.meld-reminant.description = [cyan]Datamine![]\n\nAbandoned and entombed with [#ea9ad6]crystaline megaliths[].\n\nHuge swarms of flying and artillery constructs have been recorded, along with an unknown [red]eradication[] tier threat.\n\n[accent]Dredge[] and [accent]process[] as many [accent]research materials[] as you can, untill the waves overwhelm you.\n\n[red]There is no [#4bb66b]Resonarum[] in this sector. Nullifiers inaccesable![]\n\n[accent]Objective: Dredge\n\n[gray][]\n[accent]-[#4bb66b]Resonarum[] depleted[]\n[#ea9ad6]-2 Meld Amplifiers spotted!\n-Spawning pools spotted![] -sector.meld-reminant.details = An endless battle, a meaningless victory, and a story to tell.\nI can hear their voices still linger. - -sector.meld-tempest-in-a-teacup.name = [#ea9ad6]Tempest in a Teacup[] -sector.meld-tempest-in-a-teacup.description = Another [accent]Strata[] instance has already setup a base here, and moved on.\n\nHarsh weather had been observed in response to activity, so the base lies dormant in a divot.\n\nEnsure you don't get overwhelmed by the [red]heavy storms[], and keep your units repaired.\n\n[accent]Objective: Weather the storm\nWeather: Clear, then [red]Torrential[], then Clear\n\n[gray][]\n[accent]\n[#ea9ad6]-2 Meld Amplifiers spotted!\n-Quick Start! -sector.meld-tempest-in-a-teacup.details = ...there were others? - -sector.meld-slumber.name = Slumber -sector.meld-slumber.description = This planet hasn't solidified yet...\n\nYou've done well to make it this far.\n\n[accent]Objective: ???\nWeather: ??? -sector.meld-slumber.details = A meaningless victory it was not. Meaning was given the moment they started to fight.\n\n[lightgray]This isn't right.\nShoudn't there be more?\n\nMaybe im using the wrong map. - -sector.meld-the-end.name = The End? -sector.meld-the-end.description = A point of interest has been located in the far reaches of Ikaru. Discover what lingers in the sector.\n\n[accent]Objective: Explore - -sector.meld-coming-soon.name = Coming Soon! - planet.meld-ikaru.name = Ikaru planet.meld-ikaru.description = A wartorn planet with scraps of usable unrefined resources. Inhabited by the [#ea9ad6]Meld[].\n\nHeavy fog lingers everywhere. planet.meld-ikaru.details = The [#ea9ad6]Meld[] was always here. There was no beginning or end to life on Ikaru. from the planet's inception to its destruction, there will be always [#ea9ad6]Meld[]. @@ -1404,4 +1255,7 @@ database-tag.heavy-industry = Heavy Industry unit.ticks = ticks -bar.spool = Spool \ No newline at end of file +bar.spool = Spool + +stat.aspect-efficiency = Efficiency +stat.aspect-density = Density \ No newline at end of file diff --git a/assets/sprites/blocks/meld/coffer/billa-coffer.png b/assets/sprites/blocks/meld/coffer/billa-coffer.png new file mode 100644 index 0000000000000000000000000000000000000000..f2dbb1d365e40157aa5f1b60f05e97dc6f364504 GIT binary patch literal 2108 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/billa/billa-crystals.png b/assets/sprites/units/meld/billa/billa-crystals.png new file mode 100644 index 0000000000000000000000000000000000000000..09d61f793efc7c1563d59438d823bd8bdb69a011 GIT binary patch literal 1523 zcmZ8fX;2af6jlqO(9Eek+ROuuN>lS3kFtOS(=5wdGCS6DManY~x5+cHRmWU4L`Sr| zQW66(4Lc?UL5tE=5Ij=z#>AA6lx6?bzM1#F_vXzv@4au{{fk~mja>(Jsi>%ExT6r4 zl$fmaJP=SB+rnc+N>GivgoLRy_aB^7Q2{i$BhL6FmoJIVN1%Q8?fAOa8=V99pM%wJ z2g7&hp>#|+dmeLqz+`ZhoJWcR0mm=#}m+TnQM zL-Pt^`3#$c8$%Z1#N0B4rdruDPW-2sC>`yTXMvRCe>KCu(rMZJ*Mhg^3C^|;YA|=T z9fk=*WBR#*Y%=RZoqkq9pH2WBML$HH`}~45n3psAIeYm|tn2qB17qVTOOe%xCPm2t z?xrp}5x78ZwX}2DIl>e8~*ZPc;YwR^&Dn_XhGN$aLr;|{KdWy`V8FXy{wQhe*}1V0#c>(sT0QQu(;<;D1wnq9QG?kdNnWq8pEcC zFWI;hdC;<~j(YZlVMHTfBl_{p0^KuOd$^z_=IW^OOt_WFd$LxWx)A;^G`@y@SYBQOk z?Ny{pAYZSv03S2`NC-Jf3%^~Lb|@ova*`aYpdk>5>|mTMCRWt>aj24QE#yU)Sdk_9aXY)e9$Wt&NgbiTZF=bH{T5T;+3{SJESyc{f>S3`phOudzE20nFf%KmG0CG%wW$L;y zOHA%I3vNyWISv4?5;=zlBYzk^={I(|h$3MFdkZ)r9bT-UuEdRo-bSAzNA{UhihD=$ zrTNxNR~}&)%tSV)iWa$4+2X7BTHL{yFAwFrZ$6iKw_RbT(7qs$1_0kQgRiU0U2H{C zk~XTTTNf{p`y-$uYn%~WC_nnh$a|CzGCh!B`$79TFO^Pre|Z~*hAlz?y1w96Ig7u_5Fu}YTES$6}q*x z3z|8``Pb9L$|Qw80hgEI?P}B=Q(iT72V4j*w?lUu^_X0pbb+t0d8kK0N(qi0j8iAz z%@U#IUg{T0WrGEIS}(km0?8c}i4$*f;i1Br-7#;o1PhIQLpx5?1GoeS$78SwzcjDR zN#cB`TObRKBjF|C`(LJuLrCva0ixjGou{6hV+m@F8g84o@%q7ItR)NvNr9w5i}5#% zb#T=5pa(7E&L;;Q=`dQnLmN)e-|H5=7&Necwv~);QEocwEv-CBwqHy1>fKvME5YP_ zhwaYvU?u?Hy|am=-1O!1`t_%RYu4WKS5jlf>3*^Z-WFB$m+$^x;VIKZNnYrEJ!7-* OU&sBd7or&!dhcITUh$#; literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/billa/billa-joint.png b/assets/sprites/units/meld/billa/billa-joint.png new file mode 100644 index 0000000000000000000000000000000000000000..546be1c7fbdde98265f7caa2586123072dd7f3a2 GIT binary patch literal 537 zcmV+!0_OdRP)Px$(@8`@R9J=Wn6Xa7Fc60SA|9a&la&pLF-%p*4t)n^#4{mg-hqJ;F|}i*Zm3fh zzeAZCInLR>JKG^b{kBw2`rYYlpAB5O!eUY4Bli!FXZQJd*cJJ1jI2NKg?iqD@8N|>&|z=-c;0{S0RZi) z?SFfJd(Fx|`7$N!VmnI;gOHAqBYz`(!88W#suc-_czS+O&zVug?gVtR8WTPJoLJ*R zXfhwoxa}-VW0EqQF)=im7ne#5)0jc_B}nB5;{H1At^!<2)INuz8P7$M^#s@^%$R3g>lAmpW!Q!9V}ThnTBRFE$r z4awahCxDcADQVQY^b7@%Th}gCubKS-LgJdPwA#f!0-5*awXJlq{yP+eEC2QRG24Fs b%D?3o=up?~zNGGQ00000NkvXXu0mjfi}Lmy literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/billa/billa-leg-base.png b/assets/sprites/units/meld/billa/billa-leg-base.png new file mode 100644 index 0000000000000000000000000000000000000000..d79773d616bf64dbbddc96730a0bc1c0a89eb7b8 GIT binary patch literal 823 zcmV-71IYY|P)Px%@kvBMRCt{2o3U=&Fc3h`ML)oX1q4Q>3`G-U&6qJmJ~>0jj$Mm1$l%E^3@Awc zKwZLsxroQRBafnNzA3h;NV+GIrxOLxXfzs)#xyMa54U&s0|3D9Uk{7m7L{;zelhr; z54&yk$E;ygkfTOXSd7mxO8P$a0dS$jjI}uyS=9^Th~aEES7GE32f|@4=M{W;eKWki zy^w8I%QXN1#M<%_{sh>Fc+QwYnR#ia)B_3Z_IT>Xr;bMkVC;wBkWkX4WiR%p_eW?dY*O{bP5 zJ&v>Ui=lKc`c`5mtL6HWG-|%#^d3yN>B!+QC))cmEm;Lgf;1*cBM0wadkpv@q9`fJ zBuN0q+)(0*FEKw^4|!&Sd~l#j4M)^5P)O${OG!lp)dW|Rw0s0cH@MXoOLG(Iy;xkV zd^Fa^qMCkkJ&22R{<4~@njj$-a~UP?kA3sK6V&La{e8C?-U zI9UC@H?Jn7RJna}_8?8FZ6+udoPOVbIr-FLsbv2S%SR!Ol$%x)LE0<#`SB@9Qp(L` zZDJggJy8t>L@G&hF<4IgxeKQzPgEnadSIhO}a|@SZS)8O+b=YkIB58kW0+1@H z)T8h0_dzajl9P{}tbbfntmbhX#i|~QlXdIzVv8HK9Cq79tqbqPJ&0OqDqRn!4Ma?o zdvL~WS2H3(v4!-~#S}Rf*n=R@zOXsS0qN3T4qQn!4I?!ZU3MjX%kHq-E^zQshW6$6 zMi??lNO=X4lGNZ6>_IiYpL&Xn3Tkp1jYgwUfdA=<;N9b$lVJb=002ovPDHLkV1oUG Bc2ocW literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/billa/billa-leg.png b/assets/sprites/units/meld/billa/billa-leg.png new file mode 100644 index 0000000000000000000000000000000000000000..0a070a4fea9865f8801c7c3145b866bcb7866eaf GIT binary patch literal 936 zcmV;Z16TZsP)Px&Vo5|nRCt{2o4sz^Fc5&xMIS+nC&8NmkMUG^^x${M9ORjT&V7dt9Scv^D8QSc z$&21W8R`ozEPw8(BO?j?mLjQs$oqICLs9^RLZMJ7d<`r5!}H7Q008jo=iAD8$3)of z_5=O-+FmAK<`}GkkSU5X#ZZAUs=F@V~^@`3k7;}FNDuSM0UWew~L>0i4gh8N~ z0x-}nq}Nki!MBwwbdLs6f^63T0K`fHX3(p31@)%>_+5AFqTlDk47g8@>mDtFf`CYJ z{XZpzBr3&)rYy|BYkOI7|u>?uclEe{XGDg&xt`X6e^iA_&pwN>0dk^)dj*)00eiP?SwpmN95a&~ZkBWOapheObn!Ipdl%bhl0;79SE22w@bWj4@rF%$Ccx& zh(wb4zLde|;wyu6Wjo1^st?To<`~O-EuPDm?S^c#IIfs$ds$uE3qT;Fb8cbBqH&s4 zRS^AKL?c+X<6b$k#zc}u+NXNdGiZz$SBpbYzAa?U?6H=k{r8VqhW_3sJ@+l1B=}C6 zjyVq}sbuJQFg`1^*D{9#^&&x`bK07R=Q&6Zv(NGK6w%RN4qQod3D8#)9D}4^*@e27 zB1VRHEpE&wkfw;o^ts3p;>;k8?^BDA@mMoxiXRUb3WY*pal8ZD3=$9l*m|Y_0000< KMNUMnLSTYQx4mir literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/billa/billa.png b/assets/sprites/units/meld/billa/billa.png new file mode 100644 index 0000000000000000000000000000000000000000..808b6d298814f075f1ef4886eabda01e6531d8a7 GIT binary patch literal 2697 zcmV;43U>90P)Px1h!Dk@WRt1sd5)9Epy%=&x56vv|=Kbe$ zXJ&Wj%?||g_U(NA_CGT_`|$uGkw_#Gi9{liNF)-8L?V$$Boc{4B9TZW5{X12kx0Z1 z4@d4VKODKgoOL;T(vWole8Gx+Vb_awQTMv2Brj^~L2~dFly%9WOAfkn)7`%>0|316 z)=TL2hrj&=008a%`+{wUXRgK?)|QNY*(Jib!yR+3r>(klhwyR~?*S0D!xF*Q)-;B(tBf`-DPpf~U(<0En=) zt?SYM)88fghD1<;e|q_(u!~*Wy1quPZW=`+D8U3?6k~)Oy%4Hkn)lCqY_u&Df)RY+ z(Bb9bj=ALT>GBlxEe%Qc>suPCir(RlIXH0Wa4_9;5Q1qTv}D4m*FRADjNUgC$%S8J zR}oPfdkO#my;r{hpybZ%SR+mU=O4Z=bQ?XJbpc#MDa0GN3&qrETLholh-xfN_08RyrPjq^0!H_uU=TfsE+q(<}^jZPHHzH5?yUMS3Y zu&hCoi}vf%BJQ(2{!5_hsC%Ab_;`sn3c zigs2g@?<^lIo#HsE?8Z9j+=iX7yynu?v&<7#?G|eGm)&5CrAV@&OBM>-J?w&PIvXD z0q0#b@9BD2skllS=hu}}o!{C-}7rjKK?Y3tSnd4d!Yn5Ov4AvIct|tP3l}^?9gi^y*-G zdyp%vu6^os(==({vO=NOq7ZBK+;<4|^mPAGSx+J&f z!K$Pkd)Z}DSrqEZ#lGva`_4@8tt;bj>&iF)v+|@v+OFBgHDQ^ov4M*)HgFN$&xhzt zy69hC`!xAKkqiLv-l>n2(?}y%*05xjZ*p+Jid2^VjYrW7|7n*xWq6NZBKM!qMr*bW z^nR(7hOgyCUeBT{m(;a?Qf=b(raM+Jf!r5;>d$@v0GOYgO|~5${#uxqXa=s*P0J>0GQqUm z_1iW806_2dZUDi1sd;x)7UHJ6H1uxou96EWBe^nBsX72E6O|-dMNz8?8@cn7vx(fi z;6yMs*`iO4M0|Vv^QynsZ`&XolU2rIRs_Ad{>oAAPvPf zU{;7Cos>~kE`fK-WE{I(;&QmdwtWbq++j4SYy;Ciu?n8x41E-;llr z348F2Otx5fl1*>y1I?T97v>hw(;?uLS@uxfba@J{k6vzY`Gy`ZT{KU!j$8sOnaqk! zB-8t->`yPNV(3Wr^%gzn?{*UX9xq*ao>(#~Ww(=cYm;lkAP~yipOwoi7rPxKKjU-o z=1HdxyhJd26stZHOg9pC`eC~ABZ^j)NS2k01*>n`XQEYcE|^qA^sx7_vnODFa<=Lm z7;89s#mnVN7dtLrJ^dqE1_E7HQjcS2Pt<&mZ37>YGhgVy8bZY2-gC);fGrWq&>5?n znwt{2^OLjMYs*SYTPKQj<&(4@jozy{hv!Y4usUQZ*}34h#+I7KNGq4%<1Oq}zma57GS)6G z@D{BW=L%}MdvpV}HMVe%bo$kQCX!VI({jo6ZC+ib z%TqOFPs%EZR@cAz;QhX6(IR_WV@uU4Pdn5$j*lO~;C1!w{reg)$vnA$)Ytb<=XsrE zsa!!J4TN=Rm|WdkbwP8o#ioyyfs|EcXz~49Ou77}5M69{(^&UhR5D+S&Kn`gg?bj8 zhnK_nAPB*>A}vJeK?nv5ldTZtLP&xkh2&7>LTG|@kR1#mOt7Gy1=kC8EjW}Y6%m|Z z6>0S#mJpoaNHujY7>nkRBE0SevxFoULKQ5MWV)@W1Xm#%>?%`;5=8_V!GwB~S+{K3 zymi_1{;fzP5{X12kw_#Gi9{liNF)-8L?V$$BwYRj9YcSn{P;Ot00000NkvXXu0mjf D(km#i literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/braig-cannon-cell.png b/assets/sprites/units/meld/braig/braig-cannon-cell.png similarity index 100% rename from assets/sprites/units/meld/braig-cannon-cell.png rename to assets/sprites/units/meld/braig/braig-cannon-cell.png diff --git a/assets/sprites/units/meld/braig-cannon.png b/assets/sprites/units/meld/braig/braig-cannon.png similarity index 100% rename from assets/sprites/units/meld/braig-cannon.png rename to assets/sprites/units/meld/braig/braig-cannon.png diff --git a/assets/sprites/units/meld/braig-leg-base.png b/assets/sprites/units/meld/braig/braig-leg-base.png similarity index 100% rename from assets/sprites/units/meld/braig-leg-base.png rename to assets/sprites/units/meld/braig/braig-leg-base.png diff --git a/assets/sprites/units/meld/braig-leg.png b/assets/sprites/units/meld/braig/braig-leg.png similarity index 100% rename from assets/sprites/units/meld/braig-leg.png rename to assets/sprites/units/meld/braig/braig-leg.png diff --git a/assets/sprites/units/meld/braig.png b/assets/sprites/units/meld/braig/braig.png similarity index 100% rename from assets/sprites/units/meld/braig.png rename to assets/sprites/units/meld/braig/braig.png diff --git a/assets/sprites/units/meld/craig-gun-cell.png b/assets/sprites/units/meld/craig/craig-gun-cell.png similarity index 100% rename from assets/sprites/units/meld/craig-gun-cell.png rename to assets/sprites/units/meld/craig/craig-gun-cell.png diff --git a/assets/sprites/units/meld/craig-gun.png b/assets/sprites/units/meld/craig/craig-gun.png similarity index 100% rename from assets/sprites/units/meld/craig-gun.png rename to assets/sprites/units/meld/craig/craig-gun.png diff --git a/assets/sprites/units/meld/craig-joint.png b/assets/sprites/units/meld/craig/craig-joint.png similarity index 100% rename from assets/sprites/units/meld/craig-joint.png rename to assets/sprites/units/meld/craig/craig-joint.png diff --git a/assets/sprites/units/meld/craig-leg.png b/assets/sprites/units/meld/craig/craig-leg.png similarity index 100% rename from assets/sprites/units/meld/craig-leg.png rename to assets/sprites/units/meld/craig/craig-leg.png diff --git a/assets/sprites/units/meld/craig.png b/assets/sprites/units/meld/craig/craig.png similarity index 100% rename from assets/sprites/units/meld/craig.png rename to assets/sprites/units/meld/craig/craig.png diff --git a/assets/sprites/units/meld/globkin-gun-blob.png b/assets/sprites/units/meld/globkin-gun-blob.png deleted file mode 100644 index fb9be7a71499da6530962fef95dc5235c79b382a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmV-}0(||6P)Px%JxN4CR9J=Wn6XO(F%-sM#o48!lOTc)g*v$k(pBhUhl0EAMZ}>)k>Xx=!9kaT zs}#C+YN3OLA~<#I;y<8Mj`a1tI%8!A|ckRBj{> z2ZNo%jx7gsl^e;!!5}y;idul$imo{<7vOR*&yGz>oO8~#%1Jf*Ts}KkJ5K>o*HKEX zLy>b5FaZ5{rq*L%@ceR~U9)+4Hdu zI^hf$jvtl*e0lF@hg3F{HXv)a?HGW65$LtLYri#Wb@ux5*#QgVdN2ryDCt}%7y!Wa z!;N%I0En;05C_%vIZo5jBs;LUxVz%nYjxpv|55~)D;x%*069^BcJw8HBDmNHeSCk3 zucNLtfW20itZ5%0U31n9n@CYJX+u+k`})SB0Hys>Vx8ma1k3Dc+{);451+Tc``X@r zJ~>xT)tZw6`X-HwX{dtW>}Yaz*r?T=`i zjxUk`39*#aBX+QIQeBx83@eoc3?RWdBZ-5QsL7EyDFp+F_{)REIT#DADKeJLaXZ%+ zrj^HYvVUJpLtF!JIM*DvOIq(HZE;{m2GB$_6v&phAq8lhb-_#APi^m0atAK>m_G6@ di9&_`SicH(FsmyhhSdN7002ovPDHLkV1l%7BTfJS diff --git a/assets/sprites/units/meld/jilla-segment0.png b/assets/sprites/units/meld/jilla/jilla-segment0.png similarity index 100% rename from assets/sprites/units/meld/jilla-segment0.png rename to assets/sprites/units/meld/jilla/jilla-segment0.png diff --git a/assets/sprites/units/meld/jilla-segment1.png b/assets/sprites/units/meld/jilla/jilla-segment1.png similarity index 100% rename from assets/sprites/units/meld/jilla-segment1.png rename to assets/sprites/units/meld/jilla/jilla-segment1.png diff --git a/assets/sprites/units/meld/jilla-segment2.png b/assets/sprites/units/meld/jilla/jilla-segment2.png similarity index 100% rename from assets/sprites/units/meld/jilla-segment2.png rename to assets/sprites/units/meld/jilla/jilla-segment2.png diff --git a/assets/sprites/units/meld/jilla.png b/assets/sprites/units/meld/jilla/jilla.png similarity index 100% rename from assets/sprites/units/meld/jilla.png rename to assets/sprites/units/meld/jilla/jilla.png diff --git a/assets/sprites/units/meld/kathid-base-joint.png b/assets/sprites/units/meld/kathid/kathid-base-joint.png similarity index 100% rename from assets/sprites/units/meld/kathid-base-joint.png rename to assets/sprites/units/meld/kathid/kathid-base-joint.png diff --git a/assets/sprites/units/meld/kathid-cell.png b/assets/sprites/units/meld/kathid/kathid-cell.png similarity index 100% rename from assets/sprites/units/meld/kathid-cell.png rename to assets/sprites/units/meld/kathid/kathid-cell.png diff --git a/assets/sprites/units/meld/kathid-crystal-heat.png b/assets/sprites/units/meld/kathid/kathid-crystal-heat.png similarity index 100% rename from assets/sprites/units/meld/kathid-crystal-heat.png rename to assets/sprites/units/meld/kathid/kathid-crystal-heat.png diff --git a/assets/sprites/units/meld/kathid-crystal.png b/assets/sprites/units/meld/kathid/kathid-crystal.png similarity index 100% rename from assets/sprites/units/meld/kathid-crystal.png rename to assets/sprites/units/meld/kathid/kathid-crystal.png diff --git a/assets/sprites/units/meld/kathid-joint.png b/assets/sprites/units/meld/kathid/kathid-joint.png similarity index 100% rename from assets/sprites/units/meld/kathid-joint.png rename to assets/sprites/units/meld/kathid/kathid-joint.png diff --git a/assets/sprites/units/meld/kathid-leg-base.png b/assets/sprites/units/meld/kathid/kathid-leg-base.png similarity index 100% rename from assets/sprites/units/meld/kathid-leg-base.png rename to assets/sprites/units/meld/kathid/kathid-leg-base.png diff --git a/assets/sprites/units/meld/kathid-leg.png b/assets/sprites/units/meld/kathid/kathid-leg.png similarity index 100% rename from assets/sprites/units/meld/kathid-leg.png rename to assets/sprites/units/meld/kathid/kathid-leg.png diff --git a/assets/sprites/units/meld/kathid.png b/assets/sprites/units/meld/kathid/kathid.png similarity index 100% rename from assets/sprites/units/meld/kathid.png rename to assets/sprites/units/meld/kathid/kathid.png diff --git a/assets/sprites/units/meld/pointago/pointago.png b/assets/sprites/units/meld/pointago/pointago.png index 9f1d73296c16531cc88efa55d973126d80809808..f25895d764c367b586d2f02ce8c42421123f4c43 100644 GIT binary patch delta 3657 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 A5&!@I literal 0 HcmV?d00001 diff --git a/assets/sprites/units/meld/scorcher/scorcher-leg.png b/assets/sprites/units/meld/scorcher/scorcher-leg.png new file mode 100644 index 0000000000000000000000000000000000000000..fa35da677c5650c2235dec947ffe7390af94266a GIT binary patch literal 560 zcmV-00?+-4P)Px$>PbXFRA_Wa5wEjiP6sa@G z2l51`2x4S$oKCuvjc2qKc!aps2_fkK3WY-9Lfh0`+tl6IaZnflfRFaA7v!?q829}> ziE4i|p*DWIu`#}^%d*;-P{ykLG1$hoO9@aLQuzDxUG>Yj(ueK37t`)^RvWA*t&TWw zW`ZwkpJC5am}zvPLEF^b&(|;6#!5>vKIoJV5fb)E5zm_8`C@f_on!D)GM+`)6~&%u z%g2bTFIt!^x6RAzv*^1y3)`TpG>6>MDvK5;t111*E@D2|6elw*nVuXZpXGpMJ2<6t z2Sp*B-T=4Ktl#iGX^p`M^`Rl{@^Fh5hUP*x31NVLX^FU%o{X?XDn2=3ON#hmyY587 zLmw_Q5NCL5(k$VS)L7CfJF(F(08$1^vgI6Am@6yYQ@gN)=Zls4w59sf6m;30&W4>~ z4QbBjbI7`{$OBy>KMhgSPOAoy;QCB@l!T`hk73H|aeCVxZ%+`^6 zbJ=SYlWl2+v9b$RnFwV$_HO#7F;`k59I7$-;+S-5^&v^vm*rLvZCiG97u*B9w6I1B yyLDpsxfS+<=ag0jv8tdR0H=6fC=?2X0?r@KrU&Qyn)7G?0000Px)zDYzuRA_^36oX(ER=TJ~LE0j1Qd+ZcQ{^ED zLLiN$eU=b~V4+ZgxX?vDNK{G^T!bW0X*UtXkY`MwAfYc1`W9^^#a-aJuyb?nxo75{ znRjnmA%6(mduQgH^P4j>Z|+BcUF~XDk5h%L4fgc()aq$JI5?PTOqm+17tSb4$Y4WD z)89uh2akPN=sJD2w)e#sG6|}(zJZDH(E|4oVk@W-bqoLlWAm}{P|(WyM(n)r%#ql6 z2;@^ikq~h>AQ`AeQX9#(uYv#zGfmjD)qy=*9k|kYq3$4Ty3%7^;3bNr1ST0T_ z77`8_GDLYBspBNyY4RG=#=&z(UKYzg`=LL0{ux!)H>{)L-u!ILflEI1a9c~i7SGiA#fZ_Djd->w*`tbnxP ztEGLhvhu>B=*%U$`1meFl0O=g3sMA>8gNJ+fYHsZF#z!_NkH_PscYJDo0?c`kO{zR z=en%Dkp`)SlgcQhFPxDSdqJwOx8>PbrG2lwkudg0=}zqY@7GtO<6D~zv9hxVpSA`y zy7VwQKGe7s9Y6Ei{#ZQ;?9QLR#?C8i4*&q4d^-_wMF*6g0|If#TR+cO1CHuEf^@+d zjV?Wm!P#`^y?3Hx4hL)l0^5+{9HbSwK@A-6Up6#wfUXm$?=(0n>niGk)JPfyS=9G| z15$nmT@79Gg{!AsT8ybX8DGdyxRCam*u+)TCOXMo@)^oh(rPU!Z5y?UXnqhXWKhyqX>R<5b#%sJb6I0pW9QCv0W{2xJSHrLD{W zpHz7P<}nm~Sr?2H50%`7r%1``c}i4#5JB#E@M;bKy7@0@|ld(_|(Y(++Vqy2#CwT zRVSCNWn9$O{gu07XK1skzTu_f4x3t^Pt-B+9IW}of#{f)?eB}fkEb?_H1n3<6K1m6 zUL8ojOE=II2mt1KhcVYX>;kcmkt+W=Mvnd!$q7v z`o7gKefuOSlMM2e&%C>%Ly*G&aUdI~_mcWkF7NLz%f+eKS5FjneD=E7 z=F_2z0JgVpW~Xwscv-C9+Snrgz5;%5K;Q?lBkiS{EsHdmmCdyX&Zeuy%c=#Xys+pE z2Lx`_w2oYhg-;zHpIpIvQSBt3>m3d%3-KkX?yuXWPaCD5lDEI=j-A`!`0%U;4hVSY z#ML%*yihJq)y#cgb#mFdK#1)h)#dk%)yd_$;ed>8cxfO!P-i>G&rKF^K*s+VQl0%P z8m_KK6}+66H6MK`v$!&SKS%>>K5;;m%{<|N9A1F(09}xI!vQ(`bCwYut{UY52jmC{ q=pplh19Aog(joJN+tsfA2l^Mz%PQjLh7QUA0000 packages = Seq.with( "meld", "meld.content", - "meld.world" + "meld.world", + "meld.fluid" ); packages.each(name -> { @@ -105,6 +108,9 @@ public void loadContent(){ MeldBlocks.load(); MeldEnvironment.load(); + //Just loads localised names + AspectGroup.loadAll(); + Vars.content.items().each(c -> { c.stats.add(Stat.buildCost, c.cost, MeldStatUnit.ticks); }); @@ -114,5 +120,9 @@ public void loadContent(){ b.deconstructDropAllLiquid = true; } }); + + Vars.content.liquids().each(l -> { + Colors.put(l.name, l.color); + }); } } diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index ebef791..433a298 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -1,6 +1,5 @@ package meld.content; -import arc.*; import arc.graphics.Blending; import arc.graphics.Color; import arc.math.Angles; @@ -12,12 +11,13 @@ import meld.*; import meld.entities.bullet.OutflowBulletType; import meld.entities.bullet.TransitionBulletType; +import meld.fluid.AspectGroup; import meld.graphics.*; import meld.world.blocks.consumers.ConsumeAspects; import meld.world.blocks.*; +import meld.world.blocks.consumers.StupidConsumeAspects; import meld.world.blocks.crafting.ModularCrafter; import meld.world.blocks.crafting.RecipeCrafter; -import meld.world.blocks.crafting.modules.rework.*; import meld.world.blocks.crafting.recipe.ItemRecipe; import meld.world.blocks.crafting.recipe.SpoolRecipe; import meld.world.blocks.crafting.modules.*; @@ -29,7 +29,8 @@ import meld.world.blocks.production.SingleBeamDrill; import meld.world.meta.*; import mindustry.Vars; -import mindustry.content.*; +import mindustry.content.Fx; +import mindustry.content.StatusEffects; import mindustry.entities.bullet.*; import mindustry.entities.effect.MultiEffect; import mindustry.entities.effect.ParticleEffect; @@ -92,9 +93,13 @@ public class MeldBlocks { //Meld blocks public static Block pipeline, pipelineRouter, pipelineCrossing, pipelineBridge, - meldCannon, meldMortar, - jillaCoffer, craigCoffer, braigCoffer, + + //More for areas outside glacier + meldCannon, meldMortar, meldNailgun, + jillaCoffer, billaCoffer, craigCoffer, braigCoffer, meldCultivator, meldNode, meldSuppressor, meldSynapse, + + //Mostly for glacier meldAmplifier, meldCapsule; public static Block crystalBarrier, crystalBarrierLarge, carbonicBarrier, carbonicBarrierLarge @@ -108,11 +113,13 @@ public static Item item(String name){ public static void load(){ + int channelHealth = 120; + channelFace = new FlexibleSizeJunction("channel-face"){{ requirements(Category.liquid, with( MeldItems.debris, 2 )); - health = 120; + health = channelHealth; solid = false; placeableLiquid = true; usePassedOffset = false; @@ -122,7 +129,7 @@ public static void load(){ requirements(Category.liquid, with( MeldItems.debris, 5 )); - health = 120; + health = channelHealth; liquidCapacity = 200; solid = false; @@ -130,13 +137,13 @@ public static void load(){ }}; aspectOutlet = new RecipeCrafter("aspect-outlet"){{ - outputDirections.putAll(MeldLiquids.aspect, new int[]{0}, MeldLiquids.boundAspect, new int[]{0}); + outputDirections.putAll(MeldLiquids.aspect, new int[]{0}, MeldLiquids.boundAspect, new int[]{0}, MeldLiquids.stormingAspect, new int[]{0}); requirements(Category.liquid, with( MeldItems.debris, 7 )); - health = 200; + health = channelHealth * 2; solid = false; hasLiquids = true; @@ -162,6 +169,8 @@ public static void load(){ //consumeLiquid(MeldLiquids.aether, outletRate/10); + + liquidCapacity = 100; inputLiquidSlots = 1; @@ -195,8 +204,9 @@ public static void load(){ requirements(Category.liquid, with( MeldItems.annealedSilver, 5, MeldItems.glassMallows, 2 )); + underBullets = false; leaks = false; - health = 200; + health = channelHealth * 2; armor = 2; insulated = true; @@ -212,7 +222,7 @@ public static void load(){ requirements(Category.liquid, with(MeldItems.debris, 80, MeldItems.shadesteel, 120)); size = 4; - health = 120 * 16; + health = channelHealth * 16; armor = 8; liquidCapacity = 100 * 100; }}; @@ -222,7 +232,7 @@ public static void load(){ MeldItems.debris, 5 )); - health = 200; + health = channelHealth; placeableLiquid = true; liquidPressure = 0.5f; @@ -236,7 +246,7 @@ public static void load(){ requirements(Category.liquid, with( MeldItems.debris, 8 )); - health = 180; + health = channelHealth; armor = 1; minPressure = -0.25f; @@ -252,7 +262,7 @@ public static void load(){ MeldItems.debris, 8 )); - health = 120; + health = channelHealth; solid = false; placeableLiquid = true; @@ -265,7 +275,7 @@ public static void load(){ )); size = 3; - health = 800; + health = channelHealth * 10; armor = 8; solid = false; @@ -279,7 +289,7 @@ public static void load(){ )); size = 1; - health = 120; + health = channelHealth; solid = false; placeableLiquid = true; @@ -292,7 +302,7 @@ public static void load(){ )); size = 1; - health = 120; + health = channelHealth; solid = false; placeableLiquid = true; @@ -304,7 +314,7 @@ public static void load(){ MeldItems.carbolith, 60 )); size = 2; - health = 840; + health = 640; range = 200; fogRadiusMultiplier = 0.25f; @@ -362,11 +372,11 @@ public static void load(){ shredstorm = new ItemTurret("shredstorm"){{ requirements(Category.turret, with( - MeldItems.debris, 45, - MeldItems.silver, 60 + MeldItems.debris, 35, + MeldItems.silver, 50 )); size = 2; - health = 600; + health = 450; range = 200; fogRadiusMultiplier = 0.25f; @@ -386,7 +396,7 @@ public static void load(){ rotate = quickRotate = false; shoot = new ShootBarrel(){{ - shotDelay = 5; + shotDelay = 0; shots = 7; float[] spreadCone = new float[]{0, 3, 6, 15}; @@ -439,74 +449,7 @@ public static void load(){ }}; ammoTypes.putAll( - MeldItems.debris, - new BasicBulletType(){{ - damage = 2; - lifetime = 5; - speed = 12; - sprite = Meld.prefix("clump"); - width = 8; - height = 10; - shrinkY = 0; - - despawnHit = true; - - fragLifeMin = 0.6f; - ammoMultiplier = 2; - - fragRandomSpread = 5; - fragBullets = 3; - - fragBullet = new BasicBulletType(12, 0.25f, Meld.prefix("clump")){{ - lifetime = 21; - drag = 0.01f; - width = 6; - height = 12; - shrinkX = 0.7f; - shrinkY = 0.2f; - - splashDamage = 1; - splashDamageRadius = 20; - knockback = 0.25f; - impact = true; - - hitEffect = Fx.none; - despawnEffect = Fx.none; - setDefaults = false; - despawnHit = false; - fragOnHit = true; - - pierce = true; - pierceCap = 2; - - fragBullets = 3; - fragRandomSpread = 60; - fragAngle = 180; - - bulletInterval = 2; - fragBullet = new BasicBulletType(9, 2, Meld.prefix("clump")){{ - speed = 9; - damage = 0.5f; - lifetime = 8; - drag = 0.002f; - width = 2; - height = 6; - shrinkY = 0.2f; - shrinkX = 1; - sticky = true; - stickyExtraLifetime = 120; - - hitEffect = Fx.none; - despawnEffect = Fx.none; - despawnHit = false; - - pierce = true; - pierceCap = 2; - status = MeldStatusEffects.impaled; - statusDuration = 5; - }}; - }}; - }}, + MeldItems.debris, MeldBullets.shredDebris, MeldItems.silver, MeldBullets.shredSilver, MeldItems.annealedSilver, MeldBullets.shredSilver @@ -1028,13 +971,13 @@ public void update(Bullet b){ shadesteelShingles = new Wall("shadesteel-shingles"){{ requirements(Category.defense, with(MeldItems.shadesteel, 64)); size = 2; - health = 2400; + health = 2000; buildCostMultiplier = 2f; }}; silverHusk = new RegenProjector("silver-husk"){{ - requirements(Category.defense, with(MeldItems.debris, 15, MeldItems.annealedSilver, 8)); - health = 600; + requirements(Category.defense, with(MeldItems.debris, 15, MeldItems.annealedSilver, 16)); + health = 450; armor = 3; range = 1; healPercent = 0.05f; @@ -1059,8 +1002,7 @@ public void update(Bullet b){ coreRaft = new CoreRaft("core-raft"){ { requirements(Category.effect, with( - MeldItems.debris, 600, - MeldItems.carbolith, 350 + MeldItems.debris, 900 )); size = 3; health = 4000; @@ -1106,7 +1048,7 @@ public void update(Bullet b){ dumpedLiquids.addAll(MeldLiquids.pollutantMixture, MeldLiquids.fumes); //Should make all the modules trigger in order - /*hookAll(BlockEvent.Defaults.proximityUpdate, + hookAll(BlockEvent.Defaults.proximityUpdate, new AttributeModule(){{ attribute = MeldAttributes.aetherAttr; baseEfficiency = 0; @@ -1123,12 +1065,12 @@ public void update(Bullet b){ efficiencyPin = 1; }} - );*/ + ); - /*modules.addAll( + modules.addAll( new ProduceLiquidModule(new LiquidStack(MeldLiquids.pollutantMixture, 1), 0), new ProduceLiquidModule(new LiquidStack(MeldLiquids.fumes, 1), 1) - );*/ + ); }}; elementalBlaster = new BeamDrill("elemental-blaster"){{ @@ -1180,97 +1122,6 @@ public void update(Bullet b){ consume(new ConsumeAspects(outletRate/2, MeldLiquids.outletEfficiencies, MeldLiquids.outletDensities)); }}; - new ModularCrafter("stupid-crafter"){{ - requirements(Category.crafting, with(MeldItems.debris, 40)); - size = 3; - - modules.addAll( - new ProduceHeatModule(0){{ - heatOutput = 10; - }}, - new AttributeModule(0){{ - attribute = Attribute.heat; - baseEfficiency = 0f; - maxBoost = 2f; - storagePin = 1; - }}, - //This is assigned to the same pin as the attribute, and updates after, - //which means it will only "top off" the efficiency that the attributes don't reach. - new ConsumeItemModule(0){{ - items = ItemStack.with(Items.pyratite, 1); - efficiencyIncrease = 4f; - time = 20f; - progressPin = 2; - }}, - //Same as above, but will also cover for the pyratite consumer. - new ConsumeItemModule(0){{ - items = ItemStack.with(Items.coal, 1); - time = 40f; - progressPin = 3; - }} - ); - }}; - - new ModularCrafter("stupid-silicon-smelter"){ - //I refuse to add sprites to the files. - @Override - public void load(){ - super.load(); - region = Core.atlas.find("silicon-smelter"); - fullIcon = Core.atlas.find("silicon-smelter"); - uiIcon = Core.atlas.find("silicon-smelter"); - if(drawer instanceof DrawMulti m && m.drawers[1] instanceof DrawHeatInput f) f.heat = Core.atlas.find("phase-heater-heat"); - if(drawer instanceof DrawMulti m && m.drawers[2] instanceof DrawRegion r) r.region = Core.atlas.find("pneumatic-drill-rotator"); - if(drawer instanceof DrawMulti m && m.drawers[3] instanceof DrawFlame f) f.top = Core.atlas.find("silicon-smelter-top"); - } - - { - requirements(Category.crafting, with(MeldItems.debris, 40)); - size = 2; - - drawer = new DrawMulti( - new DrawDefault(), - new DrawHeatInput(), - new DrawRegion("", 3f, true), - new DrawFlame(Color.valueOf("ffef99")) - ); - - modules.addAll( - new ProduceItemModule( 1, 2){{ - items = ItemStack.with(Items.silicon, 1); - time = 60f; - progressPin = 0; - }}, - new ConsumeItemModule(1){{ - items = ItemStack.with(Items.sand, 1); - time = 20f; - progressPin = -1; - }}, - new ConsumeHeatModule(2), - new AttributeModule(2){{ - attribute = Attribute.heat; - baseEfficiency = 0f; - maxBoost = 2f; - storagePin = -2; - }}, - //This is assigned to the same pin as the attribute, and updates after, - //which means it will only "top off" the efficiency that the attributes don't reach. - new ConsumeItemModule(2){{ - items = ItemStack.with(Items.pyratite, 1); - efficiencyIncrease = 2.5f; - time = 20f; - progressPin = -3; - }}, - //Same as above, but will also cover for the pyratite consumer. - new ConsumeItemModule(2){{ - items = ItemStack.with(Items.coal, 1); - time = 40f; - progressPin = -4; - }} - ); - } - }; - earthboundInfuser = new ModularCrafter("earthbound-infuser"){{ requirements(Category.crafting, with( MeldItems.debris, 40, @@ -1287,7 +1138,7 @@ public void load(){ dumpedItems.add(MeldItems.carbolith); - /*//Should make all the modules trigger in order + //Should make all the modules trigger in order hookAll(BlockEvent.Defaults.proximityUpdate, new AttributeModule(){{ attribute = Attribute.steam; @@ -1297,11 +1148,11 @@ public void load(){ efficiencyPin = 0; }} - );*/ + ); ItemRecipe carbolith = new ItemRecipe(with(MeldItems.debris, 1), with(MeldItems.carbolith, 1)); - /*modules.addAll( + modules.addAll( new ProduceLiquidModule(new LiquidStack(MeldLiquids.fumes, 2f), 0), new GateModule(1, new GateModule.RecipeCondition(carbolith)), new ConsumeLiquidModule(LiquidStack.with(MeldLiquids.fumes, 1, MeldLiquids.aspect, outletRate * 2), 1, 2), @@ -1311,7 +1162,7 @@ public void load(){ craftTime = 12; recipe = carbolith; }} - );*/ + ); /* @@ -1530,7 +1381,7 @@ public void load(){ float produceTime = 30; - /*modules.addAll( + modules.addAll( new GateModule( ModOUT.ZERO, new GateModule.RecipeCondition(aspectPipe1) ), @@ -1570,7 +1421,7 @@ public void load(){ progressPin = ModOUT.SIX; craftTime = produceTime; }} - );*/ + ); }}; sharkFactory = new UnitFactory("shark-factory"){{ @@ -1603,7 +1454,7 @@ public void load(){ liquidCapacity = 1 * outletRate * 60; hasLiquids = true; - consume(new ConsumeAspects(outletRate * 2, MeldLiquids.aspectEfficiencies, MeldLiquids.aspectDensities)); + consume(new StupidConsumeAspects(outletRate * 2, AspectGroup.aspect)); }}; movementAnchor = new MovementAnchor("movement-anchor"){{ @@ -1638,7 +1489,7 @@ public void load(){ size = 2; consume( - new ConsumeLiquid(MeldLiquids.fumes, 0.5f) + new StupidConsumeAspects(0.5f, AspectGroup.fumes) ); }}; @@ -1657,7 +1508,7 @@ public void load(){ new SpoolRecipe(new ItemStack(MeldItems.resonarum, 2), 200) ); - consumeLiquid(MeldLiquids.fumes, 1f); + consume(new StupidConsumeAspects(1f, AspectGroup.fumes)); consumeItem(MeldItems.resonarum, 2); }}; @@ -1693,6 +1544,8 @@ public void load(){ requirements(Category.distribution, with(MeldItems.debris, 450)); size = 3; health = 900; + armor = 30; + speed = 1f; solid = false; @@ -1963,6 +1816,8 @@ public void load(){ splashDamage = 25; splashDamageRadius = 16; + buildingDamageMultiplier = 2; + status = MeldStatusEffects.interference; statusDuration = 90; @@ -2013,10 +1868,11 @@ public void load(){ }}; jillaCoffer = new Wall("jilla-coffer"){{ + requirements(Category.defense, with(MeldItems.larvalPlating, 50, MeldItems.meldShard, 35)); size = 2; health = 400; destroyBulletSameTeam = true; - destroyBullet = new ExplosionBulletType(){{ + destroyBullet = new TransitionBulletType(){{ hitEffect = despawnEffect = Fx.none; fragBullets = 3; fragBullet = new BulletType(){{ @@ -2025,7 +1881,25 @@ public void load(){ }}; }}; + billaCoffer = new Wall("billa-coffer"){{ + requirements(Category.defense, with(MeldItems.larvalPlating, 50, MeldItems.meldShard, 35)); + size = 2; + health = 400; + armor = 80; + destroyBulletSameTeam = true; + + //TODO: WTF is the purpose of this... + destroyBullet = new TransitionBulletType(){{ + hitEffect = despawnEffect = Fx.none; + fragBullets = 1; + fragBullet = new BulletType(){{ + spawnUnit = MeldUnits.billa; + }}; + }}; + }}; + craigCoffer = new LiquidTurret("craig-coffer"){{ + requirements(Category.defense, with(MeldItems.larvalPlating, 50, MeldItems.meldShard, 35)); size = 2; health = 400; destroyBulletSameTeam = true; @@ -2064,6 +1938,7 @@ public void load(){ }}; braigCoffer = new LiquidTurret("braig-coffer"){{ + requirements(Category.defense, with(MeldItems.larvalPlating, 200, MeldItems.meldShard, 120)); size = 3; health = 1200; destroyBulletSameTeam = true; @@ -2105,9 +1980,10 @@ public void load(){ height = 14; pierce = true; + pierceBuilding = true; pierceCap = 2; - damage = 10; + splashDamage = 25; splashDamageRadius = 25; knockback = 4f; @@ -2115,6 +1991,26 @@ public void load(){ hitEffect = despawnEffect = Fx.explosion; impact = true; + + fragBullets = 3; + fragRandomSpread = 45; + + fragBullet = new BasicBulletType(4, 15, Meld.prefix("clump")){{ + speed = 4; + lifetime = 10; + width = 3; + height = 8; + + pierce = true; + pierceBuilding = true; + pierceCap = 2; + + knockback = 4f; + + impact = true; + + hitEffect = despawnEffect = Fx.none; + }}; }} ); }}; diff --git a/src/meld/content/MeldBullets.java b/src/meld/content/MeldBullets.java index fc7b02e..d42e747 100644 --- a/src/meld/content/MeldBullets.java +++ b/src/meld/content/MeldBullets.java @@ -18,7 +18,7 @@ public class MeldBullets { pulsarBlast, pulsarShrapnel, sunderDebris, sunderGlass, - shredSilver; + shredDebris, shredSilver; public static void load(){ @@ -135,17 +135,19 @@ public static void load(){ }}; }}; - sunderGlass = new FlakBulletType(){{ + sunderGlass = new FlakBulletType(6, 1){{ + sprite = Meld.prefix("clump"); + frontColor = MeldPal.glassMallowsFront; backColor = MeldPal.glassMallowsBack; collidesGround = true; scaleLife = true; reloadMultiplier = 2; + rangeChange = -80; - speed = 6; damage = 0.5f; - lifetime = 30; + lifetime = 15; width = 3; height = 8; @@ -174,7 +176,7 @@ public static void load(){ fragBullets = 5; - fragBullet = new BasicBulletType(8, 8, Meld.prefix("clump")){{ + fragBullet = new BasicBulletType(6, 8, Meld.prefix("clump")){{ frontColor = MeldPal.glassMallowsFront; backColor = MeldPal.glassMallowsBack; lightRadius = 0; @@ -198,15 +200,85 @@ public static void load(){ }}; }}; + shredDebris = new BasicBulletType(){{ + damage = 2; + lifetime = 5; + speed = 12; + sprite = Meld.prefix("clump"); + width = 8; + height = 10; + shrinkY = 0; + + despawnHit = true; + + fragLifeMin = 0.6f; + ammoMultiplier = 2; + + fragRandomSpread = 5; + fragBullets = 3; + + fragBullet = new BasicBulletType(12, 1, Meld.prefix("clump")){{ + lifetime = 21; + drag = 0.01f; + width = 6; + height = 12; + shrinkX = 0.7f; + shrinkY = 0.2f; + + splashDamage = 1; + splashDamageRadius = 20; + knockback = 0.25f; + impact = true; + + hitEffect = Fx.none; + despawnEffect = Fx.none; + setDefaults = false; + despawnHit = false; + fragOnHit = true; + + pierce = true; + pierceCap = 2; + + fragBullets = 3; + fragRandomSpread = 60; + + bulletInterval = 2; + + fragBullet = new BasicBulletType(9, 2, Meld.prefix("clump")){{ + speed = 9; + damage = 0.5f; + lifetime = 8; + drag = 0.002f; + width = 1; + height = 6; + shrinkY = 0.2f; + shrinkX = 1; + + lightRadius = 0; + + hitEffect = Fx.none; + despawnEffect = Fx.none; + despawnHit = false; + + sticky = true; + stickyExtraLifetime = 60; + + status = MeldStatusEffects.impaled; + statusDuration = 5; + }}; + }}; + }}; + shredSilver = new TransitionBulletType(){{ fragLifeMin = 0.8f; ammoMultiplier = 2; + reloadMultiplier = 2f; fragRandomSpread = 5; - fragBullets = 2; + fragBullets = 1; - fragBullet = new RicochetBulletType(8, 6, "shell"){{ + fragBullet = new RicochetBulletType(8, 10, "shell"){{ frontColor = Color.white; backColor = MeldPal.aspect; @@ -214,14 +286,17 @@ public static void load(){ drag = 0.01f; width = 8; height = 8; - shrinkX = 0.7f; - shrinkY = 0.7f; + shrinkX = 1; + shrinkY = 1f; + shrinkInterp = Interp.pow10Out; + + lightRadius = 0; splashDamage = 5; splashDamageRadius = 20; //For some reason impact + knockback on bouncy bullets doesn't work how I want it to... - knockback = 3; + knockback = 5; bounceEffect = Fx.none; hitEffect = Fx.none; diff --git a/src/meld/content/MeldEnvironment.java b/src/meld/content/MeldEnvironment.java index 82b4d99..15ccfbd 100644 --- a/src/meld/content/MeldEnvironment.java +++ b/src/meld/content/MeldEnvironment.java @@ -337,7 +337,7 @@ public static void load(){ }}; meldCluster = new WobbleProp("meld-cluster-small"){{ - requirements(Category.effect, ItemStack.with(MeldItems.debris, 40)); + requirements(Category.effect, ItemStack.with(MeldItems.debris, 50)); buildVisibility = BuildVisibility.sandboxOnly; @@ -347,7 +347,7 @@ public static void load(){ }}; meldClusterLarge = new AetherCluster("meld-cluster-large"){{ - requirements(Category.effect, ItemStack.with(MeldItems.debris, 150)); + requirements(Category.effect, ItemStack.with(MeldItems.debris, 300)); size = 3; buildVisibility = BuildVisibility.sandboxOnly; diff --git a/src/meld/content/MeldItems.java b/src/meld/content/MeldItems.java index 1939f0c..4a15505 100644 --- a/src/meld/content/MeldItems.java +++ b/src/meld/content/MeldItems.java @@ -14,7 +14,7 @@ public class MeldItems { //Heavy industry content public static Item - tenbris, shadesteel, elnarDust, annealedSilver, glassMallows, cruciblePlating, aspectPipe; + tenbris, motis, shadesteel, elnarDust, annealedSilver, glassMallows, cruciblePlating, aspectPipe; public static void load(){ @@ -46,6 +46,10 @@ public static void load(){ }}; + motis = new Item("motis"){{ + + }}; + shadesteel = new Item("shadesteel"){{ }}; @@ -68,7 +72,7 @@ public static void load(){ }}; - Seq heavyIndustry = Seq.with(tenbris, shadesteel, elnarDust, annealedSilver, glassMallows, cruciblePlating, aspectPipe); + Seq heavyIndustry = Seq.with(tenbris, motis, shadesteel, elnarDust, annealedSilver, glassMallows, cruciblePlating, aspectPipe); heavyIndustry.each(i -> i.databaseTag = "heavy-industry"); }; } diff --git a/src/meld/content/MeldLiquids.java b/src/meld/content/MeldLiquids.java index 1f8b6d6..a8d130e 100644 --- a/src/meld/content/MeldLiquids.java +++ b/src/meld/content/MeldLiquids.java @@ -5,8 +5,11 @@ import arc.struct.ObjectMap; import arc.struct.Seq; import meld.fluid.Aspect; +import meld.fluid.AspectGroup; import mindustry.type.Liquid; +import static meld.fluid.AspectGroup.*; + public class MeldLiquids { public static Aspect aether, aspect, meld, fumes, @@ -79,6 +82,24 @@ public static void load(){ temperature = 0.6f; }}; + put(aether, AspectGroup.aether, new AspectStats(1, 1)); + put(pollutantMixture, AspectGroup.aether, new AspectStats(1, 0.5f)); + put(pollutantMixture, AspectGroup.aether, new AspectStats(1, 0.5f)); + put(thunderingAether, AspectGroup.aether, new AspectStats(2, 0.5f)); + + put(aspect, AspectGroup.aspect, new AspectStats(1, 1)); + put(boundAspect, AspectGroup.aspect, new AspectStats(1, 2)); + put(stormingAspect, AspectGroup.aspect, new AspectStats(2, 1)); + + put(fumes, AspectGroup.fumes, new AspectStats(1, 1)); + put(pollutantMixture, AspectGroup.fumes, new AspectStats(1, 0.5f)); + + outletMapping.putAll( + aether, aspect, + pollutantMixture, boundAspect, + thunderingAether, stormingAspect + ); + aetherEfficiencies.put(aether, 1); aetherEfficiencies.put(pollutantMixture, 1); aetherEfficiencies.put(thunderingAether, 2); @@ -100,18 +121,16 @@ public static void load(){ meld.databaseTag = "aspect-omnipotence"; fumes.databaseTag = "aspect-vita"; - outletMapping.putAll( - aether, aspect, - pollutantMixture, boundAspect, - thunderingAether, stormingAspect - ); + mapOutlet(aether, aspect); + mapOutlet(pollutantMixture, boundAspect); + mapOutlet(thunderingAether, stormingAspect); + } + + public static void mapOutlet(Liquid input, Liquid output){ + outletMapping.putAll(input, output); - Seq outletLiquids = Seq.with(aether, pollutantMixture, thunderingAether); + outletEfficiencies.put(input, aetherEfficiencies.get(input, 1) * aspectEfficiencies.get(output, 1)); - outletLiquids.each(liquid -> { - Liquid aspecti = outletMapping.get(liquid); - outletEfficiencies.put(liquid, aetherEfficiencies.get(liquid, 1) * aspectEfficiencies.get(aspecti, 1)); - outletDensities.put(liquid, aetherDensities.get(liquid, 1) * aspectDensities.get(aspecti, 1)); - }); + outletDensities.put(input, aetherDensities.get(input, 1) * aspectDensities.get(output, 1)); } } diff --git a/src/meld/content/MeldStatusEffects.java b/src/meld/content/MeldStatusEffects.java index 303aff6..cb32e74 100644 --- a/src/meld/content/MeldStatusEffects.java +++ b/src/meld/content/MeldStatusEffects.java @@ -9,6 +9,7 @@ public class MeldStatusEffects { public static StatusEffect amplified, rally, anchored, aspectBurn, sentry, spurting, newborn, interference, drenched, stunned; public static StatusEffect lacerated, impaled; + public static StatusEffect rush; public static void load(){ @@ -54,10 +55,30 @@ public void update(Unit unit, StatusEntry entry) { } { speedMultiplier = 1.25f; - reloadMultiplier = 2; - + reloadMultiplier = 3; }}; + rush = new StatusEffect("rush"){ + @Override + public void update(Unit unit, StatusEntry entry) { + + unit.speedMultiplier /= speedMultiplier; + //Start the falloff at 180 ticks remaining + unit.speedMultiplier *= Mathf.lerp( + 1, speedMultiplier, + + Interp.pow3.apply( + Mathf.clamp(Math.min(entry.time, 180)/(180)) + ) + ); + } + { + damage = -1; + healthMultiplier = 2; + speedMultiplier = 2; + disarm = true; + }}; + anchored = new StatusEffect("anchored"){ @Override @@ -99,6 +120,7 @@ public void update(Unit unit, StatusEntry entry) { ) ); }{ + damage = 0.2f; speedMultiplier = 0.01f; dragMultiplier = 0.3f; disarm = true; diff --git a/src/meld/content/MeldUnits.java b/src/meld/content/MeldUnits.java index 04f03e7..1947326 100644 --- a/src/meld/content/MeldUnits.java +++ b/src/meld/content/MeldUnits.java @@ -14,15 +14,13 @@ import arc.util.Tmp; import meld.*; import meld.entities.bullet.TransitionBulletType; -import meld.entities.unit.abilities.BezerkAbility; +import meld.entities.unit.abilities.*; +import meld.entities.unit.weapons.template.BraigWeapon; import meld.entities.unit.weapons.template.PointagoWeapon; import meld.graphics.part.*; import meld.entities.unit.*; import meld.entities.unit.type.*; import meld.graphics.*; -import meld.entities.unit.abilities.DeathBirthAbility; -import meld.entities.unit.abilities.SlipstreamHullAbility; -import meld.entities.unit.abilities.SolidSpeedAbility; import meld.entities.unit.weapons.DeathWeapon; import meld.entities.unit.weapons.ShadowVisualWeapon; import mindustry.Vars; @@ -39,10 +37,12 @@ import mindustry.entities.effect.ParticleEffect; import mindustry.entities.effect.WaveEffect; import mindustry.entities.part.*; +import mindustry.entities.pattern.ShootBarrel; import mindustry.entities.pattern.ShootSpread; import mindustry.gen.*; import mindustry.graphics.Layer; import mindustry.graphics.Pal; +import mindustry.type.StatusEffect; import mindustry.type.UnitType; import mindustry.type.Weapon; import mindustry.type.weapons.RepairBeamWeapon; @@ -102,7 +102,7 @@ public static int classID(Class type){ //enemy units cannonOverseer, blob, afraig, craig, braig, pointago, globkin, - jilla, kathid; + jilla, billa, scorcher, kathid; public static void load(){ setupID(); @@ -111,6 +111,7 @@ public static void load(){ float IR = 80; health = 240; + armor = -40; lifetime = 360; speed = 0; flying = true; @@ -1019,7 +1020,7 @@ public static void load(){ weapons.addAll( craigWeapon(-6, 2), - braigWeapon(6, -3), + new BraigWeapon(6, -3), new DeathWeapon(){{ bullet = new ExplosionBulletType(250, 35){{ shootEffect = new MultiEffect( @@ -1201,10 +1202,15 @@ public static void load(){ legContinuousMove = true; legPhysicsLayer = false; - Weapon backMount = braigWeapon(0.25f, -11); - backMount.shootStatus = StatusEffects.none; weapons.add( - backMount, + new BraigWeapon(0.25f, -11){{ + shootStatus = StatusEffects.none; + reload /= 2f; + controllable = aiControllable = false; + autoTarget = true; + targetSwitchInterval = 20; + targetInterval = 15; + }}, new Weapon(""){{ x = 16.25f; y = 4.25f; @@ -1236,6 +1242,36 @@ public static void load(){ fragRandomSpread = 45; recoil = 4.5f; rangeOverride = 160; + spawnBullets.addAll( + new TransitionBulletType(){{ + fragRandomSpread = 45; + recoil = 4.5f; + fragBullets = 12; + + fragBullet = new BasicBulletType(8.5f, 1.5f, "meld-clump"){{ + width = 5; + height = 9; + shrinkX = shrinkY = 1; + + splashDamage = 12; + splashDamageRadius = 25; + + status = MeldStatusEffects.stunned; + statusDuration = 35; + + pierce = true; + pierceCap = 2; + speed = 8.5f; + drag = 0.01f; + + lifetime = 45; + + knockback = 5.5f; + + keepVelocity = false; + }}; + }} + ); fragBullet = new ArtilleryBulletType(){{ width = 16; @@ -1362,6 +1398,215 @@ public static void load(){ constructor = CrawlUnit::create; }}; + billa = new MeldUnitType("billa"){{ + health = 450; + + armor = 120; + drag = 0.25f; + accel = 0.35f; + speed = 1.5f; + + hitSize = 32; + rotateSpeed = 8; + faceTarget = true; + + hovering = true; + + legCount = 6; + legLength = 60; + legMaxLength = 1; + legGroupSize = 2; + legSpeed = 1; + legForwardScl = 3; + legBaseOffset = 8; + drawCell = false; + legPhysicsLayer = true; + + deathExplosionEffect = Fx.none; + + weapons.add( + new Weapon(Meld.prefix("billa-crystals")){{ + x = y = 0; + mirror = alternate = rotate = false; + reload = 14; + + shootCone = 180; + recoil = 0; + + shootY = 12.5f; + continuous = alwaysContinuous = true; + shootSound = Sounds.shootSublimate; + + parts.addAll( + new RegionPart("-glow"){{ + outline = false; + progress = PartProgress.warmup; + color = Color.clear; + colorTo = Color.white; + blending = Blending.additive; + }} + ); + + shootStatus = MeldStatusEffects.sentry; + shootStatusDuration = 4; + + bullet = new ContinuousFlameBulletType(){{ + lifetime = 85; + damage = 13; + length = 75; + width = 1.5f; + knockback = 3; + pierceCap = 3; + flareLength = 0; + rangeOverride = 55; + recoil = 0.01f; + hitUnder = true; + + colors = new Color[]{ + Color.valueOf("f9e1f343"), + Color.valueOf("ee5de9a9"), + Color.valueOf("ef85e3e3"), + Color.valueOf("d22fee"), + Color.white + }; + }}; + }}, + new Weapon(){{ + alternate = rotate = false; + mirror = true; + reload = 14; + x = 10; + y = 3.75f; + baseRotation = -45; + + shootCone = 180; + recoil = 0; + + shootY = 0; + continuous = alwaysContinuous = true; + shootSound = Sounds.shootSublimate; + parts.addAll( + new RegionPart("-glow"){{ + outline = false; + progress = PartProgress.warmup; + color = Color.clear; + colorTo = Color.white; + blending = Blending.additive; + }} + ); + + shootStatus = MeldStatusEffects.sentry; + shootStatusDuration = 4; + + bullet = new ContinuousFlameBulletType(){{ + lifetime = 85; + damage = 10; + length = 75; + width = 0.5f; + knockback = 3; + pierceCap = 3; + flareLength = 0; + rangeOverride = 55; + recoil = 0.01f; + hitUnder = true; + + colors = new Color[]{ + Color.valueOf("f9e1f343"), + Color.valueOf("ee5de9a9"), + Color.valueOf("ef85e3e3"), + Color.valueOf("d22fee"), + Color.white + }; + }}; + }} + ); + + abilities.addAll( + new DeathBirthAbility(MeldUnits.jilla, 3), + new SpawnRushAbility() + ); + + constructor = LegsUnit::create; + }}; + + scorcher = new MeldUnitType("scorcher"){{ + speed = 0.4f; + + health = 200; + drownTimeMultiplier = 0.35f; + + drag = 0.08f; + accel = 0.15f; + range = 40; + + hitSize = 12; + rotateSpeed = 12; + faceTarget = true; + + legCount = 4; + legLength = 10; + legMaxLength = 1.2f; + legGroupSize = 2; + legSpeed = 1; + legForwardScl = 2; + legBaseOffset = -1f; + + drawCell = false; + legPhysicsLayer = false; + + + weapons.add( + new Weapon(Meld.prefix("scorcher-crystal")){{ + x = y = 0; + mirror = alternate = rotate = false; + reload = 14; + + shootCone = 15; + recoil = 0; + + shootY = 8; + continuous = alwaysContinuous = true; + shootSound = Sounds.shootSublimate; + parts.addAll( + //TODO: wtf is this naming + new RegionPart("-shine"){{ + outline = false; + progress = PartProgress.warmup; + color = Color.clear; + colorTo = Color.white; + blending = Blending.additive; + }} + ); + + shootStatus = MeldStatusEffects.sentry; + shootStatusDuration = 4; + + bullet = new ContinuousFlameBulletType(){{ + lifetime = 85; + damage = 13; + length = 35; + width = 3.5f; + knockback = 3; + pierceCap = 3; + flareLength = 8; + rangeOverride = 55; + recoil = -0.05f; + hitUnder = true; + + colors = new Color[]{ + Color.valueOf("f9e1f343"), + Color.valueOf("ee5de9a9"), + Color.valueOf("ef85e3e3"), + Color.valueOf("d22fee"), + Color.white + }; + }}; + }} + ); + + constructor = LegsUnit::create; + }}; + kathid = new MeldUnitType("kathid"){{ shadowElevation = 0.1f; speed = 0.7f; @@ -1391,6 +1636,17 @@ public static void load(){ deathExplosionEffect = Fx.none; + parts.addAll( + new RegionPart(){{ + name = Meld.prefix("kathid-crystal-heat"); + progress = PartProgress.warmup; + color = Color.valueOf("11111100"); + colorTo = Color.valueOf("111111"); + blending = Blending.additive; + outline = false; + }} + ); + weapons.addAll( new Weapon(){{ x = y = 0; @@ -1401,13 +1657,14 @@ public static void load(){ shootCone = 10; shootStatus = MeldStatusEffects.sentry; - shootStatusDuration = 5; + shootStatusDuration = 25; shootSound = Sounds.shootSublimate; bullet = new ContinuousFlameBulletType(){{ + lifetime = 25; damage = 13; flareLength = 12; - length = 85; + length = 115; width = 1.7f; knockback = 1; pierceCap = 2; @@ -1419,17 +1676,6 @@ public static void load(){ Color.valueOf("d22fee"), Color.white }; - - parts.addAll( - new RegionPart(){{ - name = "kathid-crystal-heat"; - progress = PartProgress.warmup; - color = Color.valueOf("11111100"); - colorTo = Color.valueOf("111111"); - blending = Blending.additive; - outline = false; - }} - ); }}; }} ); @@ -1575,41 +1821,6 @@ public static Weapon afraigWeapon(float wx, float wy){ } public static Weapon braigWeapon(float wx, float wy){ - return new Weapon(Meld.prefix("braig-cannon")){{ - x = wx; - y = wy; - shootY = 5; - reload = 360; - shootCone = 60; - rotate = true; - mirror = false; - alternate = false; - parentizeEffects = false; - shootStatus = MeldStatusEffects.sentry; - shootStatusDuration = 180; - - shoot = new ShootSpread(){{ - shots = 3; - shotDelay = 7; - spread = 1; - }}; - - inaccuracy = 5; - - bullet = new BasicBulletType(){{ - sprite = Meld.prefix("clump"); - speed = 4; - lifetime = 75; - width = 10; - height = 12; - damage = 10; - splashDamage = 25; - splashDamageRadius = 25; - knockback = 6; - shootEffect = Fx.shootBig; - hitEffect = Fx.explosion; - impact = true; - }}; - }}; + return new BraigWeapon(wx, wy); } } diff --git a/src/meld/entities/bullet/TransitionBulletType.java b/src/meld/entities/bullet/TransitionBulletType.java index c70822e..9a476b5 100644 --- a/src/meld/entities/bullet/TransitionBulletType.java +++ b/src/meld/entities/bullet/TransitionBulletType.java @@ -2,8 +2,13 @@ import mindustry.content.Fx; import mindustry.entities.bullet.BulletType; +import mindustry.gen.Bullet; +import mindustry.type.StatusEffect; public class TransitionBulletType extends BulletType { + public StatusEffect spawnStatus; + public float spawnStatusDuration = 0; + public TransitionBulletType(){ super(); speed = 0.00001f; @@ -12,4 +17,9 @@ public TransitionBulletType(){ collides = absorbable = reflectable = hittable = false; shootEffect = chargeEffect = despawnEffect = hitEffect = Fx.none; } + + @Override + public void createUnits(Bullet b, float x, float y) { + super.createUnits(b, x, y); + } } diff --git a/src/meld/entities/unit/BulbheadEntity.java b/src/meld/entities/unit/BulbheadEntity.java index 014a11f..6481f66 100644 --- a/src/meld/entities/unit/BulbheadEntity.java +++ b/src/meld/entities/unit/BulbheadEntity.java @@ -78,6 +78,7 @@ public void update() { } public void getNearbyLink(){ + if(CoreRaft.rafts.isEmpty()) return; nearbyRaft = CoreRaft.rafts.sort(raft -> raft.dst(this)).first(); if(!within(nearbyRaft, type.fogRadius * Vars.tilesize)) nearbyRaft = null; } diff --git a/src/meld/entities/unit/abilities/DeathBirthAbility.java b/src/meld/entities/unit/abilities/DeathBirthAbility.java index a606314..8d35f2c 100644 --- a/src/meld/entities/unit/abilities/DeathBirthAbility.java +++ b/src/meld/entities/unit/abilities/DeathBirthAbility.java @@ -6,10 +6,22 @@ import mindustry.Vars; import mindustry.entities.abilities.SpawnDeathAbility; import mindustry.gen.Unit; +import mindustry.type.UnitType; //literally the same but with a status effect attached to it. yes, that's literally it. public class DeathBirthAbility extends SpawnDeathAbility { + public DeathBirthAbility(UnitType unit, int amount, float spread){ + super(unit, amount, spread); + } + public DeathBirthAbility(UnitType unit, int amount){ + super(unit, amount, 8); + } + + public DeathBirthAbility(){ + super(); + } + @Override public void death(Unit unit) { if (!Vars.net.client()) { diff --git a/src/meld/entities/unit/abilities/SpawnRushAbility.java b/src/meld/entities/unit/abilities/SpawnRushAbility.java new file mode 100644 index 0000000..ebc30f4 --- /dev/null +++ b/src/meld/entities/unit/abilities/SpawnRushAbility.java @@ -0,0 +1,16 @@ +package meld.entities.unit.abilities; + +import meld.content.MeldStatusEffects; +import mindustry.entities.abilities.Ability; +import mindustry.gen.Unit; + +public class SpawnRushAbility extends Ability { + + public float duration = 240; + + @Override + public void created(Unit unit) { + super.created(unit); + unit.apply(MeldStatusEffects.rush, duration); + } +} diff --git a/src/meld/entities/unit/weapons/template/BraigWeapon.java b/src/meld/entities/unit/weapons/template/BraigWeapon.java new file mode 100644 index 0000000..79e9a8b --- /dev/null +++ b/src/meld/entities/unit/weapons/template/BraigWeapon.java @@ -0,0 +1,48 @@ +package meld.entities.unit.weapons.template; + +import meld.Meld; +import meld.content.MeldStatusEffects; +import meld.entities.unit.weapons.BaseWeapon; +import mindustry.content.Fx; +import mindustry.entities.bullet.BasicBulletType; +import mindustry.entities.pattern.ShootSpread; + +public class BraigWeapon extends BaseWeapon { + public BraigWeapon(float wx, float wy){ + super(Meld.prefix("braig-cannon")); + x = wx; + y = wy; + shootY = 5; + reload = 360; + shootCone = 60; + rotate = true; + mirror = false; + alternate = false; + parentizeEffects = false; + shootStatus = MeldStatusEffects.sentry; + shootStatusDuration = 180; + + shoot = new ShootSpread(){{ + shots = 3; + shotDelay = 7; + spread = 1; + }}; + + inaccuracy = 5; + + bullet = new BasicBulletType(){{ + sprite = Meld.prefix("clump"); + speed = 4; + lifetime = 75; + width = 10; + height = 12; + damage = 10; + splashDamage = 25; + splashDamageRadius = 25; + knockback = 6; + shootEffect = Fx.shootBig; + hitEffect = Fx.explosion; + impact = true; + }}; + } +} diff --git a/src/meld/fluid/Aspect.java b/src/meld/fluid/Aspect.java index e806c15..5204f70 100644 --- a/src/meld/fluid/Aspect.java +++ b/src/meld/fluid/Aspect.java @@ -1,6 +1,11 @@ package meld.fluid; import arc.graphics.Color; +import arc.struct.ObjectMap; +import arc.struct.ObjectSet; +import arc.struct.Seq; +import meld.meta.MeldStatUnit; +import meld.meta.MeldStats; import mindustry.ctype.ContentType; import mindustry.type.Liquid; @@ -9,4 +14,10 @@ public Aspect(String name) { super(name, Color.white); databaseCategory = "aspect"; } + + @Override + public void setStats() { + super.setStats(); + MeldStats.aspectStats(this, stats); + } } diff --git a/src/meld/fluid/AspectGroup.java b/src/meld/fluid/AspectGroup.java new file mode 100644 index 0000000..9a50577 --- /dev/null +++ b/src/meld/fluid/AspectGroup.java @@ -0,0 +1,72 @@ +package meld.fluid; + +import arc.Core; +import arc.struct.*; +import mindustry.Vars; +import mindustry.type.Liquid; +import mindustry.world.blocks.Attributes; + +//Just a centralised way for me to track what does what +public class AspectGroup { + + public static Seq groups = new Seq<>(); + public String name, localizedName; + + public AspectGroup(String name){ + //hgjkgvjgfh + this.name = Vars.content.transformName(name); + groups.add(this); + } + + public static void loadAll(){ + groups.each(AspectGroup::load); + } + + //Kept separate since I want to test some things ingame and be able to reset a single group's name over calling a load function on everything + public void load(){ + localizedName = Core.bundle.get("aspect-group." + name, name); + } + + public ObjectMap stats = new ObjectMap<>(); + + public static void put(Liquid liquid, AspectGroup group, AspectStats stats){ + group.stats.put(liquid, stats); + } + + public static AspectGroup + aether = new AspectGroup("aether"), + aspect = new AspectGroup("aspect"), + outlet = new AspectGroup("outlet"), + fumes = new AspectGroup("fumes"); + + public float getDensity(Liquid liquid){ + AspectStats stat = stats.get(liquid); + return stat == null ? 0 : stat.density; + } + + public float getEfficiency(Liquid liquid){ + AspectStats stat = stats.get(liquid); + return stat == null ? 0 : stat.efficiency; + } + + public static class AspectStats{ + public AspectStats(float efficiency, float density){ + this.efficiency = efficiency; + this.density = density; + } + + //No idea if ill use this or not but I really just want something to work atp + public AspectStats efficiency(float efficiency){ + this.efficiency = efficiency; + return this; + } + + public AspectStats density(float density){ + this.density = density; + return this; + } + + public float efficiency; + public float density; + } +} diff --git a/src/meld/meta/MeldStatUnit.java b/src/meld/meta/MeldStatUnit.java index baaa480..878c715 100644 --- a/src/meld/meta/MeldStatUnit.java +++ b/src/meld/meta/MeldStatUnit.java @@ -4,6 +4,8 @@ public class MeldStatUnit { public static final StatUnit - ticks = new StatUnit("ticks") + ticks = new StatUnit("ticks"), + aspectEfficiency = new StatUnit("aspect-efficiency"), + aspectDensity = new StatUnit("aspect-density"); ; } diff --git a/src/meld/meta/MeldStats.java b/src/meld/meta/MeldStats.java new file mode 100644 index 0000000..7a4afbd --- /dev/null +++ b/src/meld/meta/MeldStats.java @@ -0,0 +1,44 @@ +package meld.meta; + +import arc.Core; +import arc.util.Strings; +import meld.fluid.AspectGroup; +import mindustry.gen.Tex; +import mindustry.type.Liquid; +import mindustry.ui.Styles; +import mindustry.world.meta.Stat; +import mindustry.world.meta.Stats; + +public class MeldStats { + + public static Stat + aspectStats = new Stat("aspect-stats"); + + public static String percent(float number){ + return (int) (number * 100) + "%"; + } + + public static void aspectStats(Liquid aspect, Stats stats){ + stats.add(aspectStats, table -> { + + table.row(); + + AspectGroup.groups.each(g -> { + AspectGroup.AspectStats aspectStat = g.stats.get(aspect); + if(aspectStat == null) return; + table.table(Styles.grayPanel, pannel -> { + + pannel.left(); + + pannel.add(g.localizedName).left(); + pannel.row(); + pannel.add("[lightgray]" + Core.bundle.get("stat.aspect-efficiency") + ":[] " + percent(g.getEfficiency(aspect))); + pannel.row(); + pannel.add("[lightgray]" + Core.bundle.get("stat.aspect-density") + ":[] " + percent(g.getDensity(aspect))).left(); + pannel.row(); + }).growX().pad(5); + table.row(); + }); + }); + } +} diff --git a/src/meld/world/blocks/Bruisekit.java b/src/meld/world/blocks/Bruisekit.java index a9164b0..8349c76 100644 --- a/src/meld/world/blocks/Bruisekit.java +++ b/src/meld/world/blocks/Bruisekit.java @@ -4,6 +4,7 @@ import arc.graphics.g2d.Lines; import arc.math.Mathf; import arc.struct.Seq; +import arc.util.Log; import mindustry.entities.Units; import mindustry.gen.Building; @@ -32,9 +33,10 @@ public void updateTile() { super.updateTile(); if(timer.get(targetTimer, retargetInterval)) findTarget(); if(target != null){ - if(target.isAdded() && target.damaged()) { - float healAmount = healSpeed; + if(target.isValid() && target.damaged()) { + float healAmount = healSpeed * edelta(); if(target.wasRecentlyDamaged()) healAmount *= recentDamageMultiplier; + Log.info(healAmount); target.heal(healAmount); } diff --git a/src/meld/world/blocks/consumers/StupidConsumeAspects.java b/src/meld/world/blocks/consumers/StupidConsumeAspects.java new file mode 100644 index 0000000..551711b --- /dev/null +++ b/src/meld/world/blocks/consumers/StupidConsumeAspects.java @@ -0,0 +1,102 @@ +package meld.world.blocks.consumers; + +import arc.Core; +import arc.graphics.Color; +import arc.math.Mathf; +import arc.struct.IntFloatMap; +import arc.struct.IntMap; +import arc.struct.ObjectFloatMap; +import arc.util.Log; +import meld.fluid.AspectGroup; +import meld.world.blocks.crafting.MeldGenericCrafter; +import mindustry.Vars; +import mindustry.gen.Building; +import mindustry.type.Liquid; +import mindustry.ui.Bar; +import mindustry.world.Block; +import mindustry.world.blocks.production.GenericCrafter; +import mindustry.world.consumers.*; + +public class StupidConsumeAspects extends ConsumeLiquidFilter implements MeldGenericCrafter.BarConsumer { + + //grab it from the group + public AspectGroup group; + + + public StupidConsumeAspects(float amount, AspectGroup group){ + super(); + this.amount = amount; + this.group = group; + + filter = group.stats::containsKey; + } + + private static Liquid currentBest; + + @Override + public void apply(Block block) { + block.hasLiquids = true; + Vars.content.liquids().each(filter, (item) -> { + block.liquidFilter[item.id] = true; + }); + } + + + @Override + public void update(Building build) { + Liquid consumed = getConsumed(build); + build.liquids.remove(consumed, this.amount * build.edelta()/group.getDensity(consumed)); + } + + @Override + public Liquid getConsumed(Building build){ + float highest = 0; + currentBest = null; + for(Liquid liquid: group.stats.keys()){ + if(!Mathf.zero(build.liquids.get(liquid))) { + float value = group.getEfficiency(liquid); + + if(value > highest){ + highest = value; + currentBest = liquid; + } + } + } + return currentBest; + } + + @Override + public float efficiency(Building build) { + Liquid liq = this.getConsumed(build); + float ed = build.edelta(); + if(Mathf.zero(ed) || liq == null) return 0; + else { + + float multi = group.getEfficiency(liq); + + return Math.min(build.liquids.get(liq) / (this.amount * ed * multi), multi); + } + } + + @Override + public float efficiencyMultiplier(Building build) { + Liquid liq = this.getConsumed(build); + return liq == null ? 0.0f : group.getEfficiency(liq); + } + + @Override + public void setBars(MeldGenericCrafter crafter) { + + crafter.addBar("liquid", entity -> { + Liquid current = getConsumed(entity); + + return new Bar( + () -> current == null || entity.liquids.get(current) <= 0.001f ? Core.bundle.get("bar.liquid") : current.localizedName, + () -> current == null ? Color.clear : current.barColor(), + () -> current == null ? 0f : entity.liquids.get(current) / crafter.liquidCapacity + ); + } + ); + + } +} diff --git a/src/meld/world/blocks/crafting/ModularCrafter.java b/src/meld/world/blocks/crafting/ModularCrafter.java index cb7e48b..a56528b 100644 --- a/src/meld/world/blocks/crafting/ModularCrafter.java +++ b/src/meld/world/blocks/crafting/ModularCrafter.java @@ -1,55 +1,44 @@ package meld.world.blocks.crafting; -import arc.scene.ui.layout.*; import arc.struct.*; -import arc.util.*; +import arc.util.Log; +import arc.util.Time; import arc.util.io.Reads; import arc.util.io.Writes; -import mindustry.ctype.*; import mindustry.game.Team; import mindustry.gen.Building; -import mindustry.type.*; +import mindustry.type.Item; +import mindustry.type.ItemStack; +import mindustry.type.Liquid; import mindustry.world.Block; -import mindustry.world.blocks.heat.*; -import mindustry.world.blocks.payloads.*; -import mindustry.world.draw.*; -import mindustry.world.meta.*; +import mindustry.world.blocks.production.GenericCrafter; +import mindustry.world.meta.Attribute; + +import java.util.HashMap; + +public class ModularCrafter extends Block { -public class ModularCrafter extends PayloadBlock{ //Modules updates every update, listeners are for modules with specific events - public Seq modules = new Seq<>(); - public ObjectMap> listeners = new ObjectMap<>(); + public Seq modules = new Seq(); + public ObjectMap> listeners = new ObjectMap>(); - //Lists of stuff which this block accepts - //note: could be a single HashSet. + //list of items/liquids which this block accepts public Seq acceptedLiquids = new Seq<>(); public Seq acceptedItems = new Seq<>(); - public Seq acceptedPayloads = new Seq<>(); - //Lists of stuff which gets dumped + //List of items/liquids which get dumped public Seq dumpedLiquids = new Seq<>(); public Seq dumpedItems = new Seq<>(); - public Seq dumpedPayloads = new Seq<>(); public boolean replaceBars = true; - public int payloadCapacity = 3; - - /// Used to show heat as a percentage. Does not affect function. - public float fullHeat = 10f; - /// Default float data array + //Default float data array public IntFloatMap defaultData = new IntFloatMap(); - /// When true, shows a table with the data map in the block's display. - public boolean debugTable = true; - - public DrawBlock drawer = new DrawDefault(); - public ModularCrafter(String name) { super(name); update = true; solid = true; - sync = true; } @Override @@ -68,21 +57,9 @@ public void setBars() { } } - @Override - public void init(){ - super.init(); - modules.each(m -> m.setup(this)); - } - - @Override - public void load(){ - super.load(); - drawer.load(this); - } - public static void trigger(ModularCrafter block, ModularCrafterBuild build, Object event){ Seq events = block.listeners.get(event); - if(events != null) events.each(c -> c.on_event(build)); + if(events != null) events.each(c -> c.update(build)); } public void hook(Object event, CrafterModule module){ @@ -94,6 +71,15 @@ public void hook(Object event, CrafterModule module){ events.add(module); } + public void hookAll(Object event, CrafterModule... module){ + Seq events = listeners.get(event); + if(events == null) { + listeners.put(event, Seq.with(module)); + return; + } + events.add(module); + } + public static class BlockEvent{ public enum Defaults{ update, @@ -101,25 +87,10 @@ public enum Defaults{ } } - @Override - public boolean outputsItems(){ - return !dumpedItems.isEmpty(); - } - public static abstract class CrafterModule{ public void update(ModularCrafterBuild build){ } - - public void setup(ModularCrafter block){ - - } - - public void on_event(ModularCrafterBuild build){ - - } - - } public static abstract class CraftingModule extends CrafterModule{ @@ -195,61 +166,31 @@ public static class ModIN{ public static final float ON = 1, OFF = 0; - public class ModularCrafterBuild extends PayloadBlockBuild implements HeatConsumer, HeatBlock{ + public class ModularCrafterBuild extends Building { + public ModularCrafter modular; public IntFloatMap data = new IntFloatMap(); - public PayloadSeq payloads = new PayloadSeq(); - - public float[] sideHeat = new float[4]; - public float heat = 0f; - - public float totalProgress = 0f; - public float visualEfficiency = 0f; - @Override - public void draw(){ - drawer.draw(this); - - drawPayload(); + public void created() { + super.created(); + data.putAll(defaultData); } @Override - public void display(Table table){ - super.display(table); - - if(debugTable){ - table.row(); - table.table(d -> { - Runnable rebuild = () -> { - d.clearChildren(); - d.table(debug -> { - data.iterator().forEachRemaining(entry -> { - debug.add(entry.key + ": " + StatValues.fixValue(entry.value)).left(); - debug.row(); - }); - }).left(); - d.table().growX(); //ugh - }; - d.update(rebuild); - rebuild.run(); - }).growX(); //It doesn't actually grow for some fucking reason. See ugh above. - } + public void updateTile() { + super.updateTile(); + this.dumpOutputs(); } - @Override - public void created() { - super.created(); - data.putAll(defaultData); + public void dumpOutputs(){ + dumpedLiquids.each(this::dumpLiquid); + dumpedItems.each(this::dump); } public void setPin(int pin, float value){ data.put(pin, value); - } - - public void setPins(int[] pins, float value){ - for(int i : pins) setPin(i, value); - } + }; public float getPin(int pin){ return data.get(pin); @@ -265,11 +206,6 @@ public boolean acceptItem(Building source, Item item) { return (this.block.consumesItem(item) || acceptedItems.contains(item)) && this.items.get(item) < this.getMaximumAccepted(item); } - @Override - public boolean acceptPayload(Building source, Payload payload){ - return this.payload == null && acceptedPayloads.contains(payload.content()) && payloads.get(payload.content()) < payloadCapacity; - } - @Override public Building create(Block block, Team team) { modular = (ModularCrafter) block; @@ -285,51 +221,12 @@ public void onProximityUpdate() { @Override public void update() { super.update(); - visualEfficiency = 0f; - modules.each(c -> c.update(this)); - - totalProgress += visualEfficiency * Time.delta; - } - - //the useful one - @Override - public float heat(){ - return heat; - } - - //only used by DrawHeatOutput - @Override - public float heatFrac(){ - return heat / fullHeat; - } - - //only used by DrawHeatInput - @Override - public float[] sideHeat(){ - return sideHeat; - } - - //only used by DrawHeatInput and DrawHeatRegion - @Override - public float heatRequirement(){ - return fullHeat; - } - - @Override - public float warmup(){ - return visualEfficiency; - } - - @Override - public float totalProgress(){ - return totalProgress; } @Override public void write(Writes write) { super.write(write); - write.i(data.size); int[] keys = data.keys().toArray().toArray(); @@ -337,26 +234,16 @@ public void write(Writes write) { write.i(i); write.f(data.get(i)); } - - write.f(heat); } @Override public void read(Reads read, byte revision) { super.read(read, revision); - data = new IntFloatMap(); int len = read.i(); for (int i = 0; i < len; i++){ data.put(read.i(), read.f()); } - - if(revision >= 1) heat = read.f(); - } - - @Override - public byte version(){ - return 1; } } } diff --git a/src/meld/world/blocks/crafting/modules/AttributeModule.java b/src/meld/world/blocks/crafting/modules/AttributeModule.java new file mode 100644 index 0000000..600085e --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/AttributeModule.java @@ -0,0 +1,27 @@ +package meld.world.blocks.crafting.modules; + +import arc.util.Log; +import meld.world.blocks.crafting.ModularCrafter; +import mindustry.world.meta.Attribute; + +public class AttributeModule extends ModularCrafter.CrafterModule { + public int efficiencyPin; + + public Attribute attribute; + public float baseEfficiency = 1; + public float boostScale = 1; + public float maxBoost = 1; + public float minEfficiency = -1; + + @Override + public void update(ModularCrafter.ModularCrafterBuild build) { + float efficiency = baseEfficiency + Math.min(maxBoost, boostScale * build.block.sumAttribute(attribute, build.tileX(), build.tileY()) + attribute.env()); + + if(minEfficiency != -1 && efficiency < minEfficiency) { + build.setPin(efficiencyPin, 0); + return; + } + + build.data.put(efficiencyPin, efficiency); + } +} \ No newline at end of file diff --git a/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java new file mode 100644 index 0000000..cc4df34 --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/ConsumeLiquidModule.java @@ -0,0 +1,41 @@ +package meld.world.blocks.crafting.modules; + +import meld.world.blocks.crafting.ModularCrafter; +import mindustry.type.LiquidStack; + +public class ConsumeLiquidModule extends ModularCrafter.CrafterModule { + + public ConsumeLiquidModule(LiquidStack[] consumed, int efficiencyPin, int outputEfficiencyPin){ + this.consumed = consumed; + this.efficiencyPin = efficiencyPin; + this.outputEfficiencyPin = outputEfficiencyPin; + } + + public int efficiencyPin; + public int outputEfficiencyPin; + public LiquidStack[] consumed; + + @Override + public void update(ModularCrafter.ModularCrafterBuild build) { + float efficiency = build.getPin(efficiencyPin); + if(efficiency == 0) return; + + float maxEfficiency = 1; + + for(LiquidStack stack: consumed){ + float target = build.liquids.get(stack.liquid); + float consumeAmount = stack.amount * efficiency; + + maxEfficiency = Math.min(maxEfficiency, target/consumeAmount); + } + + efficiency *= maxEfficiency; + + for (LiquidStack stack: consumed){ + float removed = efficiency * stack.amount; + build.liquids.remove(stack.liquid, removed); + } + + build.setPin(outputEfficiencyPin, efficiency); + } +} diff --git a/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java new file mode 100644 index 0000000..590998e --- /dev/null +++ b/src/meld/world/blocks/crafting/modules/ProduceLiquidModule.java @@ -0,0 +1,22 @@ +package meld.world.blocks.crafting.modules; + +import arc.math.Mathf; +import meld.world.blocks.crafting.ModularCrafter; +import mindustry.type.LiquidStack; + +public class ProduceLiquidModule extends ModularCrafter.CrafterModule { + public LiquidStack liquid; + public int efficiencyPin; + + public ProduceLiquidModule(LiquidStack liquid, int efficiencyPin){ + this.liquid = liquid; + this.efficiencyPin = efficiencyPin; + } + + @Override + public void update(ModularCrafter.ModularCrafterBuild build) { + float efficiency = build.getPin(efficiencyPin); + float addedAmount = Math.min(liquid.amount * efficiency, Mathf.maxZero(build.block.liquidCapacity - build.liquids.get(liquid.liquid))); + build.liquids.add(liquid.liquid, addedAmount); + } +} diff --git a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java b/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java deleted file mode 100644 index dd8bc08..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/AttributeModule.java +++ /dev/null @@ -1,45 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.ModularCrafter; -import meld.world.blocks.crafting.ModularCrafter.*; -import mindustry.world.meta.Attribute; - -public class AttributeModule extends CrafterModule { - /// Pins to provide efficiency on. - public int[] outputPins; - /// Pin to store the efficiency that should be output. Don't let other modules touch it. - public int storagePin; - - public Attribute attribute; - public float baseEfficiency = 1; - public float boostScale = 1; - public float maxBoost = 1; - /// The minimum attribute efficiency needed for it to affect output. - public float minEfficiency = -1; - - public AttributeModule(int... outputPins){ - this.outputPins = outputPins; - } - - @Override - public void update(ModularCrafterBuild build){ - for(int i : outputPins) build.setPin(i, Math.max(build.getPin(storagePin), build.getPin(i))); - } - - @Override - public void on_event(ModularCrafterBuild build){ - float efficiency = baseEfficiency + Math.min(maxBoost, boostScale * build.block.sumAttribute(attribute, build.tileX(), build.tileY()) + attribute.env()); - - if(efficiency < minEfficiency) { - build.setPin(storagePin, 0); - return; - } - - build.setPin(storagePin, efficiency); - } - - @Override - public void setup(ModularCrafter block){ - block.hook(BlockEvent.Defaults.proximityUpdate, this); - } -} \ No newline at end of file diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java deleted file mode 100644 index 6ecdde7..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeHeatModule.java +++ /dev/null @@ -1,30 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.math.*; -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; - -public class ConsumeHeatModule extends ConsumeModule{ - public float baseEfficiency = 0f; - /// Base heat requirement for 100% efficiency. - public float heatRequirement = 10f; - /// After heat meets this requirement, excess heat will be scaled by this number. - public float overheatScale = 1f; - /// Maximum possible efficiency after overheat. - public float maxEfficiency = 4f; - - public ConsumeHeatModule(int... outputPins){ - super(outputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - build.heat = build.calculateHeat(build.sideHeat); - - float over = Math.max(build.heat - heatRequirement, 0f); - float scaled = Math.min(Mathf.clamp(build.heat / heatRequirement) + over / heatRequirement * overheatScale, maxEfficiency); - - for(int i : outputPins) build.setPin(i, Math.max(baseEfficiency + scaled, build.getPin(i))); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java deleted file mode 100644 index 3690bd8..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeHighestModule.java +++ /dev/null @@ -1,35 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; - -/// Will only consume from the pin with the highest efficiency. -public class ConsumeHighestModule extends ConsumeModule{ - public int[] inputPins; - - public ConsumeHighestModule(int... outputPins){ - super(outputPins); - } - - public void update(ModularCrafterBuild build){ - //Get the highest input pin - float input = 0f; - int inputPin = 0; - for(int i : inputPins){ - if(build.getPin(i) > input){ - input = build.getPin(i); - inputPin = i; - } - } - - //Find the least consumed output, it signals inactivity - float current = getCurrent(build); - - if(input > current){ - //Just swap pin contents - build.setPin(inputPin, current); - build.setPins(outputPins, input); - } - - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java deleted file mode 100644 index bd8713a..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeItemModule.java +++ /dev/null @@ -1,32 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.type.*; - -public class ConsumeItemModule extends ConsumeDiscreteModule{ - public ItemStack[] items; - - public ConsumeItemModule(int... outputPins){ - super(outputPins); - } - - @Override - public boolean canConsume(ModularCrafterBuild build){ - return build.items.has(items); - } - - @Override - public void consume(ModularCrafterBuild build){ - build.items.remove(items); - } - - @Override - public void setup(ModularCrafter block){ - block.hasItems = true; - for(ItemStack stack : items){ - block.acceptedItems.add(stack.item); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java deleted file mode 100644 index c799091..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumeLiquidModule.java +++ /dev/null @@ -1,52 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.util.*; -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.type.*; - -public class ConsumeLiquidModule extends ConsumeModule{ - public LiquidStack[] liquids; - - public float baseEfficiency = 0; - public float efficiencyIncrease = 1; - - public ConsumeLiquidModule(int... outputPins){ - super(outputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - //Find the least consumed output, it signals inactivity - float current = getCurrent(build); - //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - float output = Math.max(baseEfficiency, current); - - //if efficiency has been used - if(consumed > 0f){ - float min = 10000f; - for(LiquidStack stack : liquids){ - min = Math.min(build.liquids.get(stack.liquid) / (stack.amount * consumed * Time.delta), min); - } - - if(min > 0.00001f){ - for(LiquidStack stack : liquids){ - build.liquids.remove(stack.liquid, stack.amount * min); - } - output = baseEfficiency + efficiencyIncrease * min; - } - } - - build.setPins(outputPins, output); - } - - @Override - public void setup(ModularCrafter block){ - block.hasLiquids = true; - for(LiquidStack stack : liquids){ - block.acceptedLiquids.add(stack.liquid); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java deleted file mode 100644 index 37ecab7..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ConsumePayloadModule.java +++ /dev/null @@ -1,55 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.type.*; -import mindustry.world.blocks.payloads.*; - -import java.util.*; - -public class ConsumePayloadModule extends ConsumeDiscreteModule{ - public PayloadStack[] payloads; - - public ConsumePayloadModule(int... outputPins){ - super(outputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - super.update(build); - - if(build.payload != null && wants(build.payload) && build.moveInPayload()){ - build.payloads.add(build.payload.content()); - build.payload = null; - } - } - - @Override - public boolean canConsume(ModularCrafterBuild build){ - return Arrays.stream(payloads).allMatch(stack -> build.payloads.contains(stack)); - } - - @Override - public void consume(ModularCrafterBuild build){ - for(PayloadStack stack : payloads){ - build.payloads.remove(stack.item, stack.amount); - } - } - - public boolean wants(Payload pay){ - for(PayloadStack stack : payloads){ - if(stack.item == pay.content()) return true; - } - return false; - } - - @Override - public void setup(ModularCrafter block){ - block.acceptsPayload = true; - block.acceptsUnitPayloads = true; - for(PayloadStack stack : payloads){ - block.acceptedPayloads.add(stack.item); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java b/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java deleted file mode 100644 index b73b9f2..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/EfficiencySourceModule.java +++ /dev/null @@ -1,21 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.ModularCrafter.*; - -/// When you have multiple modules draw from the same pin, the first module in -/// the update order to craft something will block the rest from working. -/// This module is made for when you need this behavior, but without a shared input. -public class EfficiencySourceModule extends CrafterModule{ - /// Pins to provide efficiency on. - public int[] outputPins; - public float baseEfficiency = 1f; - - public EfficiencySourceModule(int... outputPins){ - this.outputPins = outputPins; - } - - @Override - public void update(ModularCrafterBuild build){ - for(int o : outputPins) build.setPin(o, baseEfficiency); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java b/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java deleted file mode 100644 index 1ca1e57..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/EfficiencyVoidModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.util.*; -import meld.world.blocks.crafting.ModularCrafter.*; - -/// Can be used to make blocks consume, regardless of whether they're doing something. -/// To do this, you can place it after a producer module, and make it consume from its input pin. -public class EfficiencyVoidModule extends CrafterModule{ - /// Pins to consume efficiency of. - public int[] inputPins; - /// The amount of efficiency consumed. - public float amount = Float.POSITIVE_INFINITY; - - public EfficiencyVoidModule(int... inputPins){ - this.inputPins = inputPins; - } - - @Override - public void update(ModularCrafterBuild build){ - for(int i : inputPins) build.setPin(i, Math.max(0f, build.getPin(i) - amount * build.timeScale() * Time.delta)); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java deleted file mode 100644 index 936b884..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceHeatModule.java +++ /dev/null @@ -1,25 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.math.*; -import arc.util.*; -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; - -public class ProduceHeatModule extends ProduceModule{ - public float heatOutput = 10f; - public float warmupRate = 0.15f; - - public ProduceHeatModule(int... inputPins){ - super(inputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - float input = takeEfficiency(build); - - build.heat = Mathf.approachDelta(build.heat, heatOutput * input, warmupRate * Time.delta); - - build.visualEfficiency = Math.max(input, build.visualEfficiency); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java deleted file mode 100644 index c0d6b2e..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceItemModule.java +++ /dev/null @@ -1,44 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.type.*; -import java.util.*; - -public class ProduceItemModule extends ProduceDiscreteModule{ - public ItemStack[] items; - - public ProduceItemModule(int... inputPins){ - super(inputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - super.update(build); - - for(ItemStack stack : items){ - build.dump(stack.item); - } - } - - @Override - public boolean canOutput(ModularCrafterBuild build){ - return Arrays.stream(items).allMatch(stack -> build.items.get(stack.item) + stack.amount <= build.block.itemCapacity); - } - - @Override - public void output(ModularCrafterBuild build){ - for(ItemStack stack : items){ - build.items.add(stack.item, stack.amount); - } - } - - @Override - public void setup(ModularCrafter block){ - block.hasItems = true; - for(ItemStack stack : items){ - block.dumpedItems.add(stack.item); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java b/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java deleted file mode 100644 index bd82e2e..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ProduceLiquidModule.java +++ /dev/null @@ -1,53 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.util.*; -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.type.*; - -public class ProduceLiquidModule extends ProduceModule{ - public LiquidStack[] liquids; - - public ProduceLiquidModule(int... inputPins){ - super(inputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - //dump/check full - boolean full = false; - for(LiquidStack stack : liquids){ - build.dumpLiquid(stack.liquid); - - if(build.liquids.get(stack.liquid) >= build.block.liquidCapacity - 0.001f){ - full = true; - } - } - if(full) return; - - //Sum efficiencies and timescale up - float input = takeEfficiency(build); - - //continuously output based on efficiency - for(LiquidStack stack : liquids){ - build.handleLiquid(build, stack.liquid, - Math.min( - stack.amount * input * Time.delta, - build.block.liquidCapacity - build.liquids.get(stack.liquid) - ) - ); - } - - build.visualEfficiency = Math.max(input, build.visualEfficiency); - } - - @Override - public void setup(ModularCrafter block){ - block.hasLiquids = true; - block.outputsLiquid = true; - for(LiquidStack stack : liquids){ - block.dumpedLiquids.add(stack.liquid); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java b/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java deleted file mode 100644 index 6ce1c90..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/ProducePayloadModule.java +++ /dev/null @@ -1,81 +0,0 @@ -package meld.world.blocks.crafting.modules.rework; - -import arc.*; -import meld.world.blocks.crafting.*; -import meld.world.blocks.crafting.ModularCrafter.*; -import meld.world.blocks.crafting.modules.rework.base.*; -import mindustry.game.EventType.*; -import mindustry.gen.*; -import mindustry.type.*; -import mindustry.world.*; -import mindustry.world.blocks.payloads.*; - -import java.util.*; - -public class ProducePayloadModule extends ProduceDiscreteModule{ - public PayloadStack[] payloads; - - public ProducePayloadModule(int... inputPins){ - super(inputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - super.update(build); - - //dump payloads - if(build.payload == null && build.payloads.any()){ - for(PayloadStack stack : payloads){ - if(build.payloads.contains(stack.item)){ - build.payloads.remove(stack.item); - - if(stack.item instanceof UnitType type){ - build.payload = new UnitPayload(type.create(build.team)); - Unit p = ((UnitPayload)build.payload).unit; - /*if(commandPos != null && p.isCommandable()){ - p.command().commandPosition(commandPos); - }*/ - Events.fire(new UnitCreateEvent(p, build)); - }else if(stack.item instanceof Block block){ - build.payload = new BuildPayload(block, build.team); - } - build.payVector.setZero(); - build.payRotation = build.rotdeg(); - break; - } - } - } - - if(build.payload != null && shouldDump(build.payload)){ - build.moveOutPayload(); - } - } - - @Override - public boolean canOutput(ModularCrafterBuild build){ - return Arrays.stream(payloads).allMatch(stack -> build.payloads.get(stack.item) + stack.amount <= build.modular.payloadCapacity); - } - - @Override - public void output(ModularCrafterBuild build){ - for(PayloadStack stack : payloads){ - build.payloads.add(stack.item, stack.amount); - } - } - - public boolean shouldDump(Payload pay){ - for(PayloadStack stack : payloads){ - if(stack.item == pay.content()) return true; - } - return false; - } - - @Override - public void setup(ModularCrafter block){ - block.rotate = true; - block.outputsPayload = true; - for(PayloadStack stack : payloads){ - block.dumpedPayloads.add(stack.item); - } - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java deleted file mode 100644 index f78f10e..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/base/ConsumeDiscreteModule.java +++ /dev/null @@ -1,45 +0,0 @@ -package meld.world.blocks.crafting.modules.rework.base; - -import arc.util.*; -import meld.world.blocks.crafting.ModularCrafter.*; - -public abstract class ConsumeDiscreteModule extends ConsumeModule{ - /// Pin to store consumption progress. - public int progressPin; - public float time = 60f; - - public float baseEfficiency = 0f; - public float efficiencyIncrease = 1f; - - public ConsumeDiscreteModule(int... outputPins){ - super(outputPins); - } - - public abstract boolean canConsume(ModularCrafterBuild build); - - public abstract void consume(ModularCrafterBuild build); - - @Override - public void update(ModularCrafterBuild build){ - float current = getCurrent(build); - //Get the amount of efficiency that was eaten - float consumed = (efficiencyIncrease - current - baseEfficiency) / (efficiencyIncrease - baseEfficiency); - float output = Math.max(baseEfficiency, current); - - //if efficiency has been used - if(consumed > 0f && canConsume(build)){ - float progress = build.getPin(progressPin); - progress += consumed * Time.delta; - output = baseEfficiency + efficiencyIncrease; - - //consumption - while(progress > time && canConsume(build)){ - consume(build); - progress -= time; - } - build.setPin(progressPin, progress); - } - - build.setPins(outputPins, output); - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java deleted file mode 100644 index 07f00b8..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/base/ConsumeModule.java +++ /dev/null @@ -1,33 +0,0 @@ -package meld.world.blocks.crafting.modules.rework.base; - -import meld.world.blocks.crafting.ModularCrafter.*; - -public abstract class ConsumeModule extends CrafterModule{ - /// Pins to provide efficiency on. - public int[] outputPins; - /// Makes the module ignore other pins that are full until no more can be output. - public boolean dumpExtra = false; - - public ConsumeModule(int... outputPins){ - this.outputPins = outputPins; - } - - public float getCurrent(ModularCrafterBuild build){ - if(!dumpExtra){ - //Find the least consumed output, it signals inactivity - float max = Float.NEGATIVE_INFINITY; - for(int i : outputPins){ - max = Math.max(max, build.getPin(i)); - } - return max; - }else{ - //If we don't care about a few full inputs, pick the minimum instead - float min = Float.POSITIVE_INFINITY; - for(int i : outputPins){ - min = Math.min(min, build.getPin(i)); - } - return min; - } - - } -} diff --git a/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java deleted file mode 100644 index d0b2213..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/base/ProduceDiscreteModule.java +++ /dev/null @@ -1,38 +0,0 @@ -package meld.world.blocks.crafting.modules.rework.base; - -import arc.util.*; -import meld.world.blocks.crafting.ModularCrafter.*; - -public abstract class ProduceDiscreteModule extends ProduceModule{ - - /// Pin used to store crafting progress. - public int progressPin; - public float time = 60f; - - public ProduceDiscreteModule(int... inputPins){ - super(inputPins); - } - - @Override - public void update(ModularCrafterBuild build){ - if(!canOutput(build)) return; - - //Sum efficiencies and timescale up - float input = takeEfficiency(build); - - float progress = build.getPin(progressPin); - progress += input * Time.delta; - - while(progress > time && canOutput(build)){ - output(build); - progress -= time; - } - build.setPin(progressPin, progress); - - build.visualEfficiency = Math.max(input, build.visualEfficiency); - } - - public abstract boolean canOutput(ModularCrafterBuild build); - - public abstract void output(ModularCrafterBuild build); -} diff --git a/src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java b/src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java deleted file mode 100644 index 428a3d7..0000000 --- a/src/meld/world/blocks/crafting/modules/rework/base/ProduceModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package meld.world.blocks.crafting.modules.rework.base; - -import meld.world.blocks.crafting.ModularCrafter.*; - -public abstract class ProduceModule extends CrafterModule{ - /// Pins to consume efficiency from. - public int[] inputPins; - - public ProduceModule(int... inputPins){ - this.inputPins = inputPins; - } - - public float takeEfficiency(ModularCrafterBuild build){ - //Sum efficiencies and timescale up - float input = build.timeScale(); - for(int i : inputPins) input *= build.getPin(i); - - //After summing, subtract the final efficiency - for(int i : inputPins) build.setPin(i, build.getPin(i) - input); - - return input; - } -} diff --git a/src/meld/world/blocks/fluid/AspectPipe.java b/src/meld/world/blocks/fluid/AspectPipe.java index e9adab8..d4eb5b4 100644 --- a/src/meld/world/blocks/fluid/AspectPipe.java +++ b/src/meld/world/blocks/fluid/AspectPipe.java @@ -19,7 +19,7 @@ import static meld.content.MeldLiquids.*; -public class AspectPipe extends ArmoredConduit { +public class AspectPipe extends Conduit { Seq liquidSeq = new Seq<>(); static final float rotatePad = 6, hpad = rotatePad / 2f / 4f; From 3d9aaa2b332df29753447b01cd63578f49b0561c Mon Sep 17 00:00:00 2001 From: Sh1p*nfire Date: Wed, 15 Apr 2026 00:37:16 +1000 Subject: [PATCH 24/24] Yeah so I uh... bulbhead can boost now -Bruisekit rework Heals 1x1 blocks at half speed, only consumes when healing/expanding its field -Aether Clusters no longer FILL TO INFINITY AND BEYOND -Rally was a bit much as it was >-< -Shark reload time doubled, burstt goes from 3 to 5 missiles -Bulbhead can... boost??? (Only over tiles which aren't solid environmental walls, but still... hehehe~) -All field pulsing blocks made solid -Core: Raft now only costs Debris (900 of it), and is solid --- assets/bundles/bundle.properties | 4 ++-- assets/bundles/bundle.shitpost | 5 +++- mod.hjson | 2 +- src/meld/content/MeldBlocks.java | 4 ++-- src/meld/content/MeldStatusEffects.java | 2 +- src/meld/content/MeldUnits.java | 21 ++++++++++++---- src/meld/entities/unit/BulbheadEntity.java | 24 +++++++++++++++---- .../unit/abilities/SolidSpeedAbility.java | 3 ++- src/meld/world/blocks/AetherCluster.java | 3 ++- src/meld/world/blocks/Bruisekit.java | 13 +++++++--- src/meld/world/blocks/FieldPulsar.java | 2 ++ src/meld/world/blocks/MovementAnchor.java | 3 ++- src/meld/world/blocks/fluid/AspectPipe.java | 2 +- 13 files changed, 65 insertions(+), 23 deletions(-) diff --git a/assets/bundles/bundle.properties b/assets/bundles/bundle.properties index 8c8e0f2..65671d6 100644 --- a/assets/bundles/bundle.properties +++ b/assets/bundles/bundle.properties @@ -101,7 +101,7 @@ block.meld-gauze.name = Gauze block.meld-gauze.description = Weaves a [accent]Resonarum[] thread using [#5b4739]Fumes[], automatically applying it to connected blocks. block.meld-bruisekit.name = Bruisekit -block.meld-bruisekit.description = Regenerates target block's health.\n\nPrioritises large, functional blocks and can't target 1x1s. +block.meld-bruisekit.description = Regenerates target block's health.\n\nPrioritises large, functional blocks and works at half speed on 1x1s. block.meld-mercurial-diffuser.name = Mercurial Diffuser block.meld-mercurial-diffuser.description = Puffs clouds which intoxicate [#ea9ad6]Meld[] units. Applies a coating of [#89828c]Mercury[] to allied units. @@ -809,7 +809,7 @@ unit.meld-observer. //Strata units unit.meld-bulbhead.name = Bulbhead -unit.meld-bulbhead.description = Commander of the Raft core. Shoots sonar pulses and [accent]Rallies[] nearby allies. Repairs allied structures. Can reconstitute itself using appropriate liquids, or with enough time. +unit.meld-bulbhead.description = Commander of the Raft core. Shoots sonar pulses and [accent]Rallies[] nearby allies. Repairs allied structures. Can reconstitute itself using appropriate liquids, or with enough time.\n\nHovers over buildings and solid floors at a reduced speed. unit.meld-bulbhead.details = The classic. unit.meld-bulbhead-sonar.name = Bulbhead Overseer diff --git a/assets/bundles/bundle.shitpost b/assets/bundles/bundle.shitpost index 4806279..a674a1b 100644 --- a/assets/bundles/bundle.shitpost +++ b/assets/bundles/bundle.shitpost @@ -5,4 +5,7 @@ item.meld-crucible-plating.name = Boiled Plates block.meld-sonar-spire.name = Slay the Sonar Spire block.meld-manual-valve.name = Gabe Nuel's Wife -block.meld-pressure-vent.name = Amogus Escape route \ No newline at end of file +block.meld-pressure-vent.name = Amogus Escape route + +unit.meld-bulbhead.name = Bulbster +unit.meld-bulbhead.description = Meldieps the allied blocks as it Meldarras.io the enemies \ No newline at end of file diff --git a/mod.hjson b/mod.hjson index f78e66c..6ca2d3f 100644 --- a/mod.hjson +++ b/mod.hjson @@ -14,7 +14,7 @@ main: "meld.Meld" description: "Meld Java.\n\n[[]accent]Only has custom game content! (play the info map to understand)[[]]\n\nIncompatable with hjson meld" #the mod version -version: gameplay expirimental v1 +version: gameplay expirimental v3 #the minimum game build required to run this mod minGameVersion: 154 diff --git a/src/meld/content/MeldBlocks.java b/src/meld/content/MeldBlocks.java index 433a298..1bd4e98 100644 --- a/src/meld/content/MeldBlocks.java +++ b/src/meld/content/MeldBlocks.java @@ -1012,7 +1012,7 @@ public void update(Bullet b){ unitCapModifier = 6; unitType = MeldUnits.bulbhead; - solid = false; + solid = true; }}; aetherAccumulator = new AttributeCrafter("aether-accumulator"){{ @@ -1548,7 +1548,7 @@ public void update(Bullet b){ speed = 1f; - solid = false; + solid = true; }}; chuteJunction = new DuctJunction("chute-crossing"){{ diff --git a/src/meld/content/MeldStatusEffects.java b/src/meld/content/MeldStatusEffects.java index cb32e74..2c3d7fa 100644 --- a/src/meld/content/MeldStatusEffects.java +++ b/src/meld/content/MeldStatusEffects.java @@ -55,7 +55,7 @@ public void update(Unit unit, StatusEntry entry) { } { speedMultiplier = 1.25f; - reloadMultiplier = 3; + reloadMultiplier = 2; }}; rush = new StatusEffect("rush"){ diff --git a/src/meld/content/MeldUnits.java b/src/meld/content/MeldUnits.java index 1947326..0fed4bf 100644 --- a/src/meld/content/MeldUnits.java +++ b/src/meld/content/MeldUnits.java @@ -291,6 +291,11 @@ public static void load(){ mineRange = IR; fogRadius = IR/ Vars.tilesize; + canBoost = true; + boostMultiplier = 0.5f; + shadowElevation = 0; + shadowElevationScl = 0.5f; + weapons.add( new RepairBeamWeapon("meld-bulbhead-healer"){{ mirror = false; @@ -447,6 +452,12 @@ public static void load(){ MeldStatusEffects.rally, MeldStatusEffects.drenched ); + + float ex = 34/4f, ey = 35/4f; + engines.add( + new MeldUnitType.ActivationEngine(ex, -ey, 3.2f, 315, 0.5f, 1, 0.5f, 1.5f), + new MeldUnitType.ActivationEngine(-ex, -ey, 3.2f, 225, 0.5f, 1, 0.5f, 1.5f) + ); }}; shark = new UnitType("shark"){{ @@ -479,12 +490,12 @@ public static void load(){ rotate = true; x = 0; y = -2; - reload = 90; + reload = 180; rotateSpeed = 3.5f; recoil = 1.25f; inaccuracy = 15; velocityRnd = 0.05f; - shoot.shots = 3; + shoot.shots = 5; shoot.shotDelay = 5; bullet = new MissileBulletType(){{ @@ -560,7 +571,7 @@ public static void load(){ shadowElevation = 0.1f; shadowElevationTo = 0.2f; layerOffset = -0.001f; - reload = 15; + reload = 30; parts.addAll( new AdjustableHoverPart(){{ x = 4; @@ -602,8 +613,8 @@ public static void load(){ //actually im gona make sharks faster on fluids cause like... fluids abilities.addAll( new SolidSpeedAbility(){{ - warmupSpeed = 2/60f; - speedMultiplier = 1/1.5f; + warmupSpeed = 0.05f; + speedMultiplier = 0.75f; }}, new SlipstreamHullAbility() ); diff --git a/src/meld/entities/unit/BulbheadEntity.java b/src/meld/entities/unit/BulbheadEntity.java index 6481f66..7d6fcee 100644 --- a/src/meld/entities/unit/BulbheadEntity.java +++ b/src/meld/entities/unit/BulbheadEntity.java @@ -9,6 +9,8 @@ import arc.math.Angles; import arc.math.Interp; import arc.math.Mathf; +import arc.math.geom.Geometry; +import arc.math.geom.Point2; import arc.math.geom.Position; import arc.util.*; import meld.content.MeldUnits; @@ -24,10 +26,7 @@ import mindustry.game.EventType; import mindustry.game.Team; import mindustry.game.Teams; -import mindustry.gen.Building; -import mindustry.gen.Call; -import mindustry.gen.Sounds; -import mindustry.gen.UnitWaterMove; +import mindustry.gen.*; import mindustry.graphics.Drawf; import mindustry.graphics.Layer; import mindustry.graphics.Pal; @@ -45,6 +44,19 @@ public class BulbheadEntity extends UnitWaterMove { //Actually counts upwards... kinda counter intuitively named public float corelinkGrace = 0; + + @Override + public EntityCollisions.SolidPred solidity() { + return (x, y) -> { + Tile tile = Vars.world.tile(x, y); + if(tile == null) return true; + + if(tile.synthetic() || !tile.solid()) return false; + + return !(tile.floor().isLiquid); + }; + } + //skip all the logic involved and just give rotation @Override public float prefRotation() { @@ -246,6 +258,8 @@ public void updateBuildLogic() { public void draw(){ super.draw(); + float actualElevation = elevation; + elevation /= 2; //Draww.drawChain(chain, x, y, Core.input.mouseWorldX(), Core.input.mouseWorldY(), 0); @@ -268,6 +282,8 @@ public void draw(){ Draww.drawChainAlpha(MeldRegions.chain, nearbyRaft.x, nearbyRaft.y, x, y, 1 - 0.5f * Interp.pow5.apply(1-stretch), 0.5f + 0.5f * stretch, 0, f -> Interp.slope.apply((f + dist)/(type.fogRadius * Vars.tilesize) % 1 + Time.time/60)); Draw.blend(Blending.normal); } + + elevation = actualElevation; } //Attempt to draw the build beam from the unit itself. If that fails, draw it to the core instead, then draw the build beam from the core diff --git a/src/meld/entities/unit/abilities/SolidSpeedAbility.java b/src/meld/entities/unit/abilities/SolidSpeedAbility.java index f11e4f6..1665b6a 100644 --- a/src/meld/entities/unit/abilities/SolidSpeedAbility.java +++ b/src/meld/entities/unit/abilities/SolidSpeedAbility.java @@ -20,8 +20,9 @@ public void update(Unit unit) { float target = 1; if(unit.tileOn() != null && unit.tileOn().floor().isLiquid) { target = 0; - unit.speedMultiplier *= Mathf.lerp(1, speedMultiplier, warmup); } warmup = Mathf.lerpDelta(warmup, target, warmupSpeed); + + unit.speedMultiplier *= Mathf.lerp(1, speedMultiplier, warmup); } } diff --git a/src/meld/world/blocks/AetherCluster.java b/src/meld/world/blocks/AetherCluster.java index 0c5a0da..308f62f 100644 --- a/src/meld/world/blocks/AetherCluster.java +++ b/src/meld/world/blocks/AetherCluster.java @@ -1,6 +1,7 @@ package meld.world.blocks; import arc.graphics.g2d.TextureRegion; +import arc.math.Mathf; import arc.math.geom.Point2; import arc.scene.ui.layout.Table; import arc.util.Time; @@ -32,7 +33,7 @@ public class AetherClusterBuild extends Building{ @Override public void updateTile() { super.updateTile(); - liquids.add(outputLiquid.liquid, outputLiquid.amount * Time.delta); + liquids.set(outputLiquid.liquid, Mathf.clamp(liquids.get(outputLiquid.liquid) + outputLiquid.amount * Time.delta, 0, liquidCapacity)); if(timer.get(liquidTimer, 5)){ updateProximity(); dumpLiquid(outputLiquid.liquid); diff --git a/src/meld/world/blocks/Bruisekit.java b/src/meld/world/blocks/Bruisekit.java index 8349c76..ce14483 100644 --- a/src/meld/world/blocks/Bruisekit.java +++ b/src/meld/world/blocks/Bruisekit.java @@ -17,7 +17,7 @@ public class Bruisekit extends FieldPulsar{ public float healSpeed = 2; public float retargetInterval = 5; - public float recentDamageMultiplier = 0.1f; + public float recentDamageMultiplier = 0.1f, smallMultiplier = 0.5f; public Bruisekit(String name) { super(name); @@ -27,6 +27,10 @@ public Bruisekit(String name) { public class BruisekitBuild extends PulsarBuild{ public Building target; + @Override + public boolean shouldConsume() { + return super.shouldConsume() && target != null && lastRadius < range; + } @Override public void updateTile() { @@ -36,6 +40,8 @@ public void updateTile() { if(target.isValid() && target.damaged()) { float healAmount = healSpeed * edelta(); if(target.wasRecentlyDamaged()) healAmount *= recentDamageMultiplier; + if(target.block.size == 0) healAmount *= smallMultiplier; + Log.info(healAmount); target.heal(healAmount); @@ -44,13 +50,14 @@ public void updateTile() { } public void findTarget(){ + target = null; tmpDamaged.clear(); Units.nearbyBuildings(x, y, smoothRadius, b -> { - if(b.team == team() && b.damaged() && b.block.size > 1) tmpDamaged.add(b); + if(b.team == team() && b.damaged()) tmpDamaged.add(b); }); - tmpDamaged.sort(b -> b.block.size + (1 - b.healthf()) + Mathf.log(10, b.block.health)/10); + tmpDamaged.sort(b -> b.block.size * 10 + (1 - b.healthf()) + Mathf.log(10, b.block.health)/10); if(!tmpDamaged.isEmpty()) target = tmpDamaged.pop(); } diff --git a/src/meld/world/blocks/FieldPulsar.java b/src/meld/world/blocks/FieldPulsar.java index 53936a9..aa2da94 100644 --- a/src/meld/world/blocks/FieldPulsar.java +++ b/src/meld/world/blocks/FieldPulsar.java @@ -51,6 +51,8 @@ public void load() { public FieldPulsar(String name) { super(name); update = true; + solid = true; + warmupSpeed = 1/60f; pulseDuration = 240; diff --git a/src/meld/world/blocks/MovementAnchor.java b/src/meld/world/blocks/MovementAnchor.java index f4daac4..ff429d9 100644 --- a/src/meld/world/blocks/MovementAnchor.java +++ b/src/meld/world/blocks/MovementAnchor.java @@ -32,12 +32,13 @@ public class MovementAnchor extends Block { public MovementAnchor(String name) { super(name); + solid = true; update = true; reload = 90; status = MeldStatusEffects.anchored; statusDuration = 240; targets = 2; - this.timerPulse = this.timers++; + timerPulse = timers++; } diff --git a/src/meld/world/blocks/fluid/AspectPipe.java b/src/meld/world/blocks/fluid/AspectPipe.java index d4eb5b4..95d2502 100644 --- a/src/meld/world/blocks/fluid/AspectPipe.java +++ b/src/meld/world/blocks/fluid/AspectPipe.java @@ -31,7 +31,7 @@ public AspectPipe(String name) { @Override public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock) { - return super.blends(tile, rotation, otherx, othery, otherrot, otherblock) || (otherblock.hasLiquids && !(otherblock instanceof Conduit || otherblock instanceof LiquidRouter) && outletMapping.keys().toSeq().find(l -> otherblock.consumesLiquid(l)) != null); + return super.blends(tile, rotation, otherx, othery, otherrot, otherblock) || (otherblock.hasLiquids && !(otherblock instanceof Conduit || otherblock instanceof LiquidRouter) && outletMapping.values().toSeq().find(l -> otherblock.consumesLiquid(l)) != null); } public class AspectPipeBuild extends ConduitBuild{