mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-11-10 15:05:23 +03:00
Merge pull request #7 from Timmeey86/baltitenger
Adapted ItemLiquidGenerator to new power system
This commit is contained in:
commit
630693d64e
@ -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{
|
||||
@ -19,17 +20,17 @@ public class PowerBlocks extends BlockList implements ContentList{
|
||||
|
||||
thermalGenerator = new LiquidHeatGenerator("thermal-generator"){{
|
||||
maxLiquidGenerate = 4f;
|
||||
// TODO: Adapt to new power system
|
||||
powerProduction = -1;
|
||||
powerPerLiquid = 0.1f;
|
||||
// TODO: Balance
|
||||
powerProduction = 0.17f;
|
||||
liquidPowerMultiplier = 0.1f;
|
||||
generateEffect = BlockFx.redgeneratespark;
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
turbineGenerator = new TurbineGenerator("turbine-generator"){{
|
||||
// TODO: Adapt to new power system
|
||||
// TODO: Balance
|
||||
powerProduction = 0.28f;
|
||||
powerPerLiquid = 0.1f;
|
||||
liquidPowerMultiplier = 0.3f;
|
||||
itemDuration = 30f;
|
||||
consumes.liquid(Liquids.water, 0.05f);
|
||||
size = 2;
|
||||
@ -41,14 +42,26 @@ public class PowerBlocks extends BlockList implements ContentList{
|
||||
itemDuration = 220f;
|
||||
}};
|
||||
|
||||
solarPanel = new PowerGenerator("solar-panel"){{
|
||||
powerProduction = 0.0045f;
|
||||
}};
|
||||
// TODO: Maybe reintroduce a class for the initial production efficiency
|
||||
solarPanel = new PowerGenerator("solar-panel"){
|
||||
{
|
||||
powerProduction = 0.0045f;
|
||||
}
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
tile.<GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
largeSolarPanel = new PowerGenerator("solar-panel-large"){{
|
||||
powerProduction = 0.055f;
|
||||
size = 3;
|
||||
}};
|
||||
largeSolarPanel = new PowerGenerator("solar-panel-large"){
|
||||
{
|
||||
powerProduction = 0.055f;
|
||||
}
|
||||
@Override
|
||||
public void update(Tile tile){
|
||||
tile.<GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
thoriumReactor = new NuclearReactor("thorium-reactor"){{
|
||||
size = 3;
|
||||
|
@ -337,9 +337,8 @@ public class Block extends BaseBlock {
|
||||
}
|
||||
|
||||
public void setBars(){
|
||||
if(consumes.has(ConsumePower.class)){
|
||||
if(consumes.has(ConsumePower.class))
|
||||
bars.add(new BlockBar(BarType.power, true, tile -> tile.entity.power.satisfaction));
|
||||
}
|
||||
if(hasLiquids)
|
||||
bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity));
|
||||
if(hasItems)
|
||||
|
@ -14,7 +14,7 @@ import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public abstract class ItemLiquidGenerator extends ItemGenerator{
|
||||
protected float minLiquidEfficiency = 0.2f;
|
||||
protected float powerPerLiquid = 0.13f;
|
||||
protected float liquidPowerMultiplier = 1.3f; // A liquid with 100% flammability will be 30% more efficient than an item with 100% flammability.
|
||||
/**Maximum liquid used per frame.*/
|
||||
protected float maxLiquidGenerate = 0.4f;
|
||||
|
||||
@ -45,44 +45,48 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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());
|
||||
// TODO: Adapt to new power system
|
||||
//used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid);
|
||||
if(liquid != null && entity.liquids.get(liquid) >= 0.001f){
|
||||
float baseLiquidEfficiency = getLiquidEfficiency(liquid) * this.liquidPowerMultiplier;
|
||||
float maximumPossible = maxLiquidGenerate * calculationDelta;
|
||||
float used = Math.min(entity.liquids.get(liquid) * calculationDelta, maximumPossible);
|
||||
|
||||
entity.liquids.remove(liquid, used);
|
||||
// TODO: Adapt to new power system
|
||||
//entity.power.amount += used * powerPerLiquid;
|
||||
|
||||
// Note: 1 Item with 100% Flammability = 100% efficiency. This means 100% is not max but rather a reference point for this generator.
|
||||
entity.productionEfficiency = baseLiquidEfficiency * used / maximumPossible;
|
||||
|
||||
if(used > 0.001f && Mathf.chance(0.05 * entity.delta())){
|
||||
Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f));
|
||||
}
|
||||
}else if(entity.cons.valid()){
|
||||
|
||||
// TODO: Adapt to new power system
|
||||
//float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * entity.delta()) * entity.efficiency;
|
||||
}else{
|
||||
|
||||
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();
|
||||
// TODO: Adapt to new power system
|
||||
//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();
|
||||
// TODO: Adapt to new power system
|
||||
//entity.power.amount += maxPower;
|
||||
entity.generateTime = Mathf.clamp(entity.generateTime);
|
||||
|
||||
if(Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import io.anuke.ucore.util.Mathf;
|
||||
|
||||
public abstract class LiquidGenerator extends PowerGenerator{
|
||||
protected float minEfficiency = 0.2f;
|
||||
protected float powerPerLiquid;
|
||||
protected float liquidPowerMultiplier;
|
||||
/**Maximum liquid used per frame.*/
|
||||
protected float maxLiquidGenerate;
|
||||
protected Effect generateEffect = BlockFx.generatespark;
|
||||
@ -50,8 +50,9 @@ public abstract class LiquidGenerator extends PowerGenerator{
|
||||
public void update(Tile tile){
|
||||
TileEntity entity = tile.entity();
|
||||
|
||||
// TODO Code duplication with ItemLiquidGenerator
|
||||
if(entity.liquids.get(entity.liquids.current()) >= 0.001f){
|
||||
float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid;
|
||||
//float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid;
|
||||
float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * entity.delta());
|
||||
// TODO Adapt to new power system
|
||||
//used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid);
|
||||
|
@ -14,9 +14,9 @@ public class LiquidHeatGenerator extends LiquidGenerator{
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
// TODO Verify for new power system
|
||||
stats.remove(BlockStat.basePowerGeneration);
|
||||
stats.add(BlockStat.basePowerGeneration, maxLiquidGenerate * powerPerLiquid * 60f, StatUnit.powerSecond);
|
||||
// TODO Adapt to new new power system. Maybe this override can be removed.
|
||||
//stats.add(BlockStat.basePowerGeneration, <Do something with maxLiquidGenerate, basePowerGeneration and liquidPowerMultiplier> * 60f, StatUnit.powerSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.anuke.mindustry.world.blocks.power;
|
||||
|
||||
import io.anuke.mindustry.world.BarType;
|
||||
import io.anuke.mindustry.world.meta.BlockBar;
|
||||
import io.anuke.mindustry.world.meta.StatUnit;
|
||||
import io.anuke.ucore.util.EnumSet;
|
||||
|
||||
@ -10,7 +12,12 @@ import io.anuke.mindustry.world.meta.BlockStat;
|
||||
|
||||
public class PowerGenerator extends PowerDistributor{
|
||||
/** The amount of power produced per tick. */
|
||||
public float powerProduction;
|
||||
protected float powerProduction;
|
||||
/** The maximum possible efficiency for this generator. Supply values larger than 1.0f if more than 100% is possible.
|
||||
* This could be the case when e.g. an item with 100% flammability is the reference point, but a more effective liquid
|
||||
* can be supplied as an alternative.
|
||||
*/
|
||||
protected float maxEfficiency = 1.0f;
|
||||
public BlockStat generationType = BlockStat.basePowerGeneration;
|
||||
|
||||
public PowerGenerator(String name){
|
||||
@ -40,8 +47,16 @@ public class PowerGenerator extends PowerDistributor{
|
||||
return new GeneratorEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
if(hasPower){
|
||||
bars.add(new BlockBar(BarType.power, true, tile -> tile.<GeneratorEntity>entity().productionEfficiency / maxEfficiency));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GeneratorEntity extends TileEntity{
|
||||
public float generateTime;
|
||||
public float productionEfficiency = 1;
|
||||
public float productionEfficiency = 0.0f;
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class PowerGraph{
|
||||
if(consumePower.isBuffered){
|
||||
// Add a percentage of the requested amount, but limit it to the mission amount.
|
||||
// TODO This can maybe be calculated without converting to absolute values first
|
||||
float maximumRate = consumePower.requestedPower(consumer.block(), consumer.entity()) * coverage;
|
||||
float maximumRate = consumePower.requestedPower(consumer.block(), consumer.entity()) * coverage * consumer.entity.delta();
|
||||
float missingAmount = consumePower.powerCapacity * (1 - consumer.entity.power.satisfaction);
|
||||
consumer.entity.power.satisfaction += Math.min(missingAmount, maximumRate) / consumePower.powerCapacity;
|
||||
}else{
|
||||
|
@ -86,7 +86,9 @@ public class ConsumePower extends Consume{
|
||||
* @return The amount of power which is requested per tick.
|
||||
*/
|
||||
public float requestedPower(Block block, TileEntity entity){
|
||||
// TODO Is it possible to make the block not consume power while items/liquids are missing?
|
||||
// TODO Make the block not consume power on the following conditions, either here or in PowerGraph:
|
||||
// - Other consumers are not valid, e.g. additional input items/liquids are missing
|
||||
// - Buffer is full
|
||||
return powerPerTick;
|
||||
}
|
||||
|
||||
|
@ -1,65 +0,0 @@
|
||||
import io.anuke.mindustry.content.blocks.Blocks;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
import io.anuke.mindustry.world.blocks.power.Battery;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.modules.PowerModule;
|
||||
|
||||
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.
|
||||
* */
|
||||
public class PowerTestFixture{
|
||||
|
||||
protected static PowerGenerator createFakeProducerBlock(float producedPower){
|
||||
return new PowerGenerator("fakegen"){{
|
||||
powerProduction = producedPower;
|
||||
}};
|
||||
}
|
||||
|
||||
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 Block("fakedirectconsumer"){{
|
||||
consumes.powerDirect(powerPerTick, minimumSatisfaction);
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Block createFakeBufferedConsumer(float capacity, float ticksToFill){
|
||||
return new Block("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);
|
||||
|
||||
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);
|
||||
|
||||
tile.entity = block.newEntity();
|
||||
tile.entity.power = new PowerModule();
|
||||
return tile;
|
||||
}catch(Exception ex){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
import com.badlogic.gdx.Gdx;
|
||||
package power;
|
||||
|
||||
import io.anuke.mindustry.core.ThreadHandler;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
|
||||
/** Fake thread handler which produces a new frame each time getFrameID is called and always provides a delta of 1. */
|
||||
public class FakeThreadHandler extends ThreadHandler{
|
||||
private int fakeFrameId = 0;
|
||||
public static final float fakeDelta = 0.5f;
|
||||
|
||||
FakeThreadHandler(){
|
||||
super();
|
||||
|
||||
Timers.setDeltaProvider(() -> 1.0f);
|
||||
Timers.setDeltaProvider(() -> fakeDelta);
|
||||
}
|
||||
@Override
|
||||
public long getFrameID(){
|
142
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
142
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
@ -0,0 +1,142 @@
|
||||
package power;
|
||||
|
||||
import io.anuke.mindustry.Vars;
|
||||
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.BurnerGenerator;
|
||||
import io.anuke.mindustry.world.blocks.power.ItemGenerator;
|
||||
import io.anuke.mindustry.world.blocks.power.ItemLiquidGenerator;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
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 ItemLiquidGenerators. Currently, testing is only performed on the BurnerGenerator subclass,
|
||||
* which means only power calculations based on flammability are tested.
|
||||
* 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 FakeThreadHandler.fakeDelta but satisfaction should not!
|
||||
*/
|
||||
public class ItemLiquidGeneratorTests extends PowerTestFixture{
|
||||
|
||||
private ItemLiquidGenerator generator;
|
||||
private Tile tile;
|
||||
private ItemGenerator.ItemGeneratorEntity entity;
|
||||
private final float fakeLiquidPowerMultiplier = 2.0f;
|
||||
private final float fakeItemDuration = 60f; // 60 ticks
|
||||
private final float maximumLiquidUsage = 0.5f;
|
||||
|
||||
@BeforeEach
|
||||
public void createBurnerGenerator(){
|
||||
// Use a burner generator instead of a custom ItemLiquidGenerator subclass since we would implement abstract methods the same way.
|
||||
generator = new BurnerGenerator("fakegen"){{
|
||||
powerProduction = 0.1f;
|
||||
itemDuration = 60f;
|
||||
liquidPowerMultiplier = fakeLiquidPowerMultiplier;
|
||||
itemDuration = fakeItemDuration;
|
||||
maxLiquidGenerate = maximumLiquidUsage;
|
||||
}};
|
||||
|
||||
tile = createFakeTile(0, 0, generator);
|
||||
entity = tile.entity();
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with liquids. */
|
||||
@TestFactory
|
||||
DynamicTest[] testLiquidConsumption(){
|
||||
return new DynamicTest[]{
|
||||
dynamicTest("01", () -> test_liquidConsumption(Liquids.oil, 0.0f, "No liquids provided")),
|
||||
dynamicTest("02", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage / 4.0f, "Low oil provided")),
|
||||
dynamicTest("03", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage * 1.0f, "Sufficient oil provided")),
|
||||
dynamicTest("04", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage * 2.0f, "Excess oil provided"))
|
||||
// Note: The generator will decline any other liquid since it's not flammable
|
||||
};
|
||||
}
|
||||
|
||||
void test_liquidConsumption(Liquid liquid, float availableLiquidAmount, String parameterDescription){
|
||||
final float baseEfficiency = fakeLiquidPowerMultiplier * 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 * FakeThreadHandler.fakeDelta);
|
||||
|
||||
assertTrue(generator.acceptLiquid(tile, null, liquid, availableLiquidAmount), parameterDescription + ": Liquids which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
// Reset liquids since BeforeEach will not be called between dynamic tests
|
||||
for(Liquid tmpLiquid : Vars.content.liquids()){
|
||||
entity.liquids.reset(tmpLiquid, 0.0f);
|
||||
}
|
||||
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), parameterDescription + ": Remaining liquid amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with items. */
|
||||
@TestFactory
|
||||
DynamicTest[] testItemConsumption(){
|
||||
return new DynamicTest[]{
|
||||
dynamicTest("01", () -> test_itemConsumption(Items.coal, 0, "No items provided")),
|
||||
dynamicTest("02", () -> test_itemConsumption(Items.coal, 1, "Sufficient coal provided")),
|
||||
dynamicTest("03", () -> test_itemConsumption(Items.coal, 10, "Excess coal provided")),
|
||||
dynamicTest("04", () -> test_itemConsumption(Items.blastCompound, 1, "Blast compound provided")),
|
||||
//dynamicTest("03", () -> test_itemConsumption(Items.plastanium, 1, "Plastanium provided")), // Not accepted by generator due to low flammability
|
||||
dynamicTest("05", () -> test_itemConsumption(Items.biomatter, 1, "Biomatter provided")),
|
||||
dynamicTest("06", () -> test_itemConsumption(Items.pyratite, 1, "Pyratite provided"))
|
||||
};
|
||||
}
|
||||
|
||||
void test_itemConsumption(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);
|
||||
assertTrue(generator.acceptItem(item, tile, null), parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
|
||||
// Clean up manually since BeforeEach will not be called between dynamic tests
|
||||
entity.items.clear();
|
||||
entity.generateTime = 0.0f;
|
||||
entity.productionEfficiency = 0.0f;
|
||||
|
||||
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), parameterDescription + ": Remaining item amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Makes sure the efficiency stays equal during the item duration. */
|
||||
@Test
|
||||
void test_efficiencyConstantDuringItemDuration(){
|
||||
|
||||
// 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 += FakeThreadHandler.fakeDelta) <= fakeItemDuration){
|
||||
generator.update(tile);
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
generator.update(tile);
|
||||
assertEquals(0.0f, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
}
|
104
tests/src/test/java/power/PowerTestFixture.java
Normal file
104
tests/src/test/java/power/PowerTestFixture.java
Normal file
@ -0,0 +1,104 @@
|
||||
package power;
|
||||
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.content.blocks.Blocks;
|
||||
import io.anuke.mindustry.core.ContentLoader;
|
||||
import io.anuke.mindustry.core.World;
|
||||
import io.anuke.mindustry.entities.TileEntity;
|
||||
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 io.anuke.ucore.entities.Entities;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
/** 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 = MathUtils.FLOAT_ROUNDING_ERROR;
|
||||
public static final float mediumRoundingTolerance = MathUtils.FLOAT_ROUNDING_ERROR * 10;
|
||||
public static final float highRoundingTolerance = MathUtils.FLOAT_ROUNDING_ERROR * 100;
|
||||
|
||||
@BeforeAll
|
||||
static void initializeDependencies(){
|
||||
Vars.content = new ContentLoader();
|
||||
Vars.content.load();
|
||||
Vars.threads = new FakeThreadHandler();
|
||||
}
|
||||
|
||||
protected static PowerGenerator createFakeProducerBlock(float producedPower){
|
||||
return new PowerGenerator("fakegen"){{
|
||||
powerProduction = producedPower;
|
||||
}};
|
||||
}
|
||||
|
||||
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.powerDirect(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);
|
||||
}
|
||||
return tile;
|
||||
}catch(Exception ex){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,26 @@
|
||||
package power;
|
||||
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.content.blocks.PowerBlocks;
|
||||
import io.anuke.mindustry.content.blocks.ProductionBlocks;
|
||||
import io.anuke.mindustry.core.ContentLoader;
|
||||
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 org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
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 FakeThreadHandler.fakeDelta but satisfaction should not!
|
||||
*/
|
||||
public class PowerTests extends PowerTestFixture{
|
||||
|
||||
@BeforeAll
|
||||
static void initializeDependencies(){
|
||||
Vars.content = new ContentLoader();
|
||||
Vars.content.load();
|
||||
Vars.threads = new FakeThreadHandler();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initTest(){
|
||||
}
|
||||
@ -39,6 +37,7 @@ public class PowerTests extends PowerTestFixture{
|
||||
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", () -> test_directConsumptionCalculation(0.0f, 1.0f, 0.0f, "0.0 produced, 1.0 consumed (no power available)")),
|
||||
dynamicTest("02", () -> test_directConsumptionCalculation(0.0f, 0.0f, 0.0f, "0.0 produced, 0.0 consumed (no power anywhere)")),
|
||||
dynamicTest("03", () -> test_directConsumptionCalculation(1.0f, 0.0f, 0.0f, "1.0 produced, 0.0 consumed (no power requested)")),
|
||||
@ -50,14 +49,15 @@ public class PowerTests extends PowerTestFixture{
|
||||
}
|
||||
void test_directConsumptionCalculation(float producedPower, float requiredPower, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
Tile directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requiredPower, 0.6f));
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(directConsumerTile);
|
||||
|
||||
assumeTrue(MathUtils.isEqual(producedPower, powerGraph.getPowerProduced()));
|
||||
assumeTrue(MathUtils.isEqual(requiredPower, powerGraph.getPowerNeeded()));
|
||||
assertEquals(producedPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerProduced(), MathUtils.FLOAT_ROUNDING_ERROR);
|
||||
assertEquals(requiredPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerNeeded(), MathUtils.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
@ -69,33 +69,36 @@ public class PowerTests extends PowerTestFixture{
|
||||
DynamicTest[] testBufferedConsumption(){
|
||||
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", () -> test_bufferedConsumptionCalculation(0.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power anywhere")),
|
||||
dynamicTest("02", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power provided")),
|
||||
dynamicTest("03", () -> test_bufferedConsumptionCalculation(1.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power requested")),
|
||||
dynamicTest("04", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One tick to fill")),
|
||||
dynamicTest("05", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Stable Power, multiple ticks to fill")),
|
||||
dynamicTest("06", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.5f, 0.0f, 1.0f, "Empty Buffer, Power excess, one tick to fill")),
|
||||
dynamicTest("07", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.1f, 0.0f, 0.2f, "Empty Buffer, Power excess, multiple ticks to fill")),
|
||||
dynamicTest("08", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one tick to fill")),
|
||||
dynamicTest("09", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power shortage, multiple ticks to fill")),
|
||||
dynamicTest("10", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")),
|
||||
dynamicTest("11", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")),
|
||||
dynamicTest("12", () -> test_bufferedConsumptionCalculation(0.2f, 1.0f, 0.5f, 0.5f, 0.7f, "Half buffer, power shortage")),
|
||||
dynamicTest("13", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.7f, 1.0f, "Buffer does not get exceeded")),
|
||||
dynamicTest("14", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.5f, 1.0f, "Half buffer, filled with excess"))
|
||||
dynamicTest("04", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Stable Power, One tick to fill")),
|
||||
dynamicTest("05", () -> test_bufferedConsumptionCalculation(2.0f, 1.0f, 2.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One delta to fill")),
|
||||
dynamicTest("06", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Stable Power, multiple ticks to fill")),
|
||||
dynamicTest("07", () -> test_bufferedConsumptionCalculation(1.2f, 0.5f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Power excess, one delta to fill")),
|
||||
dynamicTest("08", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power excess, multiple ticks to fill")),
|
||||
dynamicTest("09", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 2.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one delta to fill")),
|
||||
dynamicTest("10", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Power shortage, multiple ticks to fill")),
|
||||
dynamicTest("11", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")),
|
||||
dynamicTest("12", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")),
|
||||
dynamicTest("13", () -> test_bufferedConsumptionCalculation(0.2f, 1.0f, 0.5f, 0.5f, 0.6f, "Half buffer, power shortage")),
|
||||
dynamicTest("14", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.9f, 1.0f, "Buffer does not get exceeded")),
|
||||
dynamicTest("15", () -> test_bufferedConsumptionCalculation(2.0f, 1.0f, 1.0f, 0.5f, 1.0f, "Half buffer, filled with excess"))
|
||||
};
|
||||
}
|
||||
void test_bufferedConsumptionCalculation(float producedPower, float maxBuffer, float powerPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){
|
||||
void test_bufferedConsumptionCalculation(float producedPower, float maxBuffer, float powerConsumedPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
Tile bufferedConsumerTile = createFakeTile(0, 1, createFakeBufferedConsumer(maxBuffer, maxBuffer > 0.0f ? maxBuffer/powerPerTick : 1.0f));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
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);
|
||||
|
||||
assumeTrue(MathUtils.isEqual(producedPower, powerGraph.getPowerProduced()));
|
||||
//assumeTrue(MathUtils.isEqual(Math.min(maxBuffer, powerPerTick), powerGraph.getPowerNeeded()));
|
||||
assertEquals(producedPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerProduced(), MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Produced power did not match");
|
||||
assertEquals(Math.min(maxBuffer, powerConsumedPerTick * FakeThreadHandler.fakeDelta), powerGraph.getPowerNeeded(), MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": ConsumedPower did not match");
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
@ -108,14 +111,15 @@ public class PowerTests extends PowerTestFixture{
|
||||
@TestFactory
|
||||
DynamicTest[] testDirectConsumptionWithBattery(){
|
||||
return new DynamicTest[]{
|
||||
dynamicTest("01", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 0.0f, 10.0f, 0.0f, "Empty battery, no consumer")),
|
||||
dynamicTest("02", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 90.0f, 100.0f, 0.0f, "Battery full after update, no consumer")),
|
||||
// Note: expectedBatteryCapacity is currently adjusted to a delta of 0.5! (FakeThreadHandler sets it to that)
|
||||
dynamicTest("01", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 0.0f, 5.0f, 0.0f, "Empty battery, no consumer")),
|
||||
dynamicTest("02", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 94.999f, 99.999f, 0.0f, "Battery almost full after update, no consumer")),
|
||||
dynamicTest("03", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 100.0f, 100.0f, 0.0f, "Full battery, no consumer")),
|
||||
dynamicTest("04", () -> test_directConsumptionWithBattery(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "No producer, no consumer, empty battery")),
|
||||
dynamicTest("05", () -> test_directConsumptionWithBattery(0.0f, 0.0f, 100.0f, 100.0f, 0.0f, "No producer, no consumer, full battery")),
|
||||
dynamicTest("06", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 0.0f, 0.0f, 0.0f, "No producer, empty battery")),
|
||||
dynamicTest("07", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 100.0f, 90.0f, 1.0f, "No producer, full battery")),
|
||||
dynamicTest("08", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 5.0f, 0.0f, 0.5f, "No producer, low battery")),
|
||||
dynamicTest("07", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 100.0f, 95.0f, 1.0f, "No producer, full battery")),
|
||||
dynamicTest("08", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 2.5f, 0.0f, 0.5f, "No producer, low battery")),
|
||||
dynamicTest("09", () -> test_directConsumptionWithBattery(5.0f, 10.0f, 5.0f, 0.0f, 1.0f, "Producer + Battery = Consumed")),
|
||||
};
|
||||
}
|
||||
@ -124,6 +128,7 @@ public class PowerTests extends PowerTestFixture{
|
||||
|
||||
if(producedPower > 0.0f){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
powerGraph.add(producerTile);
|
||||
}
|
||||
Tile directConsumerTile = null;
|
||||
@ -138,10 +143,35 @@ public class PowerTests extends PowerTestFixture{
|
||||
powerGraph.add(batteryTile);
|
||||
|
||||
powerGraph.update();
|
||||
assertEquals(expectedBatteryCapacity, batteryTile.entity.power.satisfaction * maxCapacity, MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery capacity did not match");
|
||||
assertEquals(expectedBatteryCapacity / maxCapacity, batteryTile.entity.power.satisfaction, MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery satisfaction did not match");
|
||||
if(directConsumerTile != null){
|
||||
assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, MathUtils.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 testDirectConsumptionStopsWithNoPower(){
|
||||
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, MathUtils.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
powerGraph.remove(producerTile);
|
||||
powerGraph.add(consumerTile);
|
||||
powerGraph.update();
|
||||
|
||||
assertEquals(0.0f, consumerTile.entity.power.satisfaction, MathUtils.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