it is done

This commit is contained in:
Anuken
2019-12-25 01:39:38 -05:00
parent 5b21873f3c
commit 514d4817c8
488 changed files with 4572 additions and 4574 deletions

View File

@@ -0,0 +1,15 @@
package mindustry.world.blocks;
import mindustry.world.meta.Attribute;
public class Attributes{
private float[] array = new float[Attribute.values().length];
public float get(Attribute attr){
return array[attr.ordinal()];
}
public void set(Attribute attr, float value){
array[attr.ordinal()] = value;
}
}

View File

@@ -0,0 +1,102 @@
package mindustry.world.blocks;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.world.*;
import java.util.*;
public interface Autotiler{
class AutotilerHolder{
static final int[] blendresult = new int[3];
static final BuildRequest[] directionals = new BuildRequest[4];
}
default @Nullable int[] getTiling(BuildRequest req, Eachable<BuildRequest> list){
if(req.tile() == null) return null;
BuildRequest[] directionals = AutotilerHolder.directionals;
Arrays.fill(directionals, null);
list.each(other -> {
if(other.breaking || other == req) return;
int i = 0;
for(Point2 point : Geometry.d4){
int x = req.x + point.x, y = req.y + point.y;
if(x >= other.x -(other.block.size - 1) / 2 && x <= other.x + (other.block.size / 2) && y >= other.y -(other.block.size - 1) / 2 && y <= other.y + (other.block.size / 2)){
directionals[i] = other;
}
i++;
}
});
return buildBlending(req.tile(), req.rotation, directionals, req.worldContext);
}
default int[] buildBlending(Tile tile, int rotation, BuildRequest[] directional, boolean world){
int[] blendresult = AutotilerHolder.blendresult;
blendresult[0] = 0;
blendresult[1] = blendresult[2] = 1;
int num =
(blends(tile, rotation, directional, 2, world) && blends(tile, rotation, directional, 1, world) && blends(tile, rotation, directional, 3, world)) ? 0 :
(blends(tile, rotation, directional, 1, world) && blends(tile, rotation, directional, 3, world)) ? 1 :
(blends(tile, rotation, directional, 1, world) && blends(tile, rotation, directional, 2, world)) ? 2 :
(blends(tile, rotation, directional, 3, world) && blends(tile, rotation, directional, 2, world)) ? 3 :
blends(tile, rotation, directional, 1, world) ? 4 :
blends(tile, rotation, directional, 3, world) ? 5 :
-1;
transformCase(num, blendresult);
return blendresult;
}
default void transformCase(int num, int[] bits){
if(num == 0){
bits[0] = 3;
}else if(num == 1){
bits[0] = 4;
}else if(num == 2){
bits[0] = 2;
}else if(num == 3){
bits[0] = 2;
bits[2] = -1;
}else if(num == 4){
bits[0] = 1;
bits[2] = -1;
}else if(num == 5){
bits[0] = 1;
}
}
default boolean blends(Tile tile, int rotation, @Nullable BuildRequest[] directional, int direction, boolean checkWorld){
int realDir = Mathf.mod(rotation - direction, 4);
if(directional != null && directional[realDir] != null){
BuildRequest req = directional[realDir];
if(blends(tile, rotation, req.x, req.y, req.rotation, req.block)){
return true;
}
}
return checkWorld && blends(tile, rotation, direction);
}
default boolean blends(Tile tile, int rotation, int direction){
Tile other = tile.getNearby(Mathf.mod(rotation - direction, 4));
if(other != null) other = other.link();
return other != null && blends(tile, rotation, other.x, other.y, other.rotation(), other.block());
}
default boolean blendsArmored(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return (Point2.equals(tile.x + Geometry.d4(rotation).x, tile.y + Geometry.d4(rotation).y, otherx, othery)
|| ((!otherblock.rotate && Edges.getFacingEdge(otherblock, otherx, othery, tile) != null &&
Edges.getFacingEdge(otherblock, otherx, othery, tile).relativeTo(tile) == rotation) || (otherblock.rotate && Point2.equals(otherx + Geometry.d4(otherrot).x, othery + Geometry.d4(otherrot).y, tile.x, tile.y))));
}
default boolean lookingAt(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return (Point2.equals(tile.x + Geometry.d4(rotation).x, tile.y + Geometry.d4(rotation).y, otherx, othery)
|| (!otherblock.rotate || Point2.equals(otherx + Geometry.d4(otherrot).x, othery + Geometry.d4(otherrot).y, tile.x, tile.y)));
}
boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock);
}

View File

@@ -0,0 +1,83 @@
package mindustry.world.blocks;
import mindustry.type.Item;
import mindustry.type.Liquid;
import mindustry.world.Block;
import mindustry.world.Tile;
/**
* Used for multiblocks. Each block that is not the center of the multiblock is a part.
* Think of these as delegates to the actual block; all events are passed to the target block.
* They are made to share all properties from the linked tile/block.
*/
public class BlockPart extends Block{
public final static int maxSize = 9;
private final static BlockPart[][] parts = new BlockPart[maxSize][maxSize];
private final int dx, dy;
public BlockPart(int dx, int dy){
super("part_" + dx + "_" + dy);
this.dx = dx;
this.dy = dy;
solid = false;
hasPower = hasItems = hasLiquids = true;
parts[dx + maxSize/2][dy + maxSize/2] = this;
}
public static BlockPart get(int dx, int dy){
if(dx == -maxSize/2 && dy == -maxSize/2) throw new IllegalArgumentException("Why are you getting a [0,0] blockpart? Stop it.");
return parts[dx + maxSize/2][dy + maxSize/2];
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return tile.link().block().acceptItem(item, tile.link(), source);
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
tile.link().block().handleItem(item, tile.link(), source);
}
@Override
public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){
Block block = tile.link().block();
block.handleLiquid(tile.link(), source, liquid, amount);
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
Block block = tile.link().block();
return block.hasLiquids && block.acceptLiquid(tile.link(), source, liquid, amount);
}
@Override
public Tile linked(Tile tile){
Tile out = tile.getNearby(-dx, -dy);
return out == null ? tile : out;
}
@Override
public void drawTeam(Tile tile){}
@Override
public void draw(Tile tile){}
@Override
public boolean synthetic(){
return true;
}
@Override
public boolean isHidden(){
return true;
}
@Override
public String toString(){
return "BlockPart[" + dx + ", " + dy + "]";
}
}

View File

@@ -0,0 +1,397 @@
package mindustry.world.blocks;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.modules.*;
import java.io.*;
import static mindustry.Vars.*;
public class BuildBlock extends Block{
public static final int maxSize = 9;
private static final BuildBlock[] buildBlocks = new BuildBlock[maxSize];
private static long lastTime = 0;
private static int pitchSeq = 0;
private static long lastPlayed;
public BuildBlock(int size){
super("build" + size);
this.size = size;
update = true;
health = 20;
layer = Layer.placement;
consumesTap = true;
solidifes = true;
entityType = BuildEntity::new;
buildBlocks[size - 1] = this;
}
/** Returns a BuildBlock by size. */
public static BuildBlock get(int size){
if(size > maxSize) throw new IllegalArgumentException("No. Don't place BuildBlocks of size greater than " + maxSize);
return buildBlocks[size - 1];
}
@Remote(called = Loc.server)
public static void onDeconstructFinish(Tile tile, Block block, int builderID){
Team team = tile.getTeam();
Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), block.size);
world.removeBlock(tile);
Events.fire(new BlockBuildEndEvent(tile, playerGroup.getByID(builderID), team, true));
if(shouldPlay()) Sounds.breaks.at(tile, calcPitch(false));
}
@Remote(called = Loc.server)
public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){
if(tile == null) return;
float healthf = tile.entity == null ? 1f : tile.entity.healthf();
world.setBlock(tile, block, team, rotation);
if(tile.entity != null){
tile.entity.health = block.health * healthf;
}
//last builder was this local client player, call placed()
if(!headless && builderID == player.id){
if(!skipConfig){
tile.block().playerPlaced(tile);
}
}
Effects.effect(Fx.placeBlock, tile.drawx(), tile.drawy(), block.size);
}
static boolean shouldPlay(){
if(Time.timeSinceMillis(lastPlayed) >= 32){
lastPlayed = Time.millis();
return true;
}else{
return false;
}
}
static float calcPitch(boolean up){
if(Time.timeSinceMillis(lastTime) < 16 * 30){
lastTime = Time.millis();
pitchSeq ++;
if(pitchSeq > 30){
pitchSeq = 0;
}
return 1f + Mathf.clamp(pitchSeq / 30f) * (up ? 1.9f : -0.4f);
}else{
pitchSeq = 0;
lastTime = Time.millis();
return Mathf.random(0.7f, 1.3f);
}
}
public static void constructed(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){
Call.onConstructFinish(tile, block, builderID, rotation, team, skipConfig);
tile.block().placed(tile);
Events.fire(new BlockBuildEndEvent(tile, playerGroup.getByID(builderID), team, false));
if(shouldPlay()) Sounds.place.at(tile, calcPitch(true));
}
@Override
public boolean isHidden(){
return true;
}
@Override
public String getDisplayName(Tile tile){
BuildEntity entity = tile.ent();
return Core.bundle.format("block.constructing", entity.cblock == null ? entity.previous.localizedName : entity.cblock.localizedName);
}
@Override
public TextureRegion getDisplayIcon(Tile tile){
BuildEntity entity = tile.ent();
return (entity.cblock == null ? entity.previous : entity.cblock).icon(mindustry.ui.Cicon.full);
}
@Override
public boolean isSolidFor(Tile tile){
BuildEntity entity = tile.ent();
return entity == null || (entity.cblock != null && entity.cblock.solid) || entity.previous == null || entity.previous.solid;
}
@Override
public Cursor getCursor(Tile tile){
return SystemCursor.hand;
}
@Override
public void tapped(Tile tile, Player player){
BuildEntity entity = tile.ent();
//if the target is constructible, begin constructing
if(entity.cblock != null){
if(player.buildWasAutoPaused && !player.isBuilding){
player.isBuilding = true;
}
//player.clearBuilding();
player.addBuildRequest(new BuildRequest(tile.x, tile.y, tile.rotation(), entity.cblock), false);
}
}
@Override
public void onDestroyed(Tile tile){
Effects.effect(Fx.blockExplosionSmoke, tile);
if(!tile.floor().solid && !tile.floor().isLiquid){
RubbleDecal.create(tile.drawx(), tile.drawy(), size);
}
}
@Override
public void draw(Tile tile){
BuildEntity entity = tile.ent();
//When breaking, don't draw the previous block... since it's the thing you were breaking
if(entity.cblock != null && entity.previous == entity.cblock){
return;
}
if(entity.previous == null) return;
if(Core.atlas.isFound(entity.previous.icon(mindustry.ui.Cicon.full))){
Draw.rect(entity.previous.icon(Cicon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.rotation() * 90 : 0);
}
}
@Override
public void drawLayer(Tile tile){
BuildEntity entity = tile.ent();
Shaders.blockbuild.color = Pal.accent;
Block target = entity.cblock == null ? entity.previous : entity.cblock;
if(target == null) return;
for(TextureRegion region : target.getGeneratedIcons()){
Shaders.blockbuild.region = region;
Shaders.blockbuild.progress = entity.progress;
Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.rotation() * 90 : 0);
Draw.flush();
}
}
public class BuildEntity extends TileEntity{
/**
* The recipe of the block that is being constructed.
* If there is no recipe for this block, as is the case with rocks, 'previous' is used.
*/
public @Nullable
Block cblock;
public float progress = 0;
public float buildCost;
/**
* The block that used to be here.
* If a non-recipe block is being deconstructed, this is the block that is being deconstructed.
*/
public Block previous;
public int builderID = -1;
private float[] accumulator;
private float[] totalAccumulator;
public boolean construct(Unit builder, @Nullable TileEntity core, float amount, boolean configured){
if(cblock == null){
kill();
return false;
}
if(cblock.requirements.length != accumulator.length || totalAccumulator.length != cblock.requirements.length){
setConstruct(previous, cblock);
}
float maxProgress = core == null ? amount : checkRequired(core.items, amount, false);
for(int i = 0; i < cblock.requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount);
accumulator[i] += Math.min(reqamount * maxProgress, reqamount - totalAccumulator[i] + 0.00001f); //add min amount progressed to the accumulator
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * maxProgress, reqamount);
}
maxProgress = core == null ? maxProgress : checkRequired(core.items, maxProgress, true);
progress = Mathf.clamp(progress + maxProgress);
if(builder instanceof Player){
builderID = builder.getID();
}
if(progress >= 1f || state.rules.infiniteResources){
constructed(tile, cblock, builderID, tile.rotation(), builder.getTeam(), configured);
return true;
}
return false;
}
public void deconstruct(Unit builder, @Nullable TileEntity core, float amount){
float deconstructMultiplier = 0.5f;
if(cblock != null){
ItemStack[] requirements = cblock.requirements;
if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){
setDeconstruct(previous);
}
//make sure you take into account that you can't deconstruct more than there is deconstructed
float clampedAmount = Math.min(amount, progress);
for(int i = 0; i < requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * requirements[i].amount);
accumulator[i] += Math.min(clampedAmount * deconstructMultiplier * reqamount, deconstructMultiplier * reqamount - totalAccumulator[i]); //add scaled amount progressed to the accumulator
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * clampedAmount * deconstructMultiplier, reqamount);
int accumulated = (int)(accumulator[i]); //get amount
if(clampedAmount > 0 && accumulated > 0){ //if it's positive, add it to the core
if(core != null){
int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder);
core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder);
accumulator[i] -= accepting;
}else{
accumulator[i] -= accumulated;
}
}
}
}
progress = Mathf.clamp(progress - amount);
if(builder instanceof Player){
builderID = builder.getID();
}
if(progress <= 0 || state.rules.infiniteResources){
Call.onDeconstructFinish(tile, this.cblock == null ? previous : this.cblock, builderID);
}
}
private float checkRequired(ItemModule inventory, float amount, boolean remove){
float maxProgress = amount;
for(int i = 0; i < cblock.requirements.length; i++){
int sclamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount);
int required = (int)(accumulator[i]); //calculate items that are required now
if(inventory.get(cblock.requirements[i].item) == 0 && sclamount != 0){
maxProgress = 0f;
}else if(required > 0){ //if this amount is positive...
//calculate how many items it can actually use
int maxUse = Math.min(required, inventory.get(cblock.requirements[i].item));
//get this as a fraction
float fraction = maxUse / (float)required;
//move max progress down if this fraction is less than 1
maxProgress = Math.min(maxProgress, maxProgress * fraction);
accumulator[i] -= maxUse;
//remove stuff that is actually used
if(remove){
inventory.remove(cblock.requirements[i].item, maxUse);
}
}
//else, no items are required yet, so just keep going
}
return maxProgress;
}
public float progress(){
return progress;
}
public void setConstruct(Block previous, Block block){
this.cblock = block;
this.previous = previous;
this.accumulator = new float[block.requirements.length];
this.totalAccumulator = new float[block.requirements.length];
this.buildCost = block.buildCost * state.rules.buildCostMultiplier;
}
public void setDeconstruct(Block previous){
this.previous = previous;
this.progress = 1f;
if(previous.buildCost >= 0.01f){
this.cblock = previous;
this.accumulator = new float[previous.requirements.length];
this.totalAccumulator = new float[previous.requirements.length];
this.buildCost = previous.buildCost * state.rules.buildCostMultiplier;
}else{
this.buildCost = 20f; //default no-requirement build cost is 20
}
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(progress);
stream.writeShort(previous == null ? -1 : previous.id);
stream.writeShort(cblock == null ? -1 : cblock.id);
if(accumulator == null){
stream.writeByte(-1);
}else{
stream.writeByte(accumulator.length);
for(int i = 0; i < accumulator.length; i++){
stream.writeFloat(accumulator[i]);
stream.writeFloat(totalAccumulator[i]);
}
}
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
progress = stream.readFloat();
short pid = stream.readShort();
short rid = stream.readShort();
byte acsize = stream.readByte();
if(acsize != -1){
accumulator = new float[acsize];
totalAccumulator = new float[acsize];
for(int i = 0; i < acsize; i++){
accumulator[i] = stream.readFloat();
totalAccumulator[i] = stream.readFloat();
}
}
if(pid != -1) previous = content.block(pid);
if(rid != -1) cblock = content.block(rid);
if(cblock != null){
buildCost = cblock.buildCost * state.rules.buildCostMultiplier;
}else{
buildCost = 20f;
}
}
}
}

View File

@@ -0,0 +1,20 @@
package mindustry.world.blocks;
import arc.graphics.g2d.Draw;
import arc.math.Mathf;
import mindustry.world.Tile;
public class DoubleOverlayFloor extends OverlayFloor{
public DoubleOverlayFloor(String name){
super(name);
}
@Override
public void draw(Tile tile){
Draw.colorl(0.4f);
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy() - 0.75f);
Draw.color();
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}
}

View File

@@ -0,0 +1,220 @@
package mindustry.world.blocks;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.g2d.TextureAtlas.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.content.*;
import mindustry.entities.Effects.*;
import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
public class Floor extends Block{
/** number of different variant regions to use */
public int variants = 3;
/** edge fallback, used mainly for ores */
public String edge = "stone";
/** Multiplies unit velocity by this when walked on. */
public float speedMultiplier = 1f;
/** Multiplies unit drag by this when walked on. */
public float dragMultiplier = 1f;
/** Damage taken per tick on this tile. */
public float damageTaken = 0f;
/** How many ticks it takes to drown on this. */
public float drownTime = 0f;
/** Effect when walking on this floor. */
public Effect walkEffect = Fx.ripple;
/** Effect displayed when drowning on this floor. */
public Effect drownUpdateEffect = Fx.bubble;
/** Status effect applied when walking on. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect. */
public float statusDuration = 60f;
/** liquids that drop from this block, used for pumps */
public Liquid liquidDrop = null;
/** item that drops from this block, used for drills */
public Item itemDrop = null;
/** whether this block can be drowned in */
public boolean isLiquid;
/** if true, this block cannot be mined by players. useful for annoying things like sand. */
public boolean playerUnmineable = false;
/** Group of blocks that this block does not draw edges on. */
public Block blendGroup = this;
/** Effect displayed when randomly updated. */
public Effect updateEffect = Fx.none;
/** Array of affinities to certain things. */
public Attributes attributes = new Attributes();
protected TextureRegion[][] edges;
protected byte eq = 0;
protected Array<Block> blenders = new Array<>();
protected IntSet blended = new IntSet();
protected TextureRegion edgeRegion;
public Floor(String name){
super(name);
}
@Override
public void load(){
super.load();
//load variant regions for drawing
if(variants > 0){
variantRegions = new TextureRegion[variants];
for(int i = 0; i < variants; i++){
variantRegions[i] = Core.atlas.find(name + (i + 1));
}
}else{
variantRegions = new TextureRegion[1];
variantRegions[0] = Core.atlas.find(name);
}
int size = (int)(tilesize / Draw.scl);
if(Core.atlas.has(name + "-edge")){
edges = Core.atlas.find(name + "-edge").split(size, size);
}
region = variantRegions[0];
edgeRegion = Core.atlas.find("edge");
}
@Override
public void createIcons(MultiPacker packer){
super.createIcons(packer);
packer.add(PageType.editor, "editor-" + name, Core.atlas.getPixmap((AtlasRegion)icon(Cicon.full)).crop());
if(blendGroup != this){
return;
}
if(variants > 0){
for(int i = 0; i < variants; i++){
String rname = name + (i + 1);
packer.add(PageType.editor, "editor-" + rname, Core.atlas.getPixmap(rname).crop());
}
}
Color color = new Color();
Color color2 = new Color();
PixmapRegion image = Core.atlas.getPixmap((AtlasRegion)generateIcons()[0]);
PixmapRegion edge = Core.atlas.getPixmap("edge-stencil");
Pixmap result = new Pixmap(edge.width, edge.height);
for(int x = 0; x < edge.width; x++){
for(int y = 0; y < edge.height; y++){
edge.getPixel(x, y, color);
result.draw(x, y, color.mul(color2.set(image.getPixel(x % image.width, y % image.height))));
}
}
packer.add(PageType.environment, name + "-edge", result);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(Core.atlas.has(name) ? name : name + "1")};
}
@Override
public void draw(Tile tile){
Mathf.random.setSeed(tile.pos());
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
drawEdges(tile);
Floor floor = tile.overlay();
if(floor != Blocks.air && floor != this){ //ore should never have itself on top, but it's possible, so prevent a crash in that case
floor.draw(tile);
}
}
public boolean isDeep(){
return drownTime > 0;
}
public void drawNonLayer(Tile tile){
Mathf.random.setSeed(tile.pos());
drawEdges(tile, true);
}
protected void drawEdges(Tile tile){
drawEdges(tile, false);
}
protected void drawEdges(Tile tile, boolean sameLayer){
blenders.clear();
blended.clear();
eq = 0;
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
Tile other = tile.getNearby(point);
if(other != null && doEdge(other.floor(), sameLayer) && other.floor().edges() != null){
if(blended.add(other.floor().id)){
blenders.add(other.floor());
}
eq |= (1 << i);
}
}
blenders.sort((a, b) -> Integer.compare(a.id, b.id));
for(Block block : blenders){
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
Tile other = tile.getNearby(point);
if(other != null && other.floor() == block){
TextureRegion region = edge((Floor)block, 2 - (point.x + 1), 2 - (point.y + 1));
Draw.rect(region, tile.worldx(), tile.worldy());
if(!sameLayer && block.cacheLayer.ordinal() > cacheLayer.ordinal()){
Draw.rect(block.variantRegions()[0], tile.worldx() + point.x*tilesize, tile.worldy() + point.y*tilesize);
}
}
}
}
}
//'new' style of edges with shadows instead of colors, not used currently
protected void drawEdgesFlat(Tile tile, boolean sameLayer){
for(int i = 0; i < 4; i++){
Tile other = tile.getNearby(i);
if(other != null && doEdge(other.floor(), sameLayer)){
Color color = other.floor().color;
Draw.color(color.r, color.g, color.b, 1f);
Draw.rect(edgeRegion, tile.worldx(), tile.worldy(), i*90);
}
}
Draw.color();
}
protected TextureRegion[][] edges(){
return ((Floor)blendGroup).edges;
}
protected boolean doEdge(Floor other, boolean sameLayer){
return (other.blendGroup.id > blendGroup.id || edges() == null) && other.edgeOnto(this) && (other.cacheLayer.ordinal() > this.cacheLayer.ordinal() || !sameLayer);
}
protected boolean edgeOnto(Floor other){
return true;
}
TextureRegion edge(Floor block, int x, int y){
return block.edges()[x][2 - y];
}
}

View File

@@ -0,0 +1,43 @@
package mindustry.world.blocks;
import arc.struct.*;
import arc.func.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import static mindustry.Vars.*;
public class ItemSelection{
public static void buildItemTable(Table table, Prov<Item> holder, Cons<Item> consumer){
Array<Item> items = content.items();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
group.setMinCheckCount(0);
Table cont = new Table();
cont.defaults().size(38);
int i = 0;
for(Item item : items){
if(!data.isUnlocked(item) && world.isZone()) continue;
ImageButton button = cont.addImageButton(Tex.whiteui, Styles.clearToggleTransi, 24, () -> control.input.frag.config.hideConfig()).group(group).get();
button.changed(() -> consumer.get(button.isChecked() ? item : null));
button.getStyle().imageUp = new TextureRegionDrawable(item.icon(Cicon.small));
button.update(() -> button.setChecked(holder.get() == item));
if(i++ % 4 == 3){
cont.row();
}
}
table.add(cont);
}
}

View File

@@ -0,0 +1,54 @@
package mindustry.world.blocks;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.meta.BlockGroup;
import mindustry.world.modules.LiquidModule;
public class LiquidBlock extends Block{
protected TextureRegion liquidRegion, bottomRegion, topRegion;
public LiquidBlock(String name){
super(name);
update = true;
solid = true;
hasLiquids = true;
group = BlockGroup.liquids;
outputsLiquid = true;
}
@Override
public void load(){
super.load();
liquidRegion = Core.atlas.find(name + "-liquid");
topRegion = Core.atlas.find(name + "-top");
bottomRegion = Core.atlas.find(name + "-bottom");
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name + "-bottom"), Core.atlas.find(name + "-top")};
}
@Override
public void draw(Tile tile){
LiquidModule mod = tile.entity.liquids;
int rotation = rotate ? tile.rotation() * 90 : 0;
Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation);
if(mod.total() > 0.001f){
Draw.color(mod.current().color);
Draw.alpha(mod.total() / liquidCapacity);
Draw.rect(liquidRegion, tile.drawx(), tile.drawy(), rotation);
Draw.color();
}
Draw.rect(topRegion, tile.drawx(), tile.drawy(), rotation);
}
}

View File

@@ -0,0 +1,85 @@
package mindustry.world.blocks;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
/**An overlay ore for a specific item type.*/
public class OreBlock extends OverlayFloor{
public OreBlock(Item ore){
super("ore-" + ore.name);
this.localizedName = ore.localizedName;
this.itemDrop = ore;
this.variants = 3;
this.color.set(ore.color);
}
/** For mod use only!*/
public OreBlock(String name){
super(name);
variants = 3;
}
public void setup(Item ore){
this.localizedName = ore.localizedName;
this.itemDrop = ore;
this.color.set(ore.color);
}
@Override
@OverrideCallSuper
public void createIcons(MultiPacker packer){
for(int i = 0; i < variants; i++){
Pixmap image = new Pixmap(32, 32);
PixmapRegion shadow = Core.atlas.getPixmap(itemDrop.name + (i + 1));
int offset = image.getWidth() / tilesize - 1;
Color color = new Color();
for(int x = 0; x < image.getWidth(); x++){
for(int y = offset; y < image.getHeight(); y++){
shadow.getPixel(x, y - offset, color);
if(color.a > 0.001f){
color.set(0, 0, 0, 0.3f);
image.draw(x, y, color);
}
}
}
image.draw(shadow);
packer.add(PageType.environment, name + (i + 1), image);
packer.add(PageType.editor, "editor-" + name + (i + 1), image);
if(i == 0){
packer.add(PageType.editor, "editor-block-" + name + "-full", image);
packer.add(PageType.main, "block-" + name + "-full", image);
}
}
}
@Override
public void init(){
super.init();
if(itemDrop != null){
setup(itemDrop);
}else{
throw new IllegalArgumentException(name + " must have an item drop!");
}
}
@Override
public String getDisplayName(Tile tile){
return itemDrop.localizedName;
}
}

View File

@@ -0,0 +1,18 @@
package mindustry.world.blocks;
import arc.graphics.g2d.Draw;
import arc.math.Mathf;
import mindustry.world.Tile;
/**A type of floor that is overlaid on top of over floors.*/
public class OverlayFloor extends Floor{
public OverlayFloor(String name){
super(name);
}
@Override
public void draw(Tile tile){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}
}

View File

@@ -0,0 +1,15 @@
package mindustry.world.blocks;
import mindustry.world.Block;
import mindustry.world.meta.BlockGroup;
public abstract class PowerBlock extends Block{
public PowerBlock(String name){
super(name);
update = true;
solid = true;
hasPower = true;
group = BlockGroup.power;
}
}

View File

@@ -0,0 +1,72 @@
package mindustry.world.blocks;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.entities.type.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.net;
public class RespawnBlock{
public static void drawRespawn(Tile tile, float heat, float progress, float time, Player player, Mech to){
progress = Mathf.clamp(progress);
Draw.color(Pal.darkMetal);
Lines.stroke(2f * heat);
Fill.poly(tile.drawx(), tile.drawy(), 4, 10f * heat);
Draw.reset();
if(player != null){
TextureRegion region = player.getIconRegion();
Draw.color(0f, 0f, 0f, 0.4f * progress);
Draw.rect("circle-shadow", tile.drawx(), tile.drawy(), region.getWidth() / 3f, region.getWidth() / 3f);
Draw.color();
Shaders.build.region = region;
Shaders.build.progress = progress;
Shaders.build.color.set(Pal.accent);
Shaders.build.time = -time / 10f;
Draw.shader(Shaders.build, true);
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.shader();
Draw.color(Pal.accentBack);
float pos = Mathf.sin(time, 6f, 8f);
Lines.lineAngleCenter(tile.drawx() + pos, tile.drawy(), 90, 16f - Math.abs(pos) * 2f);
Draw.reset();
}
Lines.stroke(2f * heat);
Draw.color(Pal.accentBack);
Lines.poly(tile.drawx(), tile.drawy(), 4, 8f * heat);
float oy = -7f, len = 6f * heat;
Lines.stroke(5f);
Draw.color(Pal.darkMetal);
Lines.line(tile.drawx() - len, tile.drawy() + oy, tile.drawx() + len, tile.drawy() + oy, CapStyle.none);
for(int i : Mathf.signs){
Fill.tri(tile.drawx() + len * i, tile.drawy() + oy - Lines.getStroke()/2f, tile.drawx() + len * i, tile.drawy() + oy + Lines.getStroke()/2f, tile.drawx() + (len + Lines.getStroke() * heat) * i, tile.drawy() + oy);
}
Lines.stroke(3f);
Draw.color(Pal.accent);
Lines.line(tile.drawx() - len, tile.drawy() + oy, tile.drawx() - len + len*2 * progress, tile.drawy() + oy, CapStyle.none);
for(int i : Mathf.signs){
Fill.tri(tile.drawx() + len * i, tile.drawy() + oy - Lines.getStroke()/2f, tile.drawx() + len * i, tile.drawy() + oy + Lines.getStroke()/2f, tile.drawx() + (len + Lines.getStroke() * heat) * i, tile.drawy() + oy);
}
Draw.reset();
if(net.active() && player != null){
tile.block().drawPlaceText(player.name, tile.x, tile.y - (Math.max((tile.block().size-1)/2, 0)), true);
}
}
}

View File

@@ -0,0 +1,45 @@
package mindustry.world.blocks;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Mathf;
import mindustry.world.Block;
import mindustry.world.Tile;
public class Rock extends Block{
protected int variants;
public Rock(String name){
super(name);
breakable = true;
alwaysReplace = true;
}
@Override
public void draw(Tile tile){
if(variants > 0){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}else{
Draw.rect(region, tile.worldx(), tile.worldy());
}
}
@Override
public TextureRegion[] generateIcons(){
return variants == 0 ? super.generateIcons() : new TextureRegion[]{Core.atlas.find(name + "1")};
}
@Override
public void load(){
super.load();
if(variants > 0){
variantRegions = new TextureRegion[variants];
for(int i = 0; i < variants; i++){
variantRegions[i] = Core.atlas.find(name + (i + 1));
}
}
}
}

View File

@@ -0,0 +1,51 @@
package mindustry.world.blocks;
import arc.Core;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import mindustry.graphics.CacheLayer;
import mindustry.world.*;
import static mindustry.Vars.*;
public class StaticWall extends Rock{
TextureRegion large;
TextureRegion[][] split;
public StaticWall(String name){
super(name);
breakable = alwaysReplace = false;
solid = true;
variants = 2;
cacheLayer = CacheLayer.walls;
}
@Override
public void draw(Tile tile){
int rx = tile.x / 2 * 2;
int ry = tile.y / 2 * 2;
if(Core.atlas.isFound(large) && eq(rx, ry) && Mathf.randomSeed(Pos.get(rx, ry)) < 0.5){
Draw.rect(split[tile.x % 2][1 - tile.y % 2], tile.worldx(), tile.worldy());
}else if(variants > 0){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}else{
Draw.rect(region, tile.worldx(), tile.worldy());
}
}
@Override
public void load(){
super.load();
large = Core.atlas.find(name + "-large");
split = large.split(32, 32);
}
boolean eq(int rx, int ry){
return rx < world.width() - 1 && ry < world.height() - 1
&& world.tile(rx + 1, ry).block() == this
&& world.tile(rx, ry + 1).block() == this
&& world.tile(rx, ry).block() == this
&& world.tile(rx + 1, ry + 1).block() == this;
}
}

View File

@@ -0,0 +1,25 @@
package mindustry.world.blocks;
import arc.graphics.g2d.Draw;
import arc.math.Mathf;
import mindustry.graphics.Layer;
import mindustry.world.Block;
import mindustry.world.Tile;
public class TreeBlock extends Block{
public TreeBlock(String name){
super(name);
solid = true;
layer = Layer.power;
expanded = true;
}
@Override
public void draw(Tile tile){}
@Override
public void drawLayer(Tile tile){
Draw.rect(region, tile.drawx(), tile.drawy(), Mathf.randomSeed(tile.pos(), 0, 4) * 90);
}
}

View File

@@ -0,0 +1,79 @@
package mindustry.world.blocks.defense;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.type.*;
import mindustry.entities.type.Bullet;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
public class DeflectorWall extends Wall{
public static final float hitTime = 10f;
protected float maxDamageDeflect = 10f;
protected Rectangle rect = new Rectangle();
protected Rectangle rect2 = new Rectangle();
public DeflectorWall(String name){
super(name);
entityType = DeflectorEntity::new;
}
@Override
public void draw(Tile tile){
super.draw(tile);
DeflectorEntity entity = tile.ent();
if(entity.hit < 0.0001f) return;
Draw.color(Color.white);
Draw.alpha(entity.hit * 0.5f);
Draw.blend(Blending.additive);
Fill.rect(tile.drawx(), tile.drawy(), tilesize * size, tilesize * size);
Draw.blend();
Draw.reset();
entity.hit = Mathf.clamp(entity.hit - Time.delta() / hitTime);
}
@Override
public void handleBulletHit(TileEntity entity, Bullet bullet){
super.handleBulletHit(entity, bullet);
//doesn't reflect powerful bullets
if(bullet.damage() > maxDamageDeflect || bullet.isDeflected()) return;
float penX = Math.abs(entity.x - bullet.x), penY = Math.abs(entity.y - bullet.y);
bullet.hitbox(rect2);
Vector2 position = Geometry.raycastRect(bullet.x - bullet.velocity().x*Time.delta(), bullet.y - bullet.velocity().y*Time.delta(), bullet.x + bullet.velocity().x*Time.delta(), bullet.y + bullet.velocity().y*Time.delta(),
rect.setSize(size * tilesize + rect2.width*2 + rect2.height*2).setCenter(entity.x, entity.y));
if(position != null){
bullet.set(position.x, position.y);
}
if(penX > penY){
bullet.velocity().x *= -1;
}else{
bullet.velocity().y *= -1;
}
//bullet.updateVelocity();
bullet.resetOwner(entity, entity.getTeam());
bullet.scaleTime(1f);
bullet.deflect();
((DeflectorEntity)entity).hit = 1f;
}
public static class DeflectorEntity extends TileEntity{
public float hit;
}
}

View File

@@ -0,0 +1,109 @@
package mindustry.world.blocks.defense;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class Door extends Wall{
protected final static Rectangle rect = new Rectangle();
public final int timerToggle = timers++;
public Effect openfx = Fx.dooropen;
public Effect closefx = Fx.doorclose;
protected TextureRegion openRegion;
public Door(String name){
super(name);
solid = false;
solidifes = true;
consumesTap = true;
entityType = DoorEntity::new;
}
@Remote(called = Loc.server)
public static void onDoorToggle(Player player, Tile tile, boolean open){
DoorEntity entity = tile.ent();
if(entity != null){
entity.open = open;
Door door = (Door)tile.block();
pathfinder.updateTile(tile);
if(!entity.open){
Effects.effect(door.openfx, tile.drawx(), tile.drawy());
}else{
Effects.effect(door.closefx, tile.drawx(), tile.drawy());
}
Sounds.door.at(tile);
}
}
@Override
public void load(){
super.load();
openRegion = Core.atlas.find(name + "-open");
}
@Override
public void draw(Tile tile){
DoorEntity entity = tile.ent();
if(!entity.open){
Draw.rect(region, tile.drawx(), tile.drawy());
}else{
Draw.rect(openRegion, tile.drawx(), tile.drawy());
}
}
@Override
public Cursor getCursor(Tile tile){
return SystemCursor.hand;
}
@Override
public boolean isSolidFor(Tile tile){
DoorEntity entity = tile.ent();
return !entity.open;
}
@Override
public void tapped(Tile tile, Player player){
DoorEntity entity = tile.ent();
if((Units.anyEntities(tile) && entity.open) || !tile.entity.timer.get(timerToggle, 30f)){
return;
}
Call.onDoorToggle(null, tile, !entity.open);
}
public class DoorEntity extends TileEntity{
public boolean open = false;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeBoolean(open);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
open = stream.readBoolean();
}
}
}

View File

@@ -0,0 +1,268 @@
package mindustry.world.blocks.defense;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.entities.type.BaseEntity;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class ForceProjector extends Block{
public final int timerUse = timers++;
public float phaseUseTime = 350f;
public float phaseRadiusBoost = 80f;
public float radius = 101.7f;
public float breakage = 550f;
public float cooldownNormal = 1.75f;
public float cooldownLiquid = 1.5f;
public float cooldownBrokenBase = 0.35f;
public float basePowerDraw = 0.2f;
public TextureRegion topRegion;
private static Tile paramTile;
private static ForceProjector paramBlock;
private static ForceEntity paramEntity;
private static Cons<AbsorbTrait> shieldConsumer = trait -> {
if(trait.canBeAbsorbed() && trait.getTeam() != paramTile.getTeam() && paramBlock.isInsideHexagon(trait.getX(), trait.getY(), paramBlock.realRadius(paramEntity) * 2f, paramTile.drawx(), paramTile.drawy())){
trait.absorb();
Effects.effect(Fx.absorb, trait);
paramEntity.hit = 1f;
paramEntity.buildup += trait.getShieldDamage() * paramEntity.warmup;
}
};
public ForceProjector(String name){
super(name);
update = true;
solid = true;
hasPower = true;
canOverdrive = false;
hasLiquids = true;
hasItems = true;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 0.1f)).boost().update(false);
entityType = ForceEntity::new;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.powerUse, basePowerDraw * 60f, StatUnit.powerSecond);
stats.add(BlockStat.boostEffect, phaseRadiusBoost / tilesize, StatUnit.blocks);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
Draw.color(Pal.accent);
Lines.stroke(1f);
Lines.poly(x * tilesize, y * tilesize, 6, radius);
Draw.color();
}
@Override
public void update(Tile tile){
ForceEntity entity = tile.ent();
if(entity.shield == null){
entity.shield = new ShieldEntity(tile);
entity.shield.add();
}
boolean phaseValid = consumes.get(ConsumeType.item).valid(tile.entity);
entity.phaseHeat = Mathf.lerpDelta(entity.phaseHeat, Mathf.num(phaseValid), 0.1f);
if(phaseValid && !entity.broken && entity.timer.get(timerUse, phaseUseTime) && entity.efficiency() > 0){
entity.cons.trigger();
}
entity.radscl = Mathf.lerpDelta(entity.radscl, entity.broken ? 0f : entity.warmup, 0.05f);
if(Mathf.chance(Time.delta() * entity.buildup / breakage * 0.1f)){
Effects.effect(Fx.reactorsmoke, tile.drawx() + Mathf.range(tilesize / 2f), tile.drawy() + Mathf.range(tilesize / 2f));
}
entity.warmup = Mathf.lerpDelta(entity.warmup, entity.efficiency(), 0.1f);
/*
if(entity.power.status < relativePowerDraw){
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.15f);
entity.power.status = 0f;
if(entity.warmup <= 0.09f){
entity.broken = true;
}
}else{
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.1f);
}*/
if(entity.buildup > 0){
float scale = !entity.broken ? cooldownNormal : cooldownBrokenBase;
ConsumeLiquidFilter cons = consumes.get(ConsumeType.liquid);
if(cons.valid(entity)){
cons.update(entity);
scale *= (cooldownLiquid * (1f + (entity.liquids.current().heatCapacity - 0.4f) * 0.9f));
}
entity.buildup -= Time.delta() * scale;
}
if(entity.broken && entity.buildup <= 0){
entity.broken = false;
}
if(entity.buildup >= breakage && !entity.broken){
entity.broken = true;
entity.buildup = breakage;
Effects.effect(Fx.shieldBreak, tile.drawx(), tile.drawy(), radius);
}
if(entity.hit > 0f){
entity.hit -= 1f / 5f * Time.delta();
}
float realRadius = realRadius(entity);
paramTile = tile;
paramEntity = entity;
paramBlock = this;
bulletGroup.intersect(tile.drawx() - realRadius, tile.drawy() - realRadius, realRadius*2f, realRadius * 2f, shieldConsumer);
}
float realRadius(ForceEntity entity){
return (radius + entity.phaseHeat * phaseRadiusBoost) * entity.radscl;
}
boolean isInsideHexagon(float x0, float y0, float d, float x, float y){
float dx = Math.abs(x - x0) / d;
float dy = Math.abs(y - y0) / d;
float a = 0.25f * Mathf.sqrt3;
return (dy <= a) && (a * dx + 0.25 * dy <= 0.5 * a);
}
@Override
public void draw(Tile tile){
super.draw(tile);
ForceEntity entity = tile.ent();
if(entity.buildup <= 0f) return;
Draw.alpha(entity.buildup / breakage * 0.75f);
Draw.blend(Blending.additive);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
Draw.blend();
Draw.reset();
}
class ForceEntity extends TileEntity{
ShieldEntity shield;
boolean broken = true;
float buildup = 0f;
float radscl = 0f;
float hit;
float warmup;
float phaseHeat;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeBoolean(broken);
stream.writeFloat(buildup);
stream.writeFloat(radscl);
stream.writeFloat(warmup);
stream.writeFloat(phaseHeat);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
broken = stream.readBoolean();
buildup = stream.readFloat();
radscl = stream.readFloat();
warmup = stream.readFloat();
phaseHeat = stream.readFloat();
}
}
public class ShieldEntity extends BaseEntity implements DrawTrait{
final ForceEntity entity;
public ShieldEntity(Tile tile){
this.entity = tile.ent();
set(tile.drawx(), tile.drawy());
}
@Override
public void update(){
if(entity.isDead() || !entity.isAdded()){
remove();
}
}
@Override
public float drawSize(){
return realRadius(entity) * 2f + 2f;
}
@Override
public void draw(){
Draw.color(Pal.accent);
Fill.poly(x, y, 6, realRadius(entity));
Draw.color();
}
public void drawOver(){
if(entity.hit <= 0f) return;
Draw.color(Color.white);
Draw.alpha(entity.hit);
Fill.poly(x, y, 6, realRadius(entity));
Draw.color();
}
public void drawSimple(){
if(realRadius(entity) < 0.5f) return;
float rad = realRadius(entity);
Draw.color(Pal.accent);
Lines.stroke(1.5f);
Draw.alpha(0.09f + 0.08f * entity.hit);
Fill.poly(x, y, 6, rad);
Draw.alpha(1f);
Lines.poly(x, y, 6, rad);
Draw.reset();
}
@Override
public EntityGroup targetGroup(){
return shieldGroup;
}
}
}

View File

@@ -0,0 +1,159 @@
package mindustry.world.blocks.defense;
import arc.Core;
import arc.struct.IntSet;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import arc.util.*;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class MendProjector extends Block{
private static final IntSet healed = new IntSet();
public final int timerUse = timers++;
public Color baseColor = Color.valueOf("84f491");
public Color phaseColor = Color.valueOf("ffd59e");
public TextureRegion topRegion;
public float reload = 250f;
public float range = 60f;
public float healPercent = 12f;
public float phaseBoost = 12f;
public float phaseRangeBoost = 50f;
public float useTime = 400f;
public MendProjector(String name){
super(name);
solid = true;
update = true;
hasPower = true;
hasItems = true;
entityType = MendEntity::new;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.repairTime, (int)(100f / healPercent * reload / 60f), StatUnit.seconds);
stats.add(BlockStat.range, range / tilesize, StatUnit.blocks);
stats.add(BlockStat.boostEffect, phaseRangeBoost / tilesize, StatUnit.blocks);
stats.add(BlockStat.boostEffect, (phaseBoost + healPercent) / healPercent, StatUnit.timesSpeed);
}
@Override
public void update(Tile tile){
MendEntity entity = tile.ent();
entity.heat = Mathf.lerpDelta(entity.heat, entity.cons.valid() || tile.isEnemyCheat() ? 1f : 0f, 0.08f);
entity.charge += entity.heat * entity.delta();
entity.phaseHeat = Mathf.lerpDelta(entity.phaseHeat, Mathf.num(entity.cons.optionalValid()), 0.1f);
if(entity.cons.optionalValid() && entity.timer.get(timerUse, useTime) && entity.efficiency() > 0){
entity.cons.trigger();
}
if(entity.charge >= reload){
float realRange = range + entity.phaseHeat * phaseRangeBoost;
entity.charge = 0f;
int tileRange = (int)(realRange / tilesize + 1);
healed.clear();
for(int x = -tileRange + tile.x; x <= tileRange + tile.x; x++){
for(int y = -tileRange + tile.y; y <= tileRange + tile.y; y++){
if(!Mathf.within(x * tilesize, y * tilesize, tile.drawx(), tile.drawy(), realRange)) continue;
Tile other = world.ltile(x, y);
if(other == null) continue;
if(other.getTeamID() == tile.getTeamID() && !healed.contains(other.pos()) && other.entity != null && other.entity.health < other.entity.maxHealth()){
other.entity.healBy(other.entity.maxHealth() * (healPercent + entity.phaseHeat * phaseBoost) / 100f * entity.efficiency());
Effects.effect(Fx.healBlockFull, Tmp.c1.set(baseColor).lerp(phaseColor, entity.phaseHeat), other.drawx(), other.drawy(), other.block().size);
healed.add(other.pos());
}
}
}
}
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range, Pal.accent);
}
@Override
public void drawSelect(Tile tile){
MendEntity entity = tile.ent();
float realRange = range + entity.phaseHeat * phaseRangeBoost;
Drawf.dashCircle(tile.drawx(), tile.drawy(), realRange, baseColor);
}
@Override
public void draw(Tile tile){
super.draw(tile);
MendEntity entity = tile.ent();
float f = 1f - (Time.time() / 100f) % 1f;
Draw.color(baseColor, phaseColor, entity.phaseHeat);
Draw.alpha(entity.heat * Mathf.absin(Time.time(), 10f, 1f) * 0.5f);
//Draw.blend(Blending.additive);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
//Draw.blend();
Draw.alpha(1f);
Lines.stroke((2f * f + 0.2f) * entity.heat);
Lines.square(tile.drawx(), tile.drawy(), ((1f - f) * 8f) * size / 2f);
Draw.reset();
}
@Override
public void drawLight(Tile tile){
renderer.lights.add(tile.drawx(), tile.drawy(), 50f * tile.entity.efficiency(), baseColor, 0.7f * tile.entity.efficiency());
}
class MendEntity extends TileEntity{
float heat;
float charge = Mathf.random(reload);
float phaseHeat;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(heat);
stream.writeFloat(phaseHeat);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
heat = stream.readFloat();
phaseHeat = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,160 @@
package mindustry.world.blocks.defense;
import arc.Core;
import arc.struct.IntSet;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class OverdriveProjector extends Block{
private static final IntSet healed = new IntSet();
public final int timerUse = timers++;
public TextureRegion topRegion;
public float reload = 60f;
public float range = 80f;
public float speedBoost = 1.5f;
public float speedBoostPhase = 0.75f;
public float useTime = 400f;
public float phaseRangeBoost = 20f;
public Color baseColor = Color.valueOf("feb380");
public Color phaseColor = Color.valueOf("ffd59e");
public OverdriveProjector(String name){
super(name);
solid = true;
update = true;
hasPower = true;
hasItems = true;
canOverdrive = false;
entityType = OverdriveEntity::new;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range, Pal.accent);
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.speedIncrease, (int)(100f * speedBoost), StatUnit.percent);
stats.add(BlockStat.range, range / tilesize, StatUnit.blocks);
stats.add(BlockStat.boostEffect, phaseRangeBoost / tilesize, StatUnit.blocks);
stats.add(BlockStat.boostEffect, (int)((speedBoost + speedBoostPhase) * 100f), StatUnit.percent);
}
@Override
public void drawLight(Tile tile){
renderer.lights.add(tile.drawx(), tile.drawy(), 50f * tile.entity.efficiency(), baseColor, 0.7f * tile.entity.efficiency());
}
@Override
public void update(Tile tile){
OverdriveEntity entity = tile.ent();
entity.heat = Mathf.lerpDelta(entity.heat, entity.cons.valid() ? 1f : 0f, 0.08f);
entity.charge += entity.heat * Time.delta();
entity.phaseHeat = Mathf.lerpDelta(entity.phaseHeat, Mathf.num(entity.cons.optionalValid()), 0.1f);
if(entity.timer.get(timerUse, useTime) && entity.efficiency() > 0){
entity.cons.trigger();
}
if(entity.charge >= reload){
float realRange = range + entity.phaseHeat * phaseRangeBoost;
float realBoost = (speedBoost + entity.phaseHeat * speedBoostPhase) * entity.efficiency();
entity.charge = 0f;
int tileRange = (int)(realRange / tilesize + 1);
healed.clear();
for(int x = -tileRange + tile.x; x <= tileRange + tile.x; x++){
for(int y = -tileRange + tile.y; y <= tileRange + tile.y; y++){
if(!Mathf.within(x * tilesize, y * tilesize, tile.drawx(), tile.drawy(), realRange)) continue;
Tile other = world.ltile(x, y);
if(other == null) continue;
if(other.getTeamID() == tile.getTeamID() && !healed.contains(other.pos()) && other.entity != null){
if(other.entity.timeScale <= realBoost){
other.entity.timeScaleDuration = Math.max(other.entity.timeScaleDuration, reload + 1f);
other.entity.timeScale = Math.max(other.entity.timeScale, realBoost);
}
healed.add(other.pos());
}
}
}
}
}
@Override
public void drawSelect(Tile tile){
OverdriveEntity entity = tile.ent();
float realRange = range + entity.phaseHeat * phaseRangeBoost;
Drawf.dashCircle(tile.drawx(), tile.drawy(), realRange, baseColor);
}
@Override
public void draw(Tile tile){
super.draw(tile);
OverdriveEntity entity = tile.ent();
float f = 1f - (Time.time() / 100f) % 1f;
Draw.color(baseColor, phaseColor, entity.phaseHeat);
Draw.alpha(entity.heat * Mathf.absin(Time.time(), 10f, 1f) * 0.5f);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
Draw.alpha(1f);
Lines.stroke((2f * f + 0.2f) * entity.heat);
Lines.square(tile.drawx(), tile.drawy(), (1f - f) * 8f);
Draw.reset();
}
class OverdriveEntity extends TileEntity{
float heat;
float charge = Mathf.random(reload);
float phaseHeat;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(heat);
stream.writeFloat(phaseHeat);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
heat = stream.readFloat();
phaseHeat = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.world.blocks.defense;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.Fill;
import arc.math.Mathf;
import mindustry.entities.effect.Lightning;
import mindustry.entities.type.Unit;
import mindustry.graphics.Layer;
import mindustry.graphics.Pal;
import mindustry.world.Block;
import mindustry.world.Tile;
public class ShockMine extends Block{
public final int timerDamage = timers++;
public float cooldown = 80f;
public float tileDamage = 5f;
public float damage = 13;
public int length = 10;
public int tendrils = 6;
public ShockMine(String name){
super(name);
update = false;
destructible = true;
solid = false;
targetable = false;
layer = Layer.overlay;
}
@Override
public void drawLayer(Tile tile){
super.draw(tile);
Draw.color(tile.getTeam().color);
Draw.alpha(0.22f);
Fill.rect(tile.drawx(), tile.drawy(), 2f, 2f);
Draw.color();
}
@Override
public void drawTeam(Tile tile){
//no
}
@Override
public void draw(Tile tile){
//nope
}
@Override
public void unitOn(Tile tile, Unit unit){
if(unit.getTeam() != tile.getTeam() && tile.entity.timer.get(timerDamage, cooldown)){
for(int i = 0; i < tendrils; i++){
Lightning.create(tile.getTeam(), Pal.lancerLaser, damage, tile.drawx(), tile.drawy(), Mathf.random(360f), length);
}
tile.entity.damage(tileDamage);
}
}
}

View File

@@ -0,0 +1,25 @@
package mindustry.world.blocks.defense;
import arc.math.Mathf;
import mindustry.entities.type.Bullet;
import mindustry.entities.effect.Lightning;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.Pal;
public class SurgeWall extends Wall{
public float lightningChance = 0.05f;
public float lightningDamage = 15f;
public int lightningLength = 17;
public SurgeWall(String name){
super(name);
}
@Override
public void handleBulletHit(TileEntity entity, Bullet bullet){
super.handleBulletHit(entity, bullet);
if(Mathf.chance(lightningChance)){
Lightning.create(entity.getTeam(), Pal.surge, lightningDamage, bullet.x, bullet.y, bullet.rot() + 180f, lightningLength);
}
}
}

View File

@@ -0,0 +1,54 @@
package mindustry.world.blocks.defense;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Mathf;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.meta.BlockGroup;
public class Wall extends Block{
public int variants = 0;
public Wall(String name){
super(name);
solid = true;
destructible = true;
group = BlockGroup.walls;
buildCostMultiplier = 5f;
}
@Override
public void load(){
super.load();
if(variants != 0){
variantRegions = new TextureRegion[variants];
for(int i = 0; i < variants; i++){
variantRegions[i] = Core.atlas.find(name + (i + 1));
}
region = variantRegions[0];
}
}
@Override
public void draw(Tile tile){
if(variants == 0){
Draw.rect(region, tile.drawx(), tile.drawy());
}else{
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.drawx(), tile.drawy());
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(Core.atlas.has(name) ? name : name + "1")};
}
@Override
public boolean canReplace(Block other){
return super.canReplace(other) && health > other.health;
}
}

View File

@@ -0,0 +1,47 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.Mathf;
import arc.math.geom.Vector2;
import mindustry.entities.Predict;
import mindustry.entities.type.Bullet;
import mindustry.entities.bullet.BulletType;
import mindustry.world.Tile;
import static mindustry.Vars.tilesize;
/**
* Artillery turrets have special shooting calculations done to hit targets.
*/
public class ArtilleryTurret extends ItemTurret{
public float velocityInaccuracy = 0f;
public ArtilleryTurret(String name){
super(name);
targetAir = false;
}
@Override
protected void shoot(Tile tile, BulletType ammo){
TurretEntity entity = tile.ent();
entity.recoil = recoil;
entity.heat = 1f;
BulletType type = peekAmmo(tile);
tr.trns(entity.rotation, size * tilesize / 2);
Vector2 predict = Predict.intercept(tile, entity.target, type.speed);
float dst = entity.dst(predict.x, predict.y);
float maxTraveled = type.lifetime * type.speed;
for(int i = 0; i < shots; i++){
Bullet.create(ammo, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y,
entity.rotation + Mathf.range(inaccuracy + type.inaccuracy), 1f + Mathf.range(velocityInaccuracy), (dst / maxTraveled));
}
effects(tile);
useAmmo(tile);
}
}

View File

@@ -0,0 +1,37 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.entities.bullet.BulletType;
import mindustry.world.Tile;
import static mindustry.Vars.tilesize;
public class BurstTurret extends ItemTurret{
public float burstSpacing = 5;
public BurstTurret(String name){
super(name);
}
@Override
protected void shoot(Tile tile, BulletType ammo){
TurretEntity entity = tile.ent();
entity.heat = 1f;
for(int i = 0; i < shots; i++){
Time.run(burstSpacing * i, () -> {
if(!(tile.entity instanceof TurretEntity) ||
!hasAmmo(tile)) return;
entity.recoil = recoil;
tr.trns(entity.rotation, size * tilesize / 2, Mathf.range(xRand));
bullet(tile, ammo, entity.rotation + Mathf.range(inaccuracy));
effects(tile);
useAmmo(tile);
});
}
}
}

View File

@@ -0,0 +1,65 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.bullet.*;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
public class ChargeTurret extends PowerTurret{
public float chargeTime = 30f;
public int chargeEffects = 5;
public float chargeMaxDelay = 10f;
public Effect chargeEffect = Fx.none;
public Effect chargeBeginEffect = Fx.none;
public ChargeTurret(String name){
super(name);
entityType = LaserTurretEntity::new;
}
@Override
public void shoot(Tile tile, BulletType ammo){
LaserTurretEntity entity = tile.ent();
useAmmo(tile);
tr.trns(entity.rotation, size * tilesize / 2);
Effects.effect(chargeBeginEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
for(int i = 0; i < chargeEffects; i++){
Time.run(Mathf.random(chargeMaxDelay), () -> {
if(!isTurret(tile)) return;
tr.trns(entity.rotation, size * tilesize / 2);
Effects.effect(chargeEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
});
}
entity.shooting = true;
Time.run(chargeTime, () -> {
if(!isTurret(tile)) return;
tr.trns(entity.rotation, size * tilesize / 2);
entity.recoil = recoil;
entity.heat = 1f;
bullet(tile, ammo, entity.rotation + Mathf.range(inaccuracy));
effects(tile);
entity.shooting = false;
});
}
@Override
public boolean shouldTurn(Tile tile){
LaserTurretEntity entity = tile.ent();
return !entity.shooting;
}
public class LaserTurretEntity extends TurretEntity{
public boolean shooting;
}
}

View File

@@ -0,0 +1,64 @@
package mindustry.world.blocks.defense.turrets;
import arc.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.game.EventType.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.tilesize;
public class CooledTurret extends Turret{
/** How much reload is lowered by for each unit of liquid of heat capacity. */
public float coolantMultiplier = 5f;
public Effect coolEffect = Fx.fuelburn;
public CooledTurret(String name){
super(name);
hasLiquids = true;
liquidCapacity = 20f;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 0.2f)).update(false).boost();
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.booster, new BoosterListValue(reload, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, true, l -> consumes.liquidfilters.get(l.id)));
}
@Override
public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){
if(tile.entity.liquids.currentAmount() <= 0.001f){
Events.fire(Trigger.turretCool);
}
super.handleLiquid(tile, source, liquid, amount);
}
@Override
protected void updateShooting(Tile tile){
super.updateShooting(tile);
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
TurretEntity entity = tile.ent();
Liquid liquid = entity.liquids.current();
float used = Math.min(Math.min(entity.liquids.get(liquid), maxUsed * Time.delta()), Math.max(0, ((reload - entity.reload) / coolantMultiplier) / liquid.heatCapacity)) * baseReloadSpeed(tile);
entity.reload += used * liquid.heatCapacity * coolantMultiplier;
entity.liquids.remove(liquid, used);
if(Mathf.chance(0.06 * used)){
Effects.effect(coolEffect, tile.drawx() + Mathf.range(size * tilesize / 2f), tile.drawy() + Mathf.range(size * tilesize / 2f));
}
}
}

View File

@@ -0,0 +1,40 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.Mathf;
import mindustry.entities.bullet.BulletType;
import mindustry.world.Tile;
import mindustry.world.meta.BlockStat;
import mindustry.world.meta.StatUnit;
import static mindustry.Vars.tilesize;
public class DoubleTurret extends ItemTurret{
public float shotWidth = 2f;
public DoubleTurret(String name){
super(name);
shots = 2;
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.reload);
stats.add(BlockStat.reload, 60f / reload, StatUnit.none);
}
@Override
protected void shoot(Tile tile, BulletType ammo){
TurretEntity entity = tile.ent();
entity.shots++;
int i = Mathf.signs[entity.shots % 2];
tr.trns(entity.rotation - 90, shotWidth * i, size * tilesize / 2);
bullet(tile, ammo, entity.rotation + Mathf.range(inaccuracy));
effects(tile);
useAmmo(tile);
}
}

View File

@@ -0,0 +1,190 @@
package mindustry.world.blocks.defense.turrets;
import arc.*;
import arc.struct.*;
import arc.scene.ui.layout.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.bullet.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import java.io.*;
import static mindustry.Vars.*;
public class ItemTurret extends CooledTurret{
public int maxAmmo = 30;
public ObjectMap<Item, BulletType> ammo = new ObjectMap<>();
public ItemTurret(String name){
super(name);
hasItems = true;
entityType = ItemTurretEntity::new;
}
/** Initializes accepted ammo map. Format: [item1, bullet1, item2, bullet2...] */
protected void ammo(Object... objects){
ammo = OrderedMap.of(objects);
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.itemCapacity);
stats.add(BlockStat.ammo, new AmmoListValue<>(ammo));
consumes.add(new ConsumeItemFilter(i -> ammo.containsKey(i)){
@Override
public void build(Tile tile, Table table){
MultiReqImage image = new MultiReqImage();
content.items().each(i -> filter.get(i) && (!world.isZone() || data.isUnlocked(i)), item -> image.add(new ReqImage(new ItemImage(item.icon(Cicon.medium)),
() -> tile.entity != null && !((ItemTurretEntity)tile.entity).ammo.isEmpty() && ((ItemEntry)tile.<ItemTurretEntity>ent().ammo.peek()).item == item)));
table.add(image).size(8 * 4);
}
@Override
public boolean valid(TileEntity entity){
//valid when there's any ammo in the turret
return !((ItemTurretEntity)entity).ammo.isEmpty();
}
@Override
public void display(BlockStats stats){
//don't display
}
});
}
@Override
public void onProximityAdded(Tile tile){
super.onProximityAdded(tile);
//add first ammo item to cheaty blocks so they can shoot properly
if(tile.isEnemyCheat() && ammo.size > 0){
handleItem(ammo.entries().next().key, tile, tile);
}
}
@Override
public void displayBars(Tile tile, Table bars){
super.displayBars(tile, bars);
TurretEntity entity = tile.ent();
bars.add(new Bar("blocks.ammo", Pal.ammo, () -> (float)entity.totalAmmo / maxAmmo)).growX();
bars.row();
}
@Override
public int acceptStack(Item item, int amount, Tile tile, Unit source){
TurretEntity entity = tile.ent();
BulletType type = ammo.get(item);
if(type == null) return 0;
return Math.min((int)((maxAmmo - entity.totalAmmo) / ammo.get(item).ammoMultiplier), amount);
}
@Override
public void handleStack(Item item, int amount, Tile tile, Unit source){
for(int i = 0; i < amount; i++){
handleItem(item, tile, null);
}
}
//currently can't remove items from turrets.
@Override
public int removeStack(Tile tile, Item item, int amount){
return 0;
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
TurretEntity entity = tile.ent();
if(entity == null) return;
if(item == Items.pyratite){
Events.fire(Trigger.flameAmmo);
}
BulletType type = ammo.get(item);
entity.totalAmmo += type.ammoMultiplier;
//find ammo entry by type
for(int i = 0; i < entity.ammo.size; i++){
ItemEntry entry = (ItemEntry)entity.ammo.get(i);
//if found, put it to the right
if(entry.item == item){
entry.amount += type.ammoMultiplier;
entity.ammo.swap(i, entity.ammo.size - 1);
return;
}
}
//must not be found
entity.ammo.add(new ItemEntry(item, (int)type.ammoMultiplier));
//fire events for the tutorial
if(state.rules.tutorial){
Events.fire(new TurretAmmoDeliverEvent());
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
TurretEntity entity = tile.ent();
return ammo != null && ammo.get(item) != null && entity.totalAmmo + ammo.get(item).ammoMultiplier <= maxAmmo;
}
public class ItemTurretEntity extends TurretEntity{
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeByte(ammo.size);
for(AmmoEntry entry : ammo){
ItemEntry i = (ItemEntry)entry;
stream.writeByte(i.item.id);
stream.writeShort(i.amount);
}
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
byte amount = stream.readByte();
for(int i = 0; i < amount; i++){
Item item = Vars.content.item(stream.readByte());
short a = stream.readShort();
totalAmmo += a;
ammo.add(new ItemEntry(item, a));
}
}
}
class ItemEntry extends AmmoEntry{
protected Item item;
ItemEntry(Item item, int amount){
this.item = item;
this.amount = amount;
}
@Override
public BulletType type(){
return ammo.get(item);
}
}
}

View File

@@ -0,0 +1,114 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.type.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.tilesize;
public class LaserTurret extends PowerTurret{
public float firingMoveFract = 0.25f;
public float shootDuration = 100f;
public LaserTurret(String name){
super(name);
canOverdrive = false;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 0.01f)).update(false);
coolantMultiplier = 1f;
entityType = LaserTurretEntity::new;
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.booster);
stats.add(BlockStat.input, new BoosterListValue(reload, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, false, l -> consumes.liquidfilters.get(l.id)));
stats.remove(BlockStat.damage);
//damages every 5 ticks, at least in meltdown's case
stats.add(BlockStat.damage, shootType.damage * 60f / 5f, StatUnit.perSecond);
}
@Override
public void update(Tile tile){
super.update(tile);
LaserTurretEntity entity = tile.ent();
if(entity.bulletLife > 0 && entity.bullet != null){
tr.trns(entity.rotation, size * tilesize / 2f, 0f);
entity.bullet.rot(entity.rotation);
entity.bullet.set(tile.drawx() + tr.x, tile.drawy() + tr.y);
entity.bullet.time(0f);
entity.heat = 1f;
entity.recoil = recoil;
entity.bulletLife -= Time.delta();
if(entity.bulletLife <= 0f){
entity.bullet = null;
}
}
}
@Override
protected void updateShooting(Tile tile){
LaserTurretEntity entity = tile.ent();
if(entity.bulletLife > 0 && entity.bullet != null){
return;
}
if(entity.reload >= reload && (entity.cons.valid() || tile.isEnemyCheat())){
BulletType type = peekAmmo(tile);
shoot(tile, type);
entity.reload = 0f;
}else{
Liquid liquid = entity.liquids.current();
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
float used = baseReloadSpeed(tile) * (tile.isEnemyCheat() ? maxUsed : Math.min(entity.liquids.get(liquid), maxUsed * Time.delta())) * liquid.heatCapacity * coolantMultiplier;
entity.reload += used;
entity.liquids.remove(liquid, used);
if(Mathf.chance(0.06 * used)){
Effects.effect(coolEffect, tile.drawx() + Mathf.range(size * tilesize / 2f), tile.drawy() + Mathf.range(size * tilesize / 2f));
}
}
}
@Override
protected void turnToTarget(Tile tile, float targetRot){
LaserTurretEntity entity = tile.ent();
entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * entity.delta() * (entity.bulletLife > 0f ? firingMoveFract : 1f));
}
@Override
protected void bullet(Tile tile, BulletType type, float angle){
LaserTurretEntity entity = tile.ent();
entity.bullet = Bullet.create(type, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y, angle);
entity.bulletLife = shootDuration;
}
@Override
public boolean shouldActiveSound(Tile tile){
LaserTurretEntity entity = tile.ent();
return entity.bulletLife > 0 && entity.bullet != null;
}
class LaserTurretEntity extends TurretEntity{
Bullet bullet;
float bulletLife;
}
}

View File

@@ -0,0 +1,130 @@
package mindustry.world.blocks.defense.turrets;
import arc.struct.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
public class LiquidTurret extends Turret{
public ObjectMap<Liquid, BulletType> ammo = new ObjectMap<>();
public LiquidTurret(String name){
super(name);
hasLiquids = true;
activeSound = Sounds.spray;
}
/** Initializes accepted ammo map. Format: [liquid1, bullet1, liquid2, bullet2...] */
protected void ammo(Object... objects){
ammo = OrderedMap.of(objects);
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.ammo, new AmmoListValue<>(ammo));
consumes.add(new ConsumeLiquidFilter(i -> ammo.containsKey(i), 1f){
@Override
public boolean valid(TileEntity entity){
return !((TurretEntity)entity).ammo.isEmpty();
}
@Override
public void display(BlockStats stats){
}
});
}
@Override
public boolean shouldActiveSound(Tile tile){
TurretEntity entity = tile.ent();
return entity.target != null && hasAmmo(tile);
}
@Override
protected boolean validateTarget(Tile tile){
TurretEntity entity = tile.ent();
if(entity.liquids.current().canExtinguish() && entity.target instanceof Tile){
return Fire.has(((Tile)entity.target).x, ((Tile)entity.target).y);
}
return super.validateTarget(tile);
}
@Override
protected void findTarget(Tile tile){
TurretEntity entity = tile.ent();
if(entity.liquids.current().canExtinguish()){
int tr = (int)(range / tilesize);
for(int x = -tr; x <= tr; x++){
for(int y = -tr; y <= tr; y++){
if(Fire.has(x + tile.x, y + tile.y)){
entity.target = world.tile(x + tile.x, y + tile.y);
return;
}
}
}
}
super.findTarget(tile);
}
@Override
protected void effects(Tile tile){
BulletType type = peekAmmo(tile);
TurretEntity entity = tile.ent();
Effects.effect(type.shootEffect, entity.liquids.current().color, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
Effects.effect(type.smokeEffect, entity.liquids.current().color, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
//shootSound.at(tile);
if(shootShake > 0){
Effects.shake(shootShake, shootShake, tile.entity);
}
entity.recoil = recoil;
}
@Override
public BulletType useAmmo(Tile tile){
TurretEntity entity = tile.ent();
if(tile.isEnemyCheat()) return ammo.get(entity.liquids.current());
BulletType type = ammo.get(entity.liquids.current());
entity.liquids.remove(entity.liquids.current(), type.ammoMultiplier);
return type;
}
@Override
public BulletType peekAmmo(Tile tile){
return ammo.get(tile.entity.liquids.current());
}
@Override
public boolean hasAmmo(Tile tile){
TurretEntity entity = tile.ent();
return ammo.get(entity.liquids.current()) != null && entity.liquids.total() >= ammo.get(entity.liquids.current()).ammoMultiplier;
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return false;
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
return ammo.get(liquid) != null
&& (tile.entity.liquids.current() == liquid || (ammo.containsKey(tile.entity.liquids.current()) && tile.entity.liquids.get(tile.entity.liquids.current()) <= ammo.get(tile.entity.liquids.current()).ammoMultiplier + 0.001f));
}
}

View File

@@ -0,0 +1,52 @@
package mindustry.world.blocks.defense.turrets;
import arc.util.ArcAnnotate.*;
import mindustry.entities.bullet.BulletType;
import mindustry.world.Tile;
import mindustry.world.meta.BlockStat;
import mindustry.world.meta.StatUnit;
public class PowerTurret extends CooledTurret{
public @NonNull BulletType shootType;
public float powerUse = 1f;
public PowerTurret(String name){
super(name);
hasPower = true;
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.damage, shootType.damage, StatUnit.none);
}
@Override
public void init(){
consumes.powerCond(powerUse, entity -> ((TurretEntity)entity).target != null);
super.init();
}
@Override
public BulletType useAmmo(Tile tile){
//nothing used directly
return shootType;
}
@Override
public boolean hasAmmo(Tile tile){
//you can always rotate, but never shoot if there's no power
return true;
}
@Override
public BulletType peekAmmo(Tile tile){
return shootType;
}
@Override
protected float baseReloadSpeed(Tile tile){
return tile.isEnemyCheat() ? 1f : tile.entity.power.status;
}
}

View File

@@ -0,0 +1,324 @@
package mindustry.world.blocks.defense.turrets;
import arc.Core;
import arc.audio.*;
import arc.struct.Array;
import arc.struct.EnumSet;
import arc.func.Cons2;
import arc.graphics.Blending;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.Angles;
import arc.math.Mathf;
import arc.math.geom.Vector2;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.*;
import mindustry.entities.Effects.Effect;
import mindustry.entities.type.Bullet;
import mindustry.entities.bullet.BulletType;
import mindustry.entities.traits.TargetTrait;
import mindustry.entities.type.TileEntity;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
public abstract class Turret extends Block{
public final int timerTarget = timers++;
public int targetInterval = 20;
public Color heatColor = Pal.turretHeat;
public Effect shootEffect = Fx.none;
public Effect smokeEffect = Fx.none;
public Effect ammoUseEffect = Fx.none;
public Sound shootSound = Sounds.shoot;
public int ammoPerShot = 1;
public float ammoEjectBack = 1f;
public float range = 50f;
public float reload = 10f;
public float inaccuracy = 0f;
public int shots = 1;
public float spread = 4f;
public float recoil = 1f;
public float restitution = 0.02f;
public float cooldown = 0.02f;
public float rotatespeed = 5f; //in degrees per tick
public float shootCone = 8f;
public float shootShake = 0f;
public float xRand = 0f;
public boolean targetAir = true;
public boolean targetGround = true;
protected Vector2 tr = new Vector2();
protected Vector2 tr2 = new Vector2();
public TextureRegion baseRegion, heatRegion;
public Cons2<Tile, TurretEntity> drawer = (tile, entity) -> Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90);
public Cons2<Tile, TurretEntity> heatDrawer = (tile, entity) -> {
if(entity.heat <= 0.00001f) return;
Draw.color(heatColor, entity.heat);
Draw.blend(Blending.additive);
Draw.rect(heatRegion, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90);
Draw.blend();
Draw.color();
};
public Turret(String name){
super(name);
priority = TargetPriority.turret;
update = true;
solid = true;
layer = Layer.turret;
group = BlockGroup.turrets;
flags = EnumSet.of(BlockFlag.turret);
outlineIcon = true;
entityType = TurretEntity::new;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void load(){
super.load();
region = Core.atlas.find(name);
baseRegion = Core.atlas.find("block-" + size);
heatRegion = Core.atlas.find(name + "-heat");
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.shootRange, range / tilesize, StatUnit.blocks);
stats.add(BlockStat.inaccuracy, (int)inaccuracy, StatUnit.degrees);
stats.add(BlockStat.reload, 60f / reload, StatUnit.none);
stats.add(BlockStat.shots, shots, StatUnit.none);
stats.add(BlockStat.targetsAir, targetAir);
stats.add(BlockStat.targetsGround, targetGround);
}
@Override
public void draw(Tile tile){
Draw.rect(baseRegion, tile.drawx(), tile.drawy());
Draw.color();
}
@Override
public void drawLayer(Tile tile){
TurretEntity entity = tile.ent();
tr2.trns(entity.rotation, -entity.recoil);
drawer.get(tile, entity);
if(heatRegion != Core.atlas.find("error")){
heatDrawer.get(tile, entity);
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find("block-" + size), Core.atlas.find(name)};
}
@Override
public void drawSelect(Tile tile){
Drawf.dashCircle(tile.drawx(), tile.drawy(), range, tile.getTeam().color);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize + offset(), y * tilesize + offset(), range, Pal.placing);
}
@Override
public void update(Tile tile){
TurretEntity entity = tile.ent();
if(!validateTarget(tile)) entity.target = null;
entity.recoil = Mathf.lerpDelta(entity.recoil, 0f, restitution);
entity.heat = Mathf.lerpDelta(entity.heat, 0f, cooldown);
if(hasAmmo(tile)){
if(entity.timer.get(timerTarget, targetInterval)){
findTarget(tile);
}
if(validateTarget(tile)){
BulletType type = peekAmmo(tile);
float speed = type.speed;
if(speed < 0.1f) speed = 9999999f;
Vector2 result = Predict.intercept(entity, entity.target, speed);
if(result.isZero()){
result.set(entity.target.getX(), entity.target.getY());
}
float targetRot = result.sub(tile.drawx(), tile.drawy()).angle();
if(Float.isNaN(entity.rotation)){
entity.rotation = 0;
}
if(shouldTurn(tile)){
turnToTarget(tile, targetRot);
}
if(Angles.angleDist(entity.rotation, targetRot) < shootCone){
updateShooting(tile);
}
}
}
}
protected boolean validateTarget(Tile tile){
TurretEntity entity = tile.ent();
return !Units.invalidateTarget(entity.target, tile.getTeam(), tile.drawx(), tile.drawy());
}
protected void findTarget(Tile tile){
TurretEntity entity = tile.ent();
if(targetAir && !targetGround){
entity.target = Units.closestEnemy(tile.getTeam(), tile.drawx(), tile.drawy(), range, e -> !e.isDead() && e.isFlying());
}else{
entity.target = Units.closestTarget(tile.getTeam(), tile.drawx(), tile.drawy(), range, e -> !e.isDead() && (!e.isFlying() || targetAir) && (e.isFlying() || targetGround));
}
}
protected void turnToTarget(Tile tile, float targetRot){
TurretEntity entity = tile.ent();
entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * entity.delta() * baseReloadSpeed(tile));
}
public boolean shouldTurn(Tile tile){
return true;
}
/** Consume ammo and return a type. */
public BulletType useAmmo(Tile tile){
if(tile.isEnemyCheat()) return peekAmmo(tile);
TurretEntity entity = tile.ent();
AmmoEntry entry = entity.ammo.peek();
entry.amount -= ammoPerShot;
if(entry.amount == 0) entity.ammo.pop();
entity.totalAmmo -= ammoPerShot;
Time.run(reload / 2f, () -> ejectEffects(tile));
return entry.type();
}
/**
* Get the ammo type that will be returned if useAmmo is called.
*/
public BulletType peekAmmo(Tile tile){
TurretEntity entity = tile.ent();
return entity.ammo.peek().type();
}
/**
* Returns whether the turret has ammo.
*/
public boolean hasAmmo(Tile tile){
TurretEntity entity = tile.ent();
return entity.ammo.size > 0 && entity.ammo.peek().amount >= ammoPerShot;
}
protected void updateShooting(Tile tile){
TurretEntity entity = tile.ent();
if(entity.reload >= reload){
BulletType type = peekAmmo(tile);
shoot(tile, type);
entity.reload = 0f;
}else{
entity.reload += tile.entity.delta() * peekAmmo(tile).reloadMultiplier * baseReloadSpeed(tile);
}
}
protected void shoot(Tile tile, BulletType type){
TurretEntity entity = tile.ent();
entity.recoil = recoil;
entity.heat = 1f;
tr.trns(entity.rotation, size * tilesize / 2f, Mathf.range(xRand));
for(int i = 0; i < shots; i++){
bullet(tile, type, entity.rotation + Mathf.range(inaccuracy + type.inaccuracy) + (i - shots / 2) * spread);
}
effects(tile);
useAmmo(tile);
}
protected void bullet(Tile tile, BulletType type, float angle){
Bullet.create(type, tile.entity, tile.getTeam(), tile.drawx() + tr.x, tile.drawy() + tr.y, angle);
}
protected void effects(Tile tile){
Effect shootEffect = this.shootEffect == Fx.none ? peekAmmo(tile).shootEffect : this.shootEffect;
Effect smokeEffect = this.smokeEffect == Fx.none ? peekAmmo(tile).smokeEffect : this.smokeEffect;
TurretEntity entity = tile.ent();
Effects.effect(shootEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
Effects.effect(smokeEffect, tile.drawx() + tr.x, tile.drawy() + tr.y, entity.rotation);
shootSound.at(tile, Mathf.random(0.9f, 1.1f));
if(shootShake > 0){
Effects.shake(shootShake, shootShake, tile.entity);
}
entity.recoil = recoil;
}
protected void ejectEffects(Tile tile){
if(!isTurret(tile)) return;
TurretEntity entity = tile.ent();
Effects.effect(ammoUseEffect, tile.drawx() - Angles.trnsx(entity.rotation, ammoEjectBack),
tile.drawy() - Angles.trnsy(entity.rotation, ammoEjectBack), entity.rotation);
}
protected float baseReloadSpeed(Tile tile){
return 1f;
}
protected boolean isTurret(Tile tile){
return (tile.entity instanceof TurretEntity);
}
public static abstract class AmmoEntry{
public int amount;
public abstract BulletType type();
}
public static class TurretEntity extends TileEntity{
public Array<AmmoEntry> ammo = new Array<>();
public int totalAmmo;
public float reload;
public float rotation = 90;
public float recoil = 0f;
public float heat;
public int shots;
public TargetTrait target;
}
}

View File

@@ -0,0 +1,21 @@
package mindustry.world.blocks.distribution;
import mindustry.type.*;
import mindustry.world.*;
public class ArmoredConveyor extends Conveyor{
public ArmoredConveyor(String name){
super(name);
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return super.acceptItem(item, tile, source) && (source.block() instanceof Conveyor || Edges.getFacingEdge(source, tile).relativeTo(tile) == tile.rotation());
}
@Override
public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock) {
return otherblock.outputsItems() && blendsArmored(tile, rotation, otherx, othery, otherrot, otherblock);
}
}

View File

@@ -0,0 +1,55 @@
package mindustry.world.blocks.distribution;
import arc.math.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
public class BufferedItemBridge extends ExtendingItemBridge{
public final int timerAccept = timers++;
public float speed = 40f;
public int bufferCapacity = 50;
public BufferedItemBridge(String name){
super(name);
hasPower = false;
hasItems = true;
entityType = BufferedItemBridgeEntity::new;
}
@Override
public void updateTransport(Tile tile, Tile other){
BufferedItemBridgeEntity entity = tile.ent();
if(entity.buffer.accepts() && entity.items.total() > 0){
entity.buffer.accept(entity.items.take());
}
Item item = entity.buffer.poll();
if(entity.timer.get(timerAccept, 4) && item != null && other.block().acceptItem(item, other, tile)){
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f);
other.block().handleItem(item, other, tile);
entity.buffer.remove();
}else{
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 0f, 0.008f);
}
}
class BufferedItemBridgeEntity extends ItemBridgeEntity{
ItemBuffer buffer = new ItemBuffer(bufferCapacity, speed);
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
buffer.write(stream);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
buffer.read(stream);
}
}
}

View File

@@ -0,0 +1,449 @@
package mindustry.world.blocks.distribution;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class Conveyor extends Block implements Autotiler{
private static final float itemSpace = 0.4f;
private static final float minmove = 1f / (Short.MAX_VALUE - 2);
private static ItemPos drawpos = new ItemPos();
private static ItemPos pos1 = new ItemPos();
private static ItemPos pos2 = new ItemPos();
private final Vector2 tr1 = new Vector2();
private final Vector2 tr2 = new Vector2();
private TextureRegion[][] regions = new TextureRegion[7][4];
public float speed = 0f;
protected Conveyor(String name){
super(name);
rotate = true;
update = true;
layer = Layer.overlay;
group = BlockGroup.transportation;
hasItems = true;
itemCapacity = 4;
conveyorPlacement = true;
entityType = ConveyorEntity::new;
idleSound = Sounds.conveyor;
idleSoundVolume = 0.004f;
unloadable = false;
}
private static int compareItems(long a, long b){
pos1.set(a, ItemPos.packShorts);
pos2.set(b, ItemPos.packShorts);
return Float.compare(pos1.y, pos2.y);
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.itemsMoved, speed * 60 / itemSpace, StatUnit.itemsSecond);
}
@Override
public void load(){
super.load();
for(int i = 0; i < regions.length; i++){
for(int j = 0; j < 4; j++){
regions[i][j] = Core.atlas.find(name + "-" + i + "-" + j);
}
}
}
@Override
public void draw(Tile tile){
ConveyorEntity entity = tile.ent();
byte rotation = tile.rotation();
int frame = entity.clogHeat <= 0.5f ? (int)(((Time.time() * speed * 8f * entity.timeScale)) % 4) : 0;
Draw.rect(regions[Mathf.clamp(entity.blendbits, 0, regions.length - 1)][Mathf.clamp(frame, 0, regions[0].length - 1)], tile.drawx(), tile.drawy(),
tilesize * entity.blendsclx, tilesize * entity.blendscly, rotation * 90);
}
@Override
public boolean shouldIdleSound(Tile tile){
ConveyorEntity entity = tile.ent();
return entity.clogHeat <= 0.5f ;
}
@Override
public void onProximityUpdate(Tile tile){
super.onProximityUpdate(tile);
ConveyorEntity entity = tile.ent();
int[] bits = buildBlending(tile, tile.rotation(), null, true);
entity.blendbits = bits[0];
entity.blendsclx = bits[1];
entity.blendscly = bits[2];
}
@Override
public void drawRequestRegion(BuildRequest req, Eachable<BuildRequest> list){
int[] bits = getTiling(req, list);
if(bits == null) return;
TextureRegion region = regions[bits[0]][0];
Draw.rect(region, req.drawx(), req.drawy(), region.getWidth() * bits[1] * Draw.scl * req.animScale, region.getHeight() * bits[2] * Draw.scl * req.animScale, req.rotation * 90);
}
@Override
public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return otherblock.outputsItems() && lookingAt(tile, rotation, otherx, othery, otherrot, otherblock);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name + "-0-0")};
}
@Override
public void drawLayer(Tile tile){
ConveyorEntity entity = tile.ent();
byte rotation = tile.rotation();
try{
for(int i = 0; i < entity.convey.size; i++){
ItemPos pos = drawpos.set(entity.convey.get(i), ItemPos.drawShorts);
if(pos.item == null) continue;
tr1.trns(rotation * 90, tilesize, 0);
tr2.trns(rotation * 90, -tilesize / 2f, pos.x * tilesize / 2f);
Draw.rect(pos.item.icon(Cicon.medium),
(tile.x * tilesize + tr1.x * pos.y + tr2.x),
(tile.y * tilesize + tr1.y * pos.y + tr2.y), itemSize, itemSize);
}
}catch(IndexOutOfBoundsException e){
Log.err(e);
}
}
@Override
public void unitOn(Tile tile, Unit unit){
ConveyorEntity entity = tile.ent();
if(entity.clogHeat > 0.5f){
return;
}
entity.noSleep();
float speed = this.speed * tilesize / 2.4f;
float centerSpeed = 0.1f;
float centerDstScl = 3f;
float tx = Geometry.d4[tile.rotation()].x, ty = Geometry.d4[tile.rotation()].y;
float centerx = 0f, centery = 0f;
if(Math.abs(tx) > Math.abs(ty)){
centery = Mathf.clamp((tile.worldy() - unit.y) / centerDstScl, -centerSpeed, centerSpeed);
if(Math.abs(tile.worldy() - unit.y) < 1f) centery = 0f;
}else{
centerx = Mathf.clamp((tile.worldx() - unit.x) / centerDstScl, -centerSpeed, centerSpeed);
if(Math.abs(tile.worldx() - unit.x) < 1f) centerx = 0f;
}
if(entity.convey.size * itemSpace < 0.9f){
unit.applyImpulse((tx * speed + centerx) * entity.delta(), (ty * speed + centery) * entity.delta());
}
}
@Override
public void update(Tile tile){
ConveyorEntity entity = tile.ent();
entity.minitem = 1f;
Tile next = tile.getNearby(tile.rotation());
if(next != null) next = next.link();
float nextMax = next != null && next.block() instanceof Conveyor && next.block().acceptItem(null, next, tile) ? 1f - Math.max(itemSpace - next.<ConveyorEntity>ent().minitem, 0) : 1f;
int minremove = Integer.MAX_VALUE;
for(int i = entity.convey.size - 1; i >= 0; i--){
long value = entity.convey.get(i);
ItemPos pos = pos1.set(value, ItemPos.updateShorts);
//..this should never happen, but in case it does, remove it and stop here
if(pos.item == null){
entity.convey.removeValue(value);
break;
}
float nextpos = (i == entity.convey.size - 1 ? 100f : pos2.set(entity.convey.get(i + 1), ItemPos.updateShorts).y) - itemSpace;
float maxmove = Math.min(nextpos - pos.y, speed * entity.delta());
if(maxmove > minmove){
pos.y += maxmove;
if(Mathf.equal(pos.x, 0, 0.1f)){
pos.x = 0f;
}
pos.x = Mathf.lerpDelta(pos.x, 0, 0.1f);
}
pos.y = Mathf.clamp(pos.y, 0, nextMax);
if(pos.y >= 0.9999f && offloadDir(tile, pos.item)){
if(next != null && next.block() instanceof Conveyor){
ConveyorEntity othere = next.ent();
ItemPos ni = pos2.set(othere.convey.get(othere.lastInserted), ItemPos.updateShorts);
if(next.rotation() == tile.rotation()){
ni.x = pos.x;
}
othere.convey.set(othere.lastInserted, ni.pack());
}
minremove = Math.min(i, minremove);
tile.entity.items.remove(pos.item, 1);
}else{
value = pos.pack();
if(pos.y < entity.minitem)
entity.minitem = pos.y;
entity.convey.set(i, value);
}
}
if(entity.minitem < itemSpace){
entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 1f, 0.02f);
}else{
entity.clogHeat = Mathf.lerpDelta(entity.clogHeat, 0f, 1f);
}
if(entity.items.total() == 0){
entity.sleep();
}else{
entity.noSleep();
}
if(minremove != Integer.MAX_VALUE) entity.convey.truncate(minremove);
}
@Override
public boolean isAccessible(){
return true;
}
@Override
public Block getReplacement(BuildRequest req, Array<BuildRequest> requests){
Boolf<Point2> cont = p -> requests.contains(o -> o.x == req.x + p.x && o.y == req.y + p.y && o.rotation == req.rotation && (req.block instanceof Conveyor || req.block instanceof Junction));
return cont.get(Geometry.d4(req.rotation)) &&
cont.get(Geometry.d4(req.rotation - 2)) &&
req.tile() != null &&
req.tile().block() instanceof Conveyor &&
Mathf.mod(req.tile().rotation() - req.rotation, 2) == 1 ? Blocks.junction : this;
}
@Override
public int removeStack(Tile tile, Item item, int amount){
ConveyorEntity entity = tile.ent();
entity.noSleep();
int removed = 0;
for(int j = 0; j < amount; j++){
for(int i = 0; i < entity.convey.size; i++){
long val = entity.convey.get(i);
ItemPos pos = pos1.set(val, ItemPos.drawShorts);
if(pos.item == item){
entity.convey.removeValue(val);
entity.items.remove(item, 1);
removed++;
break;
}
}
}
return removed;
}
@Override
public void getStackOffset(Item item, Tile tile, Vector2 trns){
trns.trns(tile.rotation() * 90 + 180f, tilesize / 2f);
}
@Override
public int acceptStack(Item item, int amount, Tile tile, Unit source){
ConveyorEntity entity = tile.ent();
return Math.min((int)(entity.minitem / itemSpace), amount);
}
@Override
public void handleStack(Item item, int amount, Tile tile, Unit source){
ConveyorEntity entity = tile.ent();
for(int i = amount - 1; i >= 0; i--){
long result = ItemPos.packItem(item, 0f, i * itemSpace);
entity.convey.insert(0, result);
entity.items.add(item, 1);
}
entity.noSleep();
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.rotation());
float minitem = tile.<ConveyorEntity>ent().minitem;
return (((direction == 0) && minitem > itemSpace) ||
((direction % 2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.rotation() + 2) % 4 == tile.rotation()));
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
byte rotation = tile.rotation();
int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation);
int ang = ((source.relativeTo(tile.x, tile.y) - rotation));
float pos = ch == 0 ? 0 : ch % 2 == 1 ? 0.5f : 1f;
float y = (ang == -1 || ang == 3) ? 1 : (ang == 1 || ang == -3) ? -1 : 0;
ConveyorEntity entity = tile.ent();
entity.noSleep();
long result = ItemPos.packItem(item, y * 0.9f, pos);
tile.entity.items.add(item, 1);
for(int i = 0; i < entity.convey.size; i++){
if(compareItems(result, entity.convey.get(i)) < 0){
entity.convey.insert(i, result);
entity.lastInserted = (byte)i;
return;
}
}
//this item must be greater than anything there...
entity.convey.add(result);
entity.lastInserted = (byte)(entity.convey.size - 1);
}
public static class ConveyorEntity extends TileEntity{
LongArray convey = new LongArray();
byte lastInserted;
float minitem = 1;
int blendbits;
int blendsclx, blendscly;
float clogHeat = 0f;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeInt(convey.size);
for(int i = 0; i < convey.size; i++){
stream.writeInt(ItemPos.toInt(convey.get(i)));
}
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
convey.clear();
int amount = stream.readInt();
convey.ensureCapacity(Math.min(amount, 10));
for(int i = 0; i < amount; i++){
convey.add(ItemPos.toLong(stream.readInt()));
}
}
}
//Container class. Do not instantiate.
static class ItemPos{
private static short[] writeShort = new short[4];
private static byte[] writeByte = new byte[4];
private static short[] packShorts = new short[4];
private static short[] drawShorts = new short[4];
private static short[] updateShorts = new short[4];
Item item;
float x, y;
private ItemPos(){
}
static long packItem(Item item, float x, float y){
short[] shorts = packShorts;
shorts[0] = (short)item.id;
shorts[1] = (short)(x * Short.MAX_VALUE);
shorts[2] = (short)((y - 1f) * Short.MAX_VALUE);
return Pack.longShorts(shorts);
}
static int toInt(long value){
short[] values = Pack.shorts(value, writeShort);
short itemid = values[0];
float x = values[1] / (float)Short.MAX_VALUE;
float y = ((float)values[2]) / Short.MAX_VALUE + 1f;
byte[] bytes = writeByte;
bytes[0] = (byte)itemid;
bytes[1] = (byte)(x * 127);
bytes[2] = (byte)(y * 255 - 128);
return Pack.intBytes(bytes);
}
static long toLong(int value){
byte[] values = Pack.bytes(value, writeByte);
short itemid = content.item(values[0]).id;
float x = values[1] / 127f;
float y = ((int)values[2] + 128) / 255f;
short[] shorts = writeShort;
shorts[0] = itemid;
shorts[1] = (short)(x * Short.MAX_VALUE);
shorts[2] = (short)((y - 1f) * Short.MAX_VALUE);
return Pack.longShorts(shorts);
}
ItemPos set(long lvalue, short[] values){
Pack.shorts(lvalue, values);
if(values[0] >= content.items().size || values[0] < 0)
item = null;
else
item = content.items().get(values[0]);
x = values[1] / (float)Short.MAX_VALUE;
y = ((float)values[2]) / Short.MAX_VALUE + 1f;
return this;
}
long pack(){
return packItem(item, x, y);
}
}
}

View File

@@ -0,0 +1,60 @@
package mindustry.world.blocks.distribution;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import arc.math.geom.Geometry;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class ExtendingItemBridge extends ItemBridge{
public ExtendingItemBridge(String name){
super(name);
hasItems = true;
}
@Override
public void drawLayer(Tile tile){
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)) return;
int i = tile.absoluteRelativeTo(other.x, other.y);
float ex = other.worldx() - tile.worldx() - Geometry.d4[i].x * tilesize / 2f,
ey = other.worldy() - tile.worldy() - Geometry.d4[i].y * tilesize / 2f;
float uptime = state.isEditor() ? 1f : entity.uptime;
ex *= uptime;
ey *= uptime;
Lines.stroke(8f);
Lines.line(bridgeRegion,
tile.worldx() + Geometry.d4[i].x * tilesize / 2f,
tile.worldy() + Geometry.d4[i].y * tilesize / 2f,
tile.worldx() + ex,
tile.worldy() + ey, CapStyle.none, 0f);
Draw.rect(endRegion, tile.drawx(), tile.drawy(), i * 90 + 90);
Draw.rect(endRegion,
tile.worldx() + ex + Geometry.d4[i].x * tilesize / 2f,
tile.worldy() + ey + Geometry.d4[i].y * tilesize / 2f, i * 90 + 270);
int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y));
int arrows = (dist) * tilesize / 6 - 1;
Draw.color();
for(int a = 0; a < arrows; a++){
Draw.alpha(Mathf.absin(a / (float)arrows - entity.time / 100f, 0.1f, 1f) * uptime);
Draw.rect(arrowRegion,
tile.worldx() + Geometry.d4[i].x * (tilesize / 2f + a * 6f + 2) * uptime,
tile.worldy() + Geometry.d4[i].y * (tilesize / 2f + a * 6f + 2) * uptime, i * 90f);
}
Draw.reset();
}
}

View File

@@ -0,0 +1,399 @@
package mindustry.world.blocks.distribution;
import arc.*;
import arc.struct.*;
import arc.struct.IntSet.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class ItemBridge extends Block{
public final int timerTransport = timers++;
public int range;
public float transportTime = 2f;
public TextureRegion endRegion, bridgeRegion, arrowRegion;
private static BuildRequest otherReq;
private static int lastPlaced = Pos.invalid;
public ItemBridge(String name){
super(name);
update = true;
solid = true;
hasPower = true;
layer = Layer.power;
expanded = true;
itemCapacity = 10;
posConfig = true;
configurable = true;
hasItems = true;
unloadable = false;
group = BlockGroup.transportation;
entityType = ItemBridgeEntity::new;
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<ItemBridgeEntity>ent().link = value;
}
@Override
public void load(){
super.load();
endRegion = Core.atlas.find(name + "-end");
bridgeRegion = Core.atlas.find(name + "-bridge");
arrowRegion = Core.atlas.find(name + "-arrow");
}
@Override
public void drawRequestConfigTop(BuildRequest req, Eachable<BuildRequest> list){
otherReq = null;
list.each(other -> {
if(other.block == this && req.config == Pos.get(other.x, other.y)){
otherReq = other;
}
});
if(otherReq == null) return;
Lines.stroke(8f);
Lines.line(bridgeRegion,
req.drawx(),
req.drawy(),
otherReq.drawx(),
otherReq.drawy(), CapStyle.none, -tilesize / 2f);
Draw.rect(arrowRegion, (req.drawx() + otherReq.drawx()) / 2f, (req.drawy() + otherReq.drawy()) / 2f,
Angles.angle(req.drawx(), req.drawy(), otherReq.drawx(), otherReq.drawy()));
}
@Override
public void playerPlaced(Tile tile){
Tile link = findLink(tile.x, tile.y);
if(linkValid(tile, link)){
link.configure(tile.pos());
}
lastPlaced = tile.pos();
}
public Tile findLink(int x, int y){
if(world.tile(x, y) != null && linkValid(world.tile(x, y), world.tile(lastPlaced)) && lastPlaced != Pos.get(x, y)){
return world.tile(lastPlaced);
}
return null;
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Tile link = findLink(x, y);
Lines.stroke(2f, Pal.placing);
for(int i = 0; i < 4; i++){
Lines.dashLine(
x * tilesize + Geometry.d4[i].x * (tilesize / 2f + 2),
y * tilesize + Geometry.d4[i].y * (tilesize / 2f + 2),
x * tilesize + Geometry.d4[i].x * (range + 0.5f) * tilesize,
y * tilesize + Geometry.d4[i].y * (range + 0.5f) * tilesize,
range);
}
Draw.reset();
Draw.color(Pal.placing);
Lines.stroke(1f);
if(link != null){
int rot = link.absoluteRelativeTo(x, y);
float w = (link.x == x ? tilesize : Math.abs(link.x - x) * tilesize - tilesize);
float h = (link.y == y ? tilesize : Math.abs(link.y - y) * tilesize - tilesize);
Lines.rect((x + link.x) / 2f * tilesize - w / 2f, (y + link.y) / 2f * tilesize - h / 2f, w, h);
Draw.rect("bridge-arrow", link.x * tilesize + Geometry.d4[rot].x * tilesize, link.y * tilesize + Geometry.d4[rot].y * tilesize, link.absoluteRelativeTo(x, y) * 90);
}
Draw.reset();
}
@Override
public void drawConfigure(Tile tile){
ItemBridgeEntity entity = tile.ent();
Draw.color(Pal.accent);
Lines.stroke(1f);
Lines.square(tile.drawx(), tile.drawy(),
tile.block().size * tilesize / 2f + 1f);
for(int i = 1; i <= range; i++){
for(int j = 0; j < 4; j++){
Tile other = tile.getNearby(Geometry.d4[j].x * i, Geometry.d4[j].y * i);
if(linkValid(tile, other)){
boolean linked = other.pos() == entity.link;
Draw.color(linked ? Pal.place : Pal.breakInvalid);
Lines.square(other.drawx(), other.drawy(),
other.block().size * tilesize / 2f + 1f + (linked ? 0f : Mathf.absin(Time.time(), 4f, 1f)));
}
}
}
Draw.reset();
}
@Override
public boolean onConfigureTileTapped(Tile tile, Tile other){
ItemBridgeEntity entity = tile.ent();
if(linkValid(tile, other)){
if(entity.link == other.pos()){
tile.configure(Pos.invalid);
}else{
tile.configure(other.pos());
}
return false;
}
return true;
}
@Override
public void update(Tile tile){
ItemBridgeEntity entity = tile.ent();
entity.time += entity.cycleSpeed * entity.delta();
entity.time2 += (entity.cycleSpeed - 1f) * entity.delta();
IntSetIterator it = entity.incoming.iterator();
while(it.hasNext){
int i = it.next();
Tile other = world.tile(i);
if(!linkValid(tile, other, false) || other.<ItemBridgeEntity>ent().link != tile.pos()){
it.remove();
}
}
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)){
tryDump(tile);
entity.uptime = 0f;
}else{
((ItemBridgeEntity)world.tile(entity.link).entity).incoming.add(tile.pos());
if(entity.cons.valid() && Mathf.zero(1f - entity.efficiency())){
entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f);
}else{
entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f);
}
updateTransport(tile, other);
}
}
public void updateTransport(Tile tile, Tile other){
ItemBridgeEntity entity = tile.ent();
if(entity.uptime >= 0.5f && entity.timer.get(timerTransport, transportTime)){
Item item = entity.items.take();
if(item != null && other.block().acceptItem(item, other, tile)){
other.block().handleItem(item, other, tile);
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f);
}else{
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f);
if(item != null) entity.items.add(item, 1);
}
}
}
@Override
public void drawLayer(Tile tile){
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)) return;
int i = tile.absoluteRelativeTo(other.x, other.y);
Draw.color(Color.white, Color.black, Mathf.absin(Time.time(), 6f, 0.07f));
Draw.alpha(Math.max(entity.uptime, 0.25f));
Draw.rect(endRegion, tile.drawx(), tile.drawy(), i * 90 + 90);
Draw.rect(endRegion, other.drawx(), other.drawy(), i * 90 + 270);
Lines.stroke(8f);
Lines.line(bridgeRegion,
tile.worldx(),
tile.worldy(),
other.worldx(),
other.worldy(), CapStyle.none, -tilesize / 2f);
int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y));
float time = entity.time2 / 1.7f;
int arrows = (dist) * tilesize / 4 - 2;
Draw.color();
for(int a = 0; a < arrows; a++){
Draw.alpha(Mathf.absin(a / (float)arrows - entity.time / 100f, 0.1f, 1f) * entity.uptime);
Draw.rect(arrowRegion,
tile.worldx() + Geometry.d4[i].x * (tilesize / 2f + a * 4f + time % 4f),
tile.worldy() + Geometry.d4[i].y * (tilesize / 2f + a * 4f + time % 4f), i * 90f);
}
Draw.reset();
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
if(tile.getTeam() != source.getTeam()) return false;
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(linkValid(tile, other)){
int rel = tile.absoluteRelativeTo(other.x, other.y);
int rel2 = tile.relativeTo(source.x, source.y);
if(rel == rel2) return false;
}else{
return source.block() instanceof ItemBridge && source.<ItemBridgeEntity>ent().link == tile.pos() && tile.entity.items.total() < itemCapacity;
}
return tile.entity.items.total() < itemCapacity;
}
@Override
public boolean canDumpLiquid(Tile tile, Tile to, Liquid liquid){
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)){
Tile edge = Edges.getFacingEdge(to, tile);
int i = tile.absoluteRelativeTo(edge.x, edge.y);
IntSetIterator it = entity.incoming.iterator();
while(it.hasNext){
int v = it.next();
if(tile.absoluteRelativeTo(Pos.x(v), Pos.y(v)) == i){
return false;
}
}
return true;
}
int rel = tile.absoluteRelativeTo(other.x, other.y);
int rel2 = tile.relativeTo(to.x, to.y);
return rel != rel2;
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
if(tile.getTeam() != source.getTeam() || !hasLiquids) return false;
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(linkValid(tile, other)){
int rel = tile.absoluteRelativeTo(other.x, other.y);
int rel2 = tile.relativeTo(source.x, source.y);
if(rel == rel2) return false;
}else if(!(source.block() instanceof ItemBridge && source.<ItemBridgeEntity>ent().link == tile.pos())){
return false;
}
return tile.entity.liquids.get(liquid) + amount < liquidCapacity && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.2f);
}
@Override
public boolean canDump(Tile tile, Tile to, Item item){
ItemBridgeEntity entity = tile.ent();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)){
Tile edge = Edges.getFacingEdge(to, tile);
int i = tile.absoluteRelativeTo(edge.x, edge.y);
IntSetIterator it = entity.incoming.iterator();
while(it.hasNext){
int v = it.next();
if(tile.absoluteRelativeTo(Pos.x(v), Pos.y(v)) == i){
return false;
}
}
return true;
}
int rel = tile.absoluteRelativeTo(other.x, other.y);
int rel2 = tile.relativeTo(to.x, to.y);
return rel != rel2;
}
public boolean linkValid(Tile tile, Tile other){
return linkValid(tile, other, true);
}
public boolean linkValid(Tile tile, Tile other, boolean checkDouble){
if(other == null || tile == null) return false;
if(tile.x == other.x){
if(Math.abs(tile.y - other.y) > range) return false;
}else if(tile.y == other.y){
if(Math.abs(tile.x - other.x) > range) return false;
}else{
return false;
}
return other.block() == this && (!checkDouble || other.<ItemBridgeEntity>ent().link != tile.pos());
}
public static class ItemBridgeEntity extends TileEntity{
public int link = Pos.invalid;
public IntSet incoming = new IntSet();
public float uptime;
public float time;
public float time2;
public float cycleSpeed = 1f;
@Override
public int config(){
return link;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeInt(link);
stream.writeFloat(uptime);
stream.writeByte(incoming.size);
IntSetIterator it = incoming.iterator();
while(it.hasNext){
stream.writeInt(it.next());
}
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
link = stream.readInt();
uptime = stream.readFloat();
byte links = stream.readByte();
for(int i = 0; i < links; i++){
incoming.add(stream.readInt());
}
}
}
}

View File

@@ -0,0 +1,106 @@
package mindustry.world.blocks.distribution;
import arc.util.Time;
import mindustry.entities.type.TileEntity;
import mindustry.entities.type.Unit;
import mindustry.gen.BufferItem;
import mindustry.type.Item;
import mindustry.world.Block;
import mindustry.world.DirectionalItemBuffer;
import mindustry.world.Tile;
import mindustry.world.meta.BlockGroup;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import static mindustry.Vars.content;
public class Junction extends Block{
public float speed = 26; //frames taken to go through this junction
public int capacity = 6;
public Junction(String name){
super(name);
update = true;
solid = true;
instantTransfer = true;
group = BlockGroup.transportation;
unloadable = false;
entityType = JunctionEntity::new;
}
@Override
public int acceptStack(Item item, int amount, Tile tile, Unit source){
return 0;
}
@Override
public boolean outputsItems(){
return true;
}
@Override
public void update(Tile tile){
JunctionEntity entity = tile.ent();
DirectionalItemBuffer buffer = entity.buffer;
for(int i = 0; i < 4; i++){
if(buffer.indexes[i] > 0){
if(buffer.indexes[i] > capacity) buffer.indexes[i] = capacity;
long l = buffer.buffers[i][0];
float time = BufferItem.time(l);
if(Time.time() >= time + speed || Time.time() < time){
Item item = content.item(BufferItem.item(l));
Tile dest = tile.getNearby(i);
if(dest != null) dest = dest.link();
//skip blocks that don't want the item, keep waiting until they do
if(dest == null || !dest.block().acceptItem(item, dest, tile)){
continue;
}
dest.block().handleItem(item, dest, tile);
System.arraycopy(buffer.buffers[i], 1, buffer.buffers[i], 0, buffer.indexes[i] - 1);
buffer.indexes[i] --;
}
}
}
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
JunctionEntity entity = tile.ent();
int relative = source.relativeTo(tile.x, tile.y);
entity.buffer.accept(relative, item);
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
JunctionEntity entity = tile.ent();
int relative = source.relativeTo(tile.x, tile.y);
if(entity == null || relative == -1 || !entity.buffer.accepts(relative))
return false;
Tile to = tile.getNearby(relative);
return to != null && to.link().entity != null;
}
class JunctionEntity extends TileEntity{
DirectionalItemBuffer buffer = new DirectionalItemBuffer(capacity, speed);
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
buffer.write(stream);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
buffer.read(stream);
}
}
}

View File

@@ -0,0 +1,334 @@
package mindustry.world.blocks.distribution;
import arc.*;
import arc.struct.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.pooling.Pool.*;
import arc.util.pooling.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class MassDriver extends Block{
public float range;
public float rotateSpeed = 0.04f;
public float translation = 7f;
public int minDistribute = 10;
public float knockback = 4f;
public float reloadTime = 100f;
public Effect shootEffect = Fx.shootBig2;
public Effect smokeEffect = Fx.shootBigSmoke2;
public Effect recieveEffect = Fx.mineBig;
public float shake = 3f;
public TextureRegion baseRegion;
public MassDriver(String name){
super(name);
update = true;
solid = true;
posConfig = true;
configurable = true;
hasItems = true;
layer = Layer.turret;
hasPower = true;
outlineIcon = true;
entityType = MassDriverEntity::new;
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<MassDriverEntity>ent().link = value;
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name + "-base"), Core.atlas.find(name)};
}
@Override
public void load(){
super.load();
baseRegion = Core.atlas.find(name + "-base");
}
@Override
public void update(Tile tile){
MassDriverEntity entity = tile.ent();
Tile link = world.tile(entity.link);
boolean hasLink = linkValid(tile);
//reload regardless of state
if(entity.reload > 0f){
entity.reload = Mathf.clamp(entity.reload - entity.delta() / reloadTime * entity.efficiency());
}
//cleanup waiting shooters that are not valid
if(!shooterValid(tile, entity.currentShooter())){
entity.waitingShooters.remove(entity.currentShooter());
}
//switch states
if(entity.state == DriverState.idle){
//start accepting when idle and there's space
if(!entity.waitingShooters.isEmpty() && (itemCapacity - entity.items.total() >= minDistribute)){
entity.state = DriverState.accepting;
}else if(hasLink){ //switch to shooting if there's a valid link.
entity.state = DriverState.shooting;
}
}
//dump when idle or accepting
if(entity.state == DriverState.idle || entity.state == DriverState.accepting){
tryDump(tile);
}
//skip when there's no power
if(!entity.cons.valid()){
return;
}
if(entity.state == DriverState.accepting){
//if there's nothing shooting at this, bail - OR, items full
if(entity.currentShooter() == null || (itemCapacity - entity.items.total() < minDistribute)){
entity.state = DriverState.idle;
return;
}
//align to shooter rotation
entity.rotation = Mathf.slerpDelta(entity.rotation, tile.angleTo(entity.currentShooter()), rotateSpeed * entity.efficiency());
}else if(entity.state == DriverState.shooting){
//if there's nothing to shoot at OR someone wants to shoot at this thing, bail
if(!hasLink || (!entity.waitingShooters.isEmpty() && (itemCapacity - entity.items.total() >= minDistribute))){
entity.state = DriverState.idle;
return;
}
float targetRotation = tile.angleTo(link);
if(
tile.entity.items.total() >= minDistribute && //must shoot minimum amount of items
link.block().itemCapacity - link.entity.items.total() >= minDistribute //must have minimum amount of space
){
MassDriverEntity other = link.ent();
other.waitingShooters.add(tile);
if(entity.reload <= 0.0001f){
//align to target location
entity.rotation = Mathf.slerpDelta(entity.rotation, targetRotation, rotateSpeed * entity.efficiency());
//fire when it's the first in the queue and angles are ready.
if(other.currentShooter() == tile &&
other.state == DriverState.accepting &&
Angles.near(entity.rotation, targetRotation, 2f) && Angles.near(other.rotation, targetRotation + 180f, 2f)){
//actually fire
fire(tile, link);
//remove waiting shooters, it's done firing
other.waitingShooters.remove(tile);
//set both states to idle
entity.state = DriverState.idle;
other.state = DriverState.idle;
}
}
}
}
}
@Override
public void draw(Tile tile){
Draw.rect(baseRegion, tile.drawx(), tile.drawy());
}
@Override
public void drawLayer(Tile tile){
MassDriverEntity entity = tile.ent();
Draw.rect(region,
tile.drawx() + Angles.trnsx(entity.rotation + 180f, entity.reload * knockback),
tile.drawy() + Angles.trnsy(entity.rotation + 180f, entity.reload * knockback), entity.rotation - 90);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize, y*tilesize, range, Pal.accent);
}
@Override
public void drawConfigure(Tile tile){
float sin = Mathf.absin(Time.time(), 6f, 1f);
Draw.color(Pal.accent);
Lines.stroke(1f);
Drawf.circles(tile.drawx(), tile.drawy(), (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.accent);
MassDriverEntity entity = tile.ent();
if(linkValid(tile)){
Tile target = world.tile(entity.link);
Drawf.circles(target.drawx(), target.drawy(), (target.block().size / 2f + 1) * tilesize + sin - 2f, Pal.place);
Drawf.arrow(tile.drawx(), tile.drawy(), target.drawx(), target.drawy(), size * tilesize + sin, 4f + sin);
}
Drawf.dashCircle(tile.drawx(), tile.drawy(), range, Pal.accent);
}
@Override
public boolean onConfigureTileTapped(Tile tile, Tile other){
if(tile == other) return false;
MassDriverEntity entity = tile.ent();
if(entity.link == other.pos()){
tile.configure(-1);
return false;
}else if(other.block() instanceof MassDriver && other.dst(tile) <= range && other.getTeam() == tile.getTeam()){
tile.configure(other.pos());
return false;
}
return true;
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
//mass drivers that ouput only cannot accept items
return tile.entity.items.total() < itemCapacity && linkValid(tile);
}
protected void fire(Tile tile, Tile target){
MassDriverEntity entity = tile.ent();
MassDriverEntity other = target.ent();
//reset reload, use power.
entity.reload = 1f;
DriverBulletData data = Pools.obtain(DriverBulletData.class, DriverBulletData::new);
data.from = entity;
data.to = other;
int totalUsed = 0;
for(int i = 0; i < content.items().size; i++){
int maxTransfer = Math.min(entity.items.get(content.item(i)), ((MassDriver)tile.block()).itemCapacity - totalUsed);
data.items[i] = maxTransfer;
totalUsed += maxTransfer;
entity.items.remove(content.item(i), maxTransfer);
}
float angle = tile.angleTo(target);
Bullet.create(Bullets.driverBolt, entity, entity.getTeam(),
tile.drawx() + Angles.trnsx(angle, translation), tile.drawy() + Angles.trnsy(angle, translation),
angle, 1f, 1f, data);
Effects.effect(shootEffect, tile.drawx() + Angles.trnsx(angle, translation),
tile.drawy() + Angles.trnsy(angle, translation), angle);
Effects.effect(smokeEffect, tile.drawx() + Angles.trnsx(angle, translation),
tile.drawy() + Angles.trnsy(angle, translation), angle);
Effects.shake(shake, shake, entity);
}
protected void handlePayload(MassDriverEntity entity, Bullet bullet, DriverBulletData data){
int totalItems = entity.items.total();
//add all the items possible
for(int i = 0; i < data.items.length; i++){
int maxAdd = Math.min(data.items[i], itemCapacity * 2 - totalItems);
entity.items.add(content.item(i), maxAdd);
data.items[i] -= maxAdd;
totalItems += maxAdd;
if(totalItems >= itemCapacity * 2){
break;
}
}
Effects.shake(shake, shake, entity);
Effects.effect(recieveEffect, bullet);
entity.reload = 1f;
bullet.remove();
}
protected boolean shooterValid(Tile tile, Tile other){
if(other == null) return true;
if(!(other.block() instanceof MassDriver)) return false;
MassDriverEntity entity = other.ent();
return entity.link == tile.pos() && tile.dst(other) <= range;
}
protected boolean linkValid(Tile tile){
if(tile == null) return false;
MassDriverEntity entity = tile.ent();
if(entity == null || entity.link == -1) return false;
Tile link = world.tile(entity.link);
return link != null && link.block() instanceof MassDriver && tile.dst(link) <= range;
}
public static class DriverBulletData implements Poolable{
public MassDriverEntity from, to;
public int[] items = new int[content.items().size];
@Override
public void reset(){
from = null;
to = null;
}
}
public class MassDriverEntity extends TileEntity{
int link = -1;
float rotation = 90;
float reload = 0f;
DriverState state = DriverState.idle;
OrderedSet<Tile> waitingShooters = new OrderedSet<>();
Tile currentShooter(){
return waitingShooters.isEmpty() ? null : waitingShooters.first();
}
public void handlePayload(Bullet bullet, DriverBulletData data){
((MassDriver)block).handlePayload(this, bullet, data);
}
@Override
public int config(){
return link;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeInt(link);
stream.writeFloat(rotation);
stream.writeByte((byte)state.ordinal());
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
link = stream.readInt();
rotation = stream.readFloat();
state = DriverState.values()[stream.readByte()];
}
}
enum DriverState{
idle, //nothing is shooting at this mass driver and it does not have any target
accepting, //currently getting shot at, unload items
shooting,
unloading
}
}

View File

@@ -0,0 +1,135 @@
package mindustry.world.blocks.distribution;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.entities.type.TileEntity;
import mindustry.type.Item;
import mindustry.world.*;
import mindustry.world.meta.BlockGroup;
import java.io.*;
public class OverflowGate extends Block{
public float speed = 1f;
public OverflowGate(String name){
super(name);
hasItems = true;
solid = true;
update = true;
group = BlockGroup.transportation;
unloadable = false;
entityType = OverflowGateEntity::new;
}
@Override
public boolean outputsItems(){
return true;
}
@Override
public int removeStack(Tile tile, Item item, int amount){
OverflowGateEntity entity = tile.ent();
int result = super.removeStack(tile, item, amount);
if(result != 0 && item == entity.lastItem){
entity.lastItem = null;
}
return result;
}
@Override
public void update(Tile tile){
OverflowGateEntity entity = tile.ent();
if(entity.lastItem == null && entity.items.total() > 0){
entity.items.clear();
}
if(entity.lastItem != null){
entity.time += 1f / speed * Time.delta();
Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput, false);
if(target != null && (entity.time >= 1f)){
getTileTarget(tile, entity.lastItem, entity.lastInput, true);
target.block().handleItem(entity.lastItem, target, Edges.getFacingEdge(tile, target));
entity.items.remove(entity.lastItem, 1);
entity.lastItem = null;
}
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
OverflowGateEntity entity = tile.ent();
return tile.getTeam() == source.getTeam() && entity.lastItem == null && entity.items.total() == 0;
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
OverflowGateEntity entity = tile.ent();
entity.items.add(item, 1);
entity.lastItem = item;
entity.time = 0f;
entity.lastInput = source;
}
Tile getTileTarget(Tile tile, Item item, Tile src, boolean flip){
int from = tile.relativeTo(src.x, src.y);
if(from == -1) return null;
Tile to = tile.getNearby((from + 2) % 4);
if(to == null) return null;
Tile edge = Edges.getFacingEdge(tile, to);
if(!to.block().acceptItem(item, to, edge) || (to.block() instanceof OverflowGate)){
Tile a = tile.getNearby(Mathf.mod(from - 1, 4));
Tile b = tile.getNearby(Mathf.mod(from + 1, 4));
boolean ac = a != null && a.block().acceptItem(item, a, edge) && !(a.block() instanceof OverflowGate);
boolean bc = b != null && b.block().acceptItem(item, b, edge) && !(b.block() instanceof OverflowGate);
if(!ac && !bc){
return null;
}
if(ac && !bc){
to = a;
}else if(bc && !ac){
to = b;
}else{
if(tile.rotation() == 0){
to = a;
if(flip) tile.rotation((byte) 1);
}else{
to = b;
if(flip) tile.rotation((byte) 0);
}
}
}
return to;
}
public class OverflowGateEntity extends TileEntity{
Item lastItem;
Tile lastInput;
float time;
@Override
public byte version(){
return 2;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
if(revision == 1){
new DirectionalItemBuffer(25, 50f).read(stream);
}
}
}
}

View File

@@ -0,0 +1,91 @@
package mindustry.world.blocks.distribution;
import arc.struct.Array;
import arc.util.Time;
import mindustry.content.*;
import mindustry.entities.type.TileEntity;
import mindustry.type.Item;
import mindustry.world.*;
import mindustry.world.meta.BlockGroup;
public class Router extends Block{
public float speed = 8f;
public Router(String name){
super(name);
solid = true;
update = true;
hasItems = true;
itemCapacity = 1;
group = BlockGroup.transportation;
unloadable = false;
entityType = RouterEntity::new;
}
@Override
public void update(Tile tile){
RouterEntity entity = tile.ent();
if(entity.lastItem == null && entity.items.total() > 0){
entity.items.clear();
}
if(entity.lastItem != null){
entity.time += 1f / speed * Time.delta();
Tile target = getTileTarget(tile, entity.lastItem, entity.lastInput, false);
if(target != null && (entity.time >= 1f || !(target.block() instanceof Router))){
getTileTarget(tile, entity.lastItem, entity.lastInput, true);
target.block().handleItem(entity.lastItem, target, Edges.getFacingEdge(tile, target));
entity.items.remove(entity.lastItem, 1);
entity.lastItem = null;
}
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
RouterEntity entity = tile.ent();
return tile.getTeam() == source.getTeam() && entity.lastItem == null && entity.items.total() == 0;
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
RouterEntity entity = tile.ent();
entity.items.add(item, 1);
entity.lastItem = item;
entity.time = 0f;
entity.lastInput = source;
}
Tile getTileTarget(Tile tile, Item item, Tile from, boolean set){
Array<Tile> proximity = tile.entity.proximity();
int counter = tile.rotation();
for(int i = 0; i < proximity.size; i++){
Tile other = proximity.get((i + counter) % proximity.size);
if(set) tile.rotation((byte)((tile.rotation() + 1) % proximity.size));
if(other == from && from.block() == Blocks.overflowGate) continue;
if(other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){
return other;
}
}
return null;
}
@Override
public int removeStack(Tile tile, Item item, int amount){
RouterEntity entity = tile.ent();
int result = super.removeStack(tile, item, amount);
if(result != 0 && item == entity.lastItem){
entity.lastItem = null;
}
return result;
}
public class RouterEntity extends TileEntity{
Item lastItem;
Tile lastInput;
float time;
}
}

View File

@@ -0,0 +1,169 @@
package mindustry.world.blocks.distribution;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.content;
public class Sorter extends Block{
private static Item lastItem;
public boolean invert;
public Sorter(String name){
super(name);
update = true;
solid = true;
instantTransfer = true;
group = BlockGroup.transportation;
configurable = true;
unloadable = false;
entityType = SorterEntity::new;
}
@Override
public boolean outputsItems(){
return true;
}
@Override
public void playerPlaced(Tile tile){
if(lastItem != null){
tile.configure(lastItem.id);
}
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<SorterEntity>ent().sortItem = content.item(value);
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "center");
}
@Override
public void draw(Tile tile){
super.draw(tile);
SorterEntity entity = tile.ent();
if(entity.sortItem == null) return;
Draw.color(entity.sortItem.color);
Draw.rect("center", tile.worldx(), tile.worldy());
Draw.color();
}
@Override
public int minimapColor(Tile tile){
return tile.<SorterEntity>ent().sortItem == null ? 0 : tile.<SorterEntity>ent().sortItem.color.rgba();
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
Tile to = getTileTarget(item, tile, source, false);
return to != null && to.block().acceptItem(item, to, tile);
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
Tile to = getTileTarget(item, tile, source, true);
to.block().handleItem(item, to, tile);
}
boolean isSame(Tile tile, Tile other){
return other != null && other.block() instanceof Sorter;
}
Tile getTileTarget(Item item, Tile dest, Tile source, boolean flip){
SorterEntity entity = dest.ent();
int dir = source.relativeTo(dest.x, dest.y);
if(dir == -1) return null;
Tile to;
if((item == entity.sortItem) != invert){
//prevent 3-chains
if(isSame(dest, source) && isSame(dest, dest.getNearby(dir))){
return null;
}
to = dest.getNearby(dir);
}else{
Tile a = dest.getNearby(Mathf.mod(dir - 1, 4));
Tile b = dest.getNearby(Mathf.mod(dir + 1, 4));
boolean ac = a != null && !(a.block().instantTransfer && source.block().instantTransfer) &&
a.block().acceptItem(item, a, dest);
boolean bc = b != null && !(b.block().instantTransfer && source.block().instantTransfer) &&
b.block().acceptItem(item, b, dest);
if(ac && !bc){
to = a;
}else if(bc && !ac){
to = b;
}else if(!bc){
return null;
}else{
if(dest.rotation() == 0){
to = a;
if(flip) dest.rotation((byte)1);
}else{
to = b;
if(flip) dest.rotation((byte)0);
}
}
}
return to;
}
@Override
public void buildConfiguration(Tile tile, Table table){
SorterEntity entity = tile.ent();
ItemSelection.buildItemTable(table, () -> entity.sortItem, item -> {
lastItem = item;
tile.configure(item == null ? -1 : item.id);
});
}
public class SorterEntity extends TileEntity{
@Nullable Item sortItem;
@Override
public int config(){
return sortItem == null ? -1 : sortItem.id;
}
@Override
public byte version(){
return 2;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeShort(sortItem == null ? -1 : sortItem.id);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
sortItem = content.item(stream.readShort());
if(revision == 1){
new DirectionalItemBuffer(20, 45f).read(stream);
}
}
}
}

View File

@@ -0,0 +1,45 @@
package mindustry.world.blocks.liquid;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import mindustry.type.Liquid;
import mindustry.world.Block;
import mindustry.world.Edges;
import mindustry.world.Tile;
public class ArmoredConduit extends Conduit{
public TextureRegion capRegion;
public ArmoredConduit(String name){
super(name);
leakResistance = 10f;
}
@Override
public void load(){
super.load();
capRegion = Core.atlas.find(name + "-cap");
}
@Override
public void draw(Tile tile){
super.draw(tile);
// draw the cap when a conduit would normally leak
Tile next = tile.front();
if(next != null && next.getTeam() == tile.getTeam() && next.block().hasLiquids) return;
Draw.rect(capRegion, tile.drawx(), tile.drawy(), tile.rotation() * 90);
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
return super.acceptLiquid(tile, source, liquid, amount) && (source.block() instanceof Conduit) || Edges.getFacingEdge(source, tile).relativeTo(tile) == tile.rotation();
}
@Override
public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return otherblock.outputsLiquid && blendsArmored(tile, rotation, otherx, othery, otherrot, otherblock);
}
}

View File

@@ -0,0 +1,139 @@
package mindustry.world.blocks.liquid;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.modules.*;
public class Conduit extends LiquidBlock implements Autotiler{
public final int timerFlow = timers++;
public TextureRegion[] topRegions = new TextureRegion[7];
public TextureRegion[] botRegions = new TextureRegion[7];
public float leakResistance = 1.5f;
public Conduit(String name){
super(name);
rotate = true;
solid = false;
floating = true;
conveyorPlacement = true;
entityType = ConduitEntity::new;
}
@Override
public void load(){
super.load();
liquidRegion = Core.atlas.find("conduit-liquid");
for(int i = 0; i < topRegions.length; i++){
topRegions[i] = Core.atlas.find(name + "-top-" + i);
botRegions[i] = Core.atlas.find("conduit-bottom-" + i);
}
}
@Override
public void onProximityUpdate(Tile tile){
super.onProximityUpdate(tile);
ConduitEntity entity = tile.ent();
int[] bits = buildBlending(tile, tile.rotation(), null, true);
entity.blendbits = bits[0];
}
@Override
public void drawRequestRegion(BuildRequest req, Eachable<BuildRequest> list){
int[] bits = getTiling(req, list);
if(bits == null) return;
Draw.colorl(0.34f);
Draw.alpha(0.5f);
Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(),
botRegions[bits[0]].getWidth() * Draw.scl * req.animScale, botRegions[bits[0]].getHeight() * Draw.scl * req.animScale,
req.rotation * 90);
Draw.color();
Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), topRegions[bits[0]].getWidth() * Draw.scl * req.animScale, topRegions[bits[0]].getHeight() * Draw.scl * req.animScale, req.rotation * 90);
}
@Override
public Block getReplacement(BuildRequest req, Array<BuildRequest> requests){
Boolf<Point2> cont = p -> requests.contains(o -> o.x == req.x + p.x && o.y == req.y + p.y && o.rotation == req.rotation && (req.block instanceof Conduit || req.block instanceof LiquidJunction));
return cont.get(Geometry.d4(req.rotation)) &&
cont.get(Geometry.d4(req.rotation - 2)) &&
req.tile() != null &&
req.tile().block() instanceof Conduit &&
Mathf.mod(req.tile().rotation() - req.rotation, 2) == 1 ? Blocks.liquidJunction : this;
}
@Override
public void transformCase(int num, int[] bits){
bits[0] = num == 0 ? 3 : num == 1 ? 6 : num == 2 ? 2 : num == 3 ? 4 : num == 4 ? 5 : num == 5 ? 1 : 0;
}
@Override
public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return otherblock.hasLiquids && otherblock.outputsLiquid && lookingAt(tile, rotation, otherx, othery, otherrot, otherblock);
}
@Override
public void draw(Tile tile){
ConduitEntity entity = tile.ent();
LiquidModule mod = tile.entity.liquids;
int rotation = tile.rotation() * 90;
Draw.colorl(0.34f);
Draw.rect(botRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation);
Draw.color(mod.current().color);
Draw.alpha(entity.smoothLiquid);
Draw.rect(botRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation);
Draw.color();
Draw.rect(topRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation);
}
@Override
public void update(Tile tile){
ConduitEntity entity = tile.ent();
entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.total() / liquidCapacity, 0.05f);
if(tile.entity.liquids.total() > 0.001f && tile.entity.timer.get(timerFlow, 1)){
tryMoveLiquid(tile, tile.getNearby(tile.rotation()), leakResistance, tile.entity.liquids.current());
entity.noSleep();
}else{
entity.sleep();
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find("conduit-bottom"), Core.atlas.find(name + "-top-0")};
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
tile.entity.noSleep();
return tile.entity.liquids.get(liquid) + amount < liquidCapacity && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.2f)
&& ((source.absoluteRelativeTo(tile.x, tile.y) + 2) % 4 != tile.rotation());
}
public static class ConduitEntity extends TileEntity{
public float smoothLiquid;
int blendbits;
}
}

View File

@@ -0,0 +1,60 @@
package mindustry.world.blocks.liquid;
import arc.math.*;
import arc.util.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.world;
public class LiquidBridge extends ItemBridge{
public LiquidBridge(String name){
super(name);
hasItems = false;
hasLiquids = true;
outputsLiquid = true;
group = BlockGroup.liquids;
}
@Override
public void update(Tile tile){
ItemBridgeEntity entity = tile.ent();
entity.time += entity.cycleSpeed * Time.delta();
entity.time2 += (entity.cycleSpeed - 1f) * Time.delta();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)){
tryDumpLiquid(tile, entity.liquids.current());
}else{
((ItemBridgeEntity)world.tile(entity.link).entity).incoming.add(tile.pos());
if(entity.cons.valid()){
float alpha = 0.04f;
if(hasPower){
alpha *= entity.efficiency(); // Exceed boot time unless power is at max.
}
entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, alpha);
}else{
entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f);
}
if(entity.uptime >= 0.5f){
if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f);
}else{
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f);
}
}
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return false;
}
}

View File

@@ -0,0 +1,56 @@
package mindustry.world.blocks.liquid;
import arc.math.*;
import arc.util.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.world;
public class LiquidExtendingBridge extends ExtendingItemBridge{
public LiquidExtendingBridge(String name){
super(name);
hasItems = false;
hasLiquids = true;
outputsLiquid = true;
group = BlockGroup.liquids;
}
@Override
public void update(Tile tile){
ItemBridgeEntity entity = tile.ent();
entity.time += entity.cycleSpeed * Time.delta();
entity.time2 += (entity.cycleSpeed - 1f) * Time.delta();
Tile other = world.tile(entity.link);
if(!linkValid(tile, other)){
tryDumpLiquid(tile, entity.liquids.current());
}else{
((ItemBridgeEntity)world.tile(entity.link).entity).incoming.add(tile.pos());
if(entity.cons.valid()){
entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f);
}else{
entity.uptime = Mathf.lerpDelta(entity.uptime, 0f, 0.02f);
}
if(entity.uptime >= 0.5f){
if(tryMoveLiquid(tile, other, false, entity.liquids.current()) > 0.1f){
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 4f, 0.05f);
}else{
entity.cycleSpeed = Mathf.lerpDelta(entity.cycleSpeed, 1f, 0.01f);
}
}
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return false;
}
}

View File

@@ -0,0 +1,48 @@
package mindustry.world.blocks.liquid;
import arc.*;
import arc.graphics.g2d.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
public class LiquidJunction extends LiquidBlock{
public LiquidJunction(String name){
super(name);
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.liquidCapacity);
}
@Override
public void setBars(){
super.setBars();
bars.remove("liquid");
}
@Override
public void draw(Tile tile){
Draw.rect(name, tile.worldx(), tile.worldy());
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name)};
}
@Override
public Tile getLiquidDestination(Tile tile, Tile source, Liquid liquid){
int dir = source.relativeTo(tile.x, tile.y);
dir = (dir + 4) % 4;
Tile next = tile.getNearbyLink(dir);
if(next == null || !next.block().acceptLiquid(next, tile, liquid, 0f) && !(next.block() instanceof LiquidJunction)){
return tile;
}
return next.block().getLiquidDestination(next, tile, liquid);
}
}

View File

@@ -0,0 +1,53 @@
package mindustry.world.blocks.liquid;
import arc.*;
import arc.graphics.g2d.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
//TODO implement later
public class LiquidOverflowGate extends LiquidBlock{
public int topRegion;
public LiquidOverflowGate(String name){
super(name);
rotate = true;
topRegion = reg("-top");
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.liquidCapacity);
}
@Override
public void setBars(){
super.setBars();
bars.remove("liquid");
}
@Override
public void draw(Tile tile){
Draw.rect(name, tile.drawx(), tile.drawy());
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy(), tile.rotation() * 90);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top")};
}
@Override
public Tile getLiquidDestination(Tile tile, Tile source, Liquid liquid){
int dir = source.relativeTo(tile.x, tile.y);
dir = (dir + 4) % 4;
Tile next = tile.getNearby(dir).link();
if(!next.block().acceptLiquid(next, tile, liquid, 0.0001f) && !(next.block() instanceof LiquidOverflowGate || next.block() instanceof LiquidJunction)){
return tile;
}
return next.block().getLiquidDestination(next, tile, liquid);
}
}

View File

@@ -0,0 +1,25 @@
package mindustry.world.blocks.liquid;
import mindustry.type.Liquid;
import mindustry.world.Tile;
import mindustry.world.blocks.LiquidBlock;
public class LiquidRouter extends LiquidBlock{
public LiquidRouter(String name){
super(name);
}
@Override
public void update(Tile tile){
if(tile.entity.liquids.total() > 0.01f){
tryDumpLiquid(tile, tile.entity.liquids.current());
}
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
return tile.entity.liquids.get(liquid) + amount < liquidCapacity && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.2f);
}
}

View File

@@ -0,0 +1,8 @@
package mindustry.world.blocks.liquid;
public class LiquidTank extends LiquidRouter{
public LiquidTank(String name){
super(name);
}
}

View File

@@ -0,0 +1,10 @@
package mindustry.world.blocks.logic;
import mindustry.world.Block;
public class LogicBlock extends Block{
public LogicBlock(String name){
super(name);
}
}

View File

@@ -0,0 +1,165 @@
package mindustry.world.blocks.logic;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.Input.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.net.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class MessageBlock extends Block{
protected static int maxTextLength = 220;
protected static int maxNewlines = 24;
public MessageBlock(String name){
super(name);
configurable = true;
solid = true;
destructible = true;
entityType = MessageBlockEntity::new;
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void setMessageBlockText(Player player, Tile tile, String text){
if(!Units.canInteract(player, tile)) return;
if(net.server() && text.length() > maxTextLength){
throw new ValidateException(player, "Player has gone above text limit.");
}
//can be broken while a player is typing
if(!(tile.block() instanceof MessageBlock)){
return;
}
StringBuilder result = new StringBuilder(text.length());
text = text.trim();
int count = 0;
for(int i = 0; i < text.length(); i++){
char c = text.charAt(i);
if(c == '\n' || c == '\r'){
count ++;
if(count <= maxNewlines){
result.append('\n');
}
}else{
result.append(c);
}
}
MessageBlockEntity entity = tile.ent();
if(entity != null){
entity.message = result.toString();
entity.lines = entity.message.split("\n");
}
}
@Override
public void drawSelect(Tile tile){
MessageBlockEntity entity = tile.ent();
BitmapFont font = Fonts.outline;
GlyphLayout l = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
boolean ints = font.usesIntegerPositions();
font.getData().setScale(1 / 4f / Scl.scl(1f));
font.setUseIntegerPositions(false);
String text = entity.message == null || entity.message.isEmpty() ? "[lightgray]" + Core.bundle.get("empty") : entity.message;
l.setText(font, text, Color.white, 90f, Align.left, true);
float offset = 1f;
Draw.color(0f, 0f, 0f, 0.2f);
Fill.rect(tile.drawx(), tile.drawy() - tilesize/2f - l.height/2f - offset, l.width + offset*2f, l.height + offset*2f);
Draw.color();
font.setColor(Color.white);
font.draw(text, tile.drawx() - l.width/2f, tile.drawy() - tilesize/2f - offset, 90f, Align.left, true);
font.setUseIntegerPositions(ints);
font.getData().setScale(1f);
Pools.free(l);
}
@Override
public void buildConfiguration(Tile tile, Table table){
MessageBlockEntity entity = tile.ent();
table.addImageButton(Icon.pencilSmall, () -> {
if(mobile){
Core.input.getTextInput(new TextInput(){{
text = entity.message;
multiline = true;
maxLength = maxTextLength;
accepted = out -> {
Call.setMessageBlockText(player, tile, out);
};
}});
}else{
FloatingDialog dialog = new FloatingDialog("$editmessage");
dialog.setFillParent(false);
TextArea a = dialog.cont.add(new TextArea(entity.message.replace("\n", "\r"))).size(380f, 160f).get();
a.setFilter((textField, c) -> {
if(c == '\n' || c == '\r'){
int count = 0;
for(int i = 0; i < textField.getText().length(); i++){
if(textField.getText().charAt(i) == '\n' || textField.getText().charAt(i) == '\r'){
count++;
}
}
return count < maxNewlines;
}
return true;
});
a.setMaxLength(maxTextLength);
dialog.buttons.addButton("$ok", () -> {
Call.setMessageBlockText(player, tile, a.getText());
dialog.hide();
}).size(130f, 60f);
dialog.update(() -> {
if(!entity.isValid()){
dialog.hide();
}
});
dialog.show();
}
control.input.frag.config.hideConfig();
}).size(40f);
}
@Override
public void updateTableAlign(Tile tile, Table table){
Vector2 pos = Core.input.mouseScreen(tile.drawx(), tile.drawy() + tile.block().size * tilesize / 2f + 1);
table.setPosition(pos.x, pos.y, Align.bottom);
}
public class MessageBlockEntity extends TileEntity{
public String message = "";
public String[] lines = {""};
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeUTF(message);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
message = stream.readUTF();
}
}
}

View File

@@ -0,0 +1,10 @@
package mindustry.world.blocks.power;
public class Battery extends PowerDistributor{
public Battery(String name){
super(name);
outputsPower = true;
consumesPower = true;
}
}

View File

@@ -0,0 +1,21 @@
package mindustry.world.blocks.power;
import mindustry.type.Item;
import mindustry.type.Liquid;
public class BurnerGenerator extends ItemLiquidGenerator{
public BurnerGenerator(String name){
super(true, false, name);
}
@Override
protected float getLiquidEfficiency(Liquid liquid){
return liquid.flammability;
}
@Override
protected float getItemEfficiency(Item item){
return item.flammability;
}
}

View File

@@ -0,0 +1,20 @@
package mindustry.world.blocks.power;
import arc.func.Boolf;
import mindustry.entities.type.TileEntity;
import mindustry.world.consumers.ConsumePower;
/** A power consumer that only activates sometimes. */
public class ConditionalConsumePower extends ConsumePower{
private final Boolf<TileEntity> consume;
public ConditionalConsumePower(float usage, Boolf<TileEntity> consume){
super(usage, 0, false);
this.consume = consume;
}
@Override
public float requestedPower(TileEntity entity){
return consume.get(entity) ? usage : 0f;
}
}

View File

@@ -0,0 +1,17 @@
package mindustry.world.blocks.power;
import mindustry.type.Item;
public class DecayGenerator extends ItemLiquidGenerator{
public DecayGenerator(String name){
super(true, false, name);
hasItems = true;
hasLiquids = false;
}
@Override
protected float getItemEfficiency(Item item){
return item.radioactivity;
}
}

View File

@@ -0,0 +1,179 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class ImpactReactor extends PowerGenerator{
public final int timerUse = timers++;
public int plasmas = 4;
public float warmupSpeed = 0.001f;
public float itemDuration = 60f;
public int explosionRadius = 50;
public int explosionDamage = 2000;
public Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b");
public int bottomRegion;
public int[] plasmaRegions;
public ImpactReactor(String name){
super(name);
hasPower = true;
hasLiquids = true;
liquidCapacity = 30f;
hasItems = true;
outputsPower = consumesPower = true;
entityType = FusionReactorEntity::new;
bottomRegion = reg("-bottom");
plasmaRegions = new int[plasmas];
for(int i = 0; i < plasmas; i++){
plasmaRegions[i] = reg("-plasma-" + i);
}
}
@Override
public void setBars(){
super.setBars();
bars.add("poweroutput", entity -> new Bar(() ->
Core.bundle.format("bar.poweroutput",
Strings.fixed(Math.max(entity.block.getPowerProduction(entity.tile) - consumes.getPower().usage, 0) * 60 * entity.timeScale, 1)),
() -> Pal.powerBar,
() -> ((GeneratorEntity)entity).productionEfficiency));
}
@Override
public void setStats(){
super.setStats();
if(hasItems){
stats.add(BlockStat.productionTime, itemDuration / 60f, StatUnit.seconds);
}
}
@Override
public void update(Tile tile){
FusionReactorEntity entity = tile.ent();
if(entity.cons.valid() && entity.power.status >= 0.99f){
boolean prevOut = getPowerProduction(tile) <= consumes.getPower().requestedPower(entity);
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, warmupSpeed);
if(Mathf.equal(entity.warmup, 1f, 0.001f)){
entity.warmup = 1f;
}
if(!prevOut && (getPowerProduction(tile) > consumes.getPower().requestedPower(entity))){
Events.fire(Trigger.impactPower);
}
if(entity.timer.get(timerUse, itemDuration / entity.timeScale)){
entity.cons.trigger();
}
}else{
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.01f);
}
entity.productionEfficiency = Mathf.pow(entity.warmup, 5f);
}
@Override
public void draw(Tile tile){
FusionReactorEntity entity = tile.ent();
Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy());
for(int i = 0; i < plasmas; i++){
float r = 29f + Mathf.absin(Time.time(), 2f + i * 1f, 5f - i * 0.5f);
Draw.color(plasma1, plasma2, (float)i / plasmas);
Draw.alpha((0.3f + Mathf.absin(Time.time(), 2f + i * 2f, 0.3f + i * 0.05f)) * entity.warmup);
Draw.blend(Blending.additive);
Draw.rect(reg(plasmaRegions[i]), tile.drawx(), tile.drawy(), r, r, Time.time() * (12 + i * 6f) * entity.warmup);
Draw.blend();
}
Draw.color();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.color();
}
@Override
public void drawLight(Tile tile){
float fract = tile.<FusionReactorEntity>ent().warmup;
renderer.lights.add(tile.drawx(), tile.drawy(), (110f + Mathf.absin(5, 5f)) * fract, Tmp.c1.set(plasma2).lerp(plasma1, Mathf.absin(7f, 0.2f)), 0.8f * fract);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name + "-bottom"), Core.atlas.find(name)};
}
@Override
public void onDestroyed(Tile tile){
super.onDestroyed(tile);
FusionReactorEntity entity = tile.ent();
if(entity.warmup < 0.4f || !state.rules.reactorExplosions) return;
Sounds.explosionbig.at(tile);
Effects.shake(6f, 16f, tile.worldx(), tile.worldy());
Effects.effect(Fx.impactShockwave, tile.worldx(), tile.worldy());
for(int i = 0; i < 6; i++){
Time.run(Mathf.random(80), () -> Effects.effect(Fx.impactcloud, tile.worldx(), tile.worldy()));
}
Damage.damage(tile.worldx(), tile.worldy(), explosionRadius * tilesize, explosionDamage * 4);
for(int i = 0; i < 20; i++){
Time.run(Mathf.random(80), () -> {
Tmp.v1.rnd(Mathf.random(40f));
Effects.effect(Fx.explosion, Tmp.v1.x + tile.worldx(), Tmp.v1.y + tile.worldy());
});
}
for(int i = 0; i < 70; i++){
Time.run(Mathf.random(90), () -> {
Tmp.v1.rnd(Mathf.random(120f));
Effects.effect(Fx.impactsmoke, Tmp.v1.x + tile.worldx(), Tmp.v1.y + tile.worldy());
});
}
}
public static class FusionReactorEntity extends GeneratorEntity{
public float warmup;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(warmup);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
warmup = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,195 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
/**
* Power generation block which can use items, liquids or both as input sources for power production.
* Liquids will take priority over items.
*/
public class ItemLiquidGenerator extends PowerGenerator{
public float minItemEfficiency = 0.2f;
/** The time in number of ticks during which a single item will produce power. */
public float itemDuration = 70f;
public float minLiquidEfficiency = 0.2f;
/** Maximum liquid used per frame. */
public float maxLiquidGenerate = 0.4f;
public Effect generateEffect = Fx.generatespark;
public Effect explodeEffect = Fx.generatespark;
public Color heatColor = Color.valueOf("ff9b59");
public TextureRegion topRegion, liquidRegion;
public boolean randomlyExplode = true;
public boolean defaults = false;
public ItemLiquidGenerator(boolean hasItems, boolean hasLiquids, String name){
this(name);
this.hasItems = hasItems;
this.hasLiquids = hasLiquids;
setDefaults();
}
public ItemLiquidGenerator(String name){
super(name);
this.entityType = ItemLiquidGeneratorEntity::new;
}
protected void setDefaults(){
if(hasItems){
consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true, false);
}
if(hasLiquids){
consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, maxLiquidGenerate)).update(false).optional(true, false);
}
defaults = true;
}
@Override
public void init(){
if(!defaults){
setDefaults();
}
super.init();
}
@Override
public void load(){
super.load();
if(hasItems){
topRegion = Core.atlas.find(name + "-top");
}
liquidRegion = Core.atlas.find(name + "-liquid");
}
@Override
public void setStats(){
super.setStats();
if(hasItems){
stats.add(BlockStat.productionTime, itemDuration / 60f, StatUnit.seconds);
}
}
@Override
public boolean productionValid(Tile tile){
ItemLiquidGeneratorEntity entity = tile.ent();
return entity.generateTime > 0;
}
@Override
public void update(Tile tile){
ItemLiquidGeneratorEntity entity = tile.ent();
//Note: Do not use this delta when calculating the amount of power or the power efficiency, but use it for resource consumption if necessary.
//Power amount is delta'd by PowerGraph class already.
float calculationDelta = entity.delta();
if(!entity.cons.valid()){
entity.productionEfficiency = 0.0f;
return;
}
Liquid liquid = null;
for(Liquid other : content.liquids()){
if(hasLiquids && entity.liquids.get(other) >= 0.001f && getLiquidEfficiency(other) >= minLiquidEfficiency){
liquid = other;
break;
}
}
entity.heat = Mathf.lerpDelta(entity.heat, entity.generateTime >= 0.001f ? 1f : 0f, 0.05f);
//liquid takes priority over solids
if(hasLiquids && liquid != null && entity.liquids.get(liquid) >= 0.001f){
float baseLiquidEfficiency = getLiquidEfficiency(liquid);
float maximumPossible = maxLiquidGenerate * calculationDelta;
float used = Math.min(entity.liquids.get(liquid) * calculationDelta, maximumPossible);
entity.liquids.remove(liquid, used * entity.power.graph.getUsageFraction());
entity.productionEfficiency = baseLiquidEfficiency * used / maximumPossible;
if(used > 0.001f && Mathf.chance(0.05 * entity.delta())){
Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f));
}
}else if(hasItems){
// No liquids accepted or none supplied, try using items if accepted
if(entity.generateTime <= 0f && entity.items.total() > 0){
Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f));
Item item = entity.items.take();
entity.productionEfficiency = getItemEfficiency(item);
entity.explosiveness = item.explosiveness;
entity.generateTime = 1f;
}
if(entity.generateTime > 0f){
entity.generateTime -= Math.min(1f / itemDuration * entity.delta() * entity.power.graph.getUsageFraction(), entity.generateTime);
if(randomlyExplode && state.rules.reactorExplosions && Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.5f))){
//this block is run last so that in the event of a block destruction, no code relies on the block type
Core.app.post(() -> {
entity.damage(Mathf.random(11f));
Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f));
});
}
}else{
entity.productionEfficiency = 0.0f;
}
}
}
@Override
public void draw(Tile tile){
super.draw(tile);
ItemLiquidGeneratorEntity entity = tile.ent();
if(hasItems){
Draw.color(heatColor);
Draw.alpha(entity.heat * 0.4f + Mathf.absin(Time.time(), 8f, 0.6f) * entity.heat);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
Draw.reset();
}
if(hasLiquids){
Draw.color(entity.liquids.current().color);
Draw.alpha(entity.liquids.currentAmount() / liquidCapacity);
Draw.rect(liquidRegion, tile.drawx(), tile.drawy());
Draw.color();
}
}
@Override
public void drawLight(Tile tile){
ItemLiquidGeneratorEntity entity = tile.ent();
renderer.lights.add(tile.drawx(), tile.drawy(), (60f + Mathf.absin(10f, 5f)) * entity.productionEfficiency * size, Color.orange, 0.5f);
}
protected float getItemEfficiency(Item item){
return 0.0f;
}
protected float getLiquidEfficiency(Liquid liquid){
return 0.0f;
}
public static class ItemLiquidGeneratorEntity extends GeneratorEntity{
public float explosiveness;
public float heat;
}
}

View File

@@ -0,0 +1,95 @@
package mindustry.world.blocks.power;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class LightBlock extends Block{
private static int lastColor = 0;
public float brightness = 0.9f;
public float radius = 200f;
public int topRegion;
public LightBlock(String name){
super(name);
hasPower = true;
update = true;
topRegion = reg("-top");
configurable = true;
entityType = LightEntity::new;
}
@Override
public void playerPlaced(Tile tile){
if(lastColor != 0){
tile.configure(lastColor);
}
}
@Override
public void draw(Tile tile){
super.draw(tile);
LightEntity entity = tile.ent();
Draw.blend(Blending.additive);
Draw.color(Tmp.c1.set(entity.color), entity.efficiency() * 0.3f);
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy());
Draw.color();
Draw.blend();
}
@Override
public void buildConfiguration(Tile tile, Table table){
LightEntity entity = tile.ent();
table.addImageButton(Icon.pencilSmall, () -> {
ui.picker.show(Tmp.c1.set(entity.color).a(0.5f), false, res -> {
entity.color = res.rgba();
lastColor = entity.color;
});
control.input.frag.config.hideConfig();
}).size(40f);
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<LightEntity>ent().color = value;
}
@Override
public void drawLight(Tile tile){
LightEntity entity = tile.ent();
renderer.lights.add(tile.drawx(), tile.drawy(), radius, Tmp.c1.set(entity.color), brightness * tile.entity.efficiency());
}
public class LightEntity extends TileEntity{
public int color = Pal.accent.rgba();
@Override
public int config(){
return color;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeInt(color);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
color = stream.readInt();
}
}
}

View File

@@ -0,0 +1,199 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class NuclearReactor extends PowerGenerator{
public final int timerFuel = timers++;
public final Vector2 tr = new Vector2();
public Color lightColor = Color.valueOf("7f19ea");
public Color coolColor = new Color(1, 1, 1, 0f);
public Color hotColor = Color.valueOf("ff9575a3");
public float itemDuration = 120; //time to consume 1 fuel
public float heating = 0.01f; //heating per frame * fullness
public float smokeThreshold = 0.3f; //threshold at which block starts smoking
public int explosionRadius = 40;
public int explosionDamage = 1350;
public float flashThreshold = 0.46f; //heat threshold at which the lights start flashing
public float coolantPower = 0.5f;
public TextureRegion topRegion, lightsRegion;
public NuclearReactor(String name){
super(name);
itemCapacity = 30;
liquidCapacity = 30;
hasItems = true;
hasLiquids = true;
entityType = NuclearReactorEntity::new;
}
@Override
public void setStats(){
super.setStats();
if(hasItems){
stats.add(BlockStat.productionTime, itemDuration / 60f, StatUnit.seconds);
}
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-center");
lightsRegion = Core.atlas.find(name + "-lights");
}
@Override
public void setBars(){
super.setBars();
bars.add("heat", entity -> new Bar("bar.heat", Pal.lightOrange, () -> ((NuclearReactorEntity)entity).heat));
}
@Override
public void update(Tile tile){
NuclearReactorEntity entity = tile.ent();
ConsumeLiquid cliquid = consumes.get(ConsumeType.liquid);
Item item = consumes.<ConsumeItems>get(ConsumeType.item).items[0].item;
int fuel = entity.items.get(item);
float fullness = (float)fuel / itemCapacity;
entity.productionEfficiency = fullness;
if(fuel > 0){
entity.heat += fullness * heating * Math.min(entity.delta(), 4f);
if(entity.timer.get(timerFuel, itemDuration / entity.timeScale)){
entity.cons.trigger();
}
}
Liquid liquid = cliquid.liquid;
if(entity.heat > 0){
float maxUsed = Math.min(entity.liquids.get(liquid), entity.heat / coolantPower);
entity.heat -= maxUsed * coolantPower;
entity.liquids.remove(liquid, maxUsed);
}
if(entity.heat > smokeThreshold){
float smoke = 1.0f + (entity.heat - smokeThreshold) / (1f - smokeThreshold); //ranges from 1.0 to 2.0
if(Mathf.chance(smoke / 20.0 * entity.delta())){
Effects.effect(Fx.reactorsmoke, tile.worldx() + Mathf.range(size * tilesize / 2f),
tile.worldy() + Mathf.random(size * tilesize / 2f));
}
}
entity.heat = Mathf.clamp(entity.heat);
if(entity.heat >= 0.999f){
Events.fire(Trigger.thoriumReactorOverheat);
entity.kill();
}
}
@Override
public void onDestroyed(Tile tile){
super.onDestroyed(tile);
Sounds.explosionbig.at(tile);
NuclearReactorEntity entity = tile.ent();
int fuel = entity.items.get(consumes.<ConsumeItems>get(ConsumeType.item).items[0].item);
if((fuel < 5 && entity.heat < 0.5f) || !state.rules.reactorExplosions) return;
Effects.shake(6f, 16f, tile.worldx(), tile.worldy());
Effects.effect(Fx.nuclearShockwave, tile.worldx(), tile.worldy());
for(int i = 0; i < 6; i++){
Time.run(Mathf.random(40), () -> Effects.effect(Fx.nuclearcloud, tile.worldx(), tile.worldy()));
}
Damage.damage(tile.worldx(), tile.worldy(), explosionRadius * tilesize, explosionDamage * 4);
for(int i = 0; i < 20; i++){
Time.run(Mathf.random(50), () -> {
tr.rnd(Mathf.random(40f));
Effects.effect(Fx.explosion, tr.x + tile.worldx(), tr.y + tile.worldy());
});
}
for(int i = 0; i < 70; i++){
Time.run(Mathf.random(80), () -> {
tr.rnd(Mathf.random(120f));
Effects.effect(Fx.nuclearsmoke, tr.x + tile.worldx(), tr.y + tile.worldy());
});
}
}
@Override
public void drawLight(Tile tile){
NuclearReactorEntity entity = tile.ent();
float fract = entity.productionEfficiency;
renderer.lights.add(tile.drawx(), tile.drawy(), (90f + Mathf.absin(5, 5f)) * fract, Tmp.c1.set(lightColor).lerp(Color.scarlet, entity.heat), 0.6f * fract);
}
@Override
public void draw(Tile tile){
super.draw(tile);
NuclearReactorEntity entity = tile.ent();
Draw.color(coolColor, hotColor, entity.heat);
Fill.rect(tile.drawx(), tile.drawy(), size * tilesize, size * tilesize);
Draw.color(entity.liquids.current().color);
Draw.alpha(entity.liquids.currentAmount() / liquidCapacity);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
if(entity.heat > flashThreshold){
float flash = 1f + ((entity.heat - flashThreshold) / (1f - flashThreshold)) * 5.4f;
entity.flash += flash * Time.delta();
Draw.color(Color.red, Color.yellow, Mathf.absin(entity.flash, 9f, 1f));
Draw.alpha(0.6f);
Draw.rect(lightsRegion, tile.drawx(), tile.drawy());
}
Draw.reset();
}
public static class NuclearReactorEntity extends GeneratorEntity{
public float heat;
public float flash;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(heat);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
heat = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,89 @@
package mindustry.world.blocks.power;
import arc.Core;
import arc.math.Mathf;
import mindustry.ui.Bar;
import arc.util.Eachable;
import mindustry.ui.Cicon;
import mindustry.world.Tile;
import mindustry.world.Block;
import arc.graphics.g2d.Draw;
import mindustry.graphics.Pal;
import arc.graphics.g2d.TextureRegion;
import mindustry.entities.traits.BuilderTrait;
public class PowerDiode extends Block{
public TextureRegion arrow;
public PowerDiode(String name){
super(name);
rotate = true;
update = true;
solid = true;
insulated = true;
}
@Override
public void update(Tile tile){
super.update(tile);
if(tile.front() == null || tile.back() == null || !tile.back().block().hasPower || !tile.front().block().hasPower) return;
PowerGraph backGraph = tile.back().entity.power.graph;
PowerGraph frontGraph = tile.front().entity.power.graph;
if(backGraph == frontGraph) return;
// 0f - 1f of battery capacity in use
float backStored = backGraph.getBatteryStored() / backGraph.getTotalBatteryCapacity();
float frontStored = frontGraph.getBatteryStored() / frontGraph.getTotalBatteryCapacity();
// try to send if the back side has more % capacity stored than the front side
if(backStored > frontStored) {
// send half of the difference
float amount = backGraph.getBatteryStored() * (backStored - frontStored) / 2;
// prevent sending more than the front can handle
amount = Mathf.clamp(amount, 0, frontGraph.getTotalBatteryCapacity() * (1 - frontStored));
backGraph.useBatteries(amount);
frontGraph.chargeBatteries(amount);
}
}
// battery % of the graph on either side, defaults to zero
public float bar(Tile tile){
return (tile != null && tile.block().hasPower) ? tile.entity.power.graph.getBatteryStored() / tile.entity.power.graph.getTotalBatteryCapacity() : 0f;
}
@Override
public void setBars(){
super.setBars();
bars.add("back", entity -> new Bar("bar.input", Pal.powerBar, () -> bar(entity.tile.back())));
bars.add("front", entity -> new Bar("bar.output", Pal.powerBar, () -> bar(entity.tile.front())));
}
@Override
public void load(){
super.load();
arrow = Core.atlas.find(name + "-arrow");
}
@Override
public void draw(Tile tile){
Draw.rect(region, tile.drawx(), tile.drawy(), 0);
Draw.rect(arrow, tile.drawx(), tile.drawy(), rotate ? tile.rotation() * 90 : 0);
}
@Override
public void drawRequestRegion(BuilderTrait.BuildRequest req, Eachable<BuilderTrait.BuildRequest> list) {
TextureRegion reg = icon(Cicon.full);
Draw.rect(icon(Cicon.full), req.drawx(), req.drawy(),
reg.getWidth() * req.animScale * Draw.scl,
reg.getHeight() * req.animScale * Draw.scl,
0);
Draw.rect(arrow, req.drawx(), req.drawy(),
arrow.getWidth() * req.animScale * Draw.scl,
arrow.getHeight() * req.animScale * Draw.scl,
!rotate ? 0 : req.rotation * 90);
}
}

View File

@@ -0,0 +1,12 @@
package mindustry.world.blocks.power;
import mindustry.world.blocks.PowerBlock;
public class PowerDistributor extends PowerBlock{
public PowerDistributor(String name){
super(name);
consumesPower = false;
outputsPower = true;
}
}

View File

@@ -0,0 +1,73 @@
package mindustry.world.blocks.power;
import arc.Core;
import arc.struct.EnumSet;
import arc.util.Strings;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.Pal;
import mindustry.ui.Bar;
import mindustry.world.Tile;
import mindustry.world.meta.*;
import java.io.*;
public class PowerGenerator extends PowerDistributor{
/** The amount of power produced per tick in case of an efficiency of 1.0, which represents 100%. */
public float powerProduction;
public BlockStat generationType = BlockStat.basePowerGeneration;
public PowerGenerator(String name){
super(name);
sync = true;
baseExplosiveness = 5f;
flags = EnumSet.of(BlockFlag.producer);
entityType = GeneratorEntity::new;
}
@Override
public void setStats(){
super.setStats();
stats.add(generationType, powerProduction * 60.0f, StatUnit.powerSecond);
}
@Override
public void setBars(){
super.setBars();
if(hasPower && outputsPower && !consumes.hasPower()){
bars.add("power", entity -> new Bar(() ->
Core.bundle.format("bar.poweroutput",
Strings.fixed(entity.block.getPowerProduction(entity.tile) * 60 * entity.timeScale, 1)),
() -> Pal.powerBar,
() -> ((GeneratorEntity)entity).productionEfficiency));
}
}
@Override
public float getPowerProduction(Tile tile){
return powerProduction * tile.<GeneratorEntity>ent().productionEfficiency;
}
@Override
public boolean outputsItems(){
return false;
}
public static class GeneratorEntity extends TileEntity{
public float generateTime;
/** The efficiency of the producer. An efficiency of 1.0 means 100% */
public float productionEfficiency = 0.0f;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(productionEfficiency);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
productionEfficiency = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,330 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.struct.*;
import arc.math.*;
import arc.util.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
public class PowerGraph{
private final static Queue<Tile> queue = new Queue<>();
private final static Array<Tile> outArray1 = new Array<>();
private final static Array<Tile> outArray2 = new Array<>();
private final static IntSet closedSet = new IntSet();
private final ObjectSet<Tile> producers = new ObjectSet<>();
private final ObjectSet<Tile> consumers = new ObjectSet<>();
private final ObjectSet<Tile> batteries = new ObjectSet<>();
private final ObjectSet<Tile> all = new ObjectSet<>();
private final WindowedMean powerBalance = new WindowedMean(60);
private float lastPowerProduced, lastPowerNeeded, lastUsageFraction;
private long lastFrameUpdated = -1;
private final int graphID;
private static int lastGraphID;
{
graphID = lastGraphID++;
}
public int getID(){
return graphID;
}
public float getPowerBalance(){
return powerBalance.getMean();
}
public float getLastPowerNeeded(){
return lastPowerNeeded;
}
public float getLastPowerProduced(){
return lastPowerProduced;
}
public float getSatisfaction(){
if(Mathf.zero(lastPowerProduced)){
return 0f;
}else if(Mathf.zero(lastPowerNeeded)){
return 1f;
}
return Mathf.clamp(lastPowerProduced / lastPowerNeeded);
}
/** @return multiplier of speed at which resources should be consumed for power generation. */
public float getUsageFraction(){
//TODO enable it later, or not?
return 1f; //lastUsageFraction;
}
public float getPowerProduced(){
float powerProduced = 0f;
for(Tile producer : producers){
if(producer.entity == null) continue;
powerProduced += producer.block().getPowerProduction(producer) * producer.entity.delta();
}
return powerProduced;
}
public float getPowerNeeded(){
float powerNeeded = 0f;
for(Tile consumer : consumers){
Consumers consumes = consumer.block().consumes;
if(consumes.hasPower()){
ConsumePower consumePower = consumes.getPower();
if(otherConsumersAreValid(consumer, consumePower)){
powerNeeded += consumePower.requestedPower(consumer.entity) * consumer.entity.delta();
}
}
}
return powerNeeded;
}
public float getBatteryStored(){
float totalAccumulator = 0f;
for(Tile battery : batteries){
Consumers consumes = battery.block().consumes;
if(consumes.hasPower()){
totalAccumulator += battery.entity.power.status * consumes.getPower().capacity;
}
}
return totalAccumulator;
}
public float getBatteryCapacity(){
float totalCapacity = 0f;
for(Tile battery : batteries){
if(battery.block().consumes.hasPower()){
ConsumePower power = battery.block().consumes.getPower();
totalCapacity += (1f - battery.entity.power.status) * power.capacity;
}
}
return totalCapacity;
}
public float getTotalBatteryCapacity(){
float totalCapacity = 0f;
for(Tile battery : batteries){
if(battery.block().consumes.hasPower()){
totalCapacity += battery.block().consumes.getPower().capacity;
}
}
return totalCapacity;
}
public float useBatteries(float needed){
float stored = getBatteryStored();
if(Mathf.equal(stored, 0f)) return 0f;
float used = Math.min(stored, needed);
float consumedPowerPercentage = Math.min(1.0f, needed / stored);
for(Tile battery : batteries){
Consumers consumes = battery.block().consumes;
if(consumes.hasPower()){
battery.entity.power.status *= (1f-consumedPowerPercentage);
}
}
return used;
}
public float chargeBatteries(float excess){
float capacity = getBatteryCapacity();
//how much of the missing in each battery % is charged
float chargedPercent = Math.min(excess/capacity, 1f);
if(Mathf.equal(capacity, 0f)) return 0f;
for(Tile battery : batteries){
Consumers consumes = battery.block().consumes;
if(consumes.hasPower()){
ConsumePower consumePower = consumes.getPower();
if(consumePower.capacity > 0f){
battery.entity.power.status += (1f-battery.entity.power.status) * chargedPercent;
}
}
}
return Math.min(excess, capacity);
}
public void distributePower(float needed, float produced){
//distribute even if not needed. this is because some might be requiring power but not using it; it updates consumers
float coverage = Mathf.zero(needed) && Mathf.zero(produced) ? 0f : Mathf.zero(needed) ? 1f : Math.min(1, produced / needed);
for(Tile consumer : consumers){
Consumers consumes = consumer.block().consumes;
if(consumes.hasPower()){
ConsumePower consumePower = consumes.getPower();
if(consumePower.buffered){
if(!Mathf.zero(consumePower.capacity)){
// Add an equal percentage of power to all buffers, based on the global power coverage in this graph
float maximumRate = consumePower.requestedPower(consumer.entity) * coverage * consumer.entity.delta();
consumer.entity.power.status = Mathf.clamp(consumer.entity.power.status + maximumRate / consumePower.capacity);
}
}else{
//valid consumers get power as usual
if(otherConsumersAreValid(consumer, consumePower)){
consumer.entity.power.status = coverage;
}else{ //invalid consumers get an estimate, if they were to activate
consumer.entity.power.status = Math.min(1, produced / (needed + consumePower.usage * consumer.entity.delta()));
//just in case
if(Float.isNaN(consumer.entity.power.status)){
consumer.entity.power.status = 0f;
}
}
}
}
}
}
public void update(){
if(Core.graphics.getFrameId() == lastFrameUpdated){
return;
}else if(!consumers.isEmpty() && consumers.first().isEnemyCheat()){
//when cheating, just set status to 1
for(Tile tile : consumers){
tile.entity.power.status = 1f;
}
lastPowerNeeded = lastPowerProduced = lastUsageFraction = 1f;
return;
}
lastFrameUpdated = Core.graphics.getFrameId();
float powerNeeded = getPowerNeeded();
float powerProduced = getPowerProduced();
float rawProduced = powerProduced;
lastPowerNeeded = powerNeeded;
lastPowerProduced = powerProduced;
if(!(consumers.size == 0 && producers.size == 0 && batteries.size == 0)){
if(!Mathf.equal(powerNeeded, powerProduced)){
if(powerNeeded > powerProduced){
float powerBatteryUsed = useBatteries(powerNeeded - powerProduced);
powerProduced += powerBatteryUsed;
lastPowerProduced += powerBatteryUsed;
}else if(powerProduced > powerNeeded){
powerProduced -= chargeBatteries(powerProduced - powerNeeded);
}
}
distributePower(powerNeeded, powerProduced);
}
powerBalance.addValue((lastPowerProduced - lastPowerNeeded) / Time.delta());
//overproducing: 10 / 20 = 0.5
//underproducing: 20 / 10 = 2 -> clamp -> 1.0
//nothing being produced: 20 / 0 -> 1.0
//nothing being consumed: 0 / 20 -> 0.0
lastUsageFraction = Mathf.zero(rawProduced) ? 1f : Mathf.clamp(powerNeeded / rawProduced);
}
public void add(PowerGraph graph){
for(Tile tile : graph.all){
add(tile);
}
}
public void add(Tile tile){
if(tile.entity == null || tile.entity.power == null) return;
tile.entity.power.graph = this;
all.add(tile);
if(tile.block().outputsPower && tile.block().consumesPower && !tile.block().consumes.getPower().buffered){
producers.add(tile);
consumers.add(tile);
}else if(tile.block().outputsPower && tile.block().consumesPower){
batteries.add(tile);
}else if(tile.block().outputsPower){
producers.add(tile);
}else if(tile.block().consumesPower){
consumers.add(tile);
}
}
public void reflow(Tile tile){
queue.clear();
queue.addLast(tile);
closedSet.clear();
while(queue.size > 0){
Tile child = queue.removeFirst();
add(child);
for(Tile next : child.block().getPowerConnections(child, outArray2)){
if(!closedSet.contains(next.pos())){
queue.addLast(next);
closedSet.add(next.pos());
}
}
}
}
private void removeSingle(Tile tile){
all.remove(tile);
producers.remove(tile);
consumers.remove(tile);
batteries.remove(tile);
}
public void remove(Tile tile){
removeSingle(tile);
//begin by clearing the closed set
closedSet.clear();
//go through all the connections of this tile
for(Tile other : tile.block().getPowerConnections(tile, outArray1)){
//a graph has already been assigned to this tile from a previous call, skip it
if(other.entity.power.graph != this) continue;
//create graph for this branch
PowerGraph graph = new PowerGraph();
graph.add(other);
//add to queue for BFS
queue.clear();
queue.addLast(other);
while(queue.size > 0){
//get child from queue
Tile child = queue.removeFirst();
//remove it from this graph
removeSingle(child);
//add it to the new branch graph
graph.add(child);
//go through connections
for(Tile next : child.block().getPowerConnections(child, outArray2)){
//make sure it hasn't looped back, and that the new graph being assigned hasn't already been assigned
//also skip closed tiles
if(next != tile && next.entity.power.graph != graph && !closedSet.contains(next.pos())){
queue.addLast(next);
closedSet.add(next.pos());
}
}
}
//update the graph once so direct consumers without any connected producer lose their power
graph.update();
}
}
private boolean otherConsumersAreValid(Tile tile, Consume consumePower){
for(Consume cons : tile.block().consumes.all()){
if(cons != consumePower && !cons.isOptional() && !cons.valid(tile.ent())){
return false;
}
}
return true;
}
@Override
public String toString(){
return "PowerGraph{" +
"producers=" + producers +
", consumers=" + consumers +
", batteries=" + batteries +
", all=" + all +
", lastFrameUpdated=" + lastFrameUpdated +
", graphID=" + graphID +
'}';
}
}

View File

@@ -0,0 +1,360 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.type.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class PowerNode extends PowerBlock{
protected static boolean returnValue = false;
protected final ObjectSet<PowerGraph> graphs = new ObjectSet<>();
protected final Vector2 t1 = new Vector2(), t2 = new Vector2();
public TextureRegion laser, laserEnd;
public float laserRange = 6;
public int maxNodes = 3;
public PowerNode(String name){
super(name);
expanded = true;
layer = Layer.power;
configurable = true;
consumesPower = false;
outputsPower = false;
}
@Override
public void configured(Tile tile, Player player, int value){
TileEntity entity = tile.entity;
Tile other = world.tile(value);
boolean contains = entity.power.links.contains(value), valid = other != null && other.entity != null && other.entity.power != null;
if(contains){
//unlink
entity.power.links.removeValue(value);
if(valid) other.entity.power.links.removeValue(tile.pos());
PowerGraph newgraph = new PowerGraph();
//reflow from this point, covering all tiles on this side
newgraph.reflow(tile);
if(valid && other.entity.power.graph != newgraph){
//create new graph for other end
PowerGraph og = new PowerGraph();
//reflow from other end
og.reflow(other);
}
}else if(linkValid(tile, other) && valid && entity.power.links.size < maxNodes){
if(!entity.power.links.contains(other.pos())){
entity.power.links.add(other.pos());
}
if(other.getTeamID() == tile.getTeamID()){
if(!other.entity.power.links.contains(tile.pos())){
other.entity.power.links.add(tile.pos());
}
}
entity.power.graph.add(other.entity.power.graph);
}
}
@Override
public void load(){
super.load();
laser = Core.atlas.find("laser");
laserEnd = Core.atlas.find("laser-end");
}
@Override
public void setBars(){
super.setBars();
bars.add("power", entity -> new Bar(() ->
Core.bundle.format("bar.powerbalance",
((entity.power.graph.getPowerBalance() >= 0 ? "+" : "") + Strings.fixed(entity.power.graph.getPowerBalance() * 60, 1))),
() -> Pal.powerBar,
() -> Mathf.clamp(entity.power.graph.getLastPowerProduced() / entity.power.graph.getLastPowerNeeded())));
bars.add("batteries", entity -> new Bar(() ->
Core.bundle.format("bar.powerstored",
(ui.formatAmount((int)entity.power.graph.getBatteryStored())), ui.formatAmount((int)entity.power.graph.getTotalBatteryCapacity())),
() -> Pal.powerBar,
() -> Mathf.clamp(entity.power.graph.getBatteryStored() / entity.power.graph.getTotalBatteryCapacity())));
}
@Override
public void placed(Tile tile){
if(net.client()) return;
Boolf<Tile> valid = other -> other != null && other != tile && ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) && linkValid(tile, other)
&& !other.entity.proximity().contains(tile) && other.entity.power.graph != tile.entity.power.graph;
tempTiles.clear();
Geometry.circle(tile.x, tile.y, (int)(laserRange + 2), (x, y) -> {
Tile other = world.ltile(x, y);
if(valid.get(other)){
if(!insulated(tile, other)){
tempTiles.add(other);
}
}
});
tempTiles.sort((a, b) -> {
int type = -Boolean.compare(a.block() instanceof PowerNode, b.block() instanceof PowerNode);
if(type != 0) return type;
return Float.compare(a.dst2(tile), b.dst2(tile));
});
tempTiles.each(valid, other -> {
if(!tile.entity.power.links.contains(other.pos())){
tile.configureAny(other.pos());
}
});
super.placed(tile);
}
private void getPotentialLinks(Tile tile, Cons<Tile> others){
Boolf<Tile> valid = other -> other != null && other != tile && other.entity != null && other.entity.power != null &&
((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) &&
overlaps(tile.x * tilesize + offset(), tile.y * tilesize + offset(), other, laserRange * tilesize) && other.getTeam() == player.getTeam()
&& !other.entity.proximity().contains(tile) && !graphs.contains(other.entity.power.graph);
tempTiles.clear();
graphs.clear();
Geometry.circle(tile.x, tile.y, (int)(laserRange + 2), (x, y) -> {
Tile other = world.ltile(x, y);
if(valid.get(other) && !tempTiles.contains(other)){
tempTiles.add(other);
}
});
tempTiles.sort((a, b) -> {
int type = -Boolean.compare(a.block() instanceof PowerNode, b.block() instanceof PowerNode);
if(type != 0) return type;
return Float.compare(a.dst2(tile), b.dst2(tile));
});
tempTiles.each(valid, t -> {
graphs.add(t.entity.power.graph);
others.get(t);
});
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.powerRange, laserRange, StatUnit.blocks);
stats.add(BlockStat.powerConnections, maxNodes, StatUnit.none);
}
@Override
public void update(Tile tile){
tile.entity.power.graph.update();
}
@Override
public boolean onConfigureTileTapped(Tile tile, Tile other){
TileEntity entity = tile.ent();
other = other.link();
if(linkValid(tile, other)){
tile.configure(other.pos());
return false;
}
if(tile == other){
if(other.entity.power.links.size == 0){
getPotentialLinks(tile, link -> {
if(!insulated(tile, link)) tile.configure(link.pos());
});
}else{
while(entity.power.links.size > 0){
tile.configure(entity.power.links.get(0));
}
}
return false;
}
return true;
}
@Override
public void drawSelect(Tile tile){
super.drawSelect(tile);
Lines.stroke(1f);
Draw.color(Pal.accent);
Drawf.circles(tile.drawx(), tile.drawy(), laserRange * tilesize);
Draw.reset();
}
@Override
public void drawConfigure(Tile tile){
Draw.color(Pal.accent);
Lines.stroke(1.5f);
Lines.circle(tile.drawx(), tile.drawy(),
tile.block().size * tilesize / 2f + 1f + Mathf.absin(Time.time(), 4f, 1f));
Drawf.circles(tile.drawx(), tile.drawy(), laserRange * tilesize);
Lines.stroke(1.5f);
for(int x = (int)(tile.x - laserRange - 2); x <= tile.x + laserRange + 2; x++){
for(int y = (int)(tile.y - laserRange - 2); y <= tile.y + laserRange + 2; y++){
Tile link = world.ltile(x, y);
if(link != tile && linkValid(tile, link, false)){
boolean linked = linked(tile, link);
if(linked){
Drawf.square(link.drawx(), link.drawy(), link.block().size * tilesize / 2f + 1f, Pal.place);
}
}
}
}
Draw.reset();
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Tile tile = world.tile(x, y);
if(tile == null) return;
Lines.stroke(1f);
Draw.color(Pal.placing);
Drawf.circles(x * tilesize + offset(), y * tilesize + offset(), laserRange * tilesize);
getPotentialLinks(tile, other -> {
Drawf.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f + 2f, Pal.place);
insulators(tile.x, tile.y, other.x, other.y, cause -> {
Drawf.square(cause.drawx(), cause.drawy(), cause.block().size * tilesize / 2f + 2f, Pal.plastanium);
});
});
Draw.reset();
}
@Override
public void drawLayer(Tile tile){
if(Core.settings.getInt("lasersopacity") == 0) return;
TileEntity entity = tile.ent();
for(int i = 0; i < entity.power.links.size; i++){
Tile link = world.tile(entity.power.links.get(i));
if(!linkValid(tile, link)) continue;
if(link.block() instanceof PowerNode && !(link.pos() < tile.pos())) continue;
drawLaser(tile, link);
}
Draw.reset();
}
protected boolean linked(Tile tile, Tile other){
return tile.entity.power.links.contains(other.pos());
}
public boolean linkValid(Tile tile, Tile link){
return linkValid(tile, link, true);
}
public boolean linkValid(Tile tile, Tile link, boolean checkMaxNodes){
if(tile == link || link == null || link.entity == null || tile.entity == null || !link.block().hasPower || tile.getTeam() != link.getTeam()) return false;
if(overlaps(tile, link, laserRange * tilesize) || (link.block() instanceof PowerNode && overlaps(link, tile, link.<PowerNode>cblock().laserRange * tilesize))){
if(checkMaxNodes && link.block() instanceof PowerNode){
return link.entity.power.links.size < link.<PowerNode>cblock().maxNodes || link.entity.power.links.contains(tile.pos());
}
return true;
}
return false;
}
protected boolean overlaps(float srcx, float srcy, Tile other, float range){
return Intersector.overlaps(Tmp.cr1.set(srcx, srcy, range), other.getHitbox(Tmp.r1));
}
protected boolean overlaps(Tile src, Tile other, float range){
return overlaps(src.drawx(), src.drawy(), other, range);
}
public boolean overlaps(@Nullable Tile src, @Nullable Tile other){
if(src == null || other == null) return true;
return Intersector.overlaps(Tmp.cr1.set(src.worldx() + offset(), src.worldy() + offset(), laserRange * tilesize), Tmp.r1.setSize(size * tilesize).setCenter(other.worldx() + offset(), other.worldy() + offset()));
}
protected void drawLaser(Tile tile, Tile target){
int opacityPercentage = Core.settings.getInt("lasersopacity");
if(opacityPercentage == 0) return;
float opacity = opacityPercentage / 100f;
float x1 = tile.drawx(), y1 = tile.drawy(),
x2 = target.drawx(), y2 = target.drawy();
float angle1 = Angles.angle(x1, y1, x2, y2);
t1.trns(angle1, tile.block().size * tilesize / 2f - 1.5f);
t2.trns(angle1 + 180f, target.block().size * tilesize / 2f - 1.5f);
x1 += t1.x;
y1 += t1.y;
x2 += t2.x;
y2 += t2.y;
float fract = 1f - tile.entity.power.graph.getSatisfaction();
Draw.color(Color.white, Pal.powerLight, fract * 0.86f + Mathf.absin(3f, 0.1f));
Draw.alpha(opacity);
Drawf.laser(laser, laserEnd, x1, y1, x2, y2, 0.25f);
Draw.color();
}
public static boolean insulated(Tile tile, Tile other){
return insulated(tile.x, tile.y, other.x, other.y);
}
public static boolean insulated(int x, int y, int x2, int y2){
returnValue = false;
insulators(x, y, x2, y2, cause -> returnValue = true);
return returnValue;
}
public static void insulators(int x, int y, int x2, int y2, Cons<Tile> iterator){
world.raycastEach(x, y, x2, y2, (wx, wy) -> {
Tile tile = world.ltile(wx, wy);
if(tile != null && tile.block() != null && tile.block().insulated){
iterator.get(tile);
}
return false;
});
}
}

View File

@@ -0,0 +1,22 @@
package mindustry.world.blocks.power;
import mindustry.type.Item;
import mindustry.type.Liquid;
public class SingleTypeGenerator extends ItemLiquidGenerator{
public SingleTypeGenerator(String name){
super(name);
defaults = true;
}
@Override
protected float getItemEfficiency(Item item){
return 1f;
}
@Override
protected float getLiquidEfficiency(Liquid liquid){
return 0f;
}
}

View File

@@ -0,0 +1,30 @@
package mindustry.world.blocks.power;
import arc.struct.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.state;
public class SolarGenerator extends PowerGenerator{
public SolarGenerator(String name){
super(name);
// Remove the BlockFlag.producer flag to make this a lower priority target than other generators.
flags = EnumSet.of();
entityType = GeneratorEntity::new;
}
@Override
public void update(Tile tile){
tile.<GeneratorEntity>ent().productionEfficiency = state.rules.lighting ? 1f - state.rules.ambientLight.a : 1f;
}
@Override
public void setStats(){
super.setStats();
// Solar Generators don't really have an efficiency (yet), so for them 100% = 1.0f
stats.remove(generationType);
stats.add(generationType, powerProduction * 60.0f, StatUnit.powerSecond);
}
}

View File

@@ -0,0 +1,61 @@
package mindustry.world.blocks.power;
import arc.*;
import arc.graphics.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.renderer;
public class ThermalGenerator extends PowerGenerator{
public Effect generateEffect = Fx.none;
public ThermalGenerator(String name){
super(name);
}
@Override
public void update(Tile tile){
GeneratorEntity entity = tile.ent();
if(entity.productionEfficiency > 0.1f && Mathf.chance(0.05 * entity.delta())){
Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f));
}
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
drawPlaceText(Core.bundle.formatFloat("bar.efficiency", sumAttribute(Attribute.heat, x, y) * 100, 1), x, y, valid);
}
@Override
public void drawLight(Tile tile){
GeneratorEntity entity = tile.ent();
renderer.lights.add(tile.drawx(), tile.drawy(), (40f + Mathf.absin(10f, 5f)) * entity.productionEfficiency * size, Color.scarlet, 0.4f);
}
@Override
public void onProximityAdded(Tile tile){
super.onProximityAdded(tile);
GeneratorEntity entity = tile.ent();
entity.productionEfficiency = sumAttribute(Attribute.heat, tile.x, tile.y);
}
@Override
public float getPowerProduction(Tile tile){
//in this case, productionEfficiency means 'total heat'
//thus, it may be greater than 1.0
return powerProduction * tile.<GeneratorEntity>ent().productionEfficiency;
}
@Override
public boolean canPlaceOn(Tile tile){
//make sure there's heat at this location
return tile.getLinkedTilesAs(this, tempTiles).sumf(other -> other.floor().attributes.get(Attribute.heat)) > 0.01f;
}
}

View File

@@ -0,0 +1,128 @@
package mindustry.world.blocks.production;
import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import arc.math.RandomXS128;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.Pal;
import mindustry.ui.Bar;
import mindustry.world.Tile;
import mindustry.world.meta.Attribute;
import java.io.*;
public class Cultivator extends GenericCrafter{
public Color plantColor = Color.valueOf("5541b1");
public Color plantColorLight = Color.valueOf("7457ce");
public Color bottomColor = Color.valueOf("474747");
public TextureRegion middleRegion, topRegion;
public RandomXS128 random = new RandomXS128(0);
public float recurrence = 6f;
public Attribute attribute = Attribute.spores;
public Cultivator(String name){
super(name);
craftEffect = Fx.none;
entityType = CultivatorEntity::new;
}
@Override
public void load(){
super.load();
middleRegion = Core.atlas.find(name + "-middle");
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void update(Tile tile){
super.update(tile);
CultivatorEntity entity = tile.ent();
entity.warmup = Mathf.lerpDelta(entity.warmup, entity.cons.valid() ? 1f : 0f, 0.015f);
}
@Override
public void setBars(){
super.setBars();
bars.add("multiplier", entity -> new Bar(() ->
Core.bundle.formatFloat("bar.efficiency",
((((CultivatorEntity)entity).boost + 1f) * ((CultivatorEntity)entity).warmup) * 100f, 1),
() -> Pal.ammo,
() -> ((CultivatorEntity)entity).warmup));
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
drawPlaceText(Core.bundle.formatFloat("bar.efficiency", (1 + sumAttribute(attribute, x, y)) * 100, 1), x, y, valid);
}
@Override
public void draw(Tile tile){
CultivatorEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.color(plantColor);
Draw.alpha(entity.warmup);
Draw.rect(middleRegion, tile.drawx(), tile.drawy());
Draw.color(bottomColor, plantColorLight, entity.warmup);
random.setSeed(tile.pos());
for(int i = 0; i < 12; i++){
float offset = random.nextFloat() * 999999f;
float x = random.range(4f), y = random.range(4f);
float life = 1f - (((Time.time() + offset) / 50f) % recurrence);
if(life > 0){
Lines.stroke(entity.warmup * (life * 1f + 0.2f));
Lines.poly(tile.drawx() + x, tile.drawy() + y, 8, (1f - life) * 3f);
}
}
Draw.color();
Draw.rect(topRegion, tile.drawx(), tile.drawy());
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top"),};
}
@Override
public void onProximityAdded(Tile tile){
super.onProximityAdded(tile);
CultivatorEntity entity = tile.ent();
entity.boost = sumAttribute(attribute, tile.x, tile.y);
}
@Override
protected float getProgressIncrease(TileEntity entity, float baseTime){
CultivatorEntity c = (CultivatorEntity)entity;
return super.getProgressIncrease(entity, baseTime) * (1f + c.boost);
}
public static class CultivatorEntity extends GenericCrafterEntity{
public float warmup;
public float boost;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(warmup);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
warmup = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,332 @@
package mindustry.world.blocks.production;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Drill extends Block{
public float hardnessDrillMultiplier = 50f;
protected final ObjectIntMap<Item> oreCount = new ObjectIntMap<>();
protected final Array<Item> itemArray = new Array<>();
/** Maximum tier of blocks this drill can mine. */
public int tier;
/** Base time to drill one ore, in frames. */
public float drillTime = 300;
/** How many times faster the drill will progress when boosted by liquid. */
public float liquidBoostIntensity = 1.6f;
/** Speed at which the drill speeds up. */
public float warmupSpeed = 0.02f;
//return variables for countOre
protected Item returnItem;
protected int returnCount;
/** Whether to draw the item this drill is mining. */
public boolean drawMineItem = false;
/** Effect played when an item is produced. This is colored. */
public Effect drillEffect = Fx.mine;
/** Speed the drill bit rotates at. */
public float rotateSpeed = 2f;
/** Effect randomly played while drilling. */
public Effect updateEffect = Fx.pulverizeSmall;
/** Chance the update effect will appear. */
public float updateEffectChance = 0.02f;
public boolean drawRim = false;
public Color heatColor = Color.valueOf("ff5512");
public TextureRegion rimRegion;
public TextureRegion rotatorRegion;
public TextureRegion topRegion;
public Drill(String name){
super(name);
update = true;
solid = true;
layer = Layer.overlay;
group = BlockGroup.drills;
hasLiquids = true;
liquidCapacity = 5f;
hasItems = true;
entityType = DrillEntity::new;
idleSound = Sounds.drill;
idleSoundVolume = 0.003f;
}
@Override
public void setBars(){
super.setBars();
bars.add("drillspeed", e -> {
DrillEntity entity = (DrillEntity)e;
return new Bar(() -> Core.bundle.format("bar.drillspeed", Strings.fixed(entity.lastDrillSpeed * 60 * entity.timeScale, 2)), () -> Pal.ammo, () -> entity.warmup);
});
}
@Override
public void load(){
super.load();
rimRegion = Core.atlas.find(name + "-rim");
rotatorRegion = Core.atlas.find(name + "-rotator");
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void drawCracks(Tile tile){}
@Override
public void draw(Tile tile){
float s = 0.3f;
float ts = 0.6f;
DrillEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
super.drawCracks(tile);
if(drawRim){
Draw.color(heatColor);
Draw.alpha(entity.warmup * ts * (1f - s + Mathf.absin(Time.time(), 3f, s)));
Draw.blend(Blending.additive);
Draw.rect(rimRegion, tile.drawx(), tile.drawy());
Draw.blend();
Draw.color();
}
Draw.rect(rotatorRegion, tile.drawx(), tile.drawy(), entity.drillTime * rotateSpeed);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
if(entity.dominantItem != null && drawMineItem){
Draw.color(entity.dominantItem.color);
Draw.rect("drill-top", tile.drawx(), tile.drawy(), 1f);
Draw.color();
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-rotator"), Core.atlas.find(name + "-top")};
}
@Override
public boolean shouldConsume(Tile tile){
return tile.entity.items.total() < itemCapacity;
}
@Override
public boolean shouldIdleSound(Tile tile){
return tile.entity.efficiency() > 0.01f;
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Tile tile = world.tile(x, y);
if(tile == null) return;
countOre(tile);
if(returnItem != null){
float width = drawPlaceText(Core.bundle.formatFloat("bar.drillspeed", 60f / (drillTime + hardnessDrillMultiplier * returnItem.hardness) * returnCount, 2), x, y, valid);
float dx = x * tilesize + offset() - width/2f - 4f, dy = y * tilesize + offset() + size * tilesize / 2f + 5;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(returnItem.icon(Cicon.small), dx, dy - 1);
Draw.reset();
Draw.rect(returnItem.icon(Cicon.small), dx, dy);
}else{
Tile to = tile.getLinkedTilesAs(this, tempTiles).find(t -> t.drop() != null && t.drop().hardness > tier);
Item item = to == null ? null : to.drop();
if(item != null){
drawPlaceText(Core.bundle.get("bar.drilltierreq"), x, y, valid);
}
}
}
@Override
public void drawSelect(Tile tile){
DrillEntity entity = tile.ent();
if(entity.dominantItem != null){
float dx = tile.drawx() - size * tilesize/2f, dy = tile.drawy() + size * tilesize/2f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(entity.dominantItem.icon(Cicon.small), dx, dy - 1);
Draw.reset();
Draw.rect(entity.dominantItem.icon(Cicon.small), dx, dy);
}
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.drillTier, table -> {
Array<Block> list = content.blocks().select(b -> b.isFloor() && b.asFloor().itemDrop != null && b.asFloor().itemDrop.hardness <= tier);
table.table(l -> {
l.left();
for(int i = 0; i < list.size; i++){
Block item = list.get(i);
l.addImage(item.icon(Cicon.small)).size(8 * 3).padRight(2).padLeft(2).padTop(3).padBottom(3);
l.add(item.localizedName).left().padLeft(1).padRight(4);
if(i % 5 == 4){
l.row();
}
}
});
});
stats.add(BlockStat.drillSpeed, 60f / drillTime * size * size, StatUnit.itemsSecond);
if(liquidBoostIntensity > 0){
stats.add(BlockStat.boostEffect, liquidBoostIntensity * liquidBoostIntensity, StatUnit.timesSpeed);
}
}
void countOre(Tile tile){
returnItem = null;
returnCount = 0;
oreCount.clear();
itemArray.clear();
for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){
if(isValid(other)){
oreCount.getAndIncrement(getDrop(other), 0, 1);
}
}
for(Item item : oreCount.keys()){
itemArray.add(item);
}
itemArray.sort((item1, item2) -> {
int type = Boolean.compare(item1 != Items.sand, item2 != Items.sand);
if(type != 0) return type;
int amounts = Integer.compare(oreCount.get(item1, 0), oreCount.get(item2, 0));
if(amounts != 0) return amounts;
return Integer.compare(item1.id, item2.id);
});
if(itemArray.size == 0){
return;
}
returnItem = itemArray.peek();
returnCount = oreCount.get(itemArray.peek(), 0);
}
@Override
public void update(Tile tile){
DrillEntity entity = tile.ent();
if(entity.dominantItem == null){
countOre(tile);
if(returnItem == null) return;
entity.dominantItem = returnItem;
entity.dominantItems = returnCount;
}
if(entity.timer.get(timerDump, dumpTime)){
tryDump(tile, entity.dominantItem);
}
entity.drillTime += entity.warmup * entity.delta();
if(entity.items.total() < itemCapacity && entity.dominantItems > 0 && entity.cons.valid()){
float speed = 1f;
if(entity.cons.optionalValid()){
speed = liquidBoostIntensity;
}
speed *= entity.efficiency(); // Drill slower when not at full power
entity.lastDrillSpeed = (speed * entity.dominantItems * entity.warmup) / (drillTime + hardnessDrillMultiplier * entity.dominantItem.hardness);
entity.warmup = Mathf.lerpDelta(entity.warmup, speed, warmupSpeed);
entity.progress += entity.delta()
* entity.dominantItems * speed * entity.warmup;
if(Mathf.chance(Time.delta() * updateEffectChance * entity.warmup))
Effects.effect(updateEffect, entity.x + Mathf.range(size * 2f), entity.y + Mathf.range(size * 2f));
}else{
entity.lastDrillSpeed = 0f;
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, warmupSpeed);
return;
}
if(entity.dominantItems > 0 && entity.progress >= drillTime + hardnessDrillMultiplier * entity.dominantItem.hardness && tile.entity.items.total() < itemCapacity){
offloadNear(tile, entity.dominantItem);
useContent(tile, entity.dominantItem);
entity.index++;
entity.progress = 0f;
Effects.effect(drillEffect, entity.dominantItem.color,
entity.x + Mathf.range(size), entity.y + Mathf.range(size));
}
}
@Override
public boolean canPlaceOn(Tile tile){
if(isMultiblock()){
for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){
if(isValid(other)){
return true;
}
}
return false;
}else{
return isValid(tile);
}
}
public int tier(){
return tier;
}
public Item getDrop(Tile tile){
return tile.drop();
}
public boolean isValid(Tile tile){
if(tile == null) return false;
Item drops = tile.drop();
return drops != null && drops.hardness <= tier;
}
public static class DrillEntity extends TileEntity{
float progress;
int index;
float warmup;
float drillTime;
float lastDrillSpeed;
int dominantItems;
Item dominantItem;
}
}

View File

@@ -0,0 +1,96 @@
package mindustry.world.blocks.production;
import arc.*;
import arc.graphics.g2d.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class Fracker extends SolidPump{
public float itemUseTime = 100f;
public TextureRegion liquidRegion;
public TextureRegion rotatorRegion;
public TextureRegion topRegion;
public Fracker(String name){
super(name);
hasItems = true;
entityType = FrackerEntity::new;
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.productionTime, itemUseTime / 60f, StatUnit.seconds);
}
@Override
public void load(){
super.load();
liquidRegion = Core.atlas.find(name + "-liquid");
rotatorRegion = Core.atlas.find(name + "-rotator");
topRegion = Core.atlas.find(name + "-top");
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void drawCracks(Tile tile){}
@Override
public boolean shouldConsume(Tile tile){
return tile.entity.liquids.get(result) < liquidCapacity - 0.01f;
}
@Override
public void draw(Tile tile){
FrackerEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
super.drawCracks(tile);
Draw.color(result.color);
Draw.alpha(tile.entity.liquids.get(result) / liquidCapacity);
Draw.rect(liquidRegion, tile.drawx(), tile.drawy());
Draw.color();
Draw.rect(rotatorRegion, tile.drawx(), tile.drawy(), entity.pumpTime);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-rotator"), Core.atlas.find(name + "-top")};
}
@Override
public void update(Tile tile){
FrackerEntity entity = tile.ent();
if(entity.cons.valid()){
if(entity.accumulator >= itemUseTime){
entity.cons.trigger();
entity.accumulator -= itemUseTime;
}
super.update(tile);
entity.accumulator += entity.delta() * entity.efficiency();
}else{
tryDumpLiquid(tile, result);
}
}
@Override
public float typeLiquid(Tile tile){
return tile.entity.liquids.get(result);
}
public static class FrackerEntity extends SolidPumpEntity{
public float accumulator;
}
}

View File

@@ -0,0 +1,179 @@
package mindustry.world.blocks.production;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import java.io.*;
public class GenericCrafter extends Block{
public ItemStack outputItem;
public LiquidStack outputLiquid;
public float craftTime = 80;
public Effect craftEffect = Fx.none;
public Effect updateEffect = Fx.none;
public float updateEffectChance = 0.04f;
public Cons<Tile> drawer = null;
public Prov<TextureRegion[]> drawIcons = null;
public GenericCrafter(String name){
super(name);
update = true;
solid = true;
hasItems = true;
health = 60;
idleSound = Sounds.machine;
sync = true;
idleSoundVolume = 0.03f;
entityType = GenericCrafterEntity::new;
}
@Override
public void setStats(){
if(consumes.has(ConsumeType.liquid)){
ConsumeLiquidBase cons = consumes.get(ConsumeType.liquid);
cons.timePeriod = craftTime;
}
super.setStats();
stats.add(BlockStat.productionTime, craftTime / 60f, StatUnit.seconds);
if(outputItem != null){
stats.add(BlockStat.output, outputItem);
}
if(outputLiquid != null){
stats.add(BlockStat.output, outputLiquid.liquid, outputLiquid.amount, false);
}
}
@Override
public boolean shouldIdleSound(Tile tile){
return tile.entity.cons.valid();
}
@Override
public void init(){
outputsLiquid = outputLiquid != null;
super.init();
}
@Override
public void draw(Tile tile){
if(drawer == null){
super.draw(tile);
}else{
drawer.get(tile);
}
}
@Override
public TextureRegion[] generateIcons(){
return drawIcons == null ? super.generateIcons() : drawIcons.get();
}
@Override
public void update(Tile tile){
GenericCrafterEntity entity = tile.ent();
if(entity.cons.valid()){
entity.progress += getProgressIncrease(entity, craftTime);
entity.totalProgress += entity.delta();
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
if(Mathf.chance(Time.delta() * updateEffectChance)){
Effects.effect(updateEffect, entity.x + Mathf.range(size * 4f), entity.y + Mathf.range(size * 4));
}
}else{
entity.warmup = Mathf.lerp(entity.warmup, 0f, 0.02f);
}
if(entity.progress >= 1f){
entity.cons.trigger();
if(outputItem != null){
useContent(tile, outputItem.item);
for(int i = 0; i < outputItem.amount; i++){
offloadNear(tile, outputItem.item);
}
}
if(outputLiquid != null){
useContent(tile, outputLiquid.liquid);
handleLiquid(tile, tile, outputLiquid.liquid, outputLiquid.amount);
}
Effects.effect(craftEffect, tile.drawx(), tile.drawy());
entity.progress = 0f;
}
if(outputItem != null && tile.entity.timer.get(timerDump, dumpTime)){
tryDump(tile, outputItem.item);
}
if(outputLiquid != null){
tryDumpLiquid(tile, outputLiquid.liquid);
}
}
@Override
public boolean outputsItems(){
return outputItem != null;
}
@Override
public boolean shouldConsume(Tile tile){
if(outputItem != null && tile.entity.items.get(outputItem.item) >= itemCapacity){
return false;
}
return outputLiquid == null || !(tile.entity.liquids.get(outputLiquid.liquid) >= liquidCapacity);
}
@Override
public int getMaximumAccepted(Tile tile, Item item){
return itemCapacity;
}
public Item outputItem(){
return outputItem == null ? null : outputItem.item;
}
public Liquid outputLiquid(){
return outputLiquid == null ? null : outputLiquid.liquid;
}
public static class GenericCrafterEntity extends TileEntity{
public float progress;
public float totalProgress;
public float warmup;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(progress);
stream.writeFloat(warmup);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
progress = stream.readFloat();
warmup = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,57 @@
package mindustry.world.blocks.production;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.world.*;
import static mindustry.Vars.renderer;
/** A GenericCrafter with a new glowing region drawn on top. */
public class GenericSmelter extends GenericCrafter{
public Color flameColor = Color.valueOf("ffc999");
public TextureRegion topRegion;
public GenericSmelter(String name){
super(name);
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void draw(Tile tile){
super.draw(tile);
GenericCrafterEntity entity = tile.ent();
//draw glowing center
if(entity.warmup > 0f && flameColor.a > 0.001f){
float g = 0.3f;
float r = 0.06f;
float cr = Mathf.random(0.1f);
Draw.alpha(((1f - g) + Mathf.absin(Time.time(), 8f, g) + Mathf.random(r) - r) * entity.warmup);
Draw.tint(flameColor);
Fill.circle(tile.drawx(), tile.drawy(), 3f + Mathf.absin(Time.time(), 5f, 2f) + cr);
Draw.color(1f, 1f, 1f, entity.warmup);
Draw.rect(topRegion, tile.drawx(), tile.drawy());
Fill.circle(tile.drawx(), tile.drawy(), 1.9f + Mathf.absin(Time.time(), 5f, 1f) + cr);
Draw.color();
}
}
@Override
public void drawLight(Tile tile){
GenericCrafterEntity entity = tile.ent();
renderer.lights.add(tile.drawx(), tile.drawy(), (60f + Mathf.absin(10f, 5f)) * entity.warmup * size, flameColor, 0.65f);
}
}

View File

@@ -0,0 +1,91 @@
package mindustry.world.blocks.production;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.Fill;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.Effects.Effect;
import mindustry.entities.type.TileEntity;
import mindustry.type.Item;
import mindustry.type.Liquid;
import mindustry.world.Block;
import mindustry.world.Tile;
public class Incinerator extends Block{
public Effect effect = Fx.fuelburn;
public Color flameColor = Color.valueOf("ffad9d");
public Incinerator(String name){
super(name);
hasPower = true;
hasLiquids = true;
update = true;
solid = true;
entityType = IncineratorEntity::new;
}
@Override
public void update(Tile tile){
IncineratorEntity entity = tile.ent();
if(entity.cons.valid()){
entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.04f);
}else{
entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.02f);
}
}
@Override
public void draw(Tile tile){
super.draw(tile);
IncineratorEntity entity = tile.ent();
if(entity.heat > 0f){
float g = 0.3f;
float r = 0.06f;
Draw.alpha(((1f - g) + Mathf.absin(Time.time(), 8f, g) + Mathf.random(r) - r) * entity.heat);
Draw.tint(flameColor);
Fill.circle(tile.drawx(), tile.drawy(), 2f);
Draw.color(1f, 1f, 1f, entity.heat);
Fill.circle(tile.drawx(), tile.drawy(), 1f);
Draw.color();
}
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
if(Mathf.chance(0.3)){
Effects.effect(effect, tile.drawx(), tile.drawy());
}
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
IncineratorEntity entity = tile.ent();
return entity.heat > 0.5f;
}
@Override
public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){
if(Mathf.chance(0.02)){
Effects.effect(effect, tile.drawx(), tile.drawy());
}
}
@Override
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
IncineratorEntity entity = tile.ent();
return entity.heat > 0.5f;
}
public static class IncineratorEntity extends TileEntity{
public float heat;
}
}

View File

@@ -0,0 +1,61 @@
package mindustry.world.blocks.production;
import mindustry.world.Tile;
import mindustry.world.consumers.ConsumeLiquidBase;
import mindustry.world.consumers.ConsumeType;
import mindustry.world.meta.BlockStat;
public class LiquidConverter extends GenericCrafter{
public LiquidConverter(String name){
super(name);
hasLiquids = true;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void init(){
ConsumeLiquidBase cl = consumes.get(ConsumeType.liquid);
cl.update(true);
outputLiquid.amount = cl.amount;
super.init();
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.output);
stats.add(BlockStat.output, outputLiquid.liquid, outputLiquid.amount * craftTime, false);
}
@Override
public void drawLight(Tile tile){
if(hasLiquids && drawLiquidLight && outputLiquid.liquid.lightColor.a > 0.001f){
drawLiquidLight(tile, outputLiquid.liquid, tile.entity.liquids.get(outputLiquid.liquid));
}
}
@Override
public void update(Tile tile){
GenericCrafterEntity entity = tile.ent();
ConsumeLiquidBase cl = consumes.get(ConsumeType.liquid);
if(tile.entity.cons.valid()){
float use = Math.min(cl.amount * entity.delta(), liquidCapacity - entity.liquids.get(outputLiquid.liquid)) * entity.efficiency();
useContent(tile, outputLiquid.liquid);
entity.progress += use / cl.amount / craftTime;
entity.liquids.add(outputLiquid.liquid, use);
if(entity.progress >= 1f){
entity.cons.trigger();
entity.progress = 0f;
}
}
tryDumpLiquid(tile, outputLiquid.liquid);
}
}

View File

@@ -0,0 +1,137 @@
package mindustry.world.blocks.production;
import arc.Core;
import arc.struct.Array;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import mindustry.graphics.Layer;
import mindustry.type.Liquid;
import mindustry.ui.Cicon;
import mindustry.world.Tile;
import mindustry.world.blocks.LiquidBlock;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.world;
public class Pump extends LiquidBlock{
protected final Array<Tile> drawTiles = new Array<>();
protected final Array<Tile> updateTiles = new Array<>();
public final int timerContentCheck = timers++;
/** Pump amount, total. */
protected float pumpAmount = 1f;
public Pump(String name){
super(name);
layer = Layer.overlay;
group = BlockGroup.liquids;
floating = true;
}
@Override
public void load(){
super.load();
liquidRegion = Core.atlas.find("pump-liquid");
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.output, 60f * pumpAmount, StatUnit.liquidSecond);
}
@Override
public void draw(Tile tile){
Draw.rect(name, tile.drawx(), tile.drawy());
Draw.color(tile.entity.liquids.current().color);
Draw.alpha(tile.entity.liquids.total() / liquidCapacity);
Draw.rect(liquidRegion, tile.drawx(), tile.drawy());
Draw.color();
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid) {
Tile tile = world.tile(x, y);
if(tile == null) return;
float tiles = 0f;
Liquid liquidDrop = null;
for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){
if(isValid(other)){
liquidDrop = other.floor().liquidDrop;
tiles++;
}
}
if(liquidDrop != null){
float width = drawPlaceText(Core.bundle.formatFloat("bar.pumpspeed", tiles * pumpAmount / size / size * 60f, 0), x, y, valid);
float dx = x * tilesize + offset() - width/2f - 4f, dy = y * tilesize + offset() + size * tilesize / 2f + 5;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(liquidDrop.icon(Cicon.small), dx, dy - 1);
Draw.reset();
Draw.rect(liquidDrop.icon(Cicon.small), dx, dy);
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name)};
}
@Override
public boolean canPlaceOn(Tile tile){
if(isMultiblock()){
Liquid last = null;
for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){
if(other.floor().liquidDrop == null)
continue;
if(other.floor().liquidDrop != last && last != null)
return false;
last = other.floor().liquidDrop;
}
return last != null;
}else{
return isValid(tile);
}
}
@Override
public void update(Tile tile){
float tiles = 0f;
Liquid liquidDrop = null;
if(isMultiblock()){
for(Tile other : tile.getLinkedTiles(updateTiles)){
if(isValid(other)){
liquidDrop = other.floor().liquidDrop;
tiles++;
}
}
}else{
tiles = 1f;
liquidDrop = tile.floor().liquidDrop;
}
if(tile.entity.cons.valid() && liquidDrop != null){
float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), tiles * pumpAmount * tile.entity.delta() / size / size) * tile.entity.efficiency();
tile.entity.liquids.add(liquidDrop, maxPump);
}
if(tile.entity.liquids.currentAmount() > 0f && tile.entity.timer.get(timerContentCheck, 10)){
useContent(tile, tile.entity.liquids.current());
}
tryDumpLiquid(tile, tile.entity.liquids.current());
}
protected boolean isValid(Tile tile){
return tile != null && tile.floor().liquidDrop != null;
}
}

View File

@@ -0,0 +1,121 @@
package mindustry.world.blocks.production;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.ArcAnnotate.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
/**
* Extracts a random list of items from an input item and an input liquid.
*/
public class Separator extends Block{
protected @NonNull ItemStack[] results;
protected float craftTime;
protected float spinnerRadius = 2.5f;
protected float spinnerLength = 1f;
protected float spinnerThickness = 1f;
protected float spinnerSpeed = 2f;
protected Color color = Color.valueOf("858585");
protected int liquidRegion;
public Separator(String name){
super(name);
update = true;
solid = true;
hasItems = true;
hasLiquids = true;
liquidRegion = reg("-liquid");
entityType = GenericCrafterEntity::new;
}
@Override
public void setStats(){
if(consumes.has(ConsumeType.liquid)){
ConsumeLiquidBase cons = consumes.get(ConsumeType.liquid);
cons.timePeriod = craftTime;
}
super.setStats();
stats.add(BlockStat.output, new ItemFilterValue(item -> {
for(ItemStack i : results){
if(item == i.item) return true;
}
return false;
}));
stats.add(BlockStat.productionTime, craftTime / 60f, StatUnit.seconds);
}
@Override
public boolean shouldConsume(Tile tile){
return tile.entity.items.total() < itemCapacity;
}
@Override
public void draw(Tile tile){
super.draw(tile);
GenericCrafterEntity entity = tile.ent();
Draw.color(tile.entity.liquids.current().color);
Draw.alpha(tile.entity.liquids.total() / liquidCapacity);
Draw.rect(reg(liquidRegion), tile.drawx(), tile.drawy());
Draw.color(color);
Lines.stroke(spinnerThickness);
Lines.spikes(tile.drawx(), tile.drawy(), spinnerRadius, spinnerLength, 3, entity.totalProgress * spinnerSpeed);
Draw.reset();
}
@Override
public void update(Tile tile){
GenericCrafterEntity entity = tile.ent();
entity.totalProgress += entity.warmup * entity.delta();
if(entity.cons.valid()){
entity.progress += getProgressIncrease(entity, craftTime);
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
}else{
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f);
}
if(entity.progress >= 1f){
entity.progress = 0f;
int sum = 0;
for(ItemStack stack : results) sum += stack.amount;
int i = Mathf.random(sum);
int count = 0;
Item item = null;
//TODO guaranteed desync since items are random
for(ItemStack stack : results){
if(i >= count && i < count + stack.amount){
item = stack.item;
break;
}
count += stack.amount;
}
entity.cons.trigger();
if(item != null && entity.items.get(item) < itemCapacity){
offloadNear(tile, item);
}
}
if(entity.timer.get(timerDump, dumpTime)){
tryDump(tile);
}
}
}

View File

@@ -0,0 +1,157 @@
package mindustry.world.blocks.production;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Mathf;
import mindustry.content.Fx;
import mindustry.content.Liquids;
import mindustry.entities.Effects;
import mindustry.entities.Effects.Effect;
import mindustry.entities.type.TileEntity;
import mindustry.graphics.Pal;
import mindustry.type.Liquid;
import mindustry.ui.Bar;
import mindustry.world.Tile;
import mindustry.world.meta.Attribute;
import mindustry.world.meta.BlockStat;
/**
* Pump that makes liquid from solids and takes in power. Only works on solid floor blocks.
*/
public class SolidPump extends Pump{
public Liquid result = Liquids.water;
public Effect updateEffect = Fx.none;
public float updateEffectChance = 0.02f;
public float rotateSpeed = 1f;
/** Attribute that is checked when calculating output. */
public Attribute attribute;
public SolidPump(String name){
super(name);
hasPower = true;
entityType = SolidPumpEntity::new;
}
@Override
public void load(){
super.load();
liquidRegion = Core.atlas.find(name + "-liquid");
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
if(attribute != null){
drawPlaceText(Core.bundle.formatFloat("bar.efficiency", (sumAttribute(attribute, x, y) + 1f) * 100 * percentSolid(x, y), 1), x, y, valid);
}
}
@Override
public void setBars(){
super.setBars();
bars.add("efficiency", entity -> new Bar(() ->
Core.bundle.formatFloat("bar.efficiency",
((((SolidPumpEntity)entity).boost + 1f) * ((SolidPumpEntity)entity).warmup) * 100 * percentSolid(entity.tile.x, entity.tile.y), 1),
() -> Pal.ammo,
() -> ((SolidPumpEntity)entity).warmup));
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.output);
stats.add(BlockStat.output, result, 60f * pumpAmount, true);
}
@Override
public void draw(Tile tile){
SolidPumpEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.color(tile.entity.liquids.current().color);
Draw.alpha(tile.entity.liquids.total() / liquidCapacity);
Draw.rect(liquidRegion, tile.drawx(), tile.drawy());
Draw.color();
Draw.rect(name + "-rotator", tile.drawx(), tile.drawy(), entity.pumpTime * rotateSpeed);
Draw.rect(name + "-top", tile.drawx(), tile.drawy());
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-rotator"), Core.atlas.find(name + "-top")};
}
@Override
public void update(Tile tile){
SolidPumpEntity entity = tile.ent();
float fraction = 0f;
if(isMultiblock()){
for(Tile other : tile.getLinkedTiles(tempTiles)){
if(isValid(other)){
fraction += 1f / (size * size);
}
}
}else{
if(isValid(tile)) fraction = 1f;
}
fraction += entity.boost;
if(tile.entity.cons.valid() && typeLiquid(tile) < liquidCapacity - 0.001f){
float maxPump = Math.min(liquidCapacity - typeLiquid(tile), pumpAmount * entity.delta() * fraction * entity.efficiency());
tile.entity.liquids.add(result, maxPump);
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
if(Mathf.chance(entity.delta() * updateEffectChance))
Effects.effect(updateEffect, entity.x + Mathf.range(size * 2f), entity.y + Mathf.range(size * 2f));
}else{
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f);
}
entity.pumpTime += entity.warmup * entity.delta();
tryDumpLiquid(tile, result);
}
@Override
public boolean canPlaceOn(Tile tile){
if(isMultiblock()){
for(Tile other : tile.getLinkedTilesAs(this, drawTiles)){
if(isValid(other)){
return true;
}
}
return false;
}else{
return isValid(tile);
}
}
@Override
protected boolean isValid(Tile tile){
return tile != null && !tile.floor().isLiquid;
}
@Override
public void onProximityAdded(Tile tile){
super.onProximityAdded(tile);
if(attribute != null){
SolidPumpEntity entity = tile.ent();
entity.boost = sumAttribute(attribute, tile.x, tile.y);
}
}
public float typeLiquid(Tile tile){
return tile.entity.liquids.total();
}
public static class SolidPumpEntity extends TileEntity{
public float warmup;
public float pumpTime;
public float boost;
}
}

View File

@@ -0,0 +1,115 @@
package mindustry.world.blocks.sandbox;
import arc.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.content;
public class ItemSource extends Block{
private static Item lastItem;
public ItemSource(String name){
super(name);
hasItems = true;
update = true;
solid = true;
group = BlockGroup.transportation;
configurable = true;
entityType = ItemSourceEntity::new;
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<ItemSourceEntity>ent().outputItem = content.item(value);
}
@Override
public void playerPlaced(Tile tile){
if(lastItem != null){
Core.app.post(() -> tile.configure(lastItem.id));
}
}
@Override
public void setBars(){
super.setBars();
bars.remove("items");
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "center");
}
@Override
public boolean outputsItems(){
return true;
}
@Override
public void draw(Tile tile){
super.draw(tile);
ItemSourceEntity entity = tile.ent();
if(entity.outputItem == null) return;
Draw.color(entity.outputItem.color);
Draw.rect("center", tile.worldx(), tile.worldy());
Draw.color();
}
@Override
public void update(Tile tile){
ItemSourceEntity entity = tile.ent();
if(entity.outputItem == null) return;
entity.items.set(entity.outputItem, 1);
tryDump(tile, entity.outputItem);
entity.items.set(entity.outputItem, 0);
}
@Override
public void buildConfiguration(Tile tile, Table table){
ItemSourceEntity entity = tile.ent();
ItemSelection.buildItemTable(table, () -> entity.outputItem, item -> {
lastItem = item;
tile.configure(item == null ? -1 : item.id);
});
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return false;
}
public class ItemSourceEntity extends TileEntity{
Item outputItem;
@Override
public int config(){
return outputItem == null ? -1 : outputItem.id;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeShort(outputItem == null ? -1 : outputItem.id);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
outputItem = content.item(stream.readShort());
}
}
}

View File

@@ -0,0 +1,22 @@
package mindustry.world.blocks.sandbox;
import mindustry.type.Item;
import mindustry.world.Block;
import mindustry.world.Tile;
public class ItemVoid extends Block{
public ItemVoid(String name){
super(name);
update = solid = true;
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return true;
}
}

View File

@@ -0,0 +1,136 @@
package mindustry.world.blocks.sandbox;
import arc.*;
import arc.struct.*;
import arc.graphics.g2d.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class LiquidSource extends Block{
public static Liquid lastLiquid;
public LiquidSource(String name){
super(name);
update = true;
solid = true;
hasLiquids = true;
liquidCapacity = 100f;
configurable = true;
outputsLiquid = true;
entityType = LiquidSourceEntity::new;
}
@Override
public void playerPlaced(Tile tile){
if(lastLiquid != null){
Core.app.post(() -> tile.configure(lastLiquid.id));
}
}
@Override
public void setBars(){
super.setBars();
bars.remove("liquid");
}
@Override
public void update(Tile tile){
LiquidSourceEntity entity = tile.ent();
if(entity.source == null){
tile.entity.liquids.clear();
}else{
tile.entity.liquids.add(entity.source, liquidCapacity);
tryDumpLiquid(tile, entity.source);
}
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.liquid(req.config), "center");
}
@Override
public void draw(Tile tile){
super.draw(tile);
LiquidSourceEntity entity = tile.ent();
if(entity.source != null){
Draw.color(entity.source.color);
Draw.rect("center", tile.worldx(), tile.worldy());
Draw.color();
}
}
@Override
public void buildConfiguration(Tile tile, Table table){
LiquidSourceEntity entity = tile.ent();
Array<Liquid> items = content.liquids();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
group.setMinCheckCount(0);
Table cont = new Table();
for(int i = 0; i < items.size; i++){
final int f = i;
ImageButton button = cont.addImageButton(Tex.clear, Styles.clearToggleTransi, 24, () -> control.input.frag.config.hideConfig()).size(38).group(group).get();
button.changed(() -> {
tile.configure(button.isChecked() ? items.get(f).id : -1);
control.input.frag.config.hideConfig();
lastLiquid = items.get(f);
});
button.getStyle().imageUp = new TextureRegionDrawable(items.get(i).icon(Cicon.medium));
button.setChecked(entity.source == items.get(i));
if(i % 4 == 3){
cont.row();
}
}
table.add(cont);
}
@Override
public void configured(Tile tile, Player player, int value){
tile.<LiquidSourceEntity>ent().source = value == -1 ? null : content.liquid(value);
}
class LiquidSourceEntity extends TileEntity{
public @Nullable Liquid source = null;
@Override
public int config(){
return source == null ? -1 : source.id;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeByte(source == null ? -1 : source.id);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
byte id = stream.readByte();
source = id == -1 ? null : content.liquid(id);
}
}
}

View File

@@ -0,0 +1,20 @@
package mindustry.world.blocks.sandbox;
import mindustry.world.Tile;
import mindustry.world.blocks.power.PowerNode;
public class PowerSource extends PowerNode{
public PowerSource(String name){
super(name);
maxNodes = 100;
outputsPower = true;
consumesPower = false;
}
@Override
public float getPowerProduction(Tile tile){
return 10000f;
}
}

View File

@@ -0,0 +1,18 @@
package mindustry.world.blocks.sandbox;
import mindustry.world.blocks.PowerBlock;
import mindustry.world.meta.BlockStat;
public class PowerVoid extends PowerBlock{
public PowerVoid(String name){
super(name);
consumes.power(Float.MAX_VALUE);
}
@Override
public void init(){
super.init();
stats.remove(BlockStat.powerUse);
}
}

View File

@@ -0,0 +1,255 @@
package mindustry.world.blocks.storage;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import mindustry.world.modules.*;
import static mindustry.Vars.*;
public class CoreBlock extends StorageBlock{
public Mech mech = Mechs.starter;
public CoreBlock(String name){
super(name);
solid = true;
update = true;
hasItems = true;
flags = EnumSet.of(BlockFlag.core, BlockFlag.producer);
activeSound = Sounds.respawning;
activeSoundVolume = 1f;
layer = Layer.overlay;
entityType = CoreEntity::new;
}
@Remote(called = Loc.server)
public static void onUnitRespawn(Tile tile, Player player){
if(player == null || tile.entity == null) return;
CoreEntity entity = tile.ent();
Effects.effect(Fx.spawn, entity);
entity.progress = 0;
entity.spawnPlayer = player;
entity.spawnPlayer.onRespawn(tile);
entity.spawnPlayer.applyImpulse(0, 8f);
entity.spawnPlayer = null;
}
@Override
public void setStats(){
super.setStats();
bars.add("capacity", e ->
new Bar(
() -> Core.bundle.format("bar.capacity", ui.formatAmount(((CoreEntity)e).storageCapacity)),
() -> Pal.items,
() -> e.items.total() / (float)(((CoreEntity)e).storageCapacity * content.items().count(i -> i.type == ItemType.material))
));
}
@Override
public void drawLight(Tile tile){
renderer.lights.add(tile.drawx(), tile.drawy(), 30f * size, Pal.accent, 0.5f + Mathf.absin(20f, 0.1f));
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return tile.entity.items.get(item) < getMaximumAccepted(tile, item);
}
@Override
public int getMaximumAccepted(Tile tile, Item item){
CoreEntity entity = tile.ent();
return item.type == ItemType.material ? entity.storageCapacity : 0;
}
@Override
public void onProximityUpdate(Tile tile){
CoreEntity entity = tile.ent();
for(Tile other : state.teams.get(tile.getTeam()).cores){
if(other != tile){
entity.items = other.entity.items;
}
}
state.teams.get(tile.getTeam()).cores.add(tile);
entity.storageCapacity = itemCapacity + entity.proximity().sum(e -> isContainer(e) ? e.block().itemCapacity : 0);
entity.proximity().each(this::isContainer, t -> {
t.entity.items = entity.items;
t.<StorageBlockEntity>ent().linkedCore = tile;
});
for(Tile other : state.teams.get(tile.getTeam()).cores){
if(other == tile) continue;
entity.storageCapacity += other.block().itemCapacity + other.entity.proximity().sum(e -> isContainer(e) ? e.block().itemCapacity : 0);
}
if(!world.isGenerating()){
for(Item item : content.items()){
entity.items.set(item, Math.min(entity.items.get(item), entity.storageCapacity));
}
}
for(Tile other : state.teams.get(tile.getTeam()).cores){
CoreEntity oe = other.ent();
oe.storageCapacity = entity.storageCapacity;
}
}
@Override
public void drawSelect(Tile tile){
Lines.stroke(1f, Pal.accent);
Cons<Tile> outline = t -> {
for(int i = 0; i < 4; i++){
Point2 p = Geometry.d8edge[i];
float offset = -Math.max(t.block().size - 1, 0) / 2f * tilesize;
Draw.rect("block-select", t.drawx() + offset * p.x, t.drawy() + offset * p.y, i * 90);
}
};
if(tile.entity.proximity().contains(e -> isContainer(e) && e.entity.items == tile.entity.items)){
outline.get(tile);
}
tile.entity.proximity().each(e -> isContainer(e) && e.entity.items == tile.entity.items, outline);
Draw.reset();
}
public boolean isContainer(Tile tile){
return tile.entity instanceof StorageBlockEntity;
}
@Override
public float handleDamage(Tile tile, float amount){
if(player != null && tile.getTeam() == player.getTeam()){
Events.fire(Trigger.teamCoreDamage);
}
return amount;
}
@Override
public boolean canBreak(Tile tile){
return false;
}
@Override
public void removed(Tile tile){
int total = tile.entity.proximity().count(e -> e.entity.items == tile.entity.items);
float fract = 1f / total / state.teams.get(tile.getTeam()).cores.size;
tile.entity.proximity().each(e -> isContainer(e) && e.entity.items == tile.entity.items, t -> {
StorageBlockEntity ent = (StorageBlockEntity)t.entity;
ent.linkedCore = null;
ent.items = new ItemModule();
for(Item item : content.items()){
ent.items.set(item, (int)(fract * tile.entity.items.get(item)));
}
});
state.teams.get(tile.getTeam()).cores.remove(tile);
int max = itemCapacity * state.teams.get(tile.getTeam()).cores.size;
for(Item item : content.items()){
tile.entity.items.set(item, Math.min(tile.entity.items.get(item), max));
}
for(Tile other : state.teams.get(tile.getTeam()).cores){
other.block().onProximityUpdate(other);
}
}
@Override
public void placed(Tile tile){
super.placed(tile);
state.teams.get(tile.getTeam()).cores.add(tile);
}
@Override
public void drawLayer(Tile tile){
CoreEntity entity = tile.ent();
if(entity.heat > 0.001f){
RespawnBlock.drawRespawn(tile, entity.heat, entity.progress, entity.time, entity.spawnPlayer, mech);
}
}
@Override
public void handleItem(Item item, Tile tile, Tile source){
if(net.server() || !net.active()){
super.handleItem(item, tile, source);
if(state.rules.tutorial){
Events.fire(new CoreItemDeliverEvent());
}
}
}
@Override
public void update(Tile tile){
CoreEntity entity = tile.ent();
if(entity.spawnPlayer != null){
if(!entity.spawnPlayer.isDead() || !entity.spawnPlayer.isAdded()){
entity.spawnPlayer = null;
return;
}
entity.spawnPlayer.set(tile.drawx(), tile.drawy());
entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.1f);
entity.time += entity.delta();
entity.progress += 1f / state.rules.respawnTime * entity.delta();
if(entity.progress >= 1f){
Call.onUnitRespawn(tile, entity.spawnPlayer);
}
}else{
entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.1f);
}
}
@Override
public boolean shouldActiveSound(Tile tile){
CoreEntity entity = tile.ent();
return entity.spawnPlayer != null;
}
public class CoreEntity extends TileEntity implements SpawnerTrait{
protected Player spawnPlayer;
protected float progress;
protected float time;
protected float heat;
protected int storageCapacity;
@Override
public boolean hasUnit(Unit unit){
return unit == spawnPlayer;
}
@Override
public void updateSpawning(Player player){
if(!netServer.isWaitingForPlayers() && spawnPlayer == null){
spawnPlayer = player;
progress = 0f;
player.mech = mech;
player.beginRespawning(this);
}
}
}
}

View File

@@ -0,0 +1,87 @@
package mindustry.world.blocks.storage;
import arc.*;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.Lines;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.Vars;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.TileEntity;
import mindustry.game.EventType.*;
import mindustry.graphics.Pal;
import mindustry.type.Item;
import mindustry.type.ItemType;
import mindustry.world.Tile;
import mindustry.world.meta.BlockStat;
import mindustry.world.meta.StatUnit;
import static mindustry.Vars.data;
import static mindustry.Vars.world;
public class LaunchPad extends StorageBlock{
public final int timerLaunch = timers++;
/** Time inbetween launches. */
public float launchTime;
public LaunchPad(String name){
super(name);
update = true;
hasItems = true;
solid = true;
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.launchTime, launchTime / 60f, StatUnit.seconds);
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
return item.type == ItemType.material && tile.entity.items.total() < itemCapacity;
}
@Override
public void draw(Tile tile){
super.draw(tile);
float progress = Mathf.clamp(Mathf.clamp((tile.entity.items.total() / (float)itemCapacity)) * ((tile.entity.timer.getTime(timerLaunch) / (launchTime / tile.entity.timeScale))));
float scale = size / 3f;
Lines.stroke(2f);
Draw.color(Pal.accentBack);
Lines.poly(tile.drawx(), tile.drawy(), 4, scale * 10f * (1f - progress), 45 + 360f * progress);
Draw.color(Pal.accent);
if(tile.entity.cons.valid()){
for(int i = 0; i < 3; i++){
float f = (Time.time() / 200f + i * 0.5f) % 1f;
Lines.stroke(((2f * (2f - Math.abs(0.5f - f) * 2f)) - 2f + 0.2f));
Lines.poly(tile.drawx(), tile.drawy(), 4, (1f - f) * 10f * scale);
}
}
Draw.reset();
}
@Override
public void update(Tile tile){
TileEntity entity = tile.entity;
if(world.isZone() && entity.cons.valid() && world.isZone() && entity.items.total() >= itemCapacity && entity.timer.get(timerLaunch, launchTime / entity.timeScale)){
for(Item item : Vars.content.items()){
Events.fire(Trigger.itemLaunch);
Effects.effect(Fx.padlaunch, tile);
int used = Math.min(entity.items.get(item), itemCapacity);
data.addItem(item, used);
entity.items.remove(item, used);
Events.fire(new LaunchItemEvent(item, used));
}
}
}
}

View File

@@ -0,0 +1,77 @@
package mindustry.world.blocks.storage;
import arc.util.ArcAnnotate.*;
import mindustry.entities.type.TileEntity;
import mindustry.type.Item;
import mindustry.world.Block;
import mindustry.world.Tile;
public abstract class StorageBlock extends Block{
public StorageBlock(String name){
super(name);
hasItems = true;
entityType = StorageBlockEntity::new;
}
@Override
public boolean acceptItem(Item item, Tile tile, Tile source){
StorageBlockEntity entity = tile.ent();
return entity.linkedCore != null ? entity.linkedCore.block().acceptItem(item, entity.linkedCore, source) : tile.entity.items.get(item) < getMaximumAccepted(tile, item);
}
@Override
public int getMaximumAccepted(Tile tile, Item item){
return itemCapacity;
}
@Override
public void drawSelect(Tile tile){
StorageBlockEntity entity = tile.ent();
if(entity.linkedCore != null){
entity.linkedCore.block().drawSelect(entity.linkedCore);
}
}
@Override
public boolean outputsItems(){
return false;
}
/**
* Removes an item and returns it. If item is not null, it should return the item.
* Returns null if no items are there.
*/
public Item removeItem(Tile tile, Item item){
TileEntity entity = tile.entity;
if(item == null){
return entity.items.take();
}else{
if(entity.items.has(item)){
entity.items.remove(item, 1);
return item;
}
return null;
}
}
/**
* Returns whether this storage block has the specified item.
* If the item is null, it should return whether it has ANY items.
*/
public boolean hasItem(Tile tile, Item item){
TileEntity entity = tile.entity;
if(item == null){
return entity.items.total() > 0;
}else{
return entity.items.has(item);
}
}
public class StorageBlockEntity extends TileEntity{
protected @Nullable
Tile linkedCore;
}
}

View File

@@ -0,0 +1,153 @@
package mindustry.world.blocks.storage;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import java.io.*;
import static mindustry.Vars.content;
public class Unloader extends Block{
public float speed = 1f;
public final int timerUnload = timers++;
private static Item lastItem;
public Unloader(String name){
super(name);
update = true;
solid = true;
health = 70;
hasItems = true;
configurable = true;
entityType = UnloaderEntity::new;
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "unloader-center");
}
@Override
public boolean canDump(Tile tile, Tile to, Item item){
return !(to.block() instanceof StorageBlock);
}
@Override
public void setBars(){
super.setBars();
bars.remove("items");
}
@Override
public void playerPlaced(Tile tile){
if(lastItem != null){
tile.configure(lastItem.id);
}
}
@Override
public void configured(Tile tile, Player player, int value){
tile.entity.items.clear();
tile.<UnloaderEntity>ent().sortItem = content.item(value);
}
@Override
public void update(Tile tile){
UnloaderEntity entity = tile.ent();
if(tile.entity.timer.get(timerUnload, speed / entity.timeScale) && tile.entity.items.total() == 0){
for(Tile other : tile.entity.proximity()){
if(other.interactable(tile.getTeam()) && other.block().unloadable && other.block().hasItems && entity.items.total() == 0 &&
((entity.sortItem == null && other.entity.items.total() > 0) || hasItem(other, entity.sortItem))){
offloadNear(tile, removeItem(other, entity.sortItem));
}
}
}
if(entity.items.total() > 0){
tryDump(tile);
}
}
/**
* Removes an item and returns it. If item is not null, it should return the item.
* Returns null if no items are there.
*/
private Item removeItem(Tile tile, Item item){
TileEntity entity = tile.entity;
if(item == null){
return entity.items.take();
}else{
if(entity.items.has(item)){
entity.items.remove(item, 1);
return item;
}
return null;
}
}
/**
* Returns whether this storage block has the specified item.
* If the item is null, it should return whether it has ANY items.
*/
private boolean hasItem(Tile tile, Item item){
TileEntity entity = tile.entity;
if(item == null){
return entity.items.total() > 0;
}else{
return entity.items.has(item);
}
}
@Override
public void draw(Tile tile){
super.draw(tile);
UnloaderEntity entity = tile.ent();
Draw.color(entity.sortItem == null ? Color.clear : entity.sortItem.color);
Draw.rect("unloader-center", tile.worldx(), tile.worldy());
Draw.color();
}
@Override
public void buildConfiguration(Tile tile, Table table){
UnloaderEntity entity = tile.ent();
ItemSelection.buildItemTable(table, () -> entity.sortItem, item -> {
lastItem = item;
tile.configure(item == null ? -1 : item.id);
});
}
public static class UnloaderEntity extends TileEntity{
public Item sortItem = null;
@Override
public int config(){
return sortItem == null ? -1 : sortItem.id;
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeByte(sortItem == null ? -1 : sortItem.id);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
byte id = stream.readByte();
sortItem = id == -1 ? null : content.items().get(id);
}
}
}

View File

@@ -0,0 +1,12 @@
package mindustry.world.blocks.storage;
public class Vault extends StorageBlock{
public Vault(String name){
super(name);
solid = true;
update = false;
destructible = true;
}
}

View File

@@ -0,0 +1,146 @@
package mindustry.world.blocks.units;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class CommandCenter extends Block{
protected TextureRegion[] commandRegions = new TextureRegion[UnitCommand.all.length];
protected Color topColor = Pal.command;
protected Color bottomColor = Color.valueOf("5e5e5e");
protected Effect effect = Fx.commandSend;
public CommandCenter(String name){
super(name);
flags = EnumSet.of(BlockFlag.comandCenter);
destructible = true;
solid = true;
configurable = true;
entityType = CommandCenterEntity::new;
}
@Override
public void placed(Tile tile){
super.placed(tile);
ObjectSet<Tile> set = indexer.getAllied(tile.getTeam(), BlockFlag.comandCenter);
if(set.size > 0){
CommandCenterEntity entity = tile.ent();
CommandCenterEntity oe = set.first().ent();
entity.command = oe.command;
}
}
@Override
public void removed(Tile tile){
super.removed(tile);
ObjectSet<Tile> set = indexer.getAllied(tile.getTeam(), BlockFlag.comandCenter);
if(set.size == 1){
for(BaseUnit unit : unitGroups[tile.getTeam().ordinal()].all()){
unit.onCommand(UnitCommand.all[0]);
}
}
}
@Override
public void load(){
super.load();
for(UnitCommand cmd : UnitCommand.all){
commandRegions[cmd.ordinal()] = Core.atlas.find("icon-command-" + cmd.name() + "-small");
}
}
@Override
public void draw(Tile tile){
CommandCenterEntity entity = tile.ent();
super.draw(tile);
float size = IconSize.small.size/4f;
Draw.color(bottomColor);
Draw.rect(commandRegions[entity.command.ordinal()], tile.drawx(), tile.drawy() - 1, size, size);
Draw.color(topColor);
Draw.rect(commandRegions[entity.command.ordinal()], tile.drawx(), tile.drawy(), size, size);
Draw.color();
}
@Override
public void buildConfiguration(Tile tile, Table table){
CommandCenterEntity entity = tile.ent();
ButtonGroup<ImageButton> group = new ButtonGroup<>();
Table buttons = new Table();
for(UnitCommand cmd : UnitCommand.all){
buttons.addImageButton(Core.atlas.drawable("icon-command-" + cmd.name() + "-small"), Styles.clearToggleTransi, () -> tile.configure(cmd.ordinal()))
.size(44).group(group).update(b -> b.setChecked(entity.command == cmd));
}
table.add(buttons);
table.row();
table.label(() -> entity.command.localized()).style(Styles.outlineLabel).center().growX().get().setAlignment(Align.center);
}
@Override
public void configured(Tile tile, Player player, int value){
UnitCommand command = UnitCommand.all[value];
Effects.effect(((CommandCenter)tile.block()).effect, tile);
for(Tile center : indexer.getAllied(tile.getTeam(), BlockFlag.comandCenter)){
if(center.block() instanceof CommandCenter){
CommandCenterEntity entity = center.ent();
entity.command = command;
}
}
Team team = (player == null ? tile.getTeam() : player.getTeam());
for(BaseUnit unit : unitGroups[team.ordinal()].all()){
unit.onCommand(command);
}
Events.fire(new CommandIssueEvent(tile, command));
}
public class CommandCenterEntity extends TileEntity{
public UnitCommand command = UnitCommand.attack;
@Override
public int config(){
return command.ordinal();
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeByte(command.ordinal());
}
@Override
public void read(DataInput stream, byte version) throws IOException{
super.read(stream, version);
command = UnitCommand.all[stream.readByte()];
}
}
}

View File

@@ -0,0 +1,177 @@
package mindustry.world.blocks.units;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.EnumSet;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class MechPad extends Block{
public @NonNull Mech mech;
public float buildTime = 60 * 5;
public MechPad(String name){
super(name);
update = true;
solid = false;
hasPower = true;
layer = Layer.overlay;
flags = EnumSet.of(BlockFlag.mechPad);
entityType = MechFactoryEntity::new;
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.productionTime, buildTime / 60f, StatUnit.seconds);
}
@Remote(targets = Loc.both, called = Loc.server)
public static void onMechFactoryTap(Player player, Tile tile){
if(player == null || tile == null || !(tile.block() instanceof MechPad) || !checkValidTap(tile, player)) return;
MechFactoryEntity entity = tile.ent();
if(!entity.cons.valid()) return;
player.beginRespawning(entity);
entity.sameMech = false;
}
@Remote(called = Loc.server)
public static void onMechFactoryDone(Tile tile){
if(!(tile.entity instanceof MechFactoryEntity)) return;
MechFactoryEntity entity = tile.ent();
Effects.effect(Fx.spawn, entity);
if(entity.player == null) return;
Mech mech = ((MechPad)tile.block()).mech;
boolean resetSpawner = !entity.sameMech && entity.player.mech == mech;
entity.player.mech = !entity.sameMech && entity.player.mech == mech ? Mechs.starter : mech;
Player player = entity.player;
entity.progress = 0;
entity.player.onRespawn(tile);
if(resetSpawner) entity.player.lastSpawner = null;
entity.player = null;
Events.fire(new MechChangeEvent(player, player.mech));
}
protected static boolean checkValidTap(Tile tile, Player player){
MechFactoryEntity entity = tile.ent();
return !player.isDead() && tile.interactable(player.getTeam()) && Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize &&
Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize && entity.cons.valid() && entity.player == null;
}
@Override
public void drawSelect(Tile tile){
Draw.color(Pal.accent);
for(int i = 0; i < 4; i++){
float length = tilesize * size / 2f + 3 + Mathf.absin(Time.time(), 5f, 2f);
Draw.rect("transfer-arrow", tile.drawx() + Geometry.d4[i].x * length, tile.drawy() + Geometry.d4[i].y * length, (i + 2) * 90);
}
Draw.color();
}
@Override
public void tapped(Tile tile, Player player){
MechFactoryEntity entity = tile.ent();
if(checkValidTap(tile, player)){
Call.onMechFactoryTap(player, tile);
}else if(player.isLocal && mobile && !player.isDead() && entity.cons.valid() && entity.player == null){
//deselect on double taps
player.moveTarget = player.moveTarget == tile.entity ? null : tile.entity;
}
}
@Override
public void drawLayer(Tile tile){
MechFactoryEntity entity = tile.ent();
if(entity.player != null){
RespawnBlock.drawRespawn(tile, entity.heat, entity.progress, entity.time, entity.player, (!entity.sameMech && entity.player.mech == mech ? mech : Mechs.starter));
}
}
@Override
public void update(Tile tile){
MechFactoryEntity entity = tile.ent();
if(entity.player != null){
entity.player.set(tile.drawx(), tile.drawy());
entity.heat = Mathf.lerpDelta(entity.heat, 1f, 0.1f);
entity.progress += 1f / buildTime * entity.delta();
entity.time += 0.5f * entity.delta();
if(entity.progress >= 1f){
Call.onMechFactoryDone(tile);
}
}else{
entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.1f);
}
}
public class MechFactoryEntity extends TileEntity implements SpawnerTrait{
Player player;
boolean sameMech;
float progress;
float time;
float heat;
@Override
public boolean hasUnit(Unit unit){
return unit == player;
}
@Override
public void updateSpawning(Player unit){
if(player == null){
progress = 0f;
player = unit;
sameMech = true;
player.beginRespawning(this);
}
}
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(progress);
stream.writeFloat(time);
stream.writeFloat(heat);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
progress = stream.readFloat();
time = stream.readFloat();
heat = stream.readFloat();
}
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.world.blocks.units;
import arc.struct.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class RallyPoint extends Block{
public RallyPoint(String name){
super(name);
update = solid = true;
flags = EnumSet.of(BlockFlag.rally);
}
}

View File

@@ -0,0 +1,147 @@
package mindustry.world.blocks.units;
import arc.Core;
import arc.struct.EnumSet;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.Angles;
import arc.math.Mathf;
import arc.math.geom.Rectangle;
import arc.util.Time;
import mindustry.entities.Units;
import mindustry.entities.type.TileEntity;
import mindustry.entities.type.Unit;
import mindustry.graphics.*;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
public class RepairPoint extends Block{
private static Rectangle rect = new Rectangle();
public int timerTarget = timers++;
public float repairRadius = 50f;
public float repairSpeed = 0.3f;
public float powerUse;
public TextureRegion baseRegion;
public TextureRegion laser, laserEnd;
public RepairPoint(String name){
super(name);
update = true;
solid = true;
flags = EnumSet.of(BlockFlag.repair);
layer = Layer.turret;
layer2 = Layer.power;
hasPower = true;
outlineIcon = true;
entityType = RepairPointEntity::new;
}
@Override
public void load(){
super.load();
baseRegion = Core.atlas.find(name + "-base");
laser = Core.atlas.find("laser");
laserEnd = Core.atlas.find("laser-end");
}
@Override
public void setStats(){
super.setStats();
stats.add(BlockStat.range, repairRadius / tilesize, StatUnit.blocks);
}
@Override
public void init(){
consumes.powerCond(powerUse, entity -> ((RepairPointEntity)entity).target != null);
super.init();
}
@Override
public void drawSelect(Tile tile){
Drawf.dashCircle(tile.drawx(), tile.drawy(), repairRadius, Pal.accent);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Drawf.dashCircle(x * tilesize + offset(), y * tilesize + offset(), repairRadius, Pal.accent);
}
@Override
public void draw(Tile tile){
Draw.rect(baseRegion, tile.drawx(), tile.drawy());
}
@Override
public void drawLayer(Tile tile){
RepairPointEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy(), entity.rotation - 90);
}
@Override
public void drawLayer2(Tile tile){
RepairPointEntity entity = tile.ent();
if(entity.target != null &&
Angles.angleDist(entity.angleTo(entity.target), entity.rotation) < 30f){
float ang = entity.angleTo(entity.target);
float len = 5f;
Draw.color(Color.valueOf("e8ffd7"));
Drawf.laser(laser, laserEnd,
tile.drawx() + Angles.trnsx(ang, len), tile.drawy() + Angles.trnsy(ang, len),
entity.target.x, entity.target.y, entity.strength);
Draw.color();
}
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name + "-base"), Core.atlas.find(name)};
}
@Override
public void update(Tile tile){
RepairPointEntity entity = tile.ent();
boolean targetIsBeingRepaired = false;
if(entity.target != null && (entity.target.isDead() || entity.target.dst(tile) > repairRadius || entity.target.health >= entity.target.maxHealth())){
entity.target = null;
}else if(entity.target != null && entity.cons.valid()){
entity.target.health += repairSpeed * Time.delta() * entity.strength * entity.efficiency();
entity.target.clampHealth();
entity.rotation = Mathf.slerpDelta(entity.rotation, entity.angleTo(entity.target), 0.5f);
targetIsBeingRepaired = true;
}
if(entity.target != null && targetIsBeingRepaired){
entity.strength = Mathf.lerpDelta(entity.strength, 1f, 0.08f * Time.delta());
}else{
entity.strength = Mathf.lerpDelta(entity.strength, 0f, 0.07f * Time.delta());
}
if(entity.timer.get(timerTarget, 20)){
rect.setSize(repairRadius * 2).setCenter(tile.drawx(), tile.drawy());
entity.target = Units.closest(tile.getTeam(), tile.drawx(), tile.drawy(), repairRadius,
unit -> unit.health < unit.maxHealth());
}
}
@Override
public boolean shouldConsume(Tile tile){
RepairPointEntity entity = tile.ent();
return entity.target != null;
}
public class RepairPointEntity extends TileEntity{
public Unit target;
public float strength, rotation = 90;
}
}

View File

@@ -0,0 +1,211 @@
package mindustry.world.blocks.units;
import arc.*;
import mindustry.annotations.Annotations.Loc;
import mindustry.annotations.Annotations.Remote;
import arc.struct.EnumSet;
import arc.graphics.g2d.*;
import arc.math.Mathf;
import mindustry.Vars;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.Call;
import mindustry.graphics.Pal;
import mindustry.graphics.Shaders;
import mindustry.type.*;
import mindustry.ui.Bar;
import mindustry.ui.Cicon;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.consumers.ConsumeItems;
import mindustry.world.consumers.ConsumeType;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.*;
public class UnitFactory extends Block{
public UnitType unitType;
public float produceTime = 1000f;
public float launchVelocity = 0f;
public TextureRegion topRegion;
public int maxSpawn = 4;
public int[] capacities;
public UnitFactory(String name){
super(name);
update = true;
hasPower = true;
hasItems = true;
solid = false;
flags = EnumSet.of(BlockFlag.producer);
entityType = UnitFactoryEntity::new;
}
@Remote(called = Loc.server)
public static void onUnitFactorySpawn(Tile tile, int spawns){
if(!(tile.entity instanceof UnitFactoryEntity) || !(tile.block() instanceof UnitFactory)) return;
UnitFactoryEntity entity = tile.ent();
UnitFactory factory = (UnitFactory)tile.block();
entity.buildTime = 0f;
entity.spawned = spawns;
Effects.shake(2f, 3f, entity);
Effects.effect(Fx.producesmoke, tile.drawx(), tile.drawy());
if(!net.client()){
BaseUnit unit = factory.unitType.create(tile.getTeam());
unit.setSpawner(tile);
unit.set(tile.drawx() + Mathf.range(4), tile.drawy() + Mathf.range(4));
unit.add();
unit.velocity().y = factory.launchVelocity;
Events.fire(new UnitCreateEvent(unit));
}
}
@Override
public void init(){
super.init();
capacities = new int[Vars.content.items().size];
if(consumes.has(ConsumeType.item)){
ConsumeItems cons = consumes.get(ConsumeType.item);
for(ItemStack stack : cons.items){
capacities[stack.item.id] = stack.amount * 2;
}
}
}
@Override
public void load(){
super.load();
topRegion = Core.atlas.find(name + "-top");
}
@Override
public void setBars(){
super.setBars();
bars.add("progress", entity -> new Bar("bar.progress", Pal.ammo, () -> ((UnitFactoryEntity)entity).buildTime / produceTime));
bars.add("spawned", entity -> new Bar(() -> Core.bundle.format("bar.spawned", ((UnitFactoryEntity)entity).spawned, maxSpawn), () -> Pal.command, () -> (float)((UnitFactoryEntity)entity).spawned / maxSpawn));
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void setStats(){
super.setStats();
stats.remove(BlockStat.itemCapacity);
stats.add(BlockStat.productionTime, produceTime / 60f, StatUnit.seconds);
stats.add(BlockStat.maxUnits, maxSpawn, StatUnit.none);
}
@Override
public void unitRemoved(Tile tile, Unit unit){
UnitFactoryEntity entity = tile.ent();
entity.spawned--;
entity.spawned = Math.max(entity.spawned, 0);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top")};
}
@Override
public void draw(Tile tile){
UnitFactoryEntity entity = tile.ent();
TextureRegion region = unitType.icon(Cicon.full);
Draw.rect(name, tile.drawx(), tile.drawy());
Shaders.build.region = region;
Shaders.build.progress = entity.buildTime / produceTime;
Shaders.build.color.set(Pal.accent);
Shaders.build.color.a = entity.speedScl;
Shaders.build.time = -entity.time / 20f;
Draw.shader(Shaders.build);
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.shader();
Draw.color(Pal.accent);
Draw.alpha(entity.speedScl);
Lines.lineAngleCenter(
tile.drawx() + Mathf.sin(entity.time, 20f, Vars.tilesize / 2f * size - 2f),
tile.drawy(),
90,
size * Vars.tilesize - 4f);
Draw.reset();
Draw.rect(topRegion, tile.drawx(), tile.drawy());
}
@Override
public void update(Tile tile){
UnitFactoryEntity entity = tile.ent();
if(entity.spawned >= maxSpawn){
return;
}
if(entity.cons.valid() || tile.isEnemyCheat()){
entity.time += entity.delta() * entity.speedScl * Vars.state.rules.unitBuildSpeedMultiplier * entity.efficiency();
entity.buildTime += entity.delta() * entity.efficiency() * Vars.state.rules.unitBuildSpeedMultiplier;
entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.05f);
}else{
entity.speedScl = Mathf.lerpDelta(entity.speedScl, 0f, 0.05f);
}
if(entity.buildTime >= produceTime){
entity.buildTime = 0f;
Call.onUnitFactorySpawn(tile, entity.spawned + 1);
useContent(tile, unitType);
entity.cons.trigger();
}
}
@Override
public int getMaximumAccepted(Tile tile, Item item){
return capacities[item.id];
}
@Override
public boolean shouldConsume(Tile tile){
UnitFactoryEntity entity = tile.ent();
return entity.spawned < maxSpawn;
}
public static class UnitFactoryEntity extends TileEntity{
float buildTime;
float time;
float speedScl;
int spawned;
@Override
public void write(DataOutput stream) throws IOException{
super.write(stream);
stream.writeFloat(buildTime);
stream.writeInt(spawned);
}
@Override
public void read(DataInput stream, byte revision) throws IOException{
super.read(stream, revision);
buildTime = stream.readFloat();
spawned = stream.readInt();
}
}
}