Added support for ceiling growing plants.

VINES, WEEPING_VINES_PLANT, SPORE_BLOSSOM, CAVE_VINES_PLANT,
TWISTING_VINES_PLANT

https://github.com/BentoBoxWorld/Greenhouses/issues/81
This commit is contained in:
tastybento 2021-09-18 10:12:47 -07:00
parent 308cb7f16a
commit 4708a70e24
4 changed files with 139 additions and 67 deletions

View File

@ -1,5 +1,6 @@
package world.bentobox.greenhouses.greenhouse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
@ -37,6 +38,7 @@ import com.google.common.collect.Multimap;
import world.bentobox.bentobox.util.Util;
import world.bentobox.greenhouses.Greenhouses;
import world.bentobox.greenhouses.data.Greenhouse;
import world.bentobox.greenhouses.managers.EcoSystemManager.GrowthBlock;
import world.bentobox.greenhouses.managers.GreenhouseManager.GreenhouseResult;
import world.bentobox.greenhouses.world.AsyncWorldCache;
@ -49,6 +51,15 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
private String name;
private String friendlyName;
private static final List<Material> CEILING_PLANTS = new ArrayList<>();
static {
CEILING_PLANTS.add(Material.VINE);
Enums.getIfPresent(Material.class, "SPORE_BLOSSOM").toJavaUtil().ifPresent(CEILING_PLANTS::add);
Enums.getIfPresent(Material.class, "CAVE_VINES_PLANT").toJavaUtil().ifPresent(CEILING_PLANTS::add);
Enums.getIfPresent(Material.class, "TWISTING_VINES_PLANT").toJavaUtil().ifPresent(CEILING_PLANTS::add);
Enums.getIfPresent(Material.class, "WEEPING_VINES_PLANT").toJavaUtil().ifPresent(CEILING_PLANTS::add);
}
private static final List<BlockFace> ADJ_BLOCKS = Arrays.asList( BlockFace.DOWN, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP, BlockFace.WEST);
// Content requirements
@ -63,7 +74,6 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
// Conversions
// Original Material, Original Type, New Material, New Type, Probability
//private final Map<Material, GreenhouseBlockConversions> conversionBlocks = new EnumMap<>(Material.class);
private final Multimap<Material, GreenhouseBlockConversions> conversionBlocks = ArrayListMultimap.create();
private int mobLimit;
@ -427,28 +437,18 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
/**
* Plants a plant on block bl if it makes sense.
* @param bl - block
* @param bl - block that can have growth
* @return true if successful
*/
public boolean growPlant(Block bl) {
public boolean growPlant(GrowthBlock block) {
Block bl = block.block();
if (!bl.isEmpty()) {
return false;
}
return getRandomPlant().map(p -> {
if (bl.getY() != 0 && Optional.of(p.plantGrownOn()).map(m -> m.equals(bl.getRelative(BlockFace.DOWN).getType())).orElse(false)) {
BlockData dataBottom = p.plantMaterial().createBlockData();
if (dataBottom instanceof Bisected) {
((Bisected) dataBottom).setHalf(Bisected.Half.BOTTOM);
BlockData dataTop = p.plantMaterial().createBlockData();
((Bisected) dataTop).setHalf(Bisected.Half.TOP);
if (bl.getRelative(BlockFace.UP).getType().equals(Material.AIR)) {
bl.setBlockData(dataBottom, false);
bl.getRelative(BlockFace.UP).setBlockData(dataTop, false);
} else {
return false; // No room
}
} else {
bl.setBlockData(dataBottom, false);
if (bl.getY() != 0 && canGrowOn(block, p)) {
if (!isBisected(bl, p)) {
return false;
}
bl.getWorld().spawnParticle(Particle.SNOWBALL, bl.getLocation(), 10, 2, 2, 2);
return true;
@ -457,6 +457,32 @@ public class BiomeRecipe implements Comparable<BiomeRecipe> {
}).orElse(false);
}
private boolean isBisected(Block bl, GreenhousePlant p) {
BlockData dataBottom = p.plantMaterial().createBlockData();
if (dataBottom instanceof Bisected bi) {
bi.setHalf(Bisected.Half.BOTTOM);
BlockData dataTop = p.plantMaterial().createBlockData();
((Bisected) dataTop).setHalf(Bisected.Half.TOP);
if (bl.getRelative(BlockFace.UP).getType().equals(Material.AIR)) {
bl.setBlockData(dataBottom, false);
bl.getRelative(BlockFace.UP).setBlockData(dataTop, false);
} else {
return false; // No room
}
} else {
bl.setBlockData(dataBottom, false);
}
return true;
}
private boolean canGrowOn(GrowthBlock block, GreenhousePlant p) {
// Ceiling plants can only grow on ceiling blocks
if (CEILING_PLANTS.contains(p.plantMaterial()) && block.floor()) {
return false;
}
return p.plantGrownOn().equals(block.block().getRelative(block.floor() ? BlockFace.DOWN : BlockFace.UP).getType());
}
/**
* @param friendlyName - set the friendly name
*/

View File

@ -153,13 +153,13 @@ public class EcoSystemManager {
.filter(e -> gh.getBiomeRecipe().getMobTypes().contains(e.getType()))
.filter(e -> gh.contains(e.getLocation())).count();
// Get the blocks in the greenhouse where spawning could occur
List<Block> list = new ArrayList<>(getAvailableBlocks(gh, false));
List<GrowthBlock> list = new ArrayList<>(getAvailableBlocks(gh, false));
Collections.shuffle(list, new Random(System.currentTimeMillis()));
Iterator<Block> it = list.iterator();
Iterator<GrowthBlock> it = list.iterator();
// Check if the greenhouse is full
while (it.hasNext() && (sum == 0 || gh.getArea() / sum >= gh.getBiomeRecipe().getMobLimit())) {
// Spawn something if chance says so
if (gh.getBiomeRecipe().spawnMob(it.next())) {
if (gh.getBiomeRecipe().spawnMob(it.next().block())) {
// Add a mob to the sum in the greenhouse
sum++;
}
@ -179,7 +179,7 @@ public class EcoSystemManager {
int bonemeal = getBoneMeal(gh);
if (bonemeal > 0) {
// Get a list of all available blocks
List<Block> list = getAvailableBlocks(gh, true);
List<GrowthBlock> list = getAvailableBlocks(gh, true);
Collections.shuffle(list);
int plantsGrown = list.stream().limit(bonemeal).mapToInt(bl -> gh.getBiomeRecipe().growPlant(bl) ? 1 : 0).sum();
if (plantsGrown > 0) {
@ -204,6 +204,7 @@ public class EcoSystemManager {
}
/**
* Get a list of the lowest level blocks inside the greenhouse. May be air, liquid or plants.
* These blocks sit just above solid blocks
@ -211,18 +212,24 @@ public class EcoSystemManager {
* @param ignoreLiquid - true if liquid blocks should be treated like air blocks
* @return List of blocks
*/
public List<Block> getAvailableBlocks(Greenhouse gh, boolean ignoreLiquid) {
List<Block> result = new ArrayList<>();
protected List<GrowthBlock> getAvailableBlocks(Greenhouse gh, boolean ignoreLiquid) {
List<GrowthBlock> result = new ArrayList<>();
if (gh.getWorld() == null) return result;
for (double x = gh.getInternalBoundingBox().getMinX(); x < gh.getInternalBoundingBox().getMaxX(); x++) {
for (double z = gh.getInternalBoundingBox().getMinZ(); z < gh.getInternalBoundingBox().getMaxZ(); z++) {
for (double y = gh.getInternalBoundingBox().getMaxY() - 1; y >= gh.getBoundingBox().getMinY(); y--) {
Block b = gh.getWorld().getBlockAt(NumberConversions.floor(x), NumberConversions.floor(y), NumberConversions.floor(z));
// Check ceiling blocks
if (b.isEmpty() && !b.getRelative(BlockFace.UP).isEmpty()) {
result.add(new GrowthBlock(b, false));
}
// Check floor blocks
if (!(b.isEmpty() || (ignoreLiquid && b.isLiquid()))
&& (b.getRelative(BlockFace.UP).isEmpty()
|| (b.getRelative(BlockFace.UP).isPassable() && !b.isLiquid())
|| (ignoreLiquid && b.isLiquid() && b.getRelative(BlockFace.UP).isPassable()))) {
result.add(b.getRelative(BlockFace.UP));
result.add(new GrowthBlock(b.getRelative(BlockFace.UP), true));
break;
}
}
@ -231,6 +238,8 @@ public class EcoSystemManager {
return result;
}
public record GrowthBlock(Block block, Boolean floor) {}
private int getBoneMeal(Greenhouse gh) {
Hopper hopper = getHopper(gh);
if (hopper == null || !hopper.getInventory().contains(Material.BONE_MEAL)) {

View File

@ -46,6 +46,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.greenhouses.Greenhouses;
import world.bentobox.greenhouses.Settings;
import world.bentobox.greenhouses.data.Greenhouse;
import world.bentobox.greenhouses.managers.EcoSystemManager.GrowthBlock;
import world.bentobox.greenhouses.managers.GreenhouseManager;
import world.bentobox.greenhouses.managers.GreenhouseMap;
@ -84,7 +85,7 @@ public class BiomeRecipeTest {
@Mock
private Settings settings;
@Before
@Before
public void setUp() {
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.createBlockData(any(Material.class))).thenReturn(bd);
@ -158,9 +159,9 @@ public class BiomeRecipeTest {
public void testAddMobs() {
EntityType mobType = EntityType.CAT;
int mobProbability = 50;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
br.addMobs(mobType, mobProbability, mobSpawnOn);
verify(addon).log(eq(" 50.0% chance for Cat to spawn on Grass Path."));
verify(addon).log(eq(" 50.0% chance for Cat to spawn on Grass Block."));
}
/**
@ -170,7 +171,7 @@ public class BiomeRecipeTest {
public void testAddMobsOver100Percent() {
EntityType mobType = EntityType.CAT;
int mobProbability = 50;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
br.addMobs(mobType, mobProbability, mobSpawnOn);
br.addMobs(mobType, mobProbability, mobSpawnOn);
br.addMobs(mobType, mobProbability, mobSpawnOn);
@ -184,7 +185,7 @@ public class BiomeRecipeTest {
public void testAddMobsOver100PercentDouble() {
EntityType mobType = EntityType.CAT;
double mobProbability = 50.5;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
br.addMobs(mobType, mobProbability, mobSpawnOn);
br.addMobs(mobType, mobProbability, mobSpawnOn);
verify(addon).logError(eq("Mob chances add up to > 100% in BADLANDS biome recipe! Skipping CAT"));
@ -432,12 +433,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMobOutsideWall() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.CAT;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Entity cat = mock(Cat.class);
// Same box as greenhouse
@ -458,12 +459,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMob() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.CAT;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Entity cat = mock(Cat.class);
// Exactly 1 block smaller than the greenhouse blocks
@ -484,12 +485,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMobHoglin() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.HOGLIN;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Hoglin hoglin = mock(Hoglin.class);
// Exactly 1 block smaller than the greenhouse blocks
@ -512,12 +513,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMobPiglin() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.PIGLIN;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Piglin piglin = mock(Piglin.class);
// Exactly 1 block smaller than the greenhouse blocks
@ -542,12 +543,12 @@ public class BiomeRecipeTest {
when(world.getEnvironment()).thenReturn(Environment.NETHER);
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.PIGLIN;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Piglin piglin = mock(Piglin.class);
// Exactly 1 block smaller than the greenhouse blocks
@ -570,12 +571,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMobWrongSurface() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getType()).thenReturn(Material.STONE);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.CAT;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
Entity cat = mock(Cat.class);
when(world.spawnEntity(any(), any())).thenReturn(cat);
@ -592,12 +593,12 @@ public class BiomeRecipeTest {
@Test
public void testSpawnMobFailToSpawn() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.GRASS_PATH);
when(block.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(any())).thenReturn(block);
EntityType mobType = EntityType.CAT;
int mobProbability = 100;
Material mobSpawnOn = Material.GRASS_PATH;
Material mobSpawnOn = Material.GRASS_BLOCK;
br.addMobs(mobType, mobProbability, mobSpawnOn);
assertFalse(br.spawnMob(block));
@ -627,7 +628,7 @@ public class BiomeRecipeTest {
@Test
public void testGrowPlantNotAir() {
when(block.getType()).thenReturn(Material.SOUL_SAND);
assertFalse(br.growPlant(block));
assertFalse(br.growPlant(new GrowthBlock(block, true)));
}
/**
@ -637,7 +638,7 @@ public class BiomeRecipeTest {
public void testGrowPlantNoPlants() {
when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true);
assertFalse(br.growPlant(block));
assertFalse(br.growPlant(new GrowthBlock(block, true)));
}
/**
@ -649,7 +650,7 @@ public class BiomeRecipeTest {
when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true);
assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK));
assertFalse(br.growPlant(block));
assertFalse(br.growPlant(new GrowthBlock(block, true)));
}
/**
@ -665,11 +666,46 @@ public class BiomeRecipeTest {
when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.BAMBOO_SAPLING, 100, Material.GRASS_BLOCK));
assertTrue(br.growPlant(block));
assertTrue(br.growPlant(new GrowthBlock(block, true)));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(block).setBlockData(eq(bd), eq(false));
}
/**
* Test method for {@link world.bentobox.greenhouses.greenhouse.BiomeRecipe#growPlant(org.bukkit.block.Block)}.
*/
@Test
public void testGrowPlantCeilingPlants() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true);
Block ob = mock(Block.class);
when(ob.getType()).thenReturn(Material.GLASS);
when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS));
assertTrue(br.growPlant(new GrowthBlock(block, false)));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(block).setBlockData(eq(bd), eq(false));
}
/**
* Test method for {@link world.bentobox.greenhouses.greenhouse.BiomeRecipe#growPlant(org.bukkit.block.Block)}.
*/
@Test
public void testGrowPlantCeilingPlantsFail() {
when(block.getY()).thenReturn(10);
when(block.getType()).thenReturn(Material.AIR);
when(block.isEmpty()).thenReturn(true);
Block ob = mock(Block.class);
when(ob.getType()).thenReturn(Material.GLASS);
when(block.getRelative(any())).thenReturn(ob);
assertTrue(br.addPlants(Material.SPORE_BLOSSOM, 100, Material.GLASS));
// Not a ceiling block
assertFalse(br.growPlant(new GrowthBlock(block, true)));
}
/**
* Test method for {@link world.bentobox.greenhouses.greenhouse.BiomeRecipe#growPlant(org.bukkit.block.Block)}.
*/
@ -682,10 +718,10 @@ public class BiomeRecipeTest {
when(block.isEmpty()).thenReturn(true);
Block ob = mock(Block.class);
when(ob.getType()).thenReturn(Material.GRASS_BLOCK);
when(block.getRelative(eq(BlockFace.DOWN))).thenReturn(ob);
when(block.getRelative(eq(BlockFace.UP))).thenReturn(block);
when(block.getRelative(BlockFace.DOWN)).thenReturn(ob);
when(block.getRelative(BlockFace.UP)).thenReturn(block);
assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK));
assertTrue(br.growPlant(block));
assertTrue(br.growPlant(new GrowthBlock(block, true)));
verify(world).spawnParticle(eq(Particle.SNOWBALL), any(Location.class), anyInt(), anyDouble(), anyDouble(), anyDouble());
verify(bisected).setHalf(eq(Half.BOTTOM));
verify(bisected).setHalf(eq(Half.TOP));
@ -706,7 +742,7 @@ public class BiomeRecipeTest {
when(block.getRelative(eq(BlockFace.DOWN))).thenReturn(ob);
when(block.getRelative(eq(BlockFace.UP))).thenReturn(ob);
assertTrue(br.addPlants(Material.SUNFLOWER, 100, Material.GRASS_BLOCK));
assertFalse(br.growPlant(block));
assertFalse(br.growPlant(new GrowthBlock(block, true)));
}
/**

View File

@ -23,6 +23,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.greenhouses.data.Greenhouse;
import world.bentobox.greenhouses.managers.EcoSystemManager.GrowthBlock;
/**
* @author tastybento
@ -88,9 +89,9 @@ public class EcoSystemManagerTest {
*/
@Test
public void testGetAvailableBlocksAirAboveBlock() {
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(16, result.size());
assertEquals(air, result.get(0));
assertEquals(air, result.get(0).block());
}
/**
@ -99,9 +100,9 @@ public class EcoSystemManagerTest {
@Test
public void testGetAvailableBlocksPlantAboveBlock() {
when(block.getRelative(eq(BlockFace.UP))).thenReturn(plant);
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(16, result.size());
assertEquals(plant, result.get(0));
assertEquals(plant, result.get(0).block());
}
/**
@ -110,7 +111,7 @@ public class EcoSystemManagerTest {
@Test
public void testGetAvailableBlocksAllAir() {
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(air);
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(0, result.size());
}
@ -121,7 +122,7 @@ public class EcoSystemManagerTest {
public void testGetAvailableBlocksAllLiquid() {
when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(liquid);
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(0, result.size());
}
@ -132,9 +133,9 @@ public class EcoSystemManagerTest {
public void testGetAvailableBlocksAllPlant() {
when(plant.getRelative(eq(BlockFace.UP))).thenReturn(plant);
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(plant);
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(16, result.size());
assertEquals(plant, result.get(0));
assertEquals(plant, result.get(0).block());
}
/**
@ -143,9 +144,9 @@ public class EcoSystemManagerTest {
@Test
public void testGetAvailableBlocksLiquidAboveBlockIgnoreLiquids() {
when(block.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
List<Block> result = eco.getAvailableBlocks(gh, true);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, true);
assertEquals(16, result.size());
assertEquals(liquid, result.get(0));
assertEquals(liquid, result.get(0).block());
}
/**
@ -159,10 +160,10 @@ public class EcoSystemManagerTest {
when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(air);
when(block.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
List<Block> result = eco.getAvailableBlocks(gh, false);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, false);
assertEquals(16, result.size());
for (Block value : result) {
assertEquals(air, value);
for (GrowthBlock value : result) {
assertEquals(air, value.block());
}
}
@ -177,10 +178,10 @@ public class EcoSystemManagerTest {
when(liquid.getRelative(eq(BlockFace.UP))).thenReturn(air);
when(block.getRelative(eq(BlockFace.UP))).thenReturn(liquid);
List<Block> result = eco.getAvailableBlocks(gh, true);
List<GrowthBlock> result = eco.getAvailableBlocks(gh, true);
assertEquals(16, result.size());
for (Block value : result) {
assertEquals(liquid, value);
for (GrowthBlock value : result) {
assertEquals(liquid, value.block());
}
}
}