Skip to content

Commit

Permalink
Implement custom smooshing reactions.
Browse files Browse the repository at this point in the history
Implement reactions between liquids or between a liquid and a solid,
such as lava and water producing stone, or certain liquids being
able to destroy certain blocks.
  • Loading branch information
4Denthusiast committed Aug 25, 2020
1 parent 0c5fd01 commit fec0da8
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 17 deletions.
9 changes: 9 additions & 0 deletions assets/prefabs/reactions/lavaWaterReaction.prefab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"LiquidSmooshingReaction" : {
"liquid" : "CoreAssets:Water",
"block" : "CoreAssets:Lava",
"product" : "CoreAssets:Stone",
"liquidRequired" : 0.5,
"reversible" : true
}
}
6 changes: 6 additions & 0 deletions assets/prefabs/reactions/testReaction.prefab
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"LiquidSmooshingReaction" : {
"liquid" : "FlowingLiquids:DebugLiquid",
"block" : "CoreAssets:Sand"
}
}
6 changes: 4 additions & 2 deletions module.txt
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -79,6 +76,8 @@ public class LiquidFlowSystem extends BaseComponentSystem implements UpdateSubsc

@In
private BlockEntityRegistry blockEntityRegistry;

private Map<Block, Map<BlockFamily, LiquidSmooshingReactionComponent>> smooshingReactions;

private Set<Vector3i> evenUpdatePositions;
private Set<Vector3i> oddUpdatePositions;
Expand All @@ -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.
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit fec0da8

Please sign in to comment.