Initial Work on Rituals

Added the framework for Rituals, including the automatic registration of rituals using the annotation.
This includes:
- The Master Ritual Stone
- The regular Ritual Stones (all 7 types)
- The Ritual Registration system
- The activation crystal items.
- Reintroduction of the Demon Will Aura (changed saved Dimension ID from Integer to ResourceLocation)

Localization needs to be completed, as well as the implementation of all the rituals.
This commit is contained in:
WayofTime 2020-10-24 14:50:25 -04:00
parent 0a9717f1ed
commit 1f0dcb608a
61 changed files with 3943 additions and 26 deletions

View file

@ -0,0 +1,654 @@
package wayoftime.bloodmagic.ritual;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.gen.feature.template.PlacementSettings;
import net.minecraft.world.gen.feature.template.Template;
import wayoftime.bloodmagic.util.Constants;
public abstract class AreaDescriptor implements Iterator<BlockPos>
{
public List<BlockPos> getContainedPositions(BlockPos pos)
{
return new ArrayList<>();
}
public AxisAlignedBB getAABB(BlockPos pos)
{
return null;
}
public abstract void resetCache();
public abstract boolean isWithinArea(BlockPos pos);
public abstract void resetIterator();
public void readFromNBT(CompoundNBT tag)
{
}
public void writeToNBT(CompoundNBT tag)
{
}
public abstract AreaDescriptor copy();
public abstract int getVolumeForOffsets(BlockPos offset1, BlockPos offset2);
public abstract boolean isWithinRange(BlockPos offset1, BlockPos offset2, int verticalLimit, int horizontalLimit);
public abstract int getVolume();
public abstract int getHeight();
public abstract boolean isWithinRange(int verticalLimit, int horizontalLimit);
/**
* This method changes the area descriptor so that its range matches the two
* blocks that are selected. When implementing this method, assume that these
* positions are the blocks that are clicked by the player.
*
* @param pos1
* @param pos2
*/
public abstract void modifyAreaByBlockPositions(BlockPos pos1, BlockPos pos2);
public abstract boolean intersects(AreaDescriptor descriptor);
public abstract AreaDescriptor offset(BlockPos offset);
public abstract AreaDescriptor rotateDescriptor(PlacementSettings settings);
public static class Rectangle extends AreaDescriptor
{
protected BlockPos minimumOffset;
protected BlockPos maximumOffset; // Non-inclusive maximum offset.
private BlockPos currentPosition;
private ArrayList<BlockPos> blockPosCache;
private BlockPos cachedPosition;
private boolean cache = true;
/**
* This constructor takes in the minimum and maximum BlockPos. The maximum
* offset is non-inclusive, meaning if you pass in (0,0,0) and (1,1,1), calling
* getContainedPositions() will only give (0,0,0).
*
* @param minimumOffset -
* @param maximumOffset -
*/
public Rectangle(BlockPos minimumOffset, BlockPos maximumOffset)
{
setOffsets(minimumOffset, maximumOffset);
}
public Rectangle(BlockPos minimumOffset, int sizeX, int sizeY, int sizeZ)
{
this(minimumOffset, minimumOffset.add(sizeX, sizeY, sizeZ));
}
public Rectangle(BlockPos minimumOffset, int size)
{
this(minimumOffset, size, size, size);
}
public Rectangle(AreaDescriptor.Rectangle rectangle)
{
this(rectangle.minimumOffset, rectangle.maximumOffset);
}
public AreaDescriptor.Rectangle copy()
{
return new AreaDescriptor.Rectangle(this);
}
@Override
public List<BlockPos> getContainedPositions(BlockPos pos)
{
if (!cache || !pos.equals(cachedPosition) || blockPosCache.isEmpty())
{
ArrayList<BlockPos> posList = new ArrayList<>();
for (int j = minimumOffset.getY(); j < maximumOffset.getY(); j++)
{
for (int i = minimumOffset.getX(); i < maximumOffset.getX(); i++)
{
for (int k = minimumOffset.getZ(); k < maximumOffset.getZ(); k++)
{
posList.add(pos.add(i, j, k));
}
}
}
blockPosCache = posList;
cachedPosition = pos;
}
return Collections.unmodifiableList(blockPosCache);
}
@Override
public AxisAlignedBB getAABB(BlockPos pos)
{
AxisAlignedBB tempAABB = new AxisAlignedBB(minimumOffset, maximumOffset);
return tempAABB.offset(pos.getX(), pos.getY(), pos.getZ());
}
@Override
public int getHeight()
{
return this.maximumOffset.getY() - this.minimumOffset.getY();
}
public BlockPos getMinimumOffset()
{
return minimumOffset;
}
public BlockPos getMaximumOffset()
{
return maximumOffset;
}
/**
* Sets the offsets of the AreaDescriptor in a safe way that will make
* minimumOffset the lowest corner
*
* @param offset1 -
* @param offset2 -
*/
public void setOffsets(BlockPos offset1, BlockPos offset2)
{
this.minimumOffset = new BlockPos(Math.min(offset1.getX(), offset2.getX()), Math.min(offset1.getY(), offset2.getY()), Math.min(offset1.getZ(), offset2.getZ()));
this.maximumOffset = new BlockPos(Math.max(offset1.getX(), offset2.getX()), Math.max(offset1.getY(), offset2.getY()), Math.max(offset1.getZ(), offset2.getZ()));
blockPosCache = new ArrayList<>();
}
@Override
public void resetCache()
{
this.blockPosCache = new ArrayList<>();
}
@Override
public boolean isWithinArea(BlockPos pos)
{
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
return x >= minimumOffset.getX() && x < maximumOffset.getX() && y >= minimumOffset.getY()
&& y < maximumOffset.getY() && z >= minimumOffset.getZ() && z < maximumOffset.getZ();
}
@Override
public boolean hasNext()
{
return currentPosition == null || !(currentPosition.getX() + 1 == maximumOffset.getX()
&& currentPosition.getY() + 1 == maximumOffset.getY()
&& currentPosition.getZ() + 1 == maximumOffset.getZ());
}
@Override
public BlockPos next()
{
if (currentPosition != null)
{
int nextX = currentPosition.getX() + 1 >= maximumOffset.getX() ? minimumOffset.getX()
: currentPosition.getX() + 1;
int nextZ = nextX != minimumOffset.getX() ? currentPosition.getZ()
: (currentPosition.getZ() + 1 >= maximumOffset.getZ() ? minimumOffset.getZ()
: currentPosition.getZ() + 1);
int nextY = (nextZ != minimumOffset.getZ() || nextX != minimumOffset.getX()) ? currentPosition.getY()
: (currentPosition.getY() + 1);
currentPosition = new BlockPos(nextX, nextY, nextZ);
} else
{
currentPosition = minimumOffset;
}
return currentPosition;
}
@Override
public void remove()
{
}
@Override
public void resetIterator()
{
currentPosition = null;
}
@Override
public void modifyAreaByBlockPositions(BlockPos pos1, BlockPos pos2)
{
setOffsets(pos1, pos2);
maximumOffset = maximumOffset.add(1, 1, 1);
resetIterator();
resetCache();
}
@Override
public void readFromNBT(CompoundNBT tag)
{
minimumOffset = new BlockPos(tag.getInt(Constants.NBT.X_COORD + "min"), tag.getInt(Constants.NBT.Y_COORD + "min"), tag.getInt(Constants.NBT.Z_COORD + "min"));
maximumOffset = new BlockPos(tag.getInt(Constants.NBT.X_COORD + "max"), tag.getInt(Constants.NBT.Y_COORD + "max"), tag.getInt(Constants.NBT.Z_COORD + "max"));
}
@Override
public void writeToNBT(CompoundNBT tag)
{
tag.putInt(Constants.NBT.X_COORD + "min", minimumOffset.getX());
tag.putInt(Constants.NBT.Y_COORD + "min", minimumOffset.getY());
tag.putInt(Constants.NBT.Z_COORD + "min", minimumOffset.getZ());
tag.putInt(Constants.NBT.X_COORD + "max", maximumOffset.getX());
tag.putInt(Constants.NBT.Y_COORD + "max", maximumOffset.getY());
tag.putInt(Constants.NBT.Z_COORD + "max", maximumOffset.getZ());
}
@Override
public int getVolumeForOffsets(BlockPos offset1, BlockPos offset2)
{
BlockPos minPos = new BlockPos(Math.min(offset1.getX(), offset2.getX()), Math.min(offset1.getY(), offset2.getY()), Math.min(offset1.getZ(), offset2.getZ()));
BlockPos maxPos = new BlockPos(Math.max(offset1.getX(), offset2.getX()), Math.max(offset1.getY(), offset2.getY()), Math.max(offset1.getZ(), offset2.getZ()));
maxPos = maxPos.add(1, 1, 1);
return (maxPos.getX() - minPos.getX()) * (maxPos.getY() - minPos.getY()) * (maxPos.getZ() - minPos.getZ());
}
@Override
public boolean isWithinRange(BlockPos offset1, BlockPos offset2, int verticalLimit, int horizontalLimit)
{
BlockPos minPos = new BlockPos(Math.min(offset1.getX(), offset2.getX()), Math.min(offset1.getY(), offset2.getY()), Math.min(offset1.getZ(), offset2.getZ()));
BlockPos maxPos = new BlockPos(Math.max(offset1.getX(), offset2.getX()), Math.max(offset1.getY(), offset2.getY()), Math.max(offset1.getZ(), offset2.getZ()));
return minPos.getY() >= -verticalLimit && maxPos.getY() <= verticalLimit
&& minPos.getX() >= -horizontalLimit && maxPos.getX() <= horizontalLimit
&& minPos.getZ() >= -horizontalLimit && maxPos.getZ() <= horizontalLimit;
}
@Override
public int getVolume()
{
return (maximumOffset.getX() - minimumOffset.getX()) * (maximumOffset.getY() - minimumOffset.getY())
* (maximumOffset.getZ() - minimumOffset.getZ());
}
@Override
public boolean isWithinRange(int verticalLimit, int horizontalLimit)
{
return minimumOffset.getY() >= -verticalLimit && maximumOffset.getY() <= verticalLimit + 1
&& minimumOffset.getX() >= -horizontalLimit && maximumOffset.getX() <= horizontalLimit + 1
&& minimumOffset.getZ() >= -horizontalLimit && maximumOffset.getZ() <= horizontalLimit + 1;
}
@Override
public boolean intersects(AreaDescriptor descriptor)
{
if (descriptor instanceof AreaDescriptor.Rectangle)
{
AreaDescriptor.Rectangle rectangle = (AreaDescriptor.Rectangle) descriptor;
return !(minimumOffset.getX() >= rectangle.maximumOffset.getX()
|| minimumOffset.getY() >= rectangle.maximumOffset.getY()
|| minimumOffset.getZ() >= rectangle.maximumOffset.getZ()
|| rectangle.minimumOffset.getX() >= maximumOffset.getX()
|| rectangle.minimumOffset.getY() >= maximumOffset.getY()
|| rectangle.minimumOffset.getZ() >= maximumOffset.getZ());
}
return false;
}
@Override
public AreaDescriptor offset(BlockPos offset)
{
return new AreaDescriptor.Rectangle(this.minimumOffset.add(offset), this.maximumOffset.add(offset));
}
@Override
public AreaDescriptor rotateDescriptor(PlacementSettings settings)
{
BlockPos rotatePos1 = Template.transformedBlockPos(settings, minimumOffset);
BlockPos rotatePos2 = Template.transformedBlockPos(settings, maximumOffset.add(-1, -1, -1)); // It works,
// shut up!
AreaDescriptor.Rectangle rectangle = new AreaDescriptor.Rectangle(this.minimumOffset, 1);
rectangle.modifyAreaByBlockPositions(rotatePos1, rotatePos2);
return rectangle;
}
}
public static class HemiSphere extends AreaDescriptor
{
private BlockPos minimumOffset;
private int radius;
private ArrayList<BlockPos> blockPosCache;
private BlockPos cachedPosition;
private boolean cache = true;
public HemiSphere(BlockPos minimumOffset, int radius)
{
setRadius(minimumOffset, radius);
}
public HemiSphere(AreaDescriptor.HemiSphere hemiSphere)
{
this(hemiSphere.minimumOffset, hemiSphere.radius);
}
public AreaDescriptor.HemiSphere copy()
{
return new AreaDescriptor.HemiSphere(this);
}
public void setRadius(BlockPos minimumOffset, int radius)
{
this.minimumOffset = new BlockPos(Math.min(minimumOffset.getX(), minimumOffset.getX()), Math.min(minimumOffset.getY(), minimumOffset.getY()), Math.min(minimumOffset.getZ(), minimumOffset.getZ()));
this.radius = radius;
blockPosCache = new ArrayList<>();
}
@Override
public int getHeight()
{
return this.radius * 2;
}
@Override
public List<BlockPos> getContainedPositions(BlockPos pos)
{
if (!cache || !pos.equals(cachedPosition) || blockPosCache.isEmpty())
{
ArrayList<BlockPos> posList = new ArrayList<>();
int i = -radius;
int j = minimumOffset.getY();
int k = -radius;
// TODO For some reason the bottom of the hemisphere is not going up with the
// minOffset
while (i <= radius)
{
while (j <= radius)
{
while (k <= radius)
{
if (i * i + j * j + k * k >= (radius + 0.5F) * (radius + 0.5F))
{
k++;
continue;
}
posList.add(pos.add(i, j, k));
k++;
}
k = -radius;
j++;
}
j = minimumOffset.getY();
i++;
}
blockPosCache = posList;
cachedPosition = pos;
}
return Collections.unmodifiableList(blockPosCache);
}
/**
* Since you can't make a box using a sphere, this returns null
*/
@Override
public AxisAlignedBB getAABB(BlockPos pos)
{
return null;
}
@Override
public void resetCache()
{
this.blockPosCache = new ArrayList<>();
}
@Override
public boolean isWithinArea(BlockPos pos)
{
return blockPosCache.contains(pos);
}
@Override
public boolean hasNext()
{
return false;
}
@Override
public BlockPos next()
{
return null;
}
@Override
public void remove()
{
}
@Override
public void resetIterator()
{
}
@Override
public void modifyAreaByBlockPositions(BlockPos pos1, BlockPos pos2)
{
}
@Override
public int getVolumeForOffsets(BlockPos pos1, BlockPos pos2)
{
return 0;
}
@Override
public boolean isWithinRange(BlockPos offset1, BlockPos offset2, int verticalLimit, int horizontalLimit)
{
return false;
}
@Override
public int getVolume()
{
return 0;
}
@Override
public boolean isWithinRange(int verticalLimit, int horizontalLimit)
{
return false;
}
@Override
public boolean intersects(AreaDescriptor descriptor)
{
return false;
}
@Override
public AreaDescriptor offset(BlockPos offset)
{
return new AreaDescriptor.HemiSphere(minimumOffset.add(offset), radius);
}
@Override
public AreaDescriptor rotateDescriptor(PlacementSettings settings)
{
return this;
}
}
public static class Cross extends AreaDescriptor
{
private ArrayList<BlockPos> blockPosCache;
private BlockPos cachedPosition;
private BlockPos centerPos;
private int size;
private boolean cache = true;
public Cross(BlockPos center, int size)
{
this.centerPos = center;
this.size = size;
this.blockPosCache = new ArrayList<>();
}
public Cross(AreaDescriptor.Cross cross)
{
this(cross.centerPos, cross.size);
}
public AreaDescriptor.Cross copy()
{
return new AreaDescriptor.Cross(this);
}
@Override
public int getHeight()
{
return this.size * 2 + 1;
}
@Override
public List<BlockPos> getContainedPositions(BlockPos pos)
{
if (!cache || !pos.equals(cachedPosition) || blockPosCache.isEmpty())
{
resetCache();
blockPosCache.add(centerPos.add(pos));
for (int i = 1; i <= size; i++)
{
blockPosCache.add(centerPos.add(pos).add(i, 0, 0));
blockPosCache.add(centerPos.add(pos).add(0, 0, i));
blockPosCache.add(centerPos.add(pos).add(-i, 0, 0));
blockPosCache.add(centerPos.add(pos).add(0, 0, -i));
}
}
cachedPosition = pos;
return Collections.unmodifiableList(blockPosCache);
}
@Override
public void resetCache()
{
blockPosCache = new ArrayList<>();
}
@Override
public boolean isWithinArea(BlockPos pos)
{
return blockPosCache.contains(pos);
}
@Override
public boolean hasNext()
{
return false;
}
@Override
public BlockPos next()
{
return null;
}
@Override
public void remove()
{
}
@Override
public void resetIterator()
{
}
@Override
public void modifyAreaByBlockPositions(BlockPos pos1, BlockPos pos2)
{
}
@Override
public int getVolumeForOffsets(BlockPos pos1, BlockPos pos2)
{
return 0;
}
@Override
public boolean isWithinRange(BlockPos offset1, BlockPos offset2, int verticalLimit, int horizontalLimit)
{
return false;
}
@Override
public int getVolume()
{
return 0;
}
@Override
public boolean isWithinRange(int verticalLimit, int horizontalLimit)
{
return false;
}
@Override
public boolean intersects(AreaDescriptor descriptor)
{
return false;
}
@Override
public AreaDescriptor offset(BlockPos offset)
{
return new AreaDescriptor.Cross(centerPos.add(offset), size);
}
@Override
public AreaDescriptor rotateDescriptor(PlacementSettings settings)
{
return this;
}
}
}

View file

@ -0,0 +1,58 @@
package wayoftime.bloodmagic.ritual;
import java.util.concurrent.Callable;
import net.minecraft.nbt.ByteNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;
public final class CapabilityRuneType
{
public static class RuneTypeStorage implements Capability.IStorage<IRitualStone.Tile>
{
@Override
public INBT writeNBT(Capability<IRitualStone.Tile> capability, IRitualStone.Tile instance, Direction side)
{
return ByteNBT.valueOf((byte) instance.getRuneType().ordinal());
}
@Override
public void readNBT(Capability<IRitualStone.Tile> capability, IRitualStone.Tile instance, Direction side, INBT nbt)
{
instance.setRuneType(EnumRuneType.byMetadata(((ByteNBT) nbt).getByte()));
}
}
public static class RuneTypeWrapper implements IRitualStone.Tile
{
private EnumRuneType type = EnumRuneType.BLANK;
@Override
public boolean isRuneType(EnumRuneType runeType)
{
return type == runeType;
}
@Override
public EnumRuneType getRuneType()
{
return type;
}
public void setRuneType(EnumRuneType runeType)
{
type = runeType;
}
}
public static class Factory implements Callable<IRitualStone.Tile>
{
@Override
public IRitualStone.Tile call()
throws Exception
{
return new RuneTypeWrapper();
}
}
}

View file

@ -0,0 +1,22 @@
package wayoftime.bloodmagic.ritual;
import java.util.Locale;
import net.minecraft.util.IStringSerializable;
public enum EnumReaderBoundaries implements IStringSerializable
{
SUCCESS, VOLUME_TOO_LARGE, NOT_WITHIN_BOUNDARIES;
@Override
public String toString()
{
return name().toLowerCase(Locale.ROOT);
}
@Override
public String getString()
{
return toString();
}
}

View file

@ -0,0 +1,54 @@
package wayoftime.bloodmagic.ritual;
import java.util.Locale;
import net.minecraft.util.IStringSerializable;
import net.minecraft.util.text.TextFormatting;
public enum EnumRuneType implements IStringSerializable
{
BLANK(TextFormatting.GRAY),
WATER(TextFormatting.AQUA),
FIRE(TextFormatting.RED),
EARTH(TextFormatting.GREEN),
AIR(TextFormatting.WHITE),
DUSK(TextFormatting.DARK_GRAY),
DAWN(TextFormatting.GOLD);
public final TextFormatting colorCode;
EnumRuneType(TextFormatting colorCode)
{
this.colorCode = colorCode;
}
@Override
public String toString()
{
return name().toLowerCase(Locale.ENGLISH);
}
@Override
public String getString()
{
return this.toString();
}
// @Nonnull
// public ItemStack getStack(int count)
// {
// ItemStack ret = new ItemStack(RegistrarBloodMagicItems.INSCRIPTION_TOOL, count, ordinal());
// CompoundNBT tag = new CompoundNBT();
// tag.putInt(Constants.NBT.USES, 10);
// ret.setTag(tag);
// return ret;
// }
public static EnumRuneType byMetadata(int meta)
{
if (meta < 0 || meta >= values().length)
meta = 0;
return values()[meta];
}
}

View file

@ -0,0 +1,81 @@
package wayoftime.bloodmagic.ritual;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import wayoftime.bloodmagic.core.data.SoulNetwork;
import wayoftime.bloodmagic.core.data.SoulTicket;
import wayoftime.bloodmagic.will.EnumDemonWillType;
/**
* This interface is for internal implementation only.
* <p>
* It is provided via the API for easy obtaining of basic data.
*/
public interface IMasterRitualStone
{
UUID getOwner();
SoulNetwork getOwnerNetwork();
boolean activateRitual(ItemStack activationCrystal, PlayerEntity activator, Ritual ritual);
void performRitual(World world, BlockPos pos);
void stopRitual(Ritual.BreakType breakType);
int getCooldown();
void setCooldown(int cooldown);
boolean isActive();
void setActive(boolean active);
Direction getDirection();
boolean areTanksEmpty();
int getRunningTime();
World getWorldObj();
BlockPos getBlockPos();
String getNextBlockRange(String range);
void provideInformationOfRitualToPlayer(PlayerEntity player);
void provideInformationOfRangeToPlayer(PlayerEntity player, String range);
void provideInformationOfWillConfigToPlayer(PlayerEntity player, List<EnumDemonWillType> typeList);
void setActiveWillConfig(PlayerEntity player, List<EnumDemonWillType> typeList);
EnumReaderBoundaries setBlockRangeByBounds(PlayerEntity player, String range, BlockPos offset1, BlockPos offset2);
List<EnumDemonWillType> getActiveWillConfig();
default SoulTicket ticket(int amount)
{
return SoulTicket.block(getWorldObj(), getBlockPos(), amount);
}
AreaDescriptor getBlockRange(String range);
void addBlockRanges(Map<String, AreaDescriptor> blockRanges);
void addBlockRange(String range, AreaDescriptor defaultRange);
void setBlockRanges(Map<String, AreaDescriptor> blockRanges);
void setBlockRange(String range, AreaDescriptor defaultRange);
Ritual getCurrentRitual();
}

View file

@ -0,0 +1,20 @@
package wayoftime.bloodmagic.ritual;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface IRitualStone
{
boolean isRuneType(World world, BlockPos pos, EnumRuneType runeType);
void setRuneType(World world, BlockPos pos, EnumRuneType runeType);
interface Tile
{
boolean isRuneType(EnumRuneType runeType);
EnumRuneType getRuneType();
void setRuneType(EnumRuneType runeType);
}
}

View file

@ -0,0 +1,427 @@
package wayoftime.bloodmagic.ritual;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.function.Consumer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import wayoftime.bloodmagic.demonaura.WorldDemonWillHandler;
import wayoftime.bloodmagic.will.DemonWillHolder;
import wayoftime.bloodmagic.will.EnumDemonWillType;
/**
* Abstract class for creating new rituals. Register your ritual by annotating
* it with {@link RitualRegister}
*/
public abstract class Ritual
{
protected final Map<String, AreaDescriptor> modableRangeMap = new HashMap<>();
protected final Map<String, Integer> volumeRangeMap = new HashMap<>();
protected final Map<String, Integer> horizontalRangeMap = new HashMap<>();
protected final Map<String, Integer> verticalRangeMap = new HashMap<>();
private final String name;
private final int crystalLevel;
private final int activationCost;
private final RitualRenderer renderer;
private final String unlocalizedName;
public Ritual(String name, int crystalLevel, int activationCost, RitualRenderer renderer, String unlocalizedName)
{
this.name = name;
this.crystalLevel = crystalLevel;
this.activationCost = activationCost;
this.renderer = renderer;
this.unlocalizedName = unlocalizedName;
}
/**
* @param name - The name of the ritual
* @param crystalLevel - Required Activation Crystal tier
* @param activationCost - Base LP cost for activating the ritual
*/
public Ritual(String name, int crystalLevel, int activationCost, String unlocalizedName)
{
this(name, crystalLevel, activationCost, null, unlocalizedName);
}
public void readFromNBT(CompoundNBT tag)
{
ListNBT tags = tag.getList("areas", 10);
if (tags.isEmpty())
{
return;
}
for (int i = 0; i < tags.size(); i++)
{
CompoundNBT newTag = tags.getCompound(i);
String rangeKey = newTag.getString("key");
CompoundNBT storedTag = newTag.getCompound("area");
AreaDescriptor desc = this.getBlockRange(rangeKey);
if (desc != null)
{
desc.readFromNBT(storedTag);
}
}
}
public void writeToNBT(CompoundNBT tag)
{
ListNBT tags = new ListNBT();
for (Entry<String, AreaDescriptor> entry : modableRangeMap.entrySet())
{
CompoundNBT newTag = new CompoundNBT();
newTag.putString("key", entry.getKey());
CompoundNBT storedTag = new CompoundNBT();
entry.getValue().writeToNBT(storedTag);
newTag.put("area", storedTag);
tags.add(newTag);
}
tag.put("areas", tags);
}
/**
* Called when the player attempts to activate the ritual.
* <p>
* {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#activateRitual(ItemStack, PlayerEntity, Ritual)}
*
* @param masterRitualStone - The {@link IMasterRitualStone} that the ritual is
* bound to
* @param player - The activating player
* @param owner - Owner of the crystal activating this ritual, or
* the current owner of the ritual if being
* reactivated.
* @return - Whether activation was successful
*/
public boolean activateRitual(IMasterRitualStone masterRitualStone, PlayerEntity player, UUID owner)
{
return true;
}
/**
* Called every {@link #getRefreshTime()} ticks while active.
* <p>
* {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#performRitual(World, BlockPos)}
*
* @param masterRitualStone - The {@link IMasterRitualStone} that the ritual is
* bound to
*/
public abstract void performRitual(IMasterRitualStone masterRitualStone);
/**
* Called when the ritual is stopped for a given {@link Ritual.BreakType}.
* <p>
* {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#stopRitual(Ritual.BreakType)}
*
* @param masterRitualStone - The {@link IMasterRitualStone} that the ritual is
* bound to
* @param breakType - The type of break that caused the stoppage.
*/
public void stopRitual(IMasterRitualStone masterRitualStone, BreakType breakType)
{
}
/**
* Used to set the amount of LP drained every {@link #getRefreshTime()} ticks.
*
* @return - The amount of LP drained per refresh
*/
public abstract int getRefreshCost();
/**
* Used to set the refresh rate of the ritual. (How often
* {@link #performRitual(IMasterRitualStone)} is called.
*
* @return - How often to perform the effect in ticks.
*/
public int getRefreshTime()
{
return 20;
}
public void addBlockRange(String range, AreaDescriptor defaultRange)
{
modableRangeMap.put(range, defaultRange);
}
/**
* Used to grab the range of a ritual for a given effect.
*
* @param range - Range that needs to be pulled.
* @return -
*/
public AreaDescriptor getBlockRange(String range)
{
if (modableRangeMap.containsKey(range))
{
return modableRangeMap.get(range);
}
return null;
}
public List<String> getListOfRanges()
{
return new ArrayList<>(modableRangeMap.keySet());
}
public String getNextBlockRange(String range)
{
List<String> rangeList = getListOfRanges();
if (rangeList.isEmpty())
{
return "";
}
if (!rangeList.contains(range))
{
return rangeList.get(0);
}
boolean hasMatch = false;
for (String rangeCheck : rangeList)
{
if (hasMatch)
{
return rangeCheck;
} else if (rangeCheck.equals(range))
{
hasMatch = true;
}
}
return rangeList.get(0);
}
public EnumReaderBoundaries canBlockRangeBeModified(String range, AreaDescriptor descriptor, IMasterRitualStone master, BlockPos offset1, BlockPos offset2, DemonWillHolder holder)
{
List<EnumDemonWillType> willConfig = master.getActiveWillConfig();
int maxVolume = getMaxVolumeForRange(range, willConfig, holder);
int maxVertical = getMaxVerticalRadiusForRange(range, willConfig, holder);
int maxHorizontal = getMaxHorizontalRadiusForRange(range, willConfig, holder);
return (maxVolume <= 0 || descriptor.getVolumeForOffsets(offset1, offset2) <= maxVolume)
? descriptor.isWithinRange(offset1, offset2, maxVertical, maxHorizontal) ? EnumReaderBoundaries.SUCCESS
: EnumReaderBoundaries.NOT_WITHIN_BOUNDARIES
: EnumReaderBoundaries.VOLUME_TOO_LARGE;
}
protected void setMaximumVolumeAndDistanceOfRange(String range, int volume, int horizontalRadius, int verticalRadius)
{
volumeRangeMap.put(range, volume);
horizontalRangeMap.put(range, horizontalRadius);
verticalRangeMap.put(range, verticalRadius);
}
protected boolean checkDescriptorIsWithinRange(AreaDescriptor descriptor, int maxVolume, int maxHorizontal, int maxVertical)
{
return descriptor.getVolume() <= maxVolume && descriptor.isWithinRange(maxVertical, maxHorizontal);
}
public int getMaxVolumeForRange(String range, List<EnumDemonWillType> activeTypes, DemonWillHolder holder)
{
return volumeRangeMap.get(range);
}
public int getMaxVerticalRadiusForRange(String range, List<EnumDemonWillType> activeTypes, DemonWillHolder holder)
{
return verticalRangeMap.get(range);
}
public int getMaxHorizontalRadiusForRange(String range, List<EnumDemonWillType> activeTypes, DemonWillHolder holder)
{
return horizontalRangeMap.get(range);
}
public ITextComponent getErrorForBlockRangeOnFail(PlayerEntity player, String range, IMasterRitualStone master, BlockPos offset1, BlockPos offset2)
{
AreaDescriptor descriptor = this.getBlockRange(range);
if (descriptor == null)
{
return new TranslationTextComponent("ritual.bloodmagic.blockRange.tooBig", "?");
}
List<EnumDemonWillType> willConfig = master.getActiveWillConfig();
DemonWillHolder holder = WorldDemonWillHandler.getWillHolder(master.getWorldObj(), master.getBlockPos());
int maxVolume = this.getMaxVolumeForRange(range, willConfig, holder);
int maxVertical = this.getMaxVerticalRadiusForRange(range, willConfig, holder);
int maxHorizontal = this.getMaxHorizontalRadiusForRange(range, willConfig, holder);
if (maxVolume > 0 && descriptor.getVolumeForOffsets(offset1, offset2) > maxVolume)
{
return new TranslationTextComponent("ritual.bloodmagic.blockRange.tooBig", maxVolume);
} else
{
return new TranslationTextComponent("ritual.bloodmagic.blockRange.tooFar", maxVertical, maxHorizontal);
}
}
public ITextComponent[] provideInformationOfRitualToPlayer(PlayerEntity player)
{
return new ITextComponent[]
{ new TranslationTextComponent(this.getTranslationKey() + ".info") };
}
public ITextComponent provideInformationOfRangeToPlayer(PlayerEntity player, String range)
{
if (getListOfRanges().contains(range))
{
return new TranslationTextComponent(this.getTranslationKey() + "." + range + ".info");
} else
{
return new TranslationTextComponent("ritual.bloodmagic.blockRange.noRange");
}
}
public abstract void gatherComponents(Consumer<RitualComponent> components);
protected final void addRune(Consumer<RitualComponent> components, int offset1, int y, int offset2, EnumRuneType rune)
{
components.accept(new RitualComponent(new BlockPos(offset1, y, offset2), rune));
}
protected final void addOffsetRunes(Consumer<RitualComponent> components, int offset1, int offset2, int y, EnumRuneType rune)
{
addRune(components, offset1, y, offset2, rune);
addRune(components, offset2, y, offset1, rune);
addRune(components, offset1, y, -offset2, rune);
addRune(components, -offset2, y, offset1, rune);
addRune(components, -offset1, y, offset2, rune);
addRune(components, offset2, y, -offset1, rune);
addRune(components, -offset1, y, -offset2, rune);
addRune(components, -offset2, y, -offset1, rune);
}
protected final void addCornerRunes(Consumer<RitualComponent> components, int offset, int y, EnumRuneType rune)
{
addRune(components, offset, y, offset, rune);
addRune(components, offset, y, -offset, rune);
addRune(components, -offset, y, -offset, rune);
addRune(components, -offset, y, offset, rune);
}
protected final void addParallelRunes(Consumer<RitualComponent> components, int offset, int y, EnumRuneType rune)
{
addRune(components, offset, y, 0, rune);
addRune(components, -offset, y, 0, rune);
addRune(components, 0, y, -offset, rune);
addRune(components, 0, y, offset, rune);
}
public double getWillRespectingConfig(World world, BlockPos pos, EnumDemonWillType type, List<EnumDemonWillType> willConfig)
{
return willConfig.contains(type) ? WorldDemonWillHandler.getCurrentWill(world, pos, type) : 0;
}
public abstract Ritual getNewCopy();
public String getName()
{
return name;
}
public int getCrystalLevel()
{
return crystalLevel;
}
public int getActivationCost()
{
return activationCost;
}
public RitualRenderer getRenderer()
{
return renderer;
}
public String getTranslationKey()
{
return unlocalizedName;
}
public Map<String, AreaDescriptor> getModableRangeMap()
{
return modableRangeMap;
}
public Map<String, Integer> getVolumeRangeMap()
{
return volumeRangeMap;
}
public Map<String, Integer> getHorizontalRangeMap()
{
return horizontalRangeMap;
}
public Map<String, Integer> getVerticalRangeMap()
{
return verticalRangeMap;
}
@Override
public String toString()
{
return new ToStringBuilder(this).append("name", name).append("crystalLevel", crystalLevel).append("activationCost", activationCost).append("renderer", renderer).append("unlocalizedName", unlocalizedName).append("modableRangeMap", modableRangeMap).append("volumeRangeMap", volumeRangeMap).append("horizontalRangeMap", horizontalRangeMap).append("verticalRangeMap", verticalRangeMap).append("refreshTime", getRefreshTime()).append("listOfRanges", getListOfRanges()).toString();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof Ritual))
return false;
Ritual ritual = (Ritual) o;
if (crystalLevel != ritual.crystalLevel)
return false;
if (activationCost != ritual.activationCost)
return false;
if (name != null ? !name.equals(ritual.name) : ritual.name != null)
return false;
return unlocalizedName != null ? unlocalizedName.equals(ritual.unlocalizedName)
: ritual.unlocalizedName == null;
}
@Override
public int hashCode()
{
int result = name != null ? name.hashCode() : 0;
result = 31 * result + crystalLevel;
result = 31 * result + activationCost;
result = 31 * result + (unlocalizedName != null ? unlocalizedName.hashCode() : 0);
return result;
}
public enum BreakType
{
REDSTONE, BREAK_MRS, BREAK_STONE, ACTIVATE, DEACTIVATE, EXPLOSION,
}
}

View file

@ -0,0 +1,70 @@
package wayoftime.bloodmagic.ritual;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* Used to set a {@link EnumRuneType} type to a given {@link BlockPos} for usage
* in Ritual creation.
*/
public class RitualComponent
{
private final BlockPos offset;
private final EnumRuneType runeType;
public RitualComponent(BlockPos offset, EnumRuneType runeType)
{
this.offset = offset;
this.runeType = runeType;
}
public int getX(Direction direction)
{
switch (direction)
{
case EAST:
return -this.getOffset().getZ();
case SOUTH:
return -this.getOffset().getX();
case WEST:
return this.getOffset().getZ();
default:
return this.getOffset().getX();
}
}
public int getY()
{
return this.getOffset().getY();
}
public int getZ(Direction direction)
{
switch (direction)
{
case EAST:
return this.getOffset().getX();
case SOUTH:
return -this.getOffset().getZ();
case WEST:
return -this.getOffset().getX();
default:
return this.getOffset().getZ();
}
}
public BlockPos getOffset(Direction direction)
{
return new BlockPos(getX(direction), offset.getY(), getZ(direction));
}
public BlockPos getOffset()
{
return offset;
}
public EnumRuneType getRuneType()
{
return runeType;
}
}

View file

@ -0,0 +1,177 @@
package wayoftime.bloodmagic.ritual;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.minecraft.block.BlockState;
import net.minecraft.util.DamageSource;
import net.minecraftforge.fml.ModList;
import wayoftime.bloodmagic.ritual.imperfect.ImperfectRitual;
import wayoftime.bloodmagic.util.BMLog;
public class RitualManager
{
public static final DamageSource RITUAL_DAMAGE = new DamageSource("ritual_damage").setDamageBypassesArmor();
private final Map<String, Ritual> rituals;
private final Map<Ritual, String> ritualsReverse;
private final List<Ritual> sortedRituals;
private final Map<String, ImperfectRitual> imperfectRituals;
private final Map<ImperfectRitual, String> imperfectRitualsReverse;
// private final Configuration config;
public RitualManager()
{
this.rituals = Maps.newTreeMap();
this.ritualsReverse = Maps.newHashMap();
this.sortedRituals = Lists.newArrayList();
this.imperfectRituals = Maps.newTreeMap();
this.imperfectRitualsReverse = Maps.newHashMap();
// this.config = config;
}
// public void discover(ASMDataTable dataTable)
public void discover()
{
ModList.get().getAllScanData().forEach(scan -> {
scan.getAnnotations().forEach(a -> {
if (a.getAnnotationType().getClassName().equals(RitualRegister.class.getName()))
{
try
{
Class<?> clazz = Class.forName(a.getClassType().getClassName());
RitualRegister ritualRegister = clazz.getAnnotation(RitualRegister.class);
String id = ritualRegister.value();
if (Ritual.class.isAssignableFrom(clazz))
{
Ritual ritual = (Ritual) clazz.newInstance();
rituals.put(id, ritual);
ritualsReverse.put(ritual, id);
BMLog.DEBUG.info("Registered ritual {}", id);
} else
{
BMLog.DEFAULT.error("Error creating ritual instance for {}.", id);
}
} catch (Exception e)
{
e.printStackTrace();
}
}
});
});
ModList.get().getAllScanData().forEach(scan -> {
scan.getAnnotations().forEach(a -> {
if (a.getAnnotationType().getClassName().equals(RitualRegister.Imperfect.class.getName()))
{
try
{
Class<?> clazz = Class.forName(a.getClassType().getClassName());
RitualRegister.Imperfect ritualRegister = clazz.getAnnotation(RitualRegister.Imperfect.class);
String id = ritualRegister.value();
if (ImperfectRitual.class.isAssignableFrom(clazz))
{
ImperfectRitual ritual = (ImperfectRitual) clazz.newInstance();
imperfectRituals.put(id, ritual);
imperfectRitualsReverse.put(ritual, id);
BMLog.DEBUG.info("Registered imperfect ritual {}", id);
} else
{
BMLog.DEFAULT.error("Error creating imperfect ritual instance for {}.", id);
}
} catch (Exception e)
{
e.printStackTrace();
}
}
});
});
// syncConfig();
// Sort rituals
sortedRituals.addAll(rituals.values());
// Oh dear this is probably so slow
sortedRituals.sort((o1, o2) -> {
Set<RitualComponent> components = Sets.newHashSet();
o1.gatherComponents(components::add);
int initialSize = components.size();
components.clear();
o2.gatherComponents(components::add);
return Integer.compare(initialSize, components.size());
});
}
public Ritual getRitual(String id)
{
return rituals.get(id);
}
public String getId(Ritual ritual)
{
return ritualsReverse.get(ritual);
}
public ImperfectRitual getImperfectRitual(BlockState state)
{
for (ImperfectRitual ritual : imperfectRituals.values()) if (ritual.getBlockRequirement().test(state))
return ritual;
return null;
}
public String getId(ImperfectRitual ritual)
{
return imperfectRitualsReverse.get(ritual);
}
public Collection<Ritual> getRituals()
{
return rituals.values();
}
public Collection<ImperfectRitual> getImperfectRituals()
{
return imperfectRituals.values();
}
public List<Ritual> getSortedRituals()
{
return sortedRituals;
}
// public void syncConfig()
// {
// config.addCustomCategoryComment("rituals", "Toggles for all rituals");
// rituals.forEach((k, v) -> config.getBoolean(k, "rituals", true, "Enable the " + k + " ritual."));
// imperfectRituals.forEach((k, v) -> config.getBoolean(k, "rituals.imperfect", true, "Enable the " + k + " imperfect ritual."));
// config.save();
// }
//
public boolean enabled(String id, boolean imperfect)
{
return id != null;
// return id != null && config.getBoolean(id, "rituals" + (imperfect ? ".imperfect" : ""), true, "");
}
//
// public Configuration getConfig()
// {
// return config;
// }
public static class BadRitualException extends RuntimeException
{
public BadRitualException(String message)
{
super(message);
}
}
}

View file

@ -0,0 +1,58 @@
package wayoftime.bloodmagic.ritual;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
import wayoftime.bloodmagic.ritual.imperfect.ImperfectRitual;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RitualRegister
{
String value();
Class<? extends Function<Class<? extends Ritual>, Ritual>> factory() default DefaultRitualFactory.class;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Imperfect
{
String value();
Class<? extends Function<Class<? extends ImperfectRitual>, ImperfectRitual>> factory() default DefaultImperfectRitualFactory.class;
}
class DefaultRitualFactory implements Function<Class<? extends Ritual>, Ritual>
{
@Override
public Ritual apply(Class<? extends Ritual> aClass)
{
try
{
return aClass.newInstance();
} catch (Exception e)
{
return null;
}
}
}
class DefaultImperfectRitualFactory implements Function<Class<? extends ImperfectRitual>, ImperfectRitual>
{
@Override
public ImperfectRitual apply(Class<? extends ImperfectRitual> aClass)
{
try
{
return aClass.newInstance();
} catch (Exception e)
{
return null;
}
}
}
}

View file

@ -0,0 +1,14 @@
package wayoftime.bloodmagic.ritual;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
public abstract class RitualRenderer
{
public abstract void renderAt(IMasterRitualStone masterRitualStone, double x, double y, double z);
protected void bindTexture(ResourceLocation resourceLocation)
{
Minecraft.getInstance().getTextureManager().bindTexture(resourceLocation);
}
}

View file

@ -0,0 +1,20 @@
package wayoftime.bloodmagic.ritual.imperfect;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
/**
* This interface is for internal implementation only.
* <p>
* It is provided via the API for easy obtaining of basic data.
*/
public interface IImperfectRitualStone
{
boolean performRitual(World world, BlockPos pos, ImperfectRitual imperfectRitual, PlayerEntity player);
World getRitualWorld();
BlockPos getRitualPos();
}

View file

@ -0,0 +1,108 @@
package wayoftime.bloodmagic.ritual.imperfect;
import java.util.function.Predicate;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
/**
* Abstract class for creating new imperfect rituals. To register, annotate your
* class with {@link WayofTime.bloodmagic.ritual.RitualRegister.Imperfect}
*/
public abstract class ImperfectRitual
{
private final String name;
private final Predicate<BlockState> blockRequirement;
private final int activationCost;
private final boolean lightShow;
private final String unlocalizedName;
public ImperfectRitual(String name, Predicate<BlockState> blockRequirement, int activationCost, boolean lightShow, String unlocalizedName)
{
this.name = name;
this.blockRequirement = blockRequirement;
this.activationCost = activationCost;
this.lightShow = lightShow;
this.unlocalizedName = unlocalizedName;
}
/**
* @param name The name of the ritual
* @param blockRequirement The block required above the ImperfectRitualStone
* @param activationCost Base LP cost for activating the ritual
*/
public ImperfectRitual(String name, Predicate<BlockState> blockRequirement, int activationCost, String unlocalizedName)
{
this(name, blockRequirement, activationCost, false, unlocalizedName);
}
/**
* Called when the player activates the ritual
* {@link WayofTime.bloodmagic.tile.TileImperfectRitualStone#performRitual(World, net.minecraft.util.math.BlockPos, ImperfectRitual, PlayerEntity)}
*
* @param imperfectRitualStone - The {@link IImperfectRitualStone} that the
* ritual is bound to
* @param player - The player activating the ritual
* @return - Whether activation was successful
*/
public abstract boolean onActivate(IImperfectRitualStone imperfectRitualStone, PlayerEntity player);
public String getName()
{
return name;
}
public Predicate<BlockState> getBlockRequirement()
{
return blockRequirement;
}
public int getActivationCost()
{
return activationCost;
}
public boolean isLightShow()
{
return lightShow;
}
public String getTranslationKey()
{
return unlocalizedName;
}
@Override
public String toString()
{
return getName() + "@" + getActivationCost();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof ImperfectRitual))
return false;
ImperfectRitual that = (ImperfectRitual) o;
if (activationCost != that.activationCost)
return false;
if (name != null ? !name.equals(that.name) : that.name != null)
return false;
return unlocalizedName != null ? unlocalizedName.equals(that.unlocalizedName) : that.unlocalizedName == null;
}
@Override
public int hashCode()
{
int result = name != null ? name.hashCode() : 0;
result = 31 * result + activationCost;
result = 31 * result + (unlocalizedName != null ? unlocalizedName.hashCode() : 0);
return result;
}
}

View file

@ -0,0 +1,85 @@
package wayoftime.bloodmagic.ritual.types;
import java.util.function.Consumer;
import net.minecraft.block.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import wayoftime.bloodmagic.BloodMagic;
import wayoftime.bloodmagic.ritual.AreaDescriptor;
import wayoftime.bloodmagic.ritual.EnumRuneType;
import wayoftime.bloodmagic.ritual.IMasterRitualStone;
import wayoftime.bloodmagic.ritual.Ritual;
import wayoftime.bloodmagic.ritual.RitualComponent;
import wayoftime.bloodmagic.ritual.RitualRegister;
@RitualRegister("water")
public class RitualWater extends Ritual
{
public static final String WATER_RANGE = "waterRange";
public RitualWater()
{
super("ritualWater", 0, 500, "ritual." + BloodMagic.MODID + ".waterRitual");
addBlockRange(WATER_RANGE, new AreaDescriptor.Rectangle(new BlockPos(0, 1, 0), 1));
setMaximumVolumeAndDistanceOfRange(WATER_RANGE, 9, 3, 3);
}
@Override
public void performRitual(IMasterRitualStone masterRitualStone)
{
World world = masterRitualStone.getWorldObj();
int currentEssence = masterRitualStone.getOwnerNetwork().getCurrentEssence();
if (currentEssence < getRefreshCost())
{
masterRitualStone.getOwnerNetwork().causeNausea();
return;
}
int maxEffects = currentEssence / getRefreshCost();
int totalEffects = 0;
AreaDescriptor waterRange = masterRitualStone.getBlockRange(WATER_RANGE);
for (BlockPos newPos : waterRange.getContainedPositions(masterRitualStone.getBlockPos()))
{
if (world.isAirBlock(newPos))
{
world.setBlockState(newPos, Blocks.WATER.getDefaultState());
totalEffects++;
}
if (totalEffects >= maxEffects)
{
break;
}
}
masterRitualStone.getOwnerNetwork().syphon(masterRitualStone.ticket(getRefreshCost() * totalEffects));
}
@Override
public int getRefreshTime()
{
return 1;
}
@Override
public int getRefreshCost()
{
return 25;
}
@Override
public void gatherComponents(Consumer<RitualComponent> components)
{
addCornerRunes(components, 1, 0, EnumRuneType.WATER);
}
@Override
public Ritual getNewCopy()
{
return new RitualWater();
}
}