mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-01-03 13:30:25 +07:00
commit
b8e6e5df61
@ -217,8 +217,9 @@ project(":tests"){
|
||||
|
||||
dependencies{
|
||||
testImplementation project(":core")
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.1.0')
|
||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.1.0')
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:5.3.1"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.3.1"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.3.1"
|
||||
compile arcModule("backends:backend-headless")
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,6 @@ public class CraftingBlocks extends BlockList implements ContentList{
|
||||
craftEffect = BlockFx.smeltsmoke;
|
||||
result = Items.silicon;
|
||||
craftTime = 40f;
|
||||
powerCapacity = 20f;
|
||||
size = 2;
|
||||
hasLiquids = false;
|
||||
flameColor = Color.valueOf("ffef99");
|
||||
@ -61,7 +60,6 @@ public class CraftingBlocks extends BlockList implements ContentList{
|
||||
craftTime = 60f;
|
||||
output = Items.plastanium;
|
||||
itemCapacity = 30;
|
||||
powerCapacity = 40f;
|
||||
size = 2;
|
||||
health = 320;
|
||||
hasPower = hasLiquids = true;
|
||||
@ -77,7 +75,6 @@ public class CraftingBlocks extends BlockList implements ContentList{
|
||||
craftEffect = BlockFx.smeltsmoke;
|
||||
result = Items.phasefabric;
|
||||
craftTime = 120f;
|
||||
powerCapacity = 50f;
|
||||
size = 2;
|
||||
|
||||
consumes.items(new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10));
|
||||
@ -88,7 +85,6 @@ public class CraftingBlocks extends BlockList implements ContentList{
|
||||
craftEffect = BlockFx.smeltsmoke;
|
||||
result = Items.surgealloy;
|
||||
craftTime = 75f;
|
||||
powerCapacity = 60f;
|
||||
size = 2;
|
||||
|
||||
useFlux = true;
|
||||
|
@ -39,28 +39,26 @@ public class DebugBlocks extends BlockList implements ContentList{
|
||||
public void load(){
|
||||
powerVoid = new PowerBlock("powervoid"){
|
||||
{
|
||||
powerCapacity = Float.MAX_VALUE;
|
||||
consumes.power(Float.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
stats.remove(BlockStat.powerCapacity);
|
||||
stats.remove(BlockStat.powerUse);
|
||||
}
|
||||
};
|
||||
|
||||
powerInfinite = new PowerNode("powerinfinite"){
|
||||
{
|
||||
powerCapacity = 10000f;
|
||||
maxNodes = 100;
|
||||
outputsPower = true;
|
||||
consumesPower = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
super.update(tile);
|
||||
tile.entity.power.amount = powerCapacity;
|
||||
public float getPowerProduction(Tile tile){
|
||||
return 10000f;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -71,19 +71,18 @@ public class DefenseBlocks extends BlockList implements ContentList{
|
||||
}};
|
||||
|
||||
mendProjector = new MendProjector("mend-projector"){{
|
||||
consumes.power(0.2f);
|
||||
consumes.power(0.2f, 1.0f);
|
||||
size = 2;
|
||||
consumes.item(Items.phasefabric).optional(true);
|
||||
}};
|
||||
|
||||
overdriveProjector = new OverdriveProjector("overdrive-projector"){{
|
||||
consumes.power(0.35f);
|
||||
consumes.power(0.35f, 1.0f);
|
||||
size = 2;
|
||||
consumes.item(Items.phasefabric).optional(true);
|
||||
}};
|
||||
|
||||
forceProjector = new ForceProjector("force-projector"){{
|
||||
consumes.power(0.2f);
|
||||
size = 3;
|
||||
consumes.item(Items.phasefabric).optional(true);
|
||||
}};
|
||||
|
@ -35,7 +35,7 @@ public class DistributionBlocks extends BlockList implements ContentList{
|
||||
phaseConveyor = new ItemBridge("phase-conveyor"){{
|
||||
range = 12;
|
||||
hasPower = true;
|
||||
consumes.power(0.03f);
|
||||
consumes.power(0.03f, 1.0f);
|
||||
}};
|
||||
|
||||
sorter = new Sorter("sorter");
|
||||
|
@ -20,7 +20,6 @@ public class LiquidBlocks extends BlockList implements ContentList{
|
||||
pumpAmount = 0.2f;
|
||||
consumes.power(0.015f);
|
||||
liquidCapacity = 30f;
|
||||
powerCapacity = 20f;
|
||||
hasPower = true;
|
||||
size = 2;
|
||||
tier = 1;
|
||||
@ -31,7 +30,6 @@ public class LiquidBlocks extends BlockList implements ContentList{
|
||||
consumes.power(0.03f);
|
||||
liquidCapacity = 40f;
|
||||
hasPower = true;
|
||||
powerCapacity = 20f;
|
||||
size = 2;
|
||||
tier = 2;
|
||||
}};
|
||||
@ -66,7 +64,7 @@ public class LiquidBlocks extends BlockList implements ContentList{
|
||||
phaseConduit = new LiquidBridge("phase-conduit"){{
|
||||
range = 12;
|
||||
hasPower = true;
|
||||
consumes.power(0.03f);
|
||||
consumes.power(0.03f, 1.0f);
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import io.anuke.mindustry.content.Liquids;
|
||||
import io.anuke.mindustry.content.fx.BlockFx;
|
||||
import io.anuke.mindustry.game.ContentList;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.*;
|
||||
|
||||
public class PowerBlocks extends BlockList implements ContentList{
|
||||
@ -13,48 +14,43 @@ public class PowerBlocks extends BlockList implements ContentList{
|
||||
@Override
|
||||
public void load(){
|
||||
combustionGenerator = new BurnerGenerator("combustion-generator"){{
|
||||
powerOutput = 0.09f;
|
||||
powerCapacity = 40f;
|
||||
powerProduction = 0.09f;
|
||||
itemDuration = 40f;
|
||||
}};
|
||||
|
||||
thermalGenerator = new LiquidHeatGenerator("thermal-generator"){{
|
||||
maxLiquidGenerate = 2f;
|
||||
powerCapacity = 40f;
|
||||
powerPerLiquid = 0.35f;
|
||||
powerProduction = 2f;
|
||||
generateEffect = BlockFx.redgeneratespark;
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
turbineGenerator = new TurbineGenerator("turbine-generator"){{
|
||||
powerOutput = 0.28f;
|
||||
powerCapacity = 40f;
|
||||
powerProduction = 0.28f;
|
||||
itemDuration = 30f;
|
||||
powerPerLiquid = 0.7f;
|
||||
consumes.liquid(Liquids.water, 0.05f);
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
rtgGenerator = new DecayGenerator("rtg-generator"){{
|
||||
powerCapacity = 40f;
|
||||
size = 2;
|
||||
powerOutput = 0.3f;
|
||||
powerProduction = 0.3f;
|
||||
itemDuration = 220f;
|
||||
}};
|
||||
|
||||
solarPanel = new SolarGenerator("solar-panel"){{
|
||||
generation = 0.0045f;
|
||||
powerProduction = 0.0045f;
|
||||
}};
|
||||
|
||||
largeSolarPanel = new SolarGenerator("solar-panel-large"){{
|
||||
size = 3;
|
||||
generation = 0.055f;
|
||||
powerProduction = 0.055f;
|
||||
}};
|
||||
|
||||
thoriumReactor = new NuclearReactor("thorium-reactor"){{
|
||||
size = 3;
|
||||
health = 700;
|
||||
powerMultiplier = 1.1f;
|
||||
powerProduction = 1.1f;
|
||||
}};
|
||||
|
||||
fusionReactor = new FusionReactor("fusion-reactor"){{
|
||||
@ -63,12 +59,12 @@ public class PowerBlocks extends BlockList implements ContentList{
|
||||
}};
|
||||
|
||||
battery = new Battery("battery"){{
|
||||
powerCapacity = 320f;
|
||||
consumes.powerBuffered(320f, 1f);
|
||||
}};
|
||||
|
||||
batteryLarge = new Battery("battery-large"){{
|
||||
size = 3;
|
||||
powerCapacity = 2000f;
|
||||
consumes.powerBuffered(2000f, 1f);
|
||||
}};
|
||||
|
||||
powerNode = new PowerNode("power-node"){{
|
||||
|
@ -92,8 +92,8 @@ public class TurretBlocks extends BlockList implements ContentList{
|
||||
recoil = 2f;
|
||||
reload = 100f;
|
||||
cooldown = 0.03f;
|
||||
powerUsed = 20f;
|
||||
powerCapacity = 60f;
|
||||
powerUsed = 1 / 3f;
|
||||
consumes.powerBuffered(60f);
|
||||
shootShake = 2f;
|
||||
shootEffect = ShootFx.lancerLaserShoot;
|
||||
smokeEffect = ShootFx.lancerLaserShootSmoke;
|
||||
@ -111,8 +111,8 @@ public class TurretBlocks extends BlockList implements ContentList{
|
||||
shootShake = 1f;
|
||||
shootCone = 40f;
|
||||
rotatespeed = 8f;
|
||||
powerUsed = 10f;
|
||||
powerCapacity = 30f;
|
||||
powerUsed = 1f / 3f;
|
||||
consumes.powerBuffered(30f);
|
||||
range = 150f;
|
||||
shootEffect = ShootFx.lightningShoot;
|
||||
heatColor = Color.RED;
|
||||
@ -249,8 +249,8 @@ public class TurretBlocks extends BlockList implements ContentList{
|
||||
recoil = 4f;
|
||||
size = 4;
|
||||
shootShake = 2f;
|
||||
powerUsed = 60f;
|
||||
powerCapacity = 120f;
|
||||
powerUsed = 0.5f;
|
||||
consumes.powerBuffered(120f);
|
||||
range = 160f;
|
||||
reload = 200f;
|
||||
firingMoveFract = 0.1f;
|
||||
|
@ -9,52 +9,53 @@ public class UpgradeBlocks extends BlockList{
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
|
||||
alphaPad = new MechPad("alpha-mech-pad"){{
|
||||
mech = Mechs.alpha;
|
||||
size = 2;
|
||||
powerCapacity = 50f;
|
||||
consumes.powerBuffered(50f);
|
||||
}};
|
||||
|
||||
deltaPad = new MechPad("delta-mech-pad"){{
|
||||
mech = Mechs.delta;
|
||||
size = 2;
|
||||
powerCapacity = 70f;
|
||||
consumes.powerBuffered(70f);
|
||||
}};
|
||||
|
||||
tauPad = new MechPad("tau-mech-pad"){{
|
||||
mech = Mechs.tau;
|
||||
size = 2;
|
||||
powerCapacity = 100f;
|
||||
consumes.powerBuffered(100f);
|
||||
}};
|
||||
|
||||
omegaPad = new MechPad("omega-mech-pad"){{
|
||||
mech = Mechs.omega;
|
||||
size = 3;
|
||||
powerCapacity = 120f;
|
||||
consumes.powerBuffered(120f);
|
||||
}};
|
||||
|
||||
dartPad = new MechPad("dart-ship-pad"){{
|
||||
mech = Mechs.dart;
|
||||
size = 2;
|
||||
powerCapacity = 50f;
|
||||
consumes.powerBuffered(50f);
|
||||
}};
|
||||
|
||||
javelinPad = new MechPad("javelin-ship-pad"){{
|
||||
mech = Mechs.javelin;
|
||||
size = 2;
|
||||
powerCapacity = 80f;
|
||||
consumes.powerBuffered(80f);
|
||||
}};
|
||||
|
||||
tridentPad = new MechPad("trident-ship-pad"){{
|
||||
mech = Mechs.trident;
|
||||
size = 2;
|
||||
powerCapacity = 100f;
|
||||
consumes.powerBuffered(100f);
|
||||
}};
|
||||
|
||||
glaivePad = new MechPad("glaive-ship-pad"){{
|
||||
mech = Mechs.glaive;
|
||||
size = 3;
|
||||
powerCapacity = 120f;
|
||||
consumes.powerBuffered(120f);
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class OperationStack{
|
||||
stack.add(action);
|
||||
|
||||
if(stack.size > maxSize){
|
||||
stack.removeAt(0);
|
||||
stack.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,7 +310,7 @@ public interface BuilderTrait extends Entity{
|
||||
return;
|
||||
}
|
||||
|
||||
Draw.color(Palette.accent);
|
||||
Lines.stroke(1f, Palette.accent);
|
||||
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
|
||||
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
|
||||
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
|
||||
|
@ -67,6 +67,7 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
@Override
|
||||
public void drawOutlined(){
|
||||
Lines.stroke(1f);
|
||||
int cursorX = tileX(Core.input.mouseX());
|
||||
int cursorY = tileY(Core.input.mouseY());
|
||||
|
||||
|
@ -656,7 +656,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
PlaceRequest request = removals.get(i);
|
||||
|
||||
if(request.scale <= 0.0001f){
|
||||
removals.removeAt(i);
|
||||
removals.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ public class FortressGenerator{
|
||||
Block block = tile.block();
|
||||
|
||||
if(block instanceof PowerTurret){
|
||||
tile.entity.power.amount = block.powerCapacity;
|
||||
tile.entity.power.satisfaction = 1.0f;
|
||||
}else if(block instanceof ItemTurret){
|
||||
ItemTurret turret = (ItemTurret)block;
|
||||
AmmoType[] type = turret.getAmmoTypes();
|
||||
|
@ -32,7 +32,7 @@ public class LoadingFragment extends Fragment{
|
||||
|
||||
public void setButton(Runnable listener){
|
||||
button.visible(true);
|
||||
button.getListeners().removeAt(button.getListeners().size - 1);
|
||||
button.getListeners().remove(button.getListeners().size - 1);
|
||||
button.clicked(listener);
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,11 @@ public abstract class BaseBlock extends MappableContent{
|
||||
public boolean outputsLiquid = false;
|
||||
public boolean singleLiquid = true;
|
||||
public boolean consumesPower = true;
|
||||
public boolean outputsPower;
|
||||
public boolean outputsPower = false;
|
||||
|
||||
public int itemCapacity;
|
||||
public float liquidCapacity = 10f;
|
||||
public float liquidFlowFactor = 4.9f;
|
||||
public float powerCapacity = 10f;
|
||||
|
||||
public Consumers consumes = new Consumers();
|
||||
public Producers produces = new Producers();
|
||||
@ -40,6 +39,10 @@ public abstract class BaseBlock extends MappableContent{
|
||||
return true;
|
||||
}
|
||||
|
||||
public float getPowerProduction(Tile tile){
|
||||
return 0f;
|
||||
}
|
||||
|
||||
/**Returns the amount of items this block can accept.*/
|
||||
public int acceptStack(Item item, int amount, Tile tile, Unit source){
|
||||
if(acceptItem(item, tile, tile) && hasItems && (source == null || source.getTeam() == tile.getTeam())){
|
||||
@ -98,19 +101,6 @@ public abstract class BaseBlock extends MappableContent{
|
||||
tile.entity.liquids.add(liquid, amount);
|
||||
}
|
||||
|
||||
public boolean acceptPower(Tile tile, Tile source, float amount){
|
||||
return true;
|
||||
}
|
||||
|
||||
/**Returns how much power is accepted.*/
|
||||
public float addPower(Tile tile, float amount){
|
||||
float canAccept = Math.min(powerCapacity - tile.entity.power.amount, amount);
|
||||
|
||||
tile.entity.power.amount += canAccept;
|
||||
|
||||
return canAccept;
|
||||
}
|
||||
|
||||
public void tryDumpLiquid(Tile tile, Liquid liquid){
|
||||
Array<Tile> proximity = tile.entity.proximity();
|
||||
int dump = tile.getDump();
|
||||
|
@ -28,6 +28,7 @@ import io.anuke.mindustry.graphics.Palette;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.ItemStack;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import io.anuke.mindustry.world.meta.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
@ -179,6 +180,14 @@ public class Block extends BaseBlock {
|
||||
return out;
|
||||
}
|
||||
|
||||
protected float getProgressIncrease(TileEntity entity, float baseTime){
|
||||
float progressIncrease = 1f / baseTime * entity.delta();
|
||||
if(hasPower){
|
||||
progressIncrease *= entity.power.satisfaction; // Reduced increase in case of low power
|
||||
}
|
||||
return progressIncrease;
|
||||
}
|
||||
|
||||
public boolean isLayer(Tile tile){
|
||||
return true;
|
||||
}
|
||||
@ -321,7 +330,7 @@ public class Block extends BaseBlock {
|
||||
|
||||
consumes.forEach(cons -> cons.display(stats));
|
||||
|
||||
if(hasPower) stats.add(BlockStat.powerCapacity, powerCapacity, StatUnit.powerUnits);
|
||||
// Note: Power stats are added by the consumers.
|
||||
if(hasLiquids) stats.add(BlockStat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits);
|
||||
if(hasItems) stats.add(BlockStat.itemCapacity, itemCapacity, StatUnit.items);
|
||||
}
|
||||
@ -379,8 +388,8 @@ public class Block extends BaseBlock {
|
||||
explosiveness += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount / 2f);
|
||||
}
|
||||
|
||||
if(hasPower){
|
||||
power += tile.entity.power.amount;
|
||||
if(consumes.has(ConsumePower.class) && consumes.get(ConsumePower.class).isBuffered){
|
||||
power += tile.entity.power.satisfaction * consumes.get(ConsumePower.class).powerCapacity;
|
||||
}
|
||||
|
||||
if(hasLiquids){
|
||||
@ -529,4 +538,4 @@ public class Block extends BaseBlock {
|
||||
"entity.graph", tile.entity.power != null && tile.entity.power.graph != null ? tile.entity.power.graph.getID() : null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,22 +59,6 @@ public class BlockPart extends Block{
|
||||
block.handleLiquid(tile.getLinked(), source, liquid, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float addPower(Tile tile, float amount){
|
||||
Block block = linked(tile);
|
||||
if(block.hasPower){
|
||||
return block.addPower(tile.getLinked(), amount);
|
||||
}else{
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptPower(Tile tile, Tile from, float amount){
|
||||
Block block = linked(tile);
|
||||
return block.hasPower && block.acceptPower(tile.getLinked(), from, amount);
|
||||
}
|
||||
|
||||
private Block linked(Tile tile){
|
||||
return tile.getLinked().block();
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import io.anuke.mindustry.graphics.Palette;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
@ -40,9 +41,12 @@ public class ForceProjector extends Block {
|
||||
protected float cooldownNormal = 1.75f;
|
||||
protected float cooldownLiquid = 1.5f;
|
||||
protected float cooldownBrokenBase = 0.35f;
|
||||
protected float basePowerDraw = 0.2f;
|
||||
protected float powerDamage = 0.1f;
|
||||
protected final ConsumeForceProjectorPower consumePower;
|
||||
protected TextureRegion topRegion;
|
||||
|
||||
|
||||
public ForceProjector(String name) {
|
||||
super(name);
|
||||
update = true;
|
||||
@ -50,10 +54,11 @@ public class ForceProjector extends Block {
|
||||
hasPower = true;
|
||||
canOverdrive = false;
|
||||
hasLiquids = true;
|
||||
powerCapacity = 60f;
|
||||
hasItems = true;
|
||||
itemCapacity = 10;
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 0.1f)).optional(true).update(false);
|
||||
consumePower = new ConsumeForceProjectorPower(60f, 60f);
|
||||
consumes.add(consumePower);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,6 +71,7 @@ public class ForceProjector extends Block {
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.powerUse, basePowerDraw * 60f, StatUnit.powerSecond);
|
||||
stats.add(BlockStat.powerDamage, powerDamage, StatUnit.powerUnits);
|
||||
}
|
||||
|
||||
@ -91,15 +97,27 @@ public class ForceProjector extends Block {
|
||||
Effects.effect(BlockFx.reactorsmoke, tile.drawx() + Mathf.range(tilesize/2f), tile.drawy() + Mathf.range(tilesize/2f));
|
||||
}
|
||||
|
||||
if(!entity.cons.valid() && !cheat){
|
||||
// Use Cases:
|
||||
// - There is enough power in the buffer, and there are no shots fired => Draw base power and keep shield up
|
||||
// - There is enough power in the buffer, but not enough power to cope for shots being fired => Draw all power and break shield
|
||||
// - There is enough power in the buffer and enough power to cope for shots being fired => Draw base power + additional power based on shots absorbed
|
||||
// - There is not enough base power in the buffer => Draw all power and break shield
|
||||
// - The generator is in the AI base and uses cheat mode => Only draw power from shots being absorbed
|
||||
|
||||
float relativePowerDraw = 0.0f;
|
||||
if(!cheat){
|
||||
relativePowerDraw = basePowerDraw / consumePower.powerCapacity;
|
||||
}
|
||||
|
||||
if(entity.power.satisfaction < relativePowerDraw){
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.15f);
|
||||
entity.power.satisfaction = .0f;
|
||||
if(entity.warmup <= 0.09f){
|
||||
entity.broken = true;
|
||||
}
|
||||
}else{
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.1f);
|
||||
float powerUse = Math.min(powerDamage * entity.delta() * (1f + entity.buildup / breakage), powerCapacity);
|
||||
entity.power.amount -= powerUse;
|
||||
entity.power.satisfaction -= Math.min(entity.power.satisfaction, relativePowerDraw);
|
||||
}
|
||||
|
||||
if(entity.buildup > 0){
|
||||
@ -134,12 +152,12 @@ public class ForceProjector extends Block {
|
||||
if(trait.canBeAbsorbed() && trait.getTeam() != tile.getTeam() && isInsideHexagon(trait.getX(), trait.getY(), realRadius * 2f, tile.drawx(), tile.drawy())){
|
||||
trait.absorb();
|
||||
Effects.effect(BulletFx.absorb, trait);
|
||||
float hit = trait.getShieldDamage()*powerDamage;
|
||||
float relativeDamagePowerDraw = trait.getShieldDamage() * powerDamage / consumePower.powerCapacity;
|
||||
entity.hit = 1f;
|
||||
entity.power.amount -= Math.min(hit, entity.power.amount);
|
||||
|
||||
if(entity.power.amount <= 0.0001f){
|
||||
entity.buildup += trait.getShieldDamage() * entity.warmup*2f;
|
||||
entity.power.satisfaction -= Math.min(relativeDamagePowerDraw, entity.power.satisfaction);
|
||||
if(entity.power.satisfaction <= 0.0001f){
|
||||
entity.buildup += trait.getShieldDamage() * entity.warmup * 2f;
|
||||
}
|
||||
entity.buildup += trait.getShieldDamage() * entity.warmup;
|
||||
}
|
||||
@ -246,4 +264,14 @@ public class ForceProjector extends Block {
|
||||
return shieldGroup;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsumeForceProjectorPower extends ConsumePower{
|
||||
public ConsumeForceProjectorPower(float powerCapacity, float ticksToFill){
|
||||
super(powerCapacity / ticksToFill, 0.0f, powerCapacity, true);
|
||||
}
|
||||
@Override
|
||||
public boolean valid(Block block, TileEntity entity){
|
||||
return entity.power.satisfaction >= basePowerDraw / powerCapacity && super.valid(block, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ public class MendProjector extends Block{
|
||||
other = other.target();
|
||||
|
||||
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);
|
||||
other.entity.healBy(other.entity.maxHealth() * (healPercent + entity.phaseHeat*phaseBoost)/100f * entity.power.satisfaction);
|
||||
Effects.effect(BlockFx.healBlockFull, Tmp.c1.set(color).lerp(phase, entity.phaseHeat), other.drawx(), other.drawy(), other.block().size);
|
||||
healed.add(other.pos());
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class OverdriveProjector extends Block{
|
||||
|
||||
if(entity.charge >= reload){
|
||||
float realRange = range + entity.phaseHeat * phaseRangeBoost;
|
||||
float realBoost = speedBoost + entity.phaseHeat*speedBoostPhase;
|
||||
float realBoost = (speedBoost + entity.phaseHeat*speedBoostPhase) * entity.power.satisfaction;
|
||||
|
||||
Effects.effect(BlockFx.overdriveWave, Tmp.c1.set(color).lerp(phase, entity.phaseHeat), tile.drawx(), tile.drawy(), realRange);
|
||||
entity.charge = 0f;
|
||||
|
@ -6,6 +6,7 @@ import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
public abstract class PowerTurret extends CooledTurret{
|
||||
/** The percentage of power which will be used per shot. */
|
||||
protected float powerUsed = 0.5f;
|
||||
protected AmmoType shootType;
|
||||
|
||||
@ -23,13 +24,15 @@ public abstract class PowerTurret extends CooledTurret{
|
||||
|
||||
@Override
|
||||
public boolean hasAmmo(Tile tile){
|
||||
return tile.entity.power.amount >= powerUsed;
|
||||
// Allow shooting as long as the turret is at least at 50% power
|
||||
return tile.entity.power.satisfaction >= powerUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmmoType useAmmo(Tile tile){
|
||||
if(tile.isEnemyCheat()) return shootType;
|
||||
tile.entity.power.amount -= powerUsed;
|
||||
// Make sure that power can not go negative in case of threading issues or similar
|
||||
tile.entity.power.satisfaction -= Math.min(powerUsed, tile.entity.power.satisfaction);
|
||||
return shootType;
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,11 @@ public class LiquidBridge extends ItemBridge{
|
||||
tryDumpLiquid(tile, entity.liquids.current());
|
||||
}else{
|
||||
if(entity.cons.valid()){
|
||||
entity.uptime = Mathf.lerpDelta(entity.uptime, 1f, 0.04f);
|
||||
float alpha = 0.04f;
|
||||
if(hasPower){
|
||||
alpha *= entity.power.satisfaction; // 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);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import io.anuke.mindustry.graphics.Palette;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
@ -48,6 +49,7 @@ public class MassDriver extends Block{
|
||||
protected Effect smokeEffect = ShootFx.shootBigSmoke2;
|
||||
protected Effect recieveEffect = BlockFx.mineBig;
|
||||
protected float shake = 3f;
|
||||
protected final static float powerPercentageUsed = 1.0f;
|
||||
protected TextureRegion turretRegion;
|
||||
|
||||
public MassDriver(String name){
|
||||
@ -58,6 +60,8 @@ public class MassDriver extends Block{
|
||||
hasItems = true;
|
||||
layer = Layer.turret;
|
||||
hasPower = true;
|
||||
consumes.powerBuffered(30f);
|
||||
consumes.require(ConsumePower.class);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
@ -77,7 +81,8 @@ public class MassDriver extends Block{
|
||||
MassDriverEntity other = target.entity();
|
||||
|
||||
entity.reload = 1f;
|
||||
entity.power.amount = 0f;
|
||||
|
||||
entity.power.satisfaction -= Math.min(entity.power.satisfaction, powerPercentageUsed);
|
||||
|
||||
DriverBulletData data = Pools.obtain(DriverBulletData.class, DriverBulletData::new);
|
||||
data.from = entity;
|
||||
@ -125,7 +130,7 @@ public class MassDriver extends Block{
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.powerShot, powerCapacity, StatUnit.powerUnits);
|
||||
stats.add(BlockStat.powerShot, consumes.get(ConsumePower.class).powerCapacity * powerPercentageUsed, StatUnit.powerUnits);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -164,8 +169,8 @@ public class MassDriver extends Block{
|
||||
|
||||
entity.rotation = Mathf.slerpDelta(entity.rotation, tile.angleTo(waiter), rotateSpeed);
|
||||
}else if(tile.entity.items.total() >= minDistribute &&
|
||||
linkValid(tile) && //only fire when at least at half-capacity and power
|
||||
tile.entity.power.amount >= powerCapacity * 0.8f &&
|
||||
linkValid(tile) && //only fire when at 100% power capacity
|
||||
tile.entity.power.satisfaction >= powerPercentageUsed &&
|
||||
link.block().itemCapacity - link.entity.items.total() >= minDistribute && entity.reload <= 0.0001f){
|
||||
|
||||
MassDriverEntity other = link.entity();
|
||||
|
@ -6,7 +6,7 @@ import io.anuke.mindustry.type.Liquid;
|
||||
public class BurnerGenerator extends ItemLiquidGenerator{
|
||||
|
||||
public BurnerGenerator(String name){
|
||||
super(name);
|
||||
super(InputType.LiquidsAndItems, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,11 +1,12 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
|
||||
public class DecayGenerator extends ItemGenerator{
|
||||
public class DecayGenerator extends ItemLiquidGenerator{
|
||||
|
||||
public DecayGenerator(String name){
|
||||
super(name);
|
||||
super(InputType.ItemsOnly, name);
|
||||
hasItems = true;
|
||||
hasLiquids = false;
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.production.GenericCrafter.GenericCrafterEntity;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FusionReactor extends PowerGenerator{
|
||||
protected int plasmas = 4;
|
||||
protected float maxPowerProduced = 2f;
|
||||
protected float warmupSpeed = 0.001f;
|
||||
|
||||
protected Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b");
|
||||
@ -25,33 +25,27 @@ public class FusionReactor extends PowerGenerator{
|
||||
super(name);
|
||||
hasPower = true;
|
||||
hasLiquids = true;
|
||||
powerCapacity = 100f;
|
||||
powerProduction = 2.0f;
|
||||
liquidCapacity = 30f;
|
||||
hasItems = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.basePowerGeneration, maxPowerProduced * 60f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
FusionReactorEntity entity = tile.entity();
|
||||
|
||||
float increaseOrDecrease = 1.0f;
|
||||
if(entity.cons.valid()){
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, warmupSpeed);
|
||||
}else{
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.01f);
|
||||
increaseOrDecrease = -1.0f;
|
||||
}
|
||||
|
||||
float powerAdded = Math.min(powerCapacity - entity.power.amount, maxPowerProduced * Mathf.pow(entity.warmup, 4f) * Time.delta());
|
||||
entity.power.amount += powerAdded;
|
||||
entity.totalProgress += entity.warmup * Time.delta();
|
||||
float efficiencyAdded = Mathf.pow(entity.warmup, 4f) * Time.delta();
|
||||
entity.productionEfficiency = Mathf.clamp(entity.productionEfficiency + efficiencyAdded * increaseOrDecrease);
|
||||
|
||||
tile.entity.power.graph.update();
|
||||
super.update(tile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,7 +89,7 @@ public class FusionReactor extends PowerGenerator{
|
||||
|
||||
Draw.rect(name + "-top", tile.drawx(), tile.drawy());
|
||||
|
||||
Draw.color(ind1, ind2, entity.warmup + Mathf.absin(entity.totalProgress, 3f, entity.warmup * 0.5f));
|
||||
Draw.color(ind1, ind2, entity.warmup + Mathf.absin(entity.productionEfficiency, 3f, entity.warmup * 0.5f));
|
||||
Draw.rect(name + "-light", tile.drawx(), tile.drawy());
|
||||
|
||||
Draw.color();
|
||||
@ -122,7 +116,19 @@ public class FusionReactor extends PowerGenerator{
|
||||
//TODO catastrophic failure
|
||||
}
|
||||
|
||||
public static class FusionReactorEntity extends GenericCrafterEntity{
|
||||
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) throws IOException{
|
||||
super.read(stream);
|
||||
warmup = stream.readFloat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,127 +0,0 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.g2d.TextureRegion;
|
||||
import io.anuke.mindustry.content.fx.BlockFx;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumeItemFilter;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
import io.anuke.arc.entities.Effects;
|
||||
import io.anuke.arc.entities.Effects.Effect;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.graphics.g2d.Draw;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public abstract class ItemGenerator extends PowerGenerator{
|
||||
protected float minItemEfficiency = 0.2f;
|
||||
protected float powerOutput;
|
||||
protected float itemDuration = 70f;
|
||||
protected Effect generateEffect = BlockFx.generatespark, explodeEffect =
|
||||
BlockFx.generatespark;
|
||||
protected Color heatColor = Color.valueOf("ff9b59");
|
||||
protected TextureRegion topRegion;
|
||||
|
||||
public ItemGenerator(String name){
|
||||
super(name);
|
||||
itemCapacity = 20;
|
||||
hasItems = true;
|
||||
|
||||
consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
super.load();
|
||||
topRegion = Core.atlas.find(name + "-top");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.basePowerGeneration, powerOutput * 60f * 0.5f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Tile tile){
|
||||
super.draw(tile);
|
||||
|
||||
GeneratorEntity entity = tile.entity();
|
||||
|
||||
if(entity.generateTime > 0){
|
||||
Draw.color(heatColor);
|
||||
float alpha = (entity.items.total() > 0 ? 1f : Mathf.clamp(entity.generateTime));
|
||||
alpha = alpha * 0.7f + Mathf.absin(Time.time(), 12f, 0.3f) * alpha;
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(topRegion, tile.drawx(), tile.drawy());
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Item item, Tile tile, Tile source){
|
||||
return getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.total() < itemCapacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
ItemGeneratorEntity entity = tile.entity();
|
||||
|
||||
float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * entity.delta()) * entity.efficiency;
|
||||
|
||||
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.efficiency = getItemEfficiency(item);
|
||||
entity.explosiveness = item.explosiveness;
|
||||
entity.generateTime = 1f;
|
||||
}
|
||||
|
||||
entity.power.graph.update();
|
||||
|
||||
if(entity.generateTime > 0f){
|
||||
entity.generateTime -= 1f / itemDuration * entity.delta();
|
||||
entity.power.amount += maxPower;
|
||||
entity.generateTime = Mathf.clamp(entity.generateTime);
|
||||
|
||||
if(Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){
|
||||
//this block is run last so that in the event of a block destruction, no code relies on the block type
|
||||
entity.damage(Mathf.random(8f));
|
||||
Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract float getItemEfficiency(Item item);
|
||||
|
||||
@Override
|
||||
public TileEntity newEntity(){
|
||||
return new ItemGeneratorEntity();
|
||||
}
|
||||
|
||||
public static class ItemGeneratorEntity extends GeneratorEntity{
|
||||
public float efficiency;
|
||||
public float explosiveness;
|
||||
|
||||
@Override
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeFloat(efficiency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInput stream) throws IOException{
|
||||
efficiency = stream.readFloat();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +1,186 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.entities.Effects;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.g2d.Draw;
|
||||
import io.anuke.arc.graphics.g2d.TextureRegion;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.content.fx.BlockFx;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumeItemFilter;
|
||||
import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter;
|
||||
import io.anuke.arc.entities.Effects;
|
||||
import io.anuke.arc.graphics.g2d.Draw;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public abstract class ItemLiquidGenerator extends ItemGenerator{
|
||||
/**
|
||||
* 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{
|
||||
|
||||
protected float minItemEfficiency = 0.2f;
|
||||
/** The time in number of ticks during which a single item will produce power. */
|
||||
protected float itemDuration = 70f;
|
||||
|
||||
protected float minLiquidEfficiency = 0.2f;
|
||||
protected float powerPerLiquid = 0.13f;
|
||||
/**Maximum liquid used per frame.*/
|
||||
/** Maximum liquid used per frame. */
|
||||
protected float maxLiquidGenerate = 0.4f;
|
||||
|
||||
public ItemLiquidGenerator(String name){
|
||||
super(name);
|
||||
hasLiquids = true;
|
||||
liquidCapacity = 10f;
|
||||
protected Effects.Effect generateEffect = BlockFx.generatespark;
|
||||
protected Effects.Effect explodeEffect = BlockFx.generatespark;
|
||||
protected Color heatColor = Color.valueOf("ff9b59");
|
||||
protected TextureRegion topRegion;
|
||||
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f, true)).update(false).optional(true);
|
||||
public enum InputType{
|
||||
ItemsOnly,
|
||||
LiquidsOnly,
|
||||
LiquidsAndItems
|
||||
}
|
||||
|
||||
public ItemLiquidGenerator(InputType inputType, String name){
|
||||
super(name);
|
||||
this.hasItems = inputType != InputType.LiquidsOnly;
|
||||
this.hasLiquids = inputType != InputType.ItemsOnly;
|
||||
|
||||
if(hasItems){
|
||||
itemCapacity = 20;
|
||||
consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true);
|
||||
}
|
||||
|
||||
if(hasLiquids){
|
||||
liquidCapacity = 10f;
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, 0.001f, true)).update(false).optional(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
public void load(){
|
||||
super.load();
|
||||
if(hasItems){
|
||||
topRegion = Core.atlas.find(name + "-top");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
ItemGeneratorEntity entity = tile.entity();
|
||||
ItemLiquidGeneratorEntity entity = tile.entity();
|
||||
|
||||
entity.power.graph.update();
|
||||
// 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(entity.liquids.get(other) >= 0.001f && getLiquidEfficiency(other) >= minLiquidEfficiency){
|
||||
if(hasLiquids && entity.liquids.get(other) >= 0.001f && getLiquidEfficiency(other) >= minLiquidEfficiency){
|
||||
liquid = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//liquid takes priority over solids
|
||||
if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){
|
||||
float powerPerLiquid = getLiquidEfficiency(liquid) * this.powerPerLiquid;
|
||||
float used = Math.min(entity.liquids.get(liquid), maxLiquidGenerate * entity.delta());
|
||||
used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid);
|
||||
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.amount += used * powerPerLiquid;
|
||||
|
||||
// Note: 0.5 = 100%. PowerGraph will multiply this efficiency by two on its own.
|
||||
entity.productionEfficiency = Mathf.clamp(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(entity.cons.valid()){
|
||||
|
||||
float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * entity.delta()) * entity.efficiency;
|
||||
|
||||
}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.efficiency = getItemEfficiency(item);
|
||||
entity.productionEfficiency = getItemEfficiency(item);
|
||||
entity.explosiveness = item.explosiveness;
|
||||
entity.generateTime = 1f;
|
||||
}
|
||||
|
||||
if(entity.generateTime > 0f){
|
||||
entity.generateTime -= 1f / itemDuration * entity.delta();
|
||||
entity.power.amount += maxPower;
|
||||
entity.generateTime = Mathf.clamp(entity.generateTime);
|
||||
entity.generateTime -= Math.min(1f / itemDuration * entity.delta(), entity.generateTime);
|
||||
|
||||
if(Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){
|
||||
//this block is run last so that in the event of a block destruction, no code relies on the block type
|
||||
entity.damage(Mathf.random(8f));
|
||||
Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f));
|
||||
}
|
||||
}else{
|
||||
entity.productionEfficiency = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
super.update(tile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Item item, Tile tile, Tile source){
|
||||
return hasItems && getItemEfficiency(item) >= minItemEfficiency && tile.entity.items.total() < itemCapacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
return hasLiquids && getLiquidEfficiency(liquid) >= minLiquidEfficiency && tile.entity.liquids.get(liquid) < liquidCapacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Tile tile){
|
||||
super.draw(tile);
|
||||
|
||||
TileEntity entity = tile.entity();
|
||||
GeneratorEntity entity = tile.entity();
|
||||
|
||||
Draw.color(entity.liquids.current().color);
|
||||
Draw.alpha(entity.liquids.currentAmount() / liquidCapacity);
|
||||
drawLiquidCenter(tile);
|
||||
Draw.color();
|
||||
}
|
||||
if(hasItems){
|
||||
if(entity.generateTime > 0){
|
||||
Draw.color(heatColor);
|
||||
float alpha = (entity.items.total() > 0 ? 1f : Mathf.clamp(entity.generateTime));
|
||||
alpha = alpha * 0.7f + Mathf.absin(Time.time(), 12f, 0.3f) * alpha;
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(topRegion, tile.drawx(), tile.drawy());
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
return getLiquidEfficiency(liquid) >= minLiquidEfficiency && tile.entity.liquids.get(liquid) < liquidCapacity;
|
||||
if(hasLiquids){
|
||||
Draw.color(entity.liquids.current().color);
|
||||
Draw.alpha(entity.liquids.currentAmount() / liquidCapacity);
|
||||
drawLiquidCenter(tile);
|
||||
Draw.color();
|
||||
}
|
||||
}
|
||||
|
||||
public void drawLiquidCenter(Tile tile){
|
||||
Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2);
|
||||
}
|
||||
|
||||
protected abstract float getLiquidEfficiency(Liquid liquid);
|
||||
protected float getItemEfficiency(Item item){
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
protected float getLiquidEfficiency(Liquid liquid){
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity newEntity(){
|
||||
return new ItemLiquidGeneratorEntity();
|
||||
}
|
||||
|
||||
public static class ItemLiquidGeneratorEntity extends GeneratorEntity{
|
||||
public float explosiveness;
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.content.fx.BlockFx;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.ItemGenerator.ItemGeneratorEntity;
|
||||
import io.anuke.mindustry.world.consumers.ConsumeLiquidFilter;
|
||||
import io.anuke.arc.entities.Effects;
|
||||
import io.anuke.arc.entities.Effects.Effect;
|
||||
import io.anuke.arc.graphics.g2d.Draw;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
|
||||
public abstract class LiquidGenerator extends PowerGenerator{
|
||||
protected float minEfficiency = 0.2f;
|
||||
protected float powerPerLiquid;
|
||||
/**Maximum liquid used per frame.*/
|
||||
protected float maxLiquidGenerate;
|
||||
protected Effect generateEffect = BlockFx.generatespark;
|
||||
|
||||
public LiquidGenerator(String name){
|
||||
super(name);
|
||||
liquidCapacity = 30f;
|
||||
hasLiquids = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> getEfficiency(liquid) >= minEfficiency, maxLiquidGenerate)).update(false);
|
||||
super.setStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Tile tile){
|
||||
super.draw(tile);
|
||||
|
||||
TileEntity entity = tile.entity();
|
||||
|
||||
Draw.color(entity.liquids.current().color);
|
||||
Draw.alpha(entity.liquids.total() / liquidCapacity);
|
||||
drawLiquidCenter(tile);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
public void drawLiquidCenter(Tile tile){
|
||||
Draw.rect("blank", tile.drawx(), tile.drawy(), 2, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
TileEntity entity = tile.entity();
|
||||
|
||||
if(entity.liquids.get(entity.liquids.current()) >= 0.001f){
|
||||
float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid;
|
||||
float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * entity.delta());
|
||||
used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid);
|
||||
|
||||
entity.liquids.remove(entity.liquids.current(), used);
|
||||
entity.power.amount += used * powerPerLiquid;
|
||||
|
||||
if(used > 0.001f && Mathf.chance(0.05 * entity.delta())){
|
||||
Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f));
|
||||
}
|
||||
}
|
||||
|
||||
tile.entity.power.graph.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
return getEfficiency(liquid) >= minEfficiency && super.acceptLiquid(tile, source, liquid, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileEntity newEntity(){
|
||||
return new ItemGeneratorEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an efficiency value for the specified liquid.
|
||||
* Greater efficiency means more power generation.
|
||||
* If a liquid's efficiency is below {@link #minEfficiency}, it is not accepted.
|
||||
*/
|
||||
protected abstract float getEfficiency(Liquid liquid);
|
||||
}
|
@ -1,24 +1,27 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.content.Liquids;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
public class LiquidHeatGenerator extends LiquidGenerator{
|
||||
public class LiquidHeatGenerator extends ItemLiquidGenerator{
|
||||
|
||||
public LiquidHeatGenerator(String name){
|
||||
super(name);
|
||||
super(InputType.LiquidsOnly, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.basePowerGeneration, maxLiquidGenerate * powerPerLiquid * 60f * 0.5f, StatUnit.powerSecond);
|
||||
stats.remove(BlockStat.basePowerGeneration);
|
||||
// Right now, Lava is the only thing that can be used.
|
||||
stats.add(BlockStat.basePowerGeneration, powerProduction * getLiquidEfficiency(Liquids.lava) / maxLiquidGenerate * 60f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getEfficiency(Liquid liquid){
|
||||
protected float getLiquidEfficiency(Liquid liquid){
|
||||
return liquid.temperature - 0.5f;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ public class NuclearReactor extends PowerGenerator{
|
||||
protected Color coolColor = new Color(1, 1, 1, 0f);
|
||||
protected Color hotColor = Color.valueOf("ff9575a3");
|
||||
protected int fuelUseTime = 120; //time to consume 1 fuel
|
||||
protected float powerMultiplier = 0.45f; //power per frame, depends on full capacity
|
||||
protected float heating = 0.013f; //heating per frame
|
||||
protected float coolantPower = 0.015f; //how much heat decreases per coolant unit
|
||||
protected float smokeThreshold = 0.3f; //threshold at which block starts smoking
|
||||
@ -48,7 +47,6 @@ public class NuclearReactor extends PowerGenerator{
|
||||
super(name);
|
||||
itemCapacity = 30;
|
||||
liquidCapacity = 50;
|
||||
powerCapacity = 80f;
|
||||
hasItems = true;
|
||||
hasLiquids = true;
|
||||
|
||||
@ -67,7 +65,10 @@ public class NuclearReactor extends PowerGenerator{
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
stats.add(BlockStat.inputLiquid, new LiquidFilterValue(liquid -> liquid.temperature <= 0.5f));
|
||||
stats.add(BlockStat.basePowerGeneration, powerMultiplier * 60f * 0.5f, StatUnit.powerSecond);
|
||||
|
||||
stats.remove(BlockStat.basePowerGeneration);
|
||||
// Display the power which will be produced at 50% efficiency
|
||||
stats.add(BlockStat.basePowerGeneration, powerProduction * 60f * 0.5f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,11 +77,11 @@ public class NuclearReactor extends PowerGenerator{
|
||||
|
||||
int fuel = entity.items.get(consumes.item());
|
||||
float fullness = (float) fuel / itemCapacity;
|
||||
entity.productionEfficiency = fullness / 2.0f; // Currently, efficiency of 0.5 = 100%
|
||||
|
||||
if(fuel > 0){
|
||||
entity.heat += fullness * heating * Math.min(entity.delta(), 4f);
|
||||
entity.power.amount += powerMultiplier * fullness * entity.delta();
|
||||
entity.power.amount = Mathf.clamp(entity.power.amount, 0f, powerCapacity);
|
||||
|
||||
if(entity.timer.get(timerFuel, fuelUseTime)){
|
||||
entity.items.remove(consumes.item(), 1);
|
||||
}
|
||||
@ -115,7 +116,7 @@ public class NuclearReactor extends PowerGenerator{
|
||||
if(entity.heat >= 0.999f){
|
||||
entity.kill();
|
||||
}else{
|
||||
tile.entity.power.graph.update();
|
||||
super.update(tile);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,20 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.meta.BlockFlag;
|
||||
import io.anuke.arc.collection.EnumSet;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.meta.BlockFlag;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PowerGenerator extends PowerDistributor{
|
||||
/** The amount of power produced per tick in case of an efficiency of 1.0, which currently represents 200%. */
|
||||
protected float powerProduction;
|
||||
public BlockStat generationType = BlockStat.basePowerGeneration;
|
||||
|
||||
public PowerGenerator(String name){
|
||||
super(name);
|
||||
@ -12,6 +22,20 @@ public class PowerGenerator extends PowerDistributor{
|
||||
flags = EnumSet.of(BlockFlag.producer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
// Divide power production by two since that is what is produced at an efficiency of 0.5, which currently represents 100%
|
||||
stats.add(generationType, powerProduction * 60.0f / 2.0f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPowerProduction(Tile tile){
|
||||
// While 0.5 efficiency currently reflects 100%, we do not need to multiply by any factor since powerProduction states the
|
||||
// power which would be produced at 1.0 efficiency
|
||||
return powerProduction * tile.<GeneratorEntity>entity().productionEfficiency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean outputsItems(){
|
||||
return false;
|
||||
@ -24,5 +48,17 @@ public class PowerGenerator extends PowerDistributor{
|
||||
|
||||
public static class GeneratorEntity extends TileEntity{
|
||||
public float generateTime;
|
||||
/** The efficiency of the producer. Currently, an efficiency of 0.5 means 100% */
|
||||
public float productionEfficiency = 0.0f;
|
||||
|
||||
@Override
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeFloat(productionEfficiency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInput stream) throws IOException{
|
||||
productionEfficiency = stream.readFloat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,11 @@ import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.IntSet;
|
||||
import io.anuke.arc.collection.ObjectSet;
|
||||
import io.anuke.arc.collection.Queue;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.Consume;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import io.anuke.mindustry.world.consumers.Consumers;
|
||||
|
||||
public class PowerGraph{
|
||||
private final static Queue<Tile> queue = new Queue<>();
|
||||
@ -15,6 +19,7 @@ public class PowerGraph{
|
||||
|
||||
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 long lastFrameUpdated;
|
||||
@ -29,71 +34,127 @@ public class PowerGraph{
|
||||
return graphID;
|
||||
}
|
||||
|
||||
public float getPowerProduced(){
|
||||
float powerProduced = 0f;
|
||||
for(Tile producer : producers){
|
||||
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.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumes.get(ConsumePower.class);
|
||||
if(otherConsumersAreValid(consumer, consumePower)){
|
||||
powerNeeded += consumePower.requestedPower(consumer.block(), consumer.entity) * consumer.entity.delta();
|
||||
}
|
||||
}
|
||||
}
|
||||
return powerNeeded;
|
||||
}
|
||||
|
||||
public float getBatteryStored(){
|
||||
float totalAccumulator = 0f;
|
||||
for(Tile battery : batteries){
|
||||
Consumers consumes = battery.block().consumes;
|
||||
if(consumes.has(ConsumePower.class)){
|
||||
totalAccumulator += battery.entity.power.satisfaction * consumes.get(ConsumePower.class).powerCapacity;
|
||||
}
|
||||
}
|
||||
return totalAccumulator;
|
||||
}
|
||||
|
||||
public float getBatteryCapacity(){
|
||||
float totalCapacity = 0f;
|
||||
for(Tile battery : batteries){
|
||||
Consumers consumes = battery.block().consumes;
|
||||
if(consumes.has(ConsumePower.class)){
|
||||
totalCapacity += consumes.get(ConsumePower.class).requestedPower(battery.block(), battery.entity) * battery.entity.delta();
|
||||
}
|
||||
}
|
||||
return totalCapacity;
|
||||
}
|
||||
|
||||
public float useBatteries(float needed){
|
||||
float stored = getBatteryStored();
|
||||
if(Mathf.isEqual(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.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumes.get(ConsumePower.class);
|
||||
if(consumePower.powerCapacity > 0f){
|
||||
battery.entity.power.satisfaction = Math.max(0.0f, battery.entity.power.satisfaction - consumedPowerPercentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
public float chargeBatteries(float excess){
|
||||
float capacity = getBatteryCapacity();
|
||||
if(Mathf.isEqual(capacity, 0f)){ return 0f; }
|
||||
|
||||
for(Tile battery : batteries){
|
||||
Consumers consumes = battery.block().consumes;
|
||||
if(consumes.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumes.get(ConsumePower.class);
|
||||
if(consumePower.powerCapacity > 0f){
|
||||
float additionalPowerPercentage = Math.min(1.0f, excess / consumePower.powerCapacity);
|
||||
battery.entity.power.satisfaction = Math.min(1.0f, battery.entity.power.satisfaction + additionalPowerPercentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.min(excess, capacity);
|
||||
}
|
||||
|
||||
public void distributePower(float needed, float produced){
|
||||
if(Mathf.isEqual(needed, 0f)){ return; }
|
||||
|
||||
float coverage = Math.min(1, produced / needed);
|
||||
for(Tile consumer : consumers){
|
||||
Consumers consumes = consumer.block().consumes;
|
||||
if(consumes.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumes.get(ConsumePower.class);
|
||||
if(!otherConsumersAreValid(consumer, consumePower)){
|
||||
consumer.entity.power.satisfaction = 0.0f; // Only supply power if the consumer would get valid that way
|
||||
}else{
|
||||
if(consumePower.isBuffered){
|
||||
// Add an equal percentage of power to all buffers, based on the global power coverage in this graph
|
||||
float maximumRate = consumePower.requestedPower(consumer.block(), consumer.entity()) * coverage * consumer.entity.delta();
|
||||
consumer.entity.power.satisfaction = Mathf.clamp(consumer.entity.power.satisfaction + maximumRate / consumePower.powerCapacity);
|
||||
}else{
|
||||
consumer.entity.power.satisfaction = coverage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(){
|
||||
if(Core.graphics.getFrameId() == lastFrameUpdated || consumers.size == 0 || producers.size == 0){
|
||||
if(Core.graphics.getFrameId() == lastFrameUpdated || consumers.size == 0 && producers.size == 0 && batteries.size == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
lastFrameUpdated = Core.graphics.getFrameId();
|
||||
|
||||
boolean charge = false;
|
||||
float powerNeeded = getPowerNeeded();
|
||||
float powerProduced = getPowerProduced();
|
||||
|
||||
float totalInput = 0f;
|
||||
float bufferInput = 0f;
|
||||
for(Tile producer : producers){
|
||||
if(producer.block().consumesPower){
|
||||
bufferInput += producer.entity.power.amount;
|
||||
}else{
|
||||
totalInput += producer.entity.power.amount;
|
||||
if(!Mathf.isEqual(powerNeeded, powerProduced)){
|
||||
if(powerNeeded > powerProduced){
|
||||
powerProduced += useBatteries(powerNeeded - powerProduced);
|
||||
}else if(powerProduced > powerNeeded){
|
||||
powerProduced -= chargeBatteries(powerProduced - powerNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
float maxOutput = 0f;
|
||||
float bufferOutput = 0f;
|
||||
for(Tile consumer : consumers){
|
||||
if(consumer.block().outputsPower){
|
||||
bufferOutput += consumer.block().powerCapacity - consumer.entity.power.amount;
|
||||
}else{
|
||||
maxOutput += consumer.block().powerCapacity - consumer.entity.power.amount;
|
||||
}
|
||||
}
|
||||
|
||||
if(maxOutput < totalInput){
|
||||
charge = true;
|
||||
}
|
||||
|
||||
if(totalInput + bufferInput <= 0.0001f || maxOutput + bufferOutput <= 0.0001f){
|
||||
return;
|
||||
}
|
||||
|
||||
float bufferUsed;
|
||||
if(charge){
|
||||
bufferUsed = Math.min((totalInput - maxOutput) / bufferOutput, 1f);
|
||||
}else{
|
||||
bufferUsed = Math.min((maxOutput - totalInput) / bufferInput, 1f);
|
||||
}
|
||||
|
||||
float inputUsed = charge ? Math.min((maxOutput + bufferOutput) / totalInput, 1f) : 1f;
|
||||
for(Tile producer : producers){
|
||||
if(producer.block().consumesPower){
|
||||
if(!charge){
|
||||
producer.entity.power.amount -= producer.entity.power.amount * bufferUsed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
producer.entity.power.amount -= producer.entity.power.amount * inputUsed;
|
||||
}
|
||||
|
||||
float outputSatisfied = charge ? 1f : Math.min((totalInput + bufferInput) / maxOutput, 1f);
|
||||
for(Tile consumer : consumers){
|
||||
if(consumer.block().outputsPower){
|
||||
if(charge){
|
||||
consumer.entity.power.amount += (consumer.block().powerCapacity - consumer.entity.power.amount) * bufferUsed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
consumer.entity.power.amount += (consumer.block().powerCapacity - consumer.entity.power.amount) * outputSatisfied;
|
||||
}
|
||||
distributePower(powerNeeded, powerProduced);
|
||||
}
|
||||
|
||||
public void add(PowerGraph graph){
|
||||
@ -106,22 +167,29 @@ public class PowerGraph{
|
||||
tile.entity.power.graph = this;
|
||||
all.add(tile);
|
||||
|
||||
if(tile.block().outputsPower){
|
||||
if(tile.block().outputsPower && tile.block().consumesPower){
|
||||
batteries.add(tile);
|
||||
}else if(tile.block().outputsPower){
|
||||
producers.add(tile);
|
||||
}
|
||||
|
||||
if(tile.block().consumesPower){
|
||||
}else if(tile.block().consumesPower){
|
||||
consumers.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
for(Tile other : all){
|
||||
if(other.entity != null && other.entity.power != null) other.entity.power.graph = null;
|
||||
if(other.entity != null && other.entity.power != null){
|
||||
if(other.block().consumes.has(ConsumePower.class) && !other.block().consumes.get(ConsumePower.class).isBuffered){
|
||||
// Reset satisfaction to zero in case of direct consumer. There is no reason to clear power from buffered consumers.
|
||||
other.entity.power.satisfaction = 0.0f;
|
||||
}
|
||||
other.entity.power.graph = null;
|
||||
}
|
||||
}
|
||||
all.clear();
|
||||
producers.clear();
|
||||
consumers.clear();
|
||||
batteries.clear();
|
||||
}
|
||||
|
||||
public void reflow(Tile tile){
|
||||
@ -146,7 +214,7 @@ public class PowerGraph{
|
||||
closedSet.clear();
|
||||
|
||||
for(Tile other : tile.block().getPowerConnections(tile, outArray1)){
|
||||
if(other.entity.power == null || other.entity.power.graph != null) continue;
|
||||
if(other.entity.power == null || other.entity.power.graph != null){ continue; }
|
||||
PowerGraph graph = new PowerGraph();
|
||||
queue.clear();
|
||||
queue.addLast(other);
|
||||
@ -161,17 +229,29 @@ public class PowerGraph{
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.block(), tile.entity())){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "PowerGraph{" +
|
||||
"producers=" + producers +
|
||||
", consumers=" + consumers +
|
||||
", all=" + all +
|
||||
", lastFrameUpdated=" + lastFrameUpdated +
|
||||
", graphID=" + graphID +
|
||||
'}';
|
||||
"producers=" + producers +
|
||||
", consumers=" + consumers +
|
||||
", batteries=" + batteries +
|
||||
", all=" + all +
|
||||
", lastFrameUpdated=" + lastFrameUpdated +
|
||||
", graphID=" + graphID +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ public class PowerNode extends PowerBlock{
|
||||
super(name);
|
||||
expanded = true;
|
||||
layer = Layer.power;
|
||||
powerCapacity = 5f;
|
||||
configurable = true;
|
||||
consumesPower = false;
|
||||
outputsPower = false;
|
||||
|
@ -1,34 +1,30 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.collection.EnumSet;
|
||||
|
||||
public class SolarGenerator extends PowerGenerator{
|
||||
/**
|
||||
* power generated per frame
|
||||
*/
|
||||
protected float generation = 0.005f;
|
||||
|
||||
public SolarGenerator(String name){
|
||||
super(name);
|
||||
// Remove the BlockFlag.producer flag to make this a lower priority target than other generators.
|
||||
flags = EnumSet.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(BlockStat.basePowerGeneration, generation * 60f, StatUnit.powerSecond);
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
addPower(tile, generation * Time.delta());
|
||||
|
||||
tile.entity.power.graph.update();
|
||||
public TileEntity newEntity(){
|
||||
return new PowerGenerator.GeneratorEntity(){{
|
||||
productionEfficiency = 1.0f;
|
||||
}};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -190,6 +190,9 @@ public class Drill extends Block{
|
||||
if(entity.consumed(ConsumeLiquid.class) && !liquidRequired){
|
||||
speed = liquidBoostIntensity;
|
||||
}
|
||||
if(hasPower){
|
||||
speed *= entity.power.satisfaction; // Drill slower when not at full power
|
||||
}
|
||||
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, speed, warmupSpeed);
|
||||
entity.progress += entity.delta()
|
||||
|
@ -65,7 +65,7 @@ public class Fracker extends SolidPump{
|
||||
|
||||
if(entity.cons.valid() && entity.accumulator < itemUseTime){
|
||||
super.update(tile);
|
||||
entity.accumulator += entity.delta();
|
||||
entity.accumulator += entity.delta() * entity.power.satisfaction;
|
||||
}else{
|
||||
tryDumpLiquid(tile, result);
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ public class GenericCrafter extends Block{
|
||||
|
||||
if(entity.cons.valid() && tile.entity.items.get(output) < itemCapacity){
|
||||
|
||||
entity.progress += 1f / craftTime * entity.delta();
|
||||
entity.progress += getProgressIncrease(entity, craftTime);
|
||||
entity.totalProgress += entity.delta();
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
|
||||
|
||||
|
@ -25,7 +25,8 @@ public class Incinerator extends Block{
|
||||
update = true;
|
||||
solid = true;
|
||||
|
||||
consumes.power(0.05f);
|
||||
// Incinerator has no speed which could be adjusted, so it will only operate fully powered for now
|
||||
consumes.power(0.05f, 1.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,6 +49,9 @@ public class LiquidMixer extends LiquidBlock{
|
||||
|
||||
if(tile.entity.cons.valid()){
|
||||
float use = Math.min(consumes.get(ConsumeLiquid.class).used() * entity.delta(), liquidCapacity - entity.liquids.get(outputLiquid));
|
||||
if(hasPower){
|
||||
use *= entity.power.satisfaction; // Produce less liquid if power is not maxed
|
||||
}
|
||||
entity.accumulator += use;
|
||||
entity.liquids.add(outputLiquid, use);
|
||||
for(int i = 0; i < (int) (entity.accumulator / liquidPerItem); i++){
|
||||
|
@ -65,7 +65,7 @@ public class PowerCrafter extends Block{
|
||||
GenericCrafterEntity entity = tile.entity();
|
||||
|
||||
if(entity.cons.valid()){
|
||||
entity.progress += 1f / craftTime * entity.delta();
|
||||
entity.progress += getProgressIncrease(entity, craftTime);
|
||||
entity.totalProgress += entity.delta();
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class PowerSmelter extends PowerBlock{
|
||||
}
|
||||
}
|
||||
|
||||
entity.craftTime += entity.delta();
|
||||
entity.craftTime += entity.delta() * entity.power.satisfaction;
|
||||
|
||||
if(entity.items.get(result) >= itemCapacity //output full
|
||||
|| entity.heat <= minHeat //not burning
|
||||
|
@ -96,6 +96,9 @@ public class Pump extends LiquidBlock{
|
||||
|
||||
if(tile.entity.cons.valid() && liquidDrop != null){
|
||||
float maxPump = Math.min(liquidCapacity - tile.entity.liquids.total(), tiles * pumpAmount * tile.entity.delta());
|
||||
if(hasPower){
|
||||
maxPump *= tile.entity.power.satisfaction; // Produce slower if not at full power
|
||||
}
|
||||
tile.entity.liquids.add(liquidDrop, maxPump);
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class Separator extends Block{
|
||||
entity.totalProgress += entity.warmup * entity.delta();
|
||||
|
||||
if(entity.cons.valid()){
|
||||
entity.progress += 1f / filterTime*entity.delta();
|
||||
entity.progress += getProgressIncrease(entity, filterTime);
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
|
||||
}else{
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f);
|
||||
|
@ -77,7 +77,7 @@ public class SolidPump extends Pump{
|
||||
}
|
||||
|
||||
if(tile.entity.cons.valid() && typeLiquid(tile) < liquidCapacity - 0.001f){
|
||||
float maxPump = Math.min(liquidCapacity - typeLiquid(tile), pumpAmount * entity.delta() * fraction);
|
||||
float maxPump = Math.min(liquidCapacity - typeLiquid(tile), pumpAmount * entity.delta() * fraction * entity.power.satisfaction);
|
||||
tile.entity.liquids.add(result, maxPump);
|
||||
entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f);
|
||||
if(Mathf.chance(entity.delta() * updateEffectChance))
|
||||
|
@ -24,7 +24,6 @@ import io.anuke.mindustry.graphics.Shaders;
|
||||
import io.anuke.mindustry.type.Mech;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePowerExact;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
|
||||
import java.io.DataInput;
|
||||
@ -37,6 +36,7 @@ import static io.anuke.mindustry.Vars.tilesize;
|
||||
public class MechPad extends Block{
|
||||
protected Mech mech;
|
||||
protected float buildTime = 60 * 5;
|
||||
protected float requiredSatisfaction = 1f;
|
||||
|
||||
protected TextureRegion openRegion;
|
||||
|
||||
@ -49,16 +49,9 @@ public class MechPad extends Block{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
consumes.add(new ConsumePowerExact(powerCapacity * 0.8f));
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
stats.remove(BlockStat.powerUse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldConsume(Tile tile){
|
||||
return false;
|
||||
@ -66,10 +59,14 @@ public class MechPad extends Block{
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server)
|
||||
public static void onMechFactoryTap(Player player, Tile tile){
|
||||
if(player == null || !checkValidTap(tile, player)) return;
|
||||
if(player == null || !checkValidTap(tile, player) || !(tile.block() instanceof MechPad)) return;
|
||||
|
||||
MechFactoryEntity entity = tile.entity();
|
||||
entity.power.amount = 0f;
|
||||
MechPad pad = (MechPad)tile.block();
|
||||
|
||||
if(entity.power.satisfaction < pad.requiredSatisfaction) return;
|
||||
|
||||
entity.power.satisfaction -= Math.min(entity.power.satisfaction, pad.requiredSatisfaction);
|
||||
player.beginRespawning(entity);
|
||||
}
|
||||
|
||||
@ -102,7 +99,7 @@ public class MechPad extends Block{
|
||||
|
||||
protected static boolean checkValidTap(Tile tile, Player player){
|
||||
MechFactoryEntity entity = tile.entity();
|
||||
return Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f &&
|
||||
return Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f &&
|
||||
Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize / 2f && entity.cons.valid() && entity.player == null;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,8 @@ import static io.anuke.mindustry.Vars.world;
|
||||
public class Reconstructor extends Block{
|
||||
protected float departTime = 30f;
|
||||
protected float arriveTime = 40f;
|
||||
protected float powerPerTeleport = 5f;
|
||||
/** Stores the percentage of buffered power to be used upon teleporting. */
|
||||
protected float powerPerTeleport = 0.5f;
|
||||
protected Effect arriveEffect = Fx.spawn;
|
||||
protected TextureRegion openRegion;
|
||||
|
||||
@ -44,13 +45,14 @@ public class Reconstructor extends Block{
|
||||
solidifes = true;
|
||||
hasPower = true;
|
||||
configurable = true;
|
||||
consumes.powerBuffered(30f);
|
||||
}
|
||||
|
||||
protected static boolean checkValidTap(Tile tile, ReconstructorEntity entity, Player player){
|
||||
return validLink(tile, entity.link) &&
|
||||
Math.abs(player.x - tile.drawx()) <= tile.block().size * tilesize / 2f &&
|
||||
Math.abs(player.y - tile.drawy()) <= tile.block().size * tilesize / 2f &&
|
||||
entity.current == null && entity.power.amount >= ((Reconstructor) tile.block()).powerPerTeleport;
|
||||
entity.current == null && entity.power.satisfaction >= ((Reconstructor) tile.block()).powerPerTeleport;
|
||||
}
|
||||
|
||||
protected static boolean validLink(Tile tile, int position){
|
||||
@ -75,13 +77,13 @@ public class Reconstructor extends Block{
|
||||
public static void reconstructPlayer(Player player, Tile tile){
|
||||
ReconstructorEntity entity = tile.entity();
|
||||
|
||||
if(!checkValidTap(tile, entity, player) || entity.power.amount < ((Reconstructor) tile.block()).powerPerTeleport)
|
||||
if(!checkValidTap(tile, entity, player) || entity.power.satisfaction < ((Reconstructor) tile.block()).powerPerTeleport)
|
||||
return;
|
||||
|
||||
entity.departing = true;
|
||||
entity.current = player;
|
||||
entity.solid = false;
|
||||
entity.power.amount -= ((Reconstructor) tile.block()).powerPerTeleport;
|
||||
entity.power.satisfaction -= Math.min(entity.power.satisfaction, ((Reconstructor) tile.block()).powerPerTeleport);
|
||||
entity.updateTime = 1f;
|
||||
entity.set(tile.drawx(), tile.drawy());
|
||||
player.rotation = 90f;
|
||||
@ -242,13 +244,13 @@ public class Reconstructor extends Block{
|
||||
entity.updateTime -= Time.delta() / departTime;
|
||||
if(entity.updateTime <= 0f){
|
||||
//no power? death.
|
||||
if(other.power.amount < powerPerTeleport){
|
||||
if(other.power.satisfaction < powerPerTeleport){
|
||||
entity.current.setDead(true);
|
||||
//entity.current.setRespawning(false);
|
||||
entity.current = null;
|
||||
return;
|
||||
}
|
||||
other.power.amount -= powerPerTeleport;
|
||||
other.power.satisfaction -= Math.min(other.power.satisfaction, powerPerTeleport);
|
||||
other.current = entity.current;
|
||||
other.departing = false;
|
||||
other.current.set(other.x, other.y);
|
||||
@ -272,8 +274,8 @@ public class Reconstructor extends Block{
|
||||
|
||||
if(validLink(tile, entity.link)){
|
||||
Tile other = world.tile(entity.link);
|
||||
if(other.entity.power.amount >= powerPerTeleport && Units.anyEntities(tile, 4f, unit -> unit.getTeam() == entity.getTeam() && unit instanceof Player) &&
|
||||
entity.power.amount >= powerPerTeleport){
|
||||
if(other.entity.power.satisfaction >= powerPerTeleport && Units.anyEntities(tile, 4f, unit -> unit.getTeam() == entity.getTeam() && unit instanceof Player) &&
|
||||
entity.power.satisfaction >= powerPerTeleport){
|
||||
entity.solid = false;
|
||||
stayOpen = true;
|
||||
}
|
||||
|
@ -18,7 +18,10 @@ import io.anuke.mindustry.graphics.Palette;
|
||||
import io.anuke.mindustry.graphics.Shapes;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import io.anuke.mindustry.world.meta.BlockFlag;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
|
||||
public class RepairPoint extends Block{
|
||||
private static Rectangle rect = new Rectangle();
|
||||
@ -27,6 +30,8 @@ public class RepairPoint extends Block{
|
||||
|
||||
protected float repairRadius = 50f;
|
||||
protected float repairSpeed = 0.3f;
|
||||
protected float powerPerEvent = 0.06f;
|
||||
protected ConsumePower consumePower;
|
||||
|
||||
protected TextureRegion topRegion;
|
||||
|
||||
@ -38,8 +43,7 @@ public class RepairPoint extends Block{
|
||||
layer = Layer.turret;
|
||||
layer2 = Layer.laser;
|
||||
hasPower = true;
|
||||
powerCapacity = 20f;
|
||||
consumes.power(0.06f);
|
||||
consumePower = consumes.powerBuffered(20f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,6 +53,12 @@ public class RepairPoint extends Block{
|
||||
topRegion = Core.atlas.find(name + "-turret");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
stats.add(BlockStat.powerUse, powerPerEvent * 60f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawSelect(Tile tile){
|
||||
Draw.color(Palette.accent);
|
||||
@ -84,16 +94,22 @@ public class RepairPoint extends Block{
|
||||
public void update(Tile tile){
|
||||
RepairPointEntity entity = tile.entity();
|
||||
|
||||
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.target.health += repairSpeed * Time.delta() * entity.strength;
|
||||
entity.target.clampHealth();
|
||||
entity.rotation = Mathf.slerpDelta(entity.rotation, entity.angleTo(entity.target), 0.5f);
|
||||
float relativeConsumption = powerPerEvent / consumePower.powerCapacity;
|
||||
if(entity.power.satisfaction > 0.0f){
|
||||
entity.target.health += repairSpeed * Time.delta() * entity.strength * Mathf.clamp(entity.power.satisfaction / relativeConsumption);
|
||||
entity.target.clampHealth();
|
||||
entity.rotation = Mathf.slerpDelta(entity.rotation, entity.angleTo(entity.target), 0.5f);
|
||||
entity.power.satisfaction -= Math.min(entity.power.satisfaction, relativeConsumption);
|
||||
targetIsBeingRepaired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(entity.target != null && entity.cons.valid()){
|
||||
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());
|
||||
|
@ -149,7 +149,7 @@ public class UnitFactory extends Block{
|
||||
|
||||
if(hasRequirements(entity.items, entity.buildTime / produceTime) && entity.cons.valid()){
|
||||
|
||||
entity.buildTime += entity.delta();
|
||||
entity.buildTime += entity.delta() * entity.power.satisfaction;
|
||||
entity.speedScl = Mathf.lerpDelta(entity.speedScl, 1f, 0.05f);
|
||||
}else{
|
||||
entity.speedScl = Mathf.lerpDelta(entity.speedScl, 0f, 0.05f);
|
||||
|
@ -1,22 +1,53 @@
|
||||
package io.anuke.mindustry.world.consumers;
|
||||
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.meta.BlockStat;
|
||||
import io.anuke.mindustry.world.meta.BlockStats;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
|
||||
/** Consumer class for blocks which consume power while being connected to a power graph. */
|
||||
public class ConsumePower extends Consume{
|
||||
protected final float use;
|
||||
/** The maximum amount of power which can be processed per tick. This might influence efficiency or load a buffer. */
|
||||
protected final float powerPerTick;
|
||||
/** The minimum power satisfaction (fraction of powerPerTick) which must be achieved before the module may work. */
|
||||
public final float minimumSatisfaction;
|
||||
/** The maximum power capacity in power units. */
|
||||
public final float powerCapacity;
|
||||
/** True if the module can store power. */
|
||||
public final boolean isBuffered;
|
||||
|
||||
public ConsumePower(float use){
|
||||
this.use = use;
|
||||
protected ConsumePower(float powerPerTick, float minimumSatisfaction, float powerCapacity, boolean isBuffered){
|
||||
this.powerPerTick = powerPerTick;
|
||||
this.minimumSatisfaction = minimumSatisfaction;
|
||||
this.powerCapacity = powerCapacity;
|
||||
this.isBuffered = isBuffered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the owner consume powerPerTick each tick and disables it unless minimumSatisfaction (1.0 = 100%) of that power is being supplied.
|
||||
* @param powerPerTick The maximum amount of power which is required per tick for 100% efficiency.
|
||||
* @param minimumSatisfaction The percentage of powerPerTick which must be available for the module to work.
|
||||
*/
|
||||
public static ConsumePower consumePowerDirect(float powerPerTick, float minimumSatisfaction){
|
||||
return new ConsumePower(powerPerTick, minimumSatisfaction, 0.0f, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a power buffer to the owner which takes ticksToFill number of ticks to be filled.
|
||||
* Note that this object does not remove power from the buffer.
|
||||
* @param powerCapacity The maximum capacity in power units.
|
||||
* @param ticksToFill The number of ticks it shall take to fill the buffer.
|
||||
*/
|
||||
public static ConsumePower consumePowerBuffered(float powerCapacity, float ticksToFill){
|
||||
return new ConsumePower(powerCapacity / ticksToFill, 0.0f, powerCapacity, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildTooltip(Table table){
|
||||
|
||||
// No tooltip for power
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -26,21 +57,41 @@ public class ConsumePower extends Consume{
|
||||
|
||||
@Override
|
||||
public void update(Block block, TileEntity entity){
|
||||
if(entity.power == null) return;
|
||||
entity.power.amount -= Math.min(use(block, entity), entity.power.amount);
|
||||
// Nothing to do since PowerGraph directly updates entity.power.satisfaction
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valid(Block block, TileEntity entity){
|
||||
return entity.power != null && entity.power.amount >= use(block, entity);
|
||||
if(isBuffered){
|
||||
return true;
|
||||
}else{
|
||||
return entity.power.satisfaction >= minimumSatisfaction;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(BlockStats stats){
|
||||
stats.add(BlockStat.powerUse, use * 60f, StatUnit.powerSecond);
|
||||
if(isBuffered){
|
||||
stats.add(BlockStat.powerCapacity, powerCapacity, StatUnit.powerSecond);
|
||||
}else{
|
||||
stats.add(BlockStat.powerUse, powerPerTick * 60f, StatUnit.powerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
protected float use(Block block, TileEntity entity){
|
||||
return Math.min(use * entity.delta(), block.powerCapacity);
|
||||
/**
|
||||
* Retrieves the amount of power which is requested for the given block and entity.
|
||||
* @param block The block which needs power.
|
||||
* @param entity The entity which contains the power module.
|
||||
* @return The amount of power which is requested per tick.
|
||||
*/
|
||||
public float requestedPower(Block block, TileEntity entity){
|
||||
if(isBuffered){
|
||||
// Stop requesting power once the buffer is full.
|
||||
return Mathf.isEqual(entity.power.satisfaction, 1.0f) ? 0.0f : powerPerTick;
|
||||
}else{
|
||||
return powerPerTick;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package io.anuke.mindustry.world.consumers;
|
||||
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
|
||||
public class ConsumePowerExact extends ConsumePower{
|
||||
|
||||
public ConsumePowerExact(float use){
|
||||
super(use);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float use(Block block, TileEntity entity){
|
||||
return this.use;
|
||||
}
|
||||
}
|
@ -30,18 +30,52 @@ public class Consumers{
|
||||
}
|
||||
}
|
||||
|
||||
public ConsumePower power(float amount){
|
||||
ConsumePower p = new ConsumePower(amount);
|
||||
add(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public ConsumeLiquid liquid(Liquid liquid, float amount){
|
||||
ConsumeLiquid c = new ConsumeLiquid(liquid, amount);
|
||||
add(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which directly uses power without buffering it. The module will work while at least 50% of power is supplied.
|
||||
* @param powerPerTick The amount of power which is required each tick for 100% efficiency.
|
||||
* @return the created consumer object.
|
||||
*/
|
||||
public ConsumePower power(float powerPerTick){
|
||||
return power(powerPerTick, 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which directly uses power without buffering it. The module will work while the available power is greater than or equal to the minimumSatisfaction percentage (0..1).
|
||||
* @param powerPerTick The amount of power which is required each tick for 100% efficiency.
|
||||
* @return the created consumer object.
|
||||
*/
|
||||
public ConsumePower power(float powerPerTick, float minimumSatisfaction){
|
||||
ConsumePower c = ConsumePower.consumePowerDirect(powerPerTick, minimumSatisfaction);
|
||||
add(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which stores power and uses it only in case of certain events (e.g. a turret firing).
|
||||
* It will take 180 ticks (three second) to fill the buffer, given enough power supplied.
|
||||
* @param powerCapacity The maximum capacity in power units.
|
||||
*/
|
||||
public ConsumePower powerBuffered(float powerCapacity){
|
||||
return powerBuffered(powerCapacity, 1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which stores power and uses it only in case of certain events (e.g. a turret firing).
|
||||
* @param powerCapacity The maximum capacity in power units.
|
||||
* @param ticksToFill The number of ticks it shall take to fill the buffer.
|
||||
*/
|
||||
public ConsumePower powerBuffered(float powerCapacity, float ticksToFill){
|
||||
ConsumePower c = ConsumePower.consumePowerBuffered(powerCapacity, ticksToFill);
|
||||
add(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
public ConsumeItem item(Item item){
|
||||
return item(item, 1);
|
||||
}
|
||||
@ -75,7 +109,7 @@ public class Consumers{
|
||||
}
|
||||
|
||||
public Consume add(Consume consume){
|
||||
map.put(consume.getClass(), consume);
|
||||
map.put((consume instanceof ConsumePower ? ConsumePower.class : consume.getClass()), consume);
|
||||
return consume;
|
||||
}
|
||||
|
||||
|
@ -8,14 +8,18 @@ import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PowerModule extends BlockModule{
|
||||
public float amount;
|
||||
/** In case of unbuffered consumers, this is the percentage (1.0f = 100%) of the demanded power which can be supplied.
|
||||
* Blocks will work at a reduced efficiency if this is not equal to 1.0f.
|
||||
* In case of buffered consumers, this is the percentage of power stored in relation to the maximum capacity.
|
||||
*/
|
||||
public float satisfaction = 0.0f;
|
||||
/** Specifies power which is required additionally, e.g. while a force projector is being shot at. */
|
||||
public float extraUse = 0f;
|
||||
public PowerGraph graph = new PowerGraph();
|
||||
public IntArray links = new IntArray();
|
||||
|
||||
@Override
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeFloat(amount);
|
||||
|
||||
stream.writeShort(links.size);
|
||||
for(int i = 0; i < links.size; i++){
|
||||
stream.writeInt(links.get(i));
|
||||
@ -24,15 +28,6 @@ public class PowerModule extends BlockModule{
|
||||
|
||||
@Override
|
||||
public void read(DataInput stream) throws IOException{
|
||||
amount = stream.readFloat();
|
||||
if(Float.isNaN(amount)){
|
||||
amount = 0f;
|
||||
}
|
||||
// Workaround: If power went negative for some reason, at least fix it when reloading the map
|
||||
if(amount < 0f){
|
||||
amount = 0f;
|
||||
}
|
||||
|
||||
short amount = stream.readShort();
|
||||
for(int i = 0; i < amount; i++){
|
||||
links.add(stream.readInt());
|
||||
|
55
tests/src/test/java/power/DirectConsumerTests.java
Normal file
55
tests/src/test/java/power/DirectConsumerTests.java
Normal file
@ -0,0 +1,55 @@
|
||||
package power;
|
||||
|
||||
import io.anuke.mindustry.content.Items;
|
||||
import io.anuke.mindustry.content.UnitTypes;
|
||||
import io.anuke.mindustry.type.ItemStack;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGraph;
|
||||
import io.anuke.mindustry.world.blocks.units.UnitFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/** Tests for direct power consumers. */
|
||||
public class DirectConsumerTests extends PowerTestFixture{
|
||||
|
||||
@Test
|
||||
void noPowerRequestedWithNoItems(){
|
||||
testUnitFactory(0, 0, 0.08f, 0.08f, 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noPowerRequestedWithInsufficientItems(){
|
||||
testUnitFactory(30, 0, 0.08f, 0.08f, 0.0f);
|
||||
testUnitFactory(0, 30, 0.08f, 0.08f, 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void powerRequestedWithSufficientItems(){
|
||||
testUnitFactory(30, 30, 0.08f, 0.08f, 1.0f);
|
||||
}
|
||||
|
||||
void testUnitFactory(int siliconAmount, int leadAmount, float producedPower, float requestedPower, float expectedSatisfaction){
|
||||
Tile consumerTile = createFakeTile(0, 0, new UnitFactory("fakefactory"){{
|
||||
type = UnitTypes.spirit;
|
||||
produceTime = 60;
|
||||
consumes.power(requestedPower);
|
||||
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30));
|
||||
}});
|
||||
consumerTile.entity.items.add(Items.silicon, siliconAmount);
|
||||
consumerTile.entity.items.add(Items.lead, leadAmount);
|
||||
|
||||
Tile producerTile = createFakeTile(2, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // 100%
|
||||
|
||||
PowerGraph graph = new PowerGraph();
|
||||
graph.add(producerTile);
|
||||
graph.add(consumerTile);
|
||||
|
||||
consumerTile.entity.update();
|
||||
graph.update();
|
||||
|
||||
assertEquals(expectedSatisfaction, consumerTile.entity.power.satisfaction);
|
||||
}
|
||||
}
|
175
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
175
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
@ -0,0 +1,175 @@
|
||||
package power;
|
||||
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.content.Items;
|
||||
import io.anuke.mindustry.content.Liquids;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.ItemLiquidGenerator;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* This class tests generators which can process items, liquids or both.
|
||||
* All tests are run with a fixed delta of 0.5 so delta considerations can be tested as well.
|
||||
* Additionally, each PowerGraph::update() call will have its own thread frame, i.e. the method will never be called twice within the same frame.
|
||||
* Both of these constraints are handled by FakeThreadHandler within PowerTestFixture.
|
||||
* Any expected power amount (produced, consumed, buffered) should be affected by FakeThreadHandler.fakeDelta but satisfaction should not!
|
||||
*/
|
||||
public class ItemLiquidGeneratorTests extends PowerTestFixture{
|
||||
|
||||
private ItemLiquidGenerator generator;
|
||||
private Tile tile;
|
||||
private ItemLiquidGenerator.ItemLiquidGeneratorEntity entity;
|
||||
private final float fakeItemDuration = 60f; // 60 ticks
|
||||
private final float maximumLiquidUsage = 0.5f;
|
||||
|
||||
public void createGenerator(ItemLiquidGenerator.InputType inputType){
|
||||
generator = new ItemLiquidGenerator(inputType, "fakegen"){
|
||||
{
|
||||
powerProduction = 0.1f;
|
||||
itemDuration = 60f;
|
||||
itemDuration = fakeItemDuration;
|
||||
maxLiquidGenerate = maximumLiquidUsage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getItemEfficiency(Item item){
|
||||
return item.flammability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getLiquidEfficiency(Liquid liquid){
|
||||
return liquid.flammability;
|
||||
}
|
||||
};
|
||||
|
||||
tile = createFakeTile(0, 0, generator);
|
||||
entity = tile.entity();
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with liquids. */
|
||||
@TestFactory
|
||||
DynamicTest[] generatorWorksProperlyWithLiquidInput(){
|
||||
|
||||
// Execute all tests for the case where only liquids are accepted and for the case where liquids and items are accepted (but supply only liquids)
|
||||
ItemLiquidGenerator.InputType[] inputTypesToBeTested = new ItemLiquidGenerator.InputType[]{
|
||||
ItemLiquidGenerator.InputType.LiquidsOnly,
|
||||
ItemLiquidGenerator.InputType.LiquidsAndItems
|
||||
};
|
||||
|
||||
ArrayList<DynamicTest> tests = new ArrayList<>();
|
||||
for(ItemLiquidGenerator.InputType inputType : inputTypesToBeTested){
|
||||
tests.add(dynamicTest("01", () -> simulateLiquidConsumption(inputType, Liquids.oil, 0.0f, "No liquids provided")));
|
||||
tests.add(dynamicTest("02", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage / 4.0f, "Low oil provided")));
|
||||
tests.add(dynamicTest("03", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage * 1.0f, "Sufficient oil provided")));
|
||||
tests.add(dynamicTest("04", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage * 2.0f, "Excess oil provided")));
|
||||
// Note: The generator will decline any other liquid since it's not flammable
|
||||
}
|
||||
DynamicTest[] testArray = new DynamicTest[tests.size()];
|
||||
testArray = tests.toArray(testArray);
|
||||
return testArray;
|
||||
}
|
||||
|
||||
void simulateLiquidConsumption(ItemLiquidGenerator.InputType inputType, Liquid liquid, float availableLiquidAmount, String parameterDescription){
|
||||
final float baseEfficiency = liquid.flammability;
|
||||
final float expectedEfficiency = Math.min(1.0f, availableLiquidAmount / maximumLiquidUsage) * baseEfficiency;
|
||||
final float expectedConsumptionPerTick = Math.min(maximumLiquidUsage, availableLiquidAmount);
|
||||
final float expectedRemainingLiquidAmount = Math.max(0.0f, availableLiquidAmount - expectedConsumptionPerTick * Time.delta());
|
||||
|
||||
createGenerator(inputType);
|
||||
assertTrue(generator.acceptLiquid(tile, null, liquid, availableLiquidAmount), inputType + " | " + parameterDescription + ": Liquids which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
entity.liquids.add(liquid, availableLiquidAmount);
|
||||
entity.cons.update(tile.entity);
|
||||
assertTrue(entity.cons.valid());
|
||||
|
||||
// Perform an update on the generator once - This should use up any resource up to the maximum liquid usage
|
||||
generator.update(tile);
|
||||
|
||||
assertEquals(expectedRemainingLiquidAmount, entity.liquids.get(liquid), inputType + " | " + parameterDescription + ": Remaining liquid amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, inputType + " | " + parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with items. */
|
||||
@TestFactory
|
||||
DynamicTest[] generatorWorksProperlyWithItemInput(){
|
||||
|
||||
// Execute all tests for the case where only items are accepted and for the case where liquids and items are accepted (but supply only items)
|
||||
ItemLiquidGenerator.InputType[] inputTypesToBeTested = new ItemLiquidGenerator.InputType[]{
|
||||
ItemLiquidGenerator.InputType.ItemsOnly,
|
||||
ItemLiquidGenerator.InputType.LiquidsAndItems
|
||||
};
|
||||
|
||||
ArrayList<DynamicTest> tests = new ArrayList<>();
|
||||
for(ItemLiquidGenerator.InputType inputType : inputTypesToBeTested){
|
||||
tests.add(dynamicTest("01", () -> simulateItemConsumption(inputType, Items.coal, 0, "No items provided")));
|
||||
tests.add(dynamicTest("02", () -> simulateItemConsumption(inputType, Items.coal, 1, "Sufficient coal provided")));
|
||||
tests.add(dynamicTest("03", () -> simulateItemConsumption(inputType, Items.coal, 10, "Excess coal provided")));
|
||||
tests.add(dynamicTest("04", () -> simulateItemConsumption(inputType, Items.blastCompound, 1, "Blast compound provided")));
|
||||
//dynamicTest("03", () -> simulateItemConsumption(inputType, Items.plastanium, 1, "Plastanium provided")), // Not accepted by generator due to low flammability
|
||||
tests.add(dynamicTest("05", () -> simulateItemConsumption(inputType, Items.biomatter, 1, "Biomatter provided")));
|
||||
tests.add(dynamicTest("06", () -> simulateItemConsumption(inputType, Items.pyratite, 1, "Pyratite provided")));
|
||||
}
|
||||
DynamicTest[] testArray = new DynamicTest[tests.size()];
|
||||
testArray = tests.toArray(testArray);
|
||||
return testArray;
|
||||
}
|
||||
|
||||
void simulateItemConsumption(ItemLiquidGenerator.InputType inputType, Item item, int amount, String parameterDescription){
|
||||
final float expectedEfficiency = Math.min(1.0f, amount > 0 ? item.flammability : 0f);
|
||||
final float expectedRemainingItemAmount = Math.max(0, amount - 1);
|
||||
|
||||
createGenerator(inputType);
|
||||
assertTrue(generator.acceptItem(item, tile, null), inputType + " | " + parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
if(amount > 0){
|
||||
entity.items.add(item, amount);
|
||||
}
|
||||
entity.cons.update(tile.entity);
|
||||
assertTrue(entity.cons.valid());
|
||||
|
||||
// Perform an update on the generator once - This should use up one or zero items - dependent on if the item is accepted and available or not.
|
||||
generator.update(tile);
|
||||
|
||||
assertEquals(expectedRemainingItemAmount, entity.items.get(item), inputType + " | " + parameterDescription + ": Remaining item amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, inputType + " | " + parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Makes sure the efficiency stays equal during the item duration. */
|
||||
@Test
|
||||
void efficiencyRemainsConstantWithinItemDuration_ItemsOnly(){
|
||||
testItemDuration(ItemLiquidGenerator.InputType.ItemsOnly);
|
||||
}
|
||||
|
||||
/** Makes sure the efficiency stays equal during the item duration. */
|
||||
@Test
|
||||
void efficiencyRemainsConstantWithinItemDuration_ItemsAndLiquids(){
|
||||
testItemDuration(ItemLiquidGenerator.InputType.LiquidsAndItems);
|
||||
}
|
||||
|
||||
void testItemDuration(ItemLiquidGenerator.InputType inputType){
|
||||
createGenerator(inputType);
|
||||
|
||||
// Burn a single coal and test for the duration
|
||||
entity.items.add(Items.coal, 1);
|
||||
entity.cons.update(tile.entity);
|
||||
generator.update(tile);
|
||||
|
||||
float expectedEfficiency = entity.productionEfficiency;
|
||||
|
||||
float currentDuration = 0.0f;
|
||||
while((currentDuration += Time.delta()) <= fakeItemDuration){
|
||||
generator.update(tile);
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
generator.update(tile);
|
||||
assertEquals(0.0f, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
}
|
105
tests/src/test/java/power/PowerTestFixture.java
Normal file
105
tests/src/test/java/power/PowerTestFixture.java
Normal file
@ -0,0 +1,105 @@
|
||||
package power;
|
||||
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.content.blocks.Blocks;
|
||||
import io.anuke.mindustry.core.ContentLoader;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.PowerBlock;
|
||||
import io.anuke.mindustry.world.blocks.power.Battery;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.modules.ConsumeModule;
|
||||
import io.anuke.mindustry.world.modules.ItemModule;
|
||||
import io.anuke.mindustry.world.modules.LiquidModule;
|
||||
import io.anuke.mindustry.world.modules.PowerModule;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/** This class provides objects commonly used by power related unit tests.
|
||||
* For now, this is a helper with static methods, but this might change.
|
||||
*
|
||||
* Note: All tests which subclass this will run with a fixed delta of 0.5!
|
||||
* */
|
||||
public class PowerTestFixture{
|
||||
|
||||
public static final float smallRoundingTolerance = Mathf.FLOAT_ROUNDING_ERROR;
|
||||
public static final float mediumRoundingTolerance = Mathf.FLOAT_ROUNDING_ERROR * 10;
|
||||
public static final float highRoundingTolerance = Mathf.FLOAT_ROUNDING_ERROR * 100;
|
||||
|
||||
@BeforeAll
|
||||
static void initializeDependencies(){
|
||||
Vars.content = new ContentLoader();
|
||||
Vars.content.load();
|
||||
Time.setDeltaProvider(() -> 0.5f);
|
||||
}
|
||||
|
||||
protected static PowerGenerator createFakeProducerBlock(float producedPower){
|
||||
// Multiply produced power by 2 since production efficiency is defined to be 0.5 = 100%
|
||||
return new PowerGenerator("fakegen"){{
|
||||
powerProduction = producedPower * 2.0f;
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Battery createFakeBattery(float capacity, float ticksToFill){
|
||||
return new Battery("fakebattery"){{
|
||||
consumes.powerBuffered(capacity, ticksToFill);
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Block createFakeDirectConsumer(float powerPerTick, float minimumSatisfaction){
|
||||
return new PowerBlock("fakedirectconsumer"){{
|
||||
consumes.power(powerPerTick, minimumSatisfaction);
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Block createFakeBufferedConsumer(float capacity, float ticksToFill){
|
||||
return new PowerBlock("fakebufferedconsumer"){{
|
||||
consumes.powerBuffered(capacity, ticksToFill);
|
||||
}};
|
||||
}
|
||||
/**
|
||||
* Creates a fake tile on the given location using the given block.
|
||||
* @param x The X coordinate.
|
||||
* @param y The y coordinate.
|
||||
* @param block The block on the tile.
|
||||
* @return The created tile or null in case of exceptions.
|
||||
*/
|
||||
protected static Tile createFakeTile(int x, int y, Block block){
|
||||
try{
|
||||
Tile tile = new Tile(x, y);
|
||||
|
||||
// Using the Tile(int, int, byte, byte) constructor would require us to register any fake block or tile we create
|
||||
// Since this part shall not be part of the test and would require more work anyway, we manually set the block and floor
|
||||
// through reflections and then simulate part of what the changed() method does.
|
||||
|
||||
Field field = Tile.class.getDeclaredField("wall");
|
||||
field.setAccessible(true);
|
||||
field.set(tile, block);
|
||||
|
||||
field = Tile.class.getDeclaredField("floor");
|
||||
field.setAccessible(true);
|
||||
field.set(tile, Blocks.sand);
|
||||
|
||||
// Simulate the "changed" method. Calling it through reflections would require half the game to be initialized.
|
||||
tile.entity = block.newEntity().init(tile, false);
|
||||
tile.entity.cons = new ConsumeModule();
|
||||
if(block.hasItems) tile.entity.items = new ItemModule();
|
||||
if(block.hasLiquids) tile.entity.liquids = new LiquidModule();
|
||||
if(block.hasPower){
|
||||
tile.entity.power = new PowerModule();
|
||||
tile.entity.power.graph.add(tile);
|
||||
}
|
||||
|
||||
// Assign incredibly high health so the block does not get destroyed on e.g. burning Blast Compound
|
||||
block.health = 100000;
|
||||
tile.entity.health = 100000.0f;
|
||||
|
||||
return tile;
|
||||
}catch(Exception ex){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
183
tests/src/test/java/power/PowerTests.java
Normal file
183
tests/src/test/java/power/PowerTests.java
Normal file
@ -0,0 +1,183 @@
|
||||
package power;
|
||||
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGraph;
|
||||
import io.anuke.mindustry.world.consumers.ConsumePower;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* Tests code related to the power system in general, but not specific blocks.
|
||||
* All tests are run with a fixed delta of 0.5 so delta considerations can be tested as well.
|
||||
* Additionally, each PowerGraph::update() call will have its own thread frame, i.e. the method will never be called twice within the same frame.
|
||||
* Both of these constraints are handled by FakeThreadHandler within PowerTestFixture.
|
||||
* Any power amount (produced, consumed, buffered) should be affected by Time.delta() but satisfaction should not!
|
||||
*/
|
||||
public class PowerTests extends PowerTestFixture{
|
||||
|
||||
@BeforeEach
|
||||
void initTest(){
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PowerGraphTests{
|
||||
|
||||
/** Tests the satisfaction of a single consumer after a single update of the power graph which contains a single producer.
|
||||
*
|
||||
* Assumption: When the consumer requests zero power, satisfaction does not change. Default is 0.0f.
|
||||
*/
|
||||
@TestFactory
|
||||
DynamicTest[] directConsumerSatisfactionIsAsExpected(){
|
||||
return new DynamicTest[]{
|
||||
// Note: Unfortunately, the display names are not yet output through gradle. See https://github.com/gradle/gradle/issues/5975
|
||||
// That's why we inject the description into the test method for now.
|
||||
// Additional Note: If you don't see any labels in front of the values supplied as function parameters, use a better IDE like IntelliJ IDEA.
|
||||
dynamicTest("01", () -> simulateDirectConsumption(0.0f, 1.0f, 0.0f, "0.0 produced, 1.0 consumed (no power available)")),
|
||||
dynamicTest("02", () -> simulateDirectConsumption(0.0f, 0.0f, 0.0f, "0.0 produced, 0.0 consumed (no power anywhere)")),
|
||||
dynamicTest("03", () -> simulateDirectConsumption(1.0f, 0.0f, 0.0f, "1.0 produced, 0.0 consumed (no power requested)")),
|
||||
dynamicTest("04", () -> simulateDirectConsumption(1.0f, 1.0f, 1.0f, "1.0 produced, 1.0 consumed (stable consumption)")),
|
||||
dynamicTest("05", () -> simulateDirectConsumption(0.5f, 1.0f, 0.5f, "0.5 produced, 1.0 consumed (power shortage)")),
|
||||
dynamicTest("06", () -> simulateDirectConsumption(1.0f, 0.5f, 1.0f, "1.0 produced, 0.5 consumed (power excess)")),
|
||||
dynamicTest("07", () -> simulateDirectConsumption(0.09f, 0.09f - Mathf.FLOAT_ROUNDING_ERROR / 10.0f, 1.0f, "floating point inaccuracy (stable consumption)"))
|
||||
};
|
||||
}
|
||||
void simulateDirectConsumption(float producedPower, float requiredPower, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // Currently, 0.5f = 100%
|
||||
Tile directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requiredPower, 0.6f));
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(directConsumerTile);
|
||||
|
||||
assertEquals(producedPower * Time.delta(), powerGraph.getPowerProduced(), Mathf.FLOAT_ROUNDING_ERROR);
|
||||
assertEquals(requiredPower * Time.delta(), powerGraph.getPowerNeeded(), Mathf.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of direct consumer did not match");
|
||||
}
|
||||
|
||||
/** Tests the satisfaction of a single buffered consumer after a single update of the power graph which contains a single producer. */
|
||||
@TestFactory
|
||||
DynamicTest[] bufferedConsumerSatisfactionIsAsExpected(){
|
||||
return new DynamicTest[]{
|
||||
// Note: powerPerTick may not be 0 in any of the test cases. This would equal a "ticksToFill" of infinite.
|
||||
// Note: Due to a fixed delta of 0.5, only half of what is defined here will in fact be produced/consumed. Keep this in mind when defining expectedSatisfaction!
|
||||
dynamicTest("01", () -> simulateBufferedConsumption(0.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power anywhere")),
|
||||
dynamicTest("02", () -> simulateBufferedConsumption(0.0f, 1.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power provided")),
|
||||
dynamicTest("03", () -> simulateBufferedConsumption(1.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power requested")),
|
||||
dynamicTest("04", () -> simulateBufferedConsumption(1.0f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Stable Power, One tick to fill")),
|
||||
dynamicTest("05", () -> simulateBufferedConsumption(2.0f, 1.0f, 2.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One delta to fill")),
|
||||
dynamicTest("06", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Stable Power, multiple ticks to fill")),
|
||||
dynamicTest("07", () -> simulateBufferedConsumption(1.2f, 0.5f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Power excess, one delta to fill")),
|
||||
dynamicTest("08", () -> simulateBufferedConsumption(1.0f, 0.5f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power excess, multiple ticks to fill")),
|
||||
dynamicTest("09", () -> simulateBufferedConsumption(1.0f, 1.0f, 2.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one delta to fill")),
|
||||
dynamicTest("10", () -> simulateBufferedConsumption(0.5f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Power shortage, multiple ticks to fill")),
|
||||
dynamicTest("11", () -> simulateBufferedConsumption(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")),
|
||||
dynamicTest("12", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")),
|
||||
dynamicTest("13", () -> simulateBufferedConsumption(0.2f, 1.0f, 0.5f, 0.5f, 0.6f, "Half buffer, power shortage")),
|
||||
dynamicTest("14", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.5f, 0.9f, 1.0f, "Buffer does not get exceeded")),
|
||||
dynamicTest("15", () -> simulateBufferedConsumption(2.0f, 1.0f, 1.0f, 0.5f, 1.0f, "Half buffer, filled with excess"))
|
||||
};
|
||||
}
|
||||
void simulateBufferedConsumption(float producedPower, float maxBuffer, float powerConsumedPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // Currently, 0.5 = 100%
|
||||
Tile bufferedConsumerTile = createFakeTile(0, 1, createFakeBufferedConsumer(maxBuffer, maxBuffer > 0.0f ? maxBuffer/powerConsumedPerTick : 1.0f));
|
||||
bufferedConsumerTile.entity.power.satisfaction = initialSatisfaction;
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(bufferedConsumerTile);
|
||||
|
||||
assertEquals(producedPower * Time.delta(), powerGraph.getPowerProduced(), Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Produced power did not match");
|
||||
float expectedPowerUsage;
|
||||
if(initialSatisfaction == 1.0f){
|
||||
expectedPowerUsage = 0f;
|
||||
}else{
|
||||
expectedPowerUsage = Math.min(maxBuffer, powerConsumedPerTick * Time.delta());
|
||||
}
|
||||
assertEquals(expectedPowerUsage, powerGraph.getPowerNeeded(), Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Consumed power did not match");
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
assertEquals(expectedSatisfaction, bufferedConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of buffered consumer did not match");
|
||||
}
|
||||
|
||||
/** Tests the satisfaction of a single direct consumer after a single update of the power graph which contains a single producer and a single battery.
|
||||
* The used battery is created with a maximum capacity of 100 and receives ten power per tick.
|
||||
*/
|
||||
@TestFactory
|
||||
DynamicTest[] batteryCapacityIsAsExpected(){
|
||||
return new DynamicTest[]{
|
||||
// Note: expectedBatteryCapacity is currently adjusted to a delta of 0.5! (FakeThreadHandler sets it to that)
|
||||
dynamicTest("01", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 0.0f, 5.0f, 0.0f, "Empty battery, no consumer")),
|
||||
dynamicTest("02", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 94.999f, 99.999f, 0.0f, "Battery almost full after update, no consumer")),
|
||||
dynamicTest("03", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 100.0f, 100.0f, 0.0f, "Full battery, no consumer")),
|
||||
dynamicTest("04", () -> simulateDirectConsumptionWithBattery(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "No producer, no consumer, empty battery")),
|
||||
dynamicTest("05", () -> simulateDirectConsumptionWithBattery(0.0f, 0.0f, 100.0f, 100.0f, 0.0f, "No producer, no consumer, full battery")),
|
||||
dynamicTest("06", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 0.0f, 0.0f, 0.0f, "No producer, empty battery")),
|
||||
dynamicTest("07", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 100.0f, 95.0f, 1.0f, "No producer, full battery")),
|
||||
dynamicTest("08", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 2.5f, 0.0f, 0.5f, "No producer, low battery")),
|
||||
dynamicTest("09", () -> simulateDirectConsumptionWithBattery(5.0f, 10.0f, 5.0f, 0.0f, 1.0f, "Producer + Battery = Consumed")),
|
||||
};
|
||||
}
|
||||
void simulateDirectConsumptionWithBattery(float producedPower, float requestedPower, float initialBatteryCapacity, float expectedBatteryCapacity, float expectedSatisfaction, String parameterDescription){
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
|
||||
if(producedPower > 0.0f){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f;
|
||||
powerGraph.add(producerTile);
|
||||
}
|
||||
Tile directConsumerTile = null;
|
||||
if(requestedPower > 0.0f){
|
||||
directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requestedPower, 0.6f));
|
||||
powerGraph.add(directConsumerTile);
|
||||
}
|
||||
float maxCapacity = 100f;
|
||||
Tile batteryTile = createFakeTile(0, 2, createFakeBattery(maxCapacity, 10 ));
|
||||
batteryTile.entity.power.satisfaction = initialBatteryCapacity / maxCapacity;
|
||||
|
||||
powerGraph.add(batteryTile);
|
||||
|
||||
powerGraph.update();
|
||||
assertEquals(expectedBatteryCapacity / maxCapacity, batteryTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery satisfaction did not match");
|
||||
if(directConsumerTile != null){
|
||||
assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of direct consumer did not match");
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes sure a direct consumer stops working after power production is set to zero. */
|
||||
@Test
|
||||
void directConsumptionStopsWithNoPower(){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(10.0f));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
Tile consumerTile = createFakeTile(0, 1, createFakeDirectConsumer(5.0f, 0.6f));
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(consumerTile);
|
||||
powerGraph.update();
|
||||
|
||||
assertEquals(1.0f, consumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
powerGraph.remove(producerTile);
|
||||
powerGraph.add(consumerTile);
|
||||
powerGraph.update();
|
||||
|
||||
assertEquals(0.0f, consumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR);
|
||||
if(consumerTile.block().consumes.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumerTile.block().consumes.get(ConsumePower.class);
|
||||
assertFalse(consumePower.valid(consumerTile.block(), consumerTile.entity()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user