diff --git a/assets/prefabs/reactions/lavaWaterReaction.prefab b/assets/prefabs/reactions/lavaWaterReaction.prefab new file mode 100644 index 0000000..8a914a4 --- /dev/null +++ b/assets/prefabs/reactions/lavaWaterReaction.prefab @@ -0,0 +1,9 @@ +{ + "LiquidSmooshingReaction" : { + "liquid" : "CoreAssets:Water", + "block" : "CoreAssets:Lava", + "product" : "CoreAssets:Stone", + "liquidRequired" : 0.5, + "reversible" : true + } +} \ No newline at end of file diff --git a/assets/prefabs/reactions/testReaction.prefab b/assets/prefabs/reactions/testReaction.prefab new file mode 100644 index 0000000..a96dcca --- /dev/null +++ b/assets/prefabs/reactions/testReaction.prefab @@ -0,0 +1,6 @@ +{ + "LiquidSmooshingReaction" : { + "liquid" : "FlowingLiquids:DebugLiquid", + "block" : "CoreAssets:Sand" + } +} diff --git a/module.txt b/module.txt index 63f0319..e3c4f4e 100644 --- a/module.txt +++ b/module.txt @@ -1,10 +1,12 @@ { "id": "FlowingLiquids", - "version": "1.2.1", + "version": "1.3.0", "author": "4Denthusiast", "displayName": "FlowingLiquids", "description": "Lets water flow, conserving its volume.", - "dependencies": [], + "dependencies": [ + { "id": "CoreAssets", "minVersion": "2.0.0", "optional" : true } + ], "serverSideOnly": false, "isLibrary": true } diff --git a/src/main/java/org/terasology/flowingliquids/world/block/LiquidFlowSystem.java b/src/main/java/org/terasology/flowingliquids/world/block/LiquidFlowSystem.java index ba1e129..9a63aa8 100644 --- a/src/main/java/org/terasology/flowingliquids/world/block/LiquidFlowSystem.java +++ b/src/main/java/org/terasology/flowingliquids/world/block/LiquidFlowSystem.java @@ -16,11 +16,7 @@ package org.terasology.flowingliquids.world.block; -import java.util.Iterator; -import java.util.Set; -import java.util.LinkedHashSet; -import java.util.Collections; -import java.util.Random; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +39,7 @@ import org.terasology.world.WorldProvider; import org.terasology.world.block.Block; import org.terasology.world.block.BlockManager; +import org.terasology.world.block.family.BlockFamily; import org.terasology.world.block.items.OnBlockItemPlaced; import org.terasology.world.block.items.BlockItemComponent; import org.terasology.world.chunks.ChunkConstants; @@ -79,6 +76,8 @@ public class LiquidFlowSystem extends BaseComponentSystem implements UpdateSubsc @In private BlockEntityRegistry blockEntityRegistry; + + private Map> smooshingReactions; private Set evenUpdatePositions; private Set oddUpdatePositions; @@ -102,8 +101,35 @@ public void initialise() { air = blockManager.getBlock(BlockManager.AIR_ID); flowIx = extraDataManager.getSlotNumber(LiquidData.EXTRA_DATA_NAME); smooshingDamageType = prefabManager.getPrefab("flowingLiquids:smooshingDamage"); + smooshingReactions = new HashMap<>(); + for (Prefab prefab : prefabManager.listPrefabs(LiquidSmooshingReactionComponent.class)) { + LiquidSmooshingReactionComponent reaction = prefab.getComponent(LiquidSmooshingReactionComponent.class); + if (blockManager.getBlock(reaction.liquid) == air || blockManager.getBlock(reaction.block) == air) { + // Possibly the module containing the required blocks isn't actually loaded, so the reaction can be safely ignored. + continue; + } + addReaction(reaction); + if (reaction.reversible) { + LiquidSmooshingReactionComponent reversed = new LiquidSmooshingReactionComponent(); + reversed.liquid = reaction.block; + reversed.block = reaction.liquid; + reversed.product = reaction.product; + reversed.liquidRequired = reaction.otherLiquidRequired; + reversed.otherLiquidRequired = reaction.liquidRequired; + addReaction(reversed); + } + } rand = new Random(); } + + private void addReaction(LiquidSmooshingReactionComponent reaction) { + Block liquid = blockManager.getBlock(reaction.liquid); + BlockFamily block = blockManager.getBlockFamily(reaction.block); + if (!smooshingReactions.containsKey(liquid)) { + smooshingReactions.put(liquid, new HashMap<>()); + } + smooshingReactions.get(liquid).put(block, reaction); + } /** * Called every time a block is changed. @@ -213,14 +239,43 @@ public void update(float delta) { height += rate; } } else if (canSmoosh(adjBlock, blockType)) { - if (blockType != air) { - blockEntityRegistry.getBlockEntityAt(pos).send(new DestroyEvent(EntityRef.NULL, EntityRef.NULL, smooshingDamageType)); - } - if (worldProvider.getBlock(pos) == air) { // Check the event didn't get cancelled or something. - blockType = adjBlock; - worldProvider.setBlock(pos, adjBlock); - height = LiquidData.getRate(adjStatus); - smooshed = true; + LiquidSmooshingReactionComponent reaction = getSmooshingReaction(adjBlock, blockType); + if (reaction == null || reaction.product == null) { + if (blockType != air) { + blockEntityRegistry.getBlockEntityAt(pos).send(new DestroyEvent(EntityRef.NULL, EntityRef.NULL, smooshingDamageType)); + } + if (worldProvider.getBlock(pos) == air) { // Check the event didn't get cancelled or something. + blockType = adjBlock; + worldProvider.setBlock(pos, adjBlock); + height = LiquidData.getRate(adjStatus); + smooshed = true; + } + } else { + float otherSufficiency = LiquidData.getRate(adjStatus) / (float) LiquidData.MAX_HEIGHT / reaction.liquidRequired; + float thisSufficiency = blockType.isLiquid() ? height / (float) LiquidData.MAX_HEIGHT / reaction.otherLiquidRequired : 1f; + // There's a much more efficient way of doing this without the loop, but this way is clearer. + boolean thisSufficient = false; + boolean otherSufficient = false; + while (!thisSufficient && !otherSufficient) { + thisSufficient = rand.nextFloat() < thisSufficiency; + otherSufficient = rand.nextFloat() < otherSufficiency; + } + + if (thisSufficient && otherSufficient) { + blockType = blockManager.getBlock(reaction.product); + if (otherSufficiency > 1 && rand.nextFloat() < 1/otherSufficiency) { + worldProvider.setExtraData(flowIx, adjPos, LiquidData.setRate(adjStatus, 0)); + } + worldProvider.setBlock(pos, blockType); + if (blockType.isLiquid()) { + height = LiquidData.MAX_HEIGHT; + } + } else if (otherSufficient) { + // consume this block but not the liquid flowing in. + worldProvider.setExtraData(flowIx, adjPos, LiquidData.setRate(adjStatus, 0)); + blockType = air; + worldProvider.setBlock(pos, air); + } // In the other case, thisSufficient && !otherSufficient, consume the liquid flowing in but not this block. } } else { worldProvider.setExtraData(flowIx, adjPos, LiquidData.setRate(adjStatus, 0)); @@ -375,9 +430,22 @@ private void randomUpdate() { * @return True if it can, false otherwise */ private boolean canSmoosh(Block liquid, Block replacing) { - return replacing == air || (replacing.isPenetrable() && !replacing.isLiquid()); + if (replacing == air || (replacing.isPenetrable() && !replacing.isLiquid())) { + return true; + } else if (!smooshingReactions.containsKey(liquid)) { + return false; + } else { + return smooshingReactions.get(liquid).containsKey(replacing.getBlockFamily()); + } } - + + /** + * Get the details of the interaction when these blocks meet, or null for the default reaction. + */ + private LiquidSmooshingReactionComponent getSmooshingReaction(Block liquid, Block replacing) { + return smooshingReactions.containsKey(liquid) ? smooshingReactions.get(liquid).get(replacing.getBlockFamily()) : null; + } + /** * Add a position to be checked. * diff --git a/src/main/java/org/terasology/flowingliquids/world/block/LiquidSmooshingReactionComponent.java b/src/main/java/org/terasology/flowingliquids/world/block/LiquidSmooshingReactionComponent.java new file mode 100644 index 0000000..8c8712d --- /dev/null +++ b/src/main/java/org/terasology/flowingliquids/world/block/LiquidSmooshingReactionComponent.java @@ -0,0 +1,29 @@ +// Copyright 2020 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.flowingliquids.world.block; + +import org.terasology.entitySystem.Component; + +/** + * Specifies that a liquid is able to flow into and destroy a particular block, + * and optionally create a different block in the process. + */ +public class LiquidSmooshingReactionComponent implements Component { + public String liquid; + + /** The block that gets smooshed */ + public String block; + + /** The block produced by the reaction, or null if the block is simply destroyed */ + public String product; + + /** The amount of liquid required (on average) to produce the product block, in units of whole blocks. */ + public float liquidRequired = 1; + + /** If the block that gets smooshed is a liquid, how much of that second liquid is required (on average) to produce the product block. */ + public float otherLiquidRequired = 1; + + /** If the block that is smooshed is also a liquid, whether the reverse reaction (`block` flowing into `liquid`) is also possible. */ + public boolean reversible; +}