From 1f0dcb608a4fc3daeba604e22e348147df951163 Mon Sep 17 00:00:00 2001
From: WayofTime <WayofTime@users.noreply.github.com>
Date: Sat, 24 Oct 2020 14:50:25 -0400
Subject: [PATCH] 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.
---
 src/generated/resources/.cache/cache          |  28 +
 .../blockstates/airritualstone.json           |   7 +
 .../blockstates/duskritualstone.json          |   7 +
 .../blockstates/earthritualstone.json         |   7 +
 .../blockstates/fireritualstone.json          |   7 +
 .../blockstates/lightritualstone.json         |   7 +
 .../bloodmagic/blockstates/ritualstone.json   |   7 +
 .../blockstates/waterritualstone.json         |   7 +
 .../models/block/airritualstone.json          |   6 +
 .../models/block/duskritualstone.json         |   6 +
 .../models/block/earthritualstone.json        |   6 +
 .../models/block/fireritualstone.json         |   6 +
 .../models/block/lightritualstone.json        |   6 +
 .../bloodmagic/models/block/ritualstone.json  |   6 +
 .../models/block/waterritualstone.json        |   6 +
 .../models/item/airritualstone.json           |   3 +
 .../models/item/duskritualstone.json          |   3 +
 .../models/item/earthritualstone.json         |   3 +
 .../models/item/fireritualstone.json          |   3 +
 .../models/item/lightritualstone.json         |   3 +
 .../bloodmagic/models/item/ritualstone.json   |   3 +
 .../models/item/waterritualstone.json         |   3 +
 .../loot_tables/blocks/airritualstone.json    |  19 +
 .../loot_tables/blocks/duskritualstone.json   |  19 +
 .../loot_tables/blocks/earthritualstone.json  |  19 +
 .../loot_tables/blocks/fireritualstone.json   |  19 +
 .../loot_tables/blocks/lightritualstone.json  |  19 +
 .../loot_tables/blocks/ritualstone.json       |  19 +
 .../loot_tables/blocks/waterritualstone.json  |  19 +
 .../java/WayofTime/bloodmagic/BloodMagic.java |   7 +-
 .../block/enums/EnumRitualController.java     |  23 +
 .../common/block/BlockMasterRitualStone.java  | 130 ++++
 .../common/block/BlockRitualStone.java        |  89 +++
 .../common/block/BloodMagicBlocks.java        |  11 +
 .../common/data/GeneratorBlockStates.java     |   7 +
 .../common/data/GeneratorItemModels.java      |   8 +
 .../common/data/GeneratorLootTable.java       |  34 +-
 .../common/item/BloodMagicItems.java          |  11 +
 .../common/item/ItemActivationCrystal.java    |  72 ++
 .../wayoftime/bloodmagic/demonaura/PosXY.java |  76 ++
 .../bloodmagic/demonaura/WillChunk.java       |  72 ++
 .../bloodmagic/demonaura/WillWorld.java       |  49 ++
 .../demonaura/WorldDemonWillHandler.java      | 218 ++++++
 .../bloodmagic/event/RitualEvent.java         | 151 ++++
 .../bloodmagic/ritual/AreaDescriptor.java     | 654 ++++++++++++++++++
 .../bloodmagic/ritual/CapabilityRuneType.java |  58 ++
 .../ritual/EnumReaderBoundaries.java          |  22 +
 .../bloodmagic/ritual/EnumRuneType.java       |  54 ++
 .../bloodmagic/ritual/IMasterRitualStone.java |  81 +++
 .../bloodmagic/ritual/IRitualStone.java       |  20 +
 .../wayoftime/bloodmagic/ritual/Ritual.java   | 427 ++++++++++++
 .../bloodmagic/ritual/RitualComponent.java    |  70 ++
 .../bloodmagic/ritual/RitualManager.java      | 177 +++++
 .../bloodmagic/ritual/RitualRegister.java     |  58 ++
 .../bloodmagic/ritual/RitualRenderer.java     |  14 +
 .../imperfect/IImperfectRitualStone.java      |  20 +
 .../ritual/imperfect/ImperfectRitual.java     | 108 +++
 .../bloodmagic/ritual/types/RitualWater.java  |  85 +++
 .../tile/TileMasterRitualStone.java           | 566 +++++++++++++++
 .../bloodmagic/tile/base/TileTicking.java     |  71 ++
 .../bloodmagic/util/helper/RitualHelper.java  | 253 +++++++
 61 files changed, 3943 insertions(+), 26 deletions(-)
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/airritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/duskritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/earthritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/fireritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/lightritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/ritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/blockstates/waterritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/airritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/duskritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/earthritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/fireritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/lightritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/ritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/block/waterritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/airritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/duskritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/earthritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/fireritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/lightritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/ritualstone.json
 create mode 100644 src/generated/resources/assets/bloodmagic/models/item/waterritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/airritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/duskritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/earthritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/fireritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/lightritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/ritualstone.json
 create mode 100644 src/generated/resources/data/bloodmagic/loot_tables/blocks/waterritualstone.json
 create mode 100644 src/main/java/wayoftime/bloodmagic/block/enums/EnumRitualController.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/common/block/BlockMasterRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/common/block/BlockRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/common/item/ItemActivationCrystal.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/demonaura/PosXY.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/demonaura/WillChunk.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/demonaura/WillWorld.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/demonaura/WorldDemonWillHandler.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/event/RitualEvent.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/AreaDescriptor.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/CapabilityRuneType.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/EnumReaderBoundaries.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/EnumRuneType.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/IMasterRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/IRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/Ritual.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/RitualComponent.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/RitualManager.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/RitualRegister.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/RitualRenderer.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/imperfect/IImperfectRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/imperfect/ImperfectRitual.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/ritual/types/RitualWater.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/tile/TileMasterRitualStone.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/tile/base/TileTicking.java
 create mode 100644 src/main/java/wayoftime/bloodmagic/util/helper/RitualHelper.java

diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache
index 3d4fa05e..79d86fc3 100644
--- a/src/generated/resources/.cache/cache
+++ b/src/generated/resources/.cache/cache
@@ -1,27 +1,42 @@
 cb435652c27b4978d8db83af2fd531ccaa82ada7 assets/bloodmagic/blockstates/accelerationrune.json
+43be0406da1c9f7cf734427bea235a65cda073d2 assets/bloodmagic/blockstates/airritualstone.json
 4a60c54def00d68368ed0a0d4783979aa63d5f60 assets/bloodmagic/blockstates/altarcapacityrune.json
 950fff9f06033741091aa8a66a62857da673efb9 assets/bloodmagic/blockstates/bettercapacityrune.json
 8a5edb859a6f4d0adfbe2f608bab6b8c8addf01a assets/bloodmagic/blockstates/blankrune.json
 904d9baa649250571bce5f965cf48fbec69c2c1a assets/bloodmagic/blockstates/bloodlight.json
 631b579c38652efbcd9e5771d09ad6e476f3ba00 assets/bloodmagic/blockstates/chargingrune.json
 6bd58d1d02a40416cec29409dee7ef80038b26d5 assets/bloodmagic/blockstates/dislocationrune.json
+ba1cd8a9475212843e3b26232c8a9943fa0d2d20 assets/bloodmagic/blockstates/duskritualstone.json
+bb3db171734f511fc0c259d86e869b49aa1d0c77 assets/bloodmagic/blockstates/earthritualstone.json
+e780d6d9e891082dc6ce83fde1697ce36281a02a assets/bloodmagic/blockstates/fireritualstone.json
+002795212cc7bf2cad2a91f873d85e2204c6367d assets/bloodmagic/blockstates/lightritualstone.json
 372ecd737f7082a4c2c70e46745f893b1179f885 assets/bloodmagic/blockstates/orbcapacityrune.json
+90daa355e528ab8a6582f796951201882f3c56da assets/bloodmagic/blockstates/ritualstone.json
 285618c1a8ec36e36d479f577190579ae7616529 assets/bloodmagic/blockstates/sacrificerune.json
 b03040d7a168653bf8df3600033b8fde2383db30 assets/bloodmagic/blockstates/selfsacrificerune.json
 487ffdc02ab7b65aafcb932e3b5cf6ea0500b21d assets/bloodmagic/blockstates/speedrune.json
+e6d9cf699667aaa47efff37b2b033895dee29c15 assets/bloodmagic/blockstates/waterritualstone.json
 f7a92ca94cbd68344d89b92dc6c26c15cd1b85b5 assets/bloodmagic/lang/en_us.json
 34445195b9f2459475cde53454bc8e37d32865d7 assets/bloodmagic/models/block/accelerationrune.json
+bcdbccc49d4509571be6988762ab87126275a4c8 assets/bloodmagic/models/block/airritualstone.json
 3c98a88c2283ad54f0efb9d7194361bbc3e93c17 assets/bloodmagic/models/block/altarcapacityrune.json
 7cd62092c6fb3109e016d42090cf89bfa3ab7fca assets/bloodmagic/models/block/bettercapacityrune.json
 1fe0f89895addb7abcacf6ce7e39b6ddc87b0d85 assets/bloodmagic/models/block/blankrune.json
 3c83e090a1cff00e2bb2c7eb475785954b6eb980 assets/bloodmagic/models/block/bloodlight.json
 320827ad2feaa51a90ebb7064a70bdc6d3765203 assets/bloodmagic/models/block/chargingrune.json
 6adbeedc17f649ef47419845a6da0d50cfc76742 assets/bloodmagic/models/block/dislocationrune.json
+81313327125e6e7396df0408595228bf0f63e1c9 assets/bloodmagic/models/block/duskritualstone.json
+c30064f4aa09c42d23e94d118ae5b148eadb3a6c assets/bloodmagic/models/block/earthritualstone.json
+4ff1cab1014cd8f655e5f032ecf60dd371f421c3 assets/bloodmagic/models/block/fireritualstone.json
+2e1a81c758bfeec2aee807b48239f23241302268 assets/bloodmagic/models/block/lightritualstone.json
 c3a813b735cd229f8597e41d04465926b2e65fe1 assets/bloodmagic/models/block/orbcapacityrune.json
+9b2bf2a44b788cbaecbe63a3e085e8de76672e1b assets/bloodmagic/models/block/ritualstone.json
 a8a1d06fcc2f8395530c72d2846133fff37d5537 assets/bloodmagic/models/block/sacrificerune.json
 791c9f2e27215ff0a45eed7efe385276bfc09aed assets/bloodmagic/models/block/selfsacrificerune.json
 65fe5e01ed2660e45a5c329ff2389a87e4d791ec assets/bloodmagic/models/block/speedrune.json
+6041f2e47f5437d90a58586e42d18dadc42df439 assets/bloodmagic/models/block/waterritualstone.json
 9462d62d9bc9408359d30728de8651dc104aacf1 assets/bloodmagic/models/item/accelerationrune.json
+fe8e3deb3ad0107ca3ebd70694c1fc55a987d912 assets/bloodmagic/models/item/airritualstone.json
 17cbe9142ef3950ea1b6be11694b849f55e93f13 assets/bloodmagic/models/item/airsigil.json
 f150f178edf7d6d250bcfd84af1c28a21cff09c6 assets/bloodmagic/models/item/altarcapacityrune.json
 866b8cdd3da56e2e82dbd5f16ab5117b5a503749 assets/bloodmagic/models/item/apprenticebloodorb.json
@@ -40,11 +55,15 @@ f404148f9df3a61da3c18175885ffa56b2a85a6a assets/bloodmagic/models/item/daggerofs
 9671199681493a396e07d7bcab20137c22d981d5 assets/bloodmagic/models/item/demonslate.json
 7af07ab578bbd20e2f834b26d9cafb5fe23bc7d4 assets/bloodmagic/models/item/dislocationrune.json
 f4531e22aa1db1cff324db5ccb344d3b9fa85c8d assets/bloodmagic/models/item/divinationsigil.json
+10aceefca3ad3f0da773cb317c4effc6c06051ea assets/bloodmagic/models/item/duskritualstone.json
+4d56efd7fdbf430f49903ce201577047687c3804 assets/bloodmagic/models/item/earthritualstone.json
 4c39378f6c14dc243a7d52564e5a21df94683415 assets/bloodmagic/models/item/etherealslate.json
+c36bde4f98c0aeb3bf0f369ad3bc067e5f0dc916 assets/bloodmagic/models/item/fireritualstone.json
 44663089f348642bcca1c5020b5081c3ab172f92 assets/bloodmagic/models/item/growthsigil.json
 f68825f667ca73b4373fd5068a47f0d1ca9b2aad assets/bloodmagic/models/item/icesigil.json
 109b5485c25d978af55b46682d5bfa7008909458 assets/bloodmagic/models/item/infusedslate.json
 588c5208e3f4ef941cd8375aeceeed44484d85d3 assets/bloodmagic/models/item/lavasigil.json
+5a76914a87fc9b99079bb6afed1d4cfe3e4a532e assets/bloodmagic/models/item/lightritualstone.json
 15d8178b626da912334774142d40d1012fb21fa0 assets/bloodmagic/models/item/magicianbloodorb.json
 0a3566d3c86403f24c22977dd32ffaec727a9ad3 assets/bloodmagic/models/item/masterbloodorb.json
 7596826c5b40c2809eb0a42eb5f5f2089290e3e5 assets/bloodmagic/models/item/miningsigil.json
@@ -58,6 +77,7 @@ baafdb5915c5fbc99b84a54670ed64a6f26cb0fe assets/bloodmagic/models/item/reagentma
 95b2925e96a7df71d72568e0ed7b03290293cbe7 assets/bloodmagic/models/item/reagentvoid.json
 fd1447d943ddc4540a51a72dcbb245d77d45da71 assets/bloodmagic/models/item/reagentwater.json
 50bf796adbed412488df48ed9250fc9b0ecd851f assets/bloodmagic/models/item/reinforcedslate.json
+2722891c9c40b124d85bf9ff8eb885e175f5e6ff assets/bloodmagic/models/item/ritualstone.json
 db73abb3bcb1731b6fc389e3577910b6aab87b10 assets/bloodmagic/models/item/sacrificerune.json
 9403d6195d4d38d5876c2a42f4edfb9bdcd05210 assets/bloodmagic/models/item/sacrificialdagger.json
 cc71421e98ee7ee047a4cfbb6cb69529c2b02d4e assets/bloodmagic/models/item/selfsacrificerune.json
@@ -102,6 +122,7 @@ ec6f6bf7f520182b2044f3cc5a10f1d4c7a8d7ab assets/bloodmagic/models/item/variants/
 2029220112f89a3f4d432ab4749dff6143846659 assets/bloodmagic/models/item/variants/soulsword_vengeful_activated.json
 0f5a3e1e5993a03ccda156eed855b71fbd0be0a2 assets/bloodmagic/models/item/variants/soulsword_vengeful_deactivated.json
 836b5a7f19915af809795a72983a23f0d5f9c5b2 assets/bloodmagic/models/item/voidsigil.json
+a31019db55828cb937a071ac2f74b125a2d0c955 assets/bloodmagic/models/item/waterritualstone.json
 7426fed5f833ce3d08602f727f1467dd3e107991 assets/bloodmagic/models/item/watersigil.json
 f72efc172699d43405019add97f455bd6b7f452b assets/bloodmagic/models/item/weakbloodorb.json
 828c0f89e747d48d37c6a86030a8ec59ca5c29cb data/bloodmagic/advancements/recipes/bloodmagictab/blood_altar.json
@@ -116,6 +137,7 @@ e897d6f91e2a0bd12b0da0a50e5c897294989e7c data/bloodmagic/advancements/recipes/bl
 2d29dd0c24c4c11d7438cdeeb26b9357d4359e2c data/bloodmagic/advancements/recipes/bloodmagictab/soul_forge.json
 7a7f9f995d2414289d07c0a145647c8e735a6b78 data/bloodmagic/advancements/recipes/bloodmagictab/soul_snare.json
 639ebb2ccabb2eaece59be96c2e6f28c31f4d2f4 data/bloodmagic/loot_tables/blocks/accelerationrune.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/airritualstone.json
 443550be9eaf1021b11fd2bbe6afcfe2cee6f7ad data/bloodmagic/loot_tables/blocks/alchemyarray.json
 17d8dcc62320d5d2eeb781e925963d9b9d5eec54 data/bloodmagic/loot_tables/blocks/altar.json
 05bb6268d7e884c962061a632e162d5baf73271e data/bloodmagic/loot_tables/blocks/altarcapacityrune.json
@@ -124,11 +146,17 @@ e897d6f91e2a0bd12b0da0a50e5c897294989e7c data/bloodmagic/advancements/recipes/bl
 f1a8e3131d85077665563372cad868534a72fb31 data/bloodmagic/loot_tables/blocks/bloodlight.json
 779b809a2a51e6dab46f9e6799249f2f14653ebb data/bloodmagic/loot_tables/blocks/chargingrune.json
 a9fcfc656fab957328c10ee1d9d33807e697b7f7 data/bloodmagic/loot_tables/blocks/dislocationrune.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/duskritualstone.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/earthritualstone.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/fireritualstone.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/lightritualstone.json
 95442c1bb740fab2eb8ee051f7184813f6023afa data/bloodmagic/loot_tables/blocks/orbcapacityrune.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/ritualstone.json
 e0239eff7762a414a4e4faa0158d844dffb8c1f6 data/bloodmagic/loot_tables/blocks/sacrificerune.json
 9b697e37046b6238b3a19eae9113b88010ccff32 data/bloodmagic/loot_tables/blocks/selfsacrificerune.json
 f748a5ba8838b50de0502f132fe2a65f4726dae6 data/bloodmagic/loot_tables/blocks/soulforge.json
 015e07226fd90935f7ec663f4bcf3873a57a82d1 data/bloodmagic/loot_tables/blocks/speedrune.json
+26e3f34021426def32602e5ae7755e4672878320 data/bloodmagic/loot_tables/blocks/waterritualstone.json
 f41b0e9dfab608c42a85c3c5c5bbc050b03f02a1 data/bloodmagic/recipes/altar/apprenticebloodorb.json
 2a67e37497a571b5ee944375d315fddccea87697 data/bloodmagic/recipes/altar/daggerofsacrifice.json
 c5a4a256a7437f2e13c574a6f0c4d75fc2e718cb data/bloodmagic/recipes/altar/demonicslate.json
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/airritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/airritualstone.json
new file mode 100644
index 00000000..a64ce156
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/airritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/airritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/duskritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/duskritualstone.json
new file mode 100644
index 00000000..474324b4
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/duskritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/duskritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/earthritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/earthritualstone.json
new file mode 100644
index 00000000..75d2c5eb
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/earthritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/earthritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/fireritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/fireritualstone.json
new file mode 100644
index 00000000..33e7f1b2
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/fireritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/fireritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/lightritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/lightritualstone.json
new file mode 100644
index 00000000..d198280b
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/lightritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/lightritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/ritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/ritualstone.json
new file mode 100644
index 00000000..8f09bcba
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/ritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/ritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/blockstates/waterritualstone.json b/src/generated/resources/assets/bloodmagic/blockstates/waterritualstone.json
new file mode 100644
index 00000000..35b1d3aa
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/blockstates/waterritualstone.json
@@ -0,0 +1,7 @@
+{
+  "variants": {
+    "": {
+      "model": "bloodmagic:block/waterritualstone"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/airritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/airritualstone.json
new file mode 100644
index 00000000..428b9552
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/airritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/airritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/duskritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/duskritualstone.json
new file mode 100644
index 00000000..ecfcfa10
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/duskritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/duskritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/earthritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/earthritualstone.json
new file mode 100644
index 00000000..e0949faa
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/earthritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/earthritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/fireritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/fireritualstone.json
new file mode 100644
index 00000000..1661fe87
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/fireritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/fireritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/lightritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/lightritualstone.json
new file mode 100644
index 00000000..89bce579
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/lightritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/lightritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/ritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/ritualstone.json
new file mode 100644
index 00000000..ca5b7b23
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/ritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/ritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/block/waterritualstone.json b/src/generated/resources/assets/bloodmagic/models/block/waterritualstone.json
new file mode 100644
index 00000000..1222a06c
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/block/waterritualstone.json
@@ -0,0 +1,6 @@
+{
+  "parent": "minecraft:block/cube_all",
+  "textures": {
+    "all": "bloodmagic:block/waterritualstone"
+  }
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/airritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/airritualstone.json
new file mode 100644
index 00000000..a32ef885
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/airritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/airritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/duskritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/duskritualstone.json
new file mode 100644
index 00000000..289ff1ee
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/duskritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/duskritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/earthritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/earthritualstone.json
new file mode 100644
index 00000000..3758ede0
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/earthritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/earthritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/fireritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/fireritualstone.json
new file mode 100644
index 00000000..6eef57c2
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/fireritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/fireritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/lightritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/lightritualstone.json
new file mode 100644
index 00000000..96ca64a5
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/lightritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/lightritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/ritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/ritualstone.json
new file mode 100644
index 00000000..4dea1d50
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/ritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/ritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/assets/bloodmagic/models/item/waterritualstone.json b/src/generated/resources/assets/bloodmagic/models/item/waterritualstone.json
new file mode 100644
index 00000000..4c02099f
--- /dev/null
+++ b/src/generated/resources/assets/bloodmagic/models/item/waterritualstone.json
@@ -0,0 +1,3 @@
+{
+  "parent": "bloodmagic:block/waterritualstone"
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/airritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/airritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/airritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/duskritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/duskritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/duskritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/earthritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/earthritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/earthritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/fireritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/fireritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/fireritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/lightritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/lightritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/lightritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/ritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/ritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/ritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/bloodmagic/loot_tables/blocks/waterritualstone.json b/src/generated/resources/data/bloodmagic/loot_tables/blocks/waterritualstone.json
new file mode 100644
index 00000000..e8935390
--- /dev/null
+++ b/src/generated/resources/data/bloodmagic/loot_tables/blocks/waterritualstone.json
@@ -0,0 +1,19 @@
+{
+  "type": "minecraft:block",
+  "pools": [
+    {
+      "rolls": 1,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "bloodmagic:ritualstone"
+        }
+      ],
+      "conditions": [
+        {
+          "condition": "minecraft:survives_explosion"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/main/java/WayofTime/bloodmagic/BloodMagic.java b/src/main/java/WayofTime/bloodmagic/BloodMagic.java
index 3ad54158..fb69752f 100644
--- a/src/main/java/WayofTime/bloodmagic/BloodMagic.java
+++ b/src/main/java/WayofTime/bloodmagic/BloodMagic.java
@@ -49,6 +49,7 @@ import wayoftime.bloodmagic.core.recipe.IngredientBloodOrb;
 import wayoftime.bloodmagic.core.registry.OrbRegistry;
 import wayoftime.bloodmagic.network.BloodMagicPacketHandler;
 import wayoftime.bloodmagic.potion.BloodMagicPotions;
+import wayoftime.bloodmagic.ritual.RitualManager;
 import wayoftime.bloodmagic.tile.TileAlchemyArray;
 import wayoftime.bloodmagic.tile.TileAltar;
 import wayoftime.bloodmagic.tile.TileSoulForge;
@@ -66,6 +67,7 @@ public class BloodMagic
 	private static Gson GSON = null;
 
 	public static final BloodMagicPacketHandler packetHandler = new BloodMagicPacketHandler();
+	public static final RitualManager RITUAL_MANAGER = new RitualManager();
 
 	public BloodMagic()
 	{
@@ -134,8 +136,11 @@ public class BloodMagic
 	public void onLoadComplete(FMLLoadCompleteEvent event)
 	{
 		OrbRegistry.tierMap.put(BloodMagicItems.ORB_WEAK.get().getTier(), new ItemStack(BloodMagicItems.WEAK_BLOOD_ORB.get()));
+		OrbRegistry.tierMap.put(BloodMagicItems.ORB_APPRENTICE.get().getTier(), new ItemStack(BloodMagicItems.APPRENTICE_BLOOD_ORB.get()));
+		OrbRegistry.tierMap.put(BloodMagicItems.ORB_MAGICIAN.get().getTier(), new ItemStack(BloodMagicItems.MAGICIAN_BLOOD_ORB.get()));
+		OrbRegistry.tierMap.put(BloodMagicItems.ORB_MASTER.get().getTier(), new ItemStack(BloodMagicItems.MASTER_BLOOD_ORB.get()));
 		BloodMagicCorePlugin.INSTANCE.register(BloodMagicAPI.INSTANCE);
-
+		RITUAL_MANAGER.discover();
 	}
 
 	public void registerTileEntityTypes(RegistryEvent.Register<TileEntityType<?>> event)
diff --git a/src/main/java/wayoftime/bloodmagic/block/enums/EnumRitualController.java b/src/main/java/wayoftime/bloodmagic/block/enums/EnumRitualController.java
new file mode 100644
index 00000000..29948a7a
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/block/enums/EnumRitualController.java
@@ -0,0 +1,23 @@
+package wayoftime.bloodmagic.block.enums;
+
+import java.util.Locale;
+
+import net.minecraft.util.IStringSerializable;
+
+//TODO: Will want to probably discontinue this due to The Flattening
+public enum EnumRitualController implements IStringSerializable
+{
+	MASTER, IMPERFECT, INVERTED,;
+
+	@Override
+	public String toString()
+	{
+		return name().toLowerCase(Locale.ENGLISH);
+	}
+
+	@Override
+	public String getString()
+	{
+		return this.toString();
+	}
+}
diff --git a/src/main/java/wayoftime/bloodmagic/common/block/BlockMasterRitualStone.java b/src/main/java/wayoftime/bloodmagic/common/block/BlockMasterRitualStone.java
new file mode 100644
index 00000000..7fb09854
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/common/block/BlockMasterRitualStone.java
@@ -0,0 +1,130 @@
+package wayoftime.bloodmagic.common.block;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.SoundType;
+import net.minecraft.block.material.Material;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ActionResultType;
+import net.minecraft.util.Direction;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.BlockRayTraceResult;
+import net.minecraft.util.text.TranslationTextComponent;
+import net.minecraft.world.Explosion;
+import net.minecraft.world.IBlockReader;
+import net.minecraft.world.IWorld;
+import net.minecraft.world.World;
+import net.minecraftforge.common.ToolType;
+import wayoftime.bloodmagic.BloodMagic;
+import wayoftime.bloodmagic.common.item.ItemActivationCrystal;
+import wayoftime.bloodmagic.iface.IBindable;
+import wayoftime.bloodmagic.ritual.Ritual;
+import wayoftime.bloodmagic.tile.TileMasterRitualStone;
+import wayoftime.bloodmagic.util.helper.RitualHelper;
+
+public class BlockMasterRitualStone extends Block
+{
+	public final boolean isInverted;
+
+	public BlockMasterRitualStone(boolean isInverted)
+	{
+		super(Properties.create(Material.ROCK).sound(SoundType.STONE).hardnessAndResistance(2.0F, 5.0F).harvestTool(ToolType.PICKAXE).harvestLevel(2));
+		this.isInverted = isInverted;
+	}
+
+	@Override
+	public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult blockRayTraceResult)
+	{
+		ItemStack heldItem = player.getHeldItem(hand);
+		TileEntity tile = world.getTileEntity(pos);
+
+		if (tile instanceof TileMasterRitualStone)
+		{
+			if (heldItem.getItem() instanceof ItemActivationCrystal)
+			{
+				if (((IBindable) heldItem.getItem()).getBinding(heldItem) == null)
+					return ActionResultType.FAIL;
+
+				String key = RitualHelper.getValidRitual(world, pos);
+				if (!key.isEmpty())
+				{
+					Ritual ritual = BloodMagic.RITUAL_MANAGER.getRitual(key);
+					if (ritual != null)
+					{
+						Direction direction = RitualHelper.getDirectionOfRitual(world, pos, ritual);
+						// TODO: Give a message stating that this ritual is not a valid ritual.
+						if (direction != null && RitualHelper.checkValidRitual(world, pos, ritual, direction))
+						{
+							if (((TileMasterRitualStone) tile).activateRitual(heldItem, player, BloodMagic.RITUAL_MANAGER.getRitual(key)))
+							{
+								((TileMasterRitualStone) tile).setDirection(direction);
+								if (isInverted)
+									((TileMasterRitualStone) tile).setInverted(true);
+							}
+						} else
+						{
+							player.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.notValid"), true);
+						}
+					} else
+					{
+						player.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.notValid"), true);
+					}
+				} else
+				{
+					player.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.notValid"), true);
+				}
+			}
+		}
+
+		return ActionResultType.FAIL;
+	}
+
+	@Override
+	public void onPlayerDestroy(IWorld world, BlockPos blockPos, BlockState blockState)
+	{
+		TileMasterRitualStone tile = (TileMasterRitualStone) world.getTileEntity(blockPos);
+		if (tile != null)
+			((TileMasterRitualStone) tile).stopRitual(Ritual.BreakType.BREAK_MRS);
+
+		super.onPlayerDestroy(world, blockPos, blockState);
+	}
+
+	@Override
+	public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving)
+	{
+		if (!state.isIn(newState.getBlock()))
+		{
+			TileEntity tile = worldIn.getTileEntity(pos);
+			if (tile instanceof TileMasterRitualStone)
+			{
+				((TileMasterRitualStone) tile).stopRitual(Ritual.BreakType.BREAK_MRS);
+			}
+
+			super.onReplaced(state, worldIn, pos, newState, isMoving);
+		}
+	}
+
+	@Override
+	public void onExplosionDestroy(World world, BlockPos pos, Explosion explosion)
+	{
+		TileEntity tile = world.getTileEntity(pos);
+
+		if (tile instanceof TileMasterRitualStone)
+			((TileMasterRitualStone) tile).stopRitual(Ritual.BreakType.EXPLOSION);
+	}
+
+	@Override
+	public boolean hasTileEntity(BlockState state)
+	{
+		return true;
+	}
+
+	@Override
+	public TileEntity createTileEntity(BlockState state, IBlockReader world)
+	{
+		return new TileMasterRitualStone();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/common/block/BlockRitualStone.java b/src/main/java/wayoftime/bloodmagic/common/block/BlockRitualStone.java
new file mode 100644
index 00000000..d76bfe60
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/common/block/BlockRitualStone.java
@@ -0,0 +1,89 @@
+package wayoftime.bloodmagic.common.block;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.SoundType;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.util.ITooltipFlag;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TranslationTextComponent;
+import net.minecraft.world.IBlockReader;
+import net.minecraft.world.World;
+import net.minecraftforge.common.ToolType;
+import wayoftime.bloodmagic.ritual.EnumRuneType;
+import wayoftime.bloodmagic.ritual.IRitualStone;
+
+public class BlockRitualStone extends Block implements IRitualStone
+{
+	private final EnumRuneType type;
+
+	public BlockRitualStone(EnumRuneType type)
+	{
+		super(Properties.create(Material.ROCK).hardnessAndResistance(2.0F, 5.0F).sound(SoundType.STONE).harvestTool(ToolType.PICKAXE).harvestLevel(2));
+		this.type = type;
+	}
+
+	@Override
+	public void addInformation(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
+	{
+		tooltip.add(new TranslationTextComponent("tooltip.bloodmagic.decoration.safe"));
+		super.addInformation(stack, world, tooltip, flag);
+	}
+
+//	@Override
+//	public int damageDropped(BlockState state)
+//	{
+//		return 0;
+//	}
+//
+//	@Override
+//	public boolean canSilkHarvest(World world, BlockPos pos, BlockState state, PlayerEntity player)
+//	{
+//		return false;
+//	}
+
+	@Override
+	public boolean isRuneType(World world, BlockPos pos, EnumRuneType runeType)
+	{
+		return type.equals(runeType);
+	}
+
+	@Override
+	public void setRuneType(World world, BlockPos pos, EnumRuneType runeType)
+	{
+		Block runeBlock = this;
+		switch (type)
+		{
+		case AIR:
+			runeBlock = BloodMagicBlocks.AIR_RITUAL_STONE.get();
+			break;
+		case BLANK:
+			runeBlock = BloodMagicBlocks.BLANK_RITUAL_STONE.get();
+			break;
+		case DAWN:
+			runeBlock = BloodMagicBlocks.DAWN_RITUAL_STONE.get();
+			break;
+		case DUSK:
+			runeBlock = BloodMagicBlocks.DUSK_RITUAL_STONE.get();
+			break;
+		case EARTH:
+			runeBlock = BloodMagicBlocks.EARTH_RITUAL_STONE.get();
+			break;
+		case FIRE:
+			runeBlock = BloodMagicBlocks.FIRE_RITUAL_STONE.get();
+			break;
+		case WATER:
+			runeBlock = BloodMagicBlocks.WATER_RITUAL_STONE.get();
+			break;
+		}
+
+		BlockState newState = runeBlock.getDefaultState();
+		world.setBlockState(pos, newState);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/common/block/BloodMagicBlocks.java b/src/main/java/wayoftime/bloodmagic/common/block/BloodMagicBlocks.java
index 955b36b8..48e0dc40 100644
--- a/src/main/java/wayoftime/bloodmagic/common/block/BloodMagicBlocks.java
+++ b/src/main/java/wayoftime/bloodmagic/common/block/BloodMagicBlocks.java
@@ -18,6 +18,7 @@ import net.minecraftforge.registries.ForgeRegistries;
 import wayoftime.bloodmagic.BloodMagic;
 import wayoftime.bloodmagic.block.enums.BloodRuneType;
 import wayoftime.bloodmagic.common.item.BloodMagicItems;
+import wayoftime.bloodmagic.ritual.EnumRuneType;
 import wayoftime.bloodmagic.tile.contailer.ContainerSoulForge;
 
 public class BloodMagicBlocks
@@ -48,6 +49,16 @@ public class BloodMagicBlocks
 	public static final RegistryObject<Block> BLOOD_ALTAR = BLOCKS.register("altar", () -> new BlockAltar());
 	public static final RegistryObject<Block> BLOOD_LIGHT = BLOCKS.register("bloodlight", () -> new BlockBloodLight());
 
+	public static final RegistryObject<Block> BLANK_RITUAL_STONE = BLOCKS.register("ritualstone", () -> new BlockRitualStone(EnumRuneType.BLANK));
+	public static final RegistryObject<Block> AIR_RITUAL_STONE = BLOCKS.register("airritualstone", () -> new BlockRitualStone(EnumRuneType.AIR));
+	public static final RegistryObject<Block> WATER_RITUAL_STONE = BLOCKS.register("waterritualstone", () -> new BlockRitualStone(EnumRuneType.WATER));
+	public static final RegistryObject<Block> FIRE_RITUAL_STONE = BLOCKS.register("fireritualstone", () -> new BlockRitualStone(EnumRuneType.FIRE));
+	public static final RegistryObject<Block> EARTH_RITUAL_STONE = BLOCKS.register("earthritualstone", () -> new BlockRitualStone(EnumRuneType.EARTH));
+	public static final RegistryObject<Block> DUSK_RITUAL_STONE = BLOCKS.register("duskritualstone", () -> new BlockRitualStone(EnumRuneType.DUSK));
+	public static final RegistryObject<Block> DAWN_RITUAL_STONE = BLOCKS.register("lightritualstone", () -> new BlockRitualStone(EnumRuneType.DAWN));
+
+	public static final RegistryObject<Block> MASTER_RITUAL_STONE = BASICBLOCKS.register("masterritualstone", () -> new BlockMasterRitualStone(false));
+
 	private static ForgeFlowingFluid.Properties makeProperties()
 	{
 		return new ForgeFlowingFluid.Properties(LIFE_ESSENCE_FLUID, LIFE_ESSENCE_FLUID_FLOWING, FluidAttributes.builder(FLUID_STILL, FLUID_FLOWING)).bucket(LIFE_ESSENCE_BUCKET).block(LIFE_ESSENCE_BLOCK);
diff --git a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorBlockStates.java b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorBlockStates.java
index cbb374ea..04b013f1 100644
--- a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorBlockStates.java
+++ b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorBlockStates.java
@@ -28,6 +28,13 @@ public class GeneratorBlockStates extends BlockStateProvider
 		}
 
 		buildCubeAll(BloodMagicBlocks.BLOOD_LIGHT.get());
+		buildCubeAll(BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.AIR_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.WATER_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.FIRE_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.EARTH_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.DUSK_RITUAL_STONE.get());
+		buildCubeAll(BloodMagicBlocks.DAWN_RITUAL_STONE.get());
 	}
 
 	private void buildCubeAll(Block block)
diff --git a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorItemModels.java b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorItemModels.java
index 06c2f49c..77739743 100644
--- a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorItemModels.java
+++ b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorItemModels.java
@@ -36,6 +36,14 @@ public class GeneratorItemModels extends ItemModelProvider
 			registerBlockModel(block.get());
 		}
 
+		registerBlockModel(BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.AIR_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.WATER_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.FIRE_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.EARTH_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.DUSK_RITUAL_STONE.get());
+		registerBlockModel(BloodMagicBlocks.DAWN_RITUAL_STONE.get());
+
 		registerToggleableItem(BloodMagicItems.GREEN_GROVE_SIGIL.get());
 		registerToggleableItem(BloodMagicItems.FAST_MINER_SIGIL.get());
 		registerToggleableItem(BloodMagicItems.MAGNETISM_SIGIL.get());
diff --git a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorLootTable.java b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorLootTable.java
index 5c4092c7..4115ea9a 100644
--- a/src/main/java/wayoftime/bloodmagic/common/data/GeneratorLootTable.java
+++ b/src/main/java/wayoftime/bloodmagic/common/data/GeneratorLootTable.java
@@ -49,33 +49,17 @@ public class GeneratorLootTable extends LootTableProvider
 				this.registerDropSelfLootTable(block.get());
 			}
 
-			this.registerDropSelfLootTable(BloodMagicBlocks.BLOOD_ALTAR.get());
+			registerDropSelfLootTable(BloodMagicBlocks.BLOOD_ALTAR.get());
 			registerNoDropLootTable(BloodMagicBlocks.ALCHEMY_ARRAY.get());
 			registerNoDropLootTable(BloodMagicBlocks.BLOOD_LIGHT.get());
-			this.registerDropSelfLootTable(BloodMagicBlocks.SOUL_FORGE.get());
-
-//			LootPool.Builder builder = LootPool.builder().name(ModBlocks.GOO_BLOCK.get().getRegistryName().toString()).rolls(ConstantRange.of(1)).acceptCondition(SurvivesExplosion.builder()).addEntry(ItemLootEntry.builder(ModItems.GOO_RESIDUE.get()));
-//			this.registerLootTable(ModBlocks.GOO_BLOCK.get(), LootTable.builder().addLootPool(builder));
-//
-//			LootPool.Builder builder2 = LootPool.builder().name(ModBlocks.GOO_BLOCK_TERRAIN.get().getRegistryName().toString()).rolls(ConstantRange.of(1)).acceptCondition(SurvivesExplosion.builder()).addEntry(ItemLootEntry.builder(ModItems.GOO_RESIDUE.get()));
-//			this.registerLootTable(ModBlocks.GOO_BLOCK_TERRAIN.get(), LootTable.builder().addLootPool(builder2));
-//
-//			this.registerDropSelfLootTable(ModBlocks.GOO_BLOCK_POISON.get());
-//			this.registerDropSelfLootTable(ModBlocks.GNT_BLOCK_T1.get());
-//			this.registerDropSelfLootTable(ModBlocks.GNT_BLOCK_T2.get());
-//			this.registerDropSelfLootTable(ModBlocks.GNT_BLOCK_T3.get());
-//			this.registerDropSelfLootTable(ModBlocks.GNT_BLOCK_T4.get());
-//			this.registerDropSelfLootTable(ModBlocks.TURRET_BLOCK.get());
-//			this.registerDropSelfLootTable(ModBlocks.ZAPPER_TURRET_BLOCK.get());
-//			this.registerDropSelfLootTable(ModBlocks.ANTI_GOO_BEACON.get());
-//			this.registerDropSelfLootTable(ModBlocks.ANTI_GOO_FIELD_GEN.get());
-//			this.registerDropSelfLootTable(ModBlocks.GOOLIMINATIONFIELDGEN.get());
-//			this.registerDropSelfLootTable(ModBlocks.GOO_DETECTOR.get());
-//			this.registerDropping(ModBlocks.GOO_RENDER.get(), ItemStack.EMPTY.getItem());
-//			// this.registerDropping(ModBlocks.GOO_RENDER_BURST.get(),
-//			// ItemStack.EMPTY.getItem());
-//			this.registerDropping(ModBlocks.GOO_RENDER_TERRAIN.get(), ItemStack.EMPTY.getItem());
-
+			registerDropSelfLootTable(BloodMagicBlocks.SOUL_FORGE.get());
+			registerDropSelfLootTable(BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.AIR_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.WATER_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.FIRE_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.EARTH_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.DUSK_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
+			registerDropping(BloodMagicBlocks.DAWN_RITUAL_STONE.get(), BloodMagicBlocks.BLANK_RITUAL_STONE.get());
 		}
 
 		private void registerNoDropLootTable(Block block)
diff --git a/src/main/java/wayoftime/bloodmagic/common/item/BloodMagicItems.java b/src/main/java/wayoftime/bloodmagic/common/item/BloodMagicItems.java
index 69855511..f79ae7b2 100644
--- a/src/main/java/wayoftime/bloodmagic/common/item/BloodMagicItems.java
+++ b/src/main/java/wayoftime/bloodmagic/common/item/BloodMagicItems.java
@@ -54,6 +54,13 @@ public class BloodMagicItems
 	public static final RegistryObject<Item> ORB_RUNE_ITEM = ITEMS.register("orbcapacityrune", () -> new BlockItem(BloodMagicBlocks.ORB_RUNE.get(), new Item.Properties().group(BloodMagic.TAB)));
 	public static final RegistryObject<Item> ACCELERATION_RUNE_ITEM = ITEMS.register("accelerationrune", () -> new BlockItem(BloodMagicBlocks.ACCELERATION_RUNE.get(), new Item.Properties().group(BloodMagic.TAB)));
 	public static final RegistryObject<Item> CHARGING_RUNE_ITEM = ITEMS.register("chargingrune", () -> new BlockItem(BloodMagicBlocks.CHARGING_RUNE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> BLANK_RITUAL_STONE_ITEM = ITEMS.register("ritualstone", () -> new BlockItem(BloodMagicBlocks.BLANK_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> AIR_RITUAL_STONE_ITEM = ITEMS.register("airritualstone", () -> new BlockItem(BloodMagicBlocks.AIR_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> WATER_RITUAL_STONE_ITEM = ITEMS.register("waterritualstone", () -> new BlockItem(BloodMagicBlocks.WATER_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> FIRE_RITUAL_STONE_ITEM = ITEMS.register("fireritualstone", () -> new BlockItem(BloodMagicBlocks.FIRE_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> EARTH_RITUAL_STONE_ITEM = ITEMS.register("earthritualstone", () -> new BlockItem(BloodMagicBlocks.EARTH_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> DUSK_RITUAL_STONE_ITEM = ITEMS.register("duskritualstone", () -> new BlockItem(BloodMagicBlocks.DUSK_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
+	public static final RegistryObject<Item> DAWN_RITUAL_STONE_ITEM = ITEMS.register("lightritualstone", () -> new BlockItem(BloodMagicBlocks.DAWN_RITUAL_STONE.get(), new Item.Properties().group(BloodMagic.TAB)));
 
 	public static final RegistryObject<Item> BLOOD_ALTAR_ITEM = ITEMS.register("altar", () -> new BlockItem(BloodMagicBlocks.BLOOD_ALTAR.get(), new Item.Properties().group(BloodMagic.TAB)));
 
@@ -87,6 +94,10 @@ public class BloodMagicItems
 	public static final RegistryObject<Item> ARCANE_ASHES = BASICITEMS.register("arcaneashes", () -> new ItemArcaneAshes());
 	public static final RegistryObject<Item> DAGGER_OF_SACRIFICE = BASICITEMS.register("daggerofsacrifice", () -> new ItemDaggerOfSacrifice());
 
+	public static final RegistryObject<Item> WEAK_ACTIVATION_CRYSTAL = BASICITEMS.register("activationcrystalweak", () -> new ItemActivationCrystal(ItemActivationCrystal.CrystalType.WEAK));
+	public static final RegistryObject<Item> AWAKENED_ACTIVATION_CRYSTAL = BASICITEMS.register("activationcrystalawakened", () -> new ItemActivationCrystal(ItemActivationCrystal.CrystalType.AWAKENED));
+	public static final RegistryObject<Item> CREATIVE_ACTIVATION_CRYSTAL = BASICITEMS.register("activationcrystalcreative", () -> new ItemActivationCrystal(ItemActivationCrystal.CrystalType.CREATIVE));
+
 	// Reagents used to make the Sigils
 	public static final RegistryObject<Item> REAGENT_WATER = BASICITEMS.register("reagentwater", () -> new ItemBase());
 	public static final RegistryObject<Item> REAGENT_LAVA = BASICITEMS.register("reagentlava", () -> new ItemBase());
diff --git a/src/main/java/wayoftime/bloodmagic/common/item/ItemActivationCrystal.java b/src/main/java/wayoftime/bloodmagic/common/item/ItemActivationCrystal.java
new file mode 100644
index 00000000..83d97492
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/common/item/ItemActivationCrystal.java
@@ -0,0 +1,72 @@
+package wayoftime.bloodmagic.common.item;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.client.util.ITooltipFlag;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TranslationTextComponent;
+import net.minecraft.world.World;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import wayoftime.bloodmagic.BloodMagic;
+import wayoftime.bloodmagic.core.data.Binding;
+import wayoftime.bloodmagic.iface.IBindable;
+
+public class ItemActivationCrystal extends Item implements IBindable
+{
+	final CrystalType type;
+
+	public ItemActivationCrystal(CrystalType type)
+	{
+		super(new Item.Properties().maxStackSize(1).group(BloodMagic.TAB));
+		this.type = type;
+	}
+
+	@Override
+	@OnlyIn(Dist.CLIENT)
+	public void addInformation(ItemStack stack, World world, List<ITextComponent> tooltip, ITooltipFlag flag)
+	{
+		tooltip.add(new TranslationTextComponent("tooltip.bloodmagic.activation_crystal." + type.name().toLowerCase()));
+
+		if (!stack.hasTag())
+			return;
+
+		Binding binding = getBinding(stack);
+		if (binding != null)
+			tooltip.add(new TranslationTextComponent("tooltip.bloodmagic.currentOwner", binding.getOwnerName()));
+
+		super.addInformation(stack, world, tooltip, flag);
+	}
+
+	public int getCrystalLevel(ItemStack stack)
+	{
+		return this.type.equals(CrystalType.CREATIVE) ? Integer.MAX_VALUE : type.ordinal() + 1;
+	}
+
+	public enum CrystalType
+	{
+		WEAK, AWAKENED, CREATIVE,;
+
+		@Nonnull
+		public static ItemStack getStack(int level)
+		{
+			if (level < 0)
+			{
+				level = 0;
+			}
+			switch (level)
+			{
+			case 0:
+				return new ItemStack(BloodMagicItems.WEAK_ACTIVATION_CRYSTAL.get());
+			case 1:
+				return new ItemStack(BloodMagicItems.AWAKENED_ACTIVATION_CRYSTAL.get());
+			default:
+				return new ItemStack(BloodMagicItems.CREATIVE_ACTIVATION_CRYSTAL.get());
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/demonaura/PosXY.java b/src/main/java/wayoftime/bloodmagic/demonaura/PosXY.java
new file mode 100644
index 00000000..2e2fd6da
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/demonaura/PosXY.java
@@ -0,0 +1,76 @@
+package wayoftime.bloodmagic.demonaura;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class PosXY implements Comparable<PosXY>
+{
+	public int x;
+	public int y;
+
+	public PosXY()
+	{
+	}
+
+	public PosXY(int x, int y)
+	{
+		this.x = x;
+		this.y = y;
+	}
+
+	@Override
+	public int compareTo(PosXY c)
+	{
+		return this.y == c.y ? this.x - c.x : this.y - c.y;
+	}
+
+	public float getDistanceSquared(int x, int z)
+	{
+		float f = this.x - x;
+		float f2 = this.y - z;
+		return f * f + f2 * f2;
+	}
+
+	public float getDistanceSquaredToChunkCoordinates(PosXY c)
+	{
+		return getDistanceSquared(c.x, c.y);
+	}
+
+	public void setX(int x)
+	{
+		this.x = x;
+	}
+
+	public void setY(int y)
+	{
+		this.y = y;
+	}
+
+	@Override
+	public String toString()
+	{
+		return new ToStringBuilder(this).append("x", x).append("y", y).toString();
+	}
+
+	@Override
+	public boolean equals(Object o)
+	{
+		if (this == o)
+			return true;
+		if (!(o instanceof PosXY))
+			return false;
+
+		PosXY posXY = (PosXY) o;
+
+		if (x != posXY.x)
+			return false;
+		return y == posXY.y;
+	}
+
+	@Override
+	public int hashCode()
+	{
+		int result = x;
+		result = 31 * result + y;
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/demonaura/WillChunk.java b/src/main/java/wayoftime/bloodmagic/demonaura/WillChunk.java
new file mode 100644
index 00000000..f93cfb8f
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/demonaura/WillChunk.java
@@ -0,0 +1,72 @@
+package wayoftime.bloodmagic.demonaura;
+
+import java.lang.ref.WeakReference;
+
+import net.minecraft.world.chunk.Chunk;
+import wayoftime.bloodmagic.will.DemonWillHolder;
+
+public class WillChunk
+{
+	PosXY loc;
+	private short base;
+	private DemonWillHolder currentWill = new DemonWillHolder();
+	private WeakReference<Chunk> chunkRef;
+
+	public WillChunk(PosXY loc)
+	{
+		this.loc = loc;
+	}
+
+	public WillChunk(Chunk chunk, short base, DemonWillHolder currentWill)
+	{
+		this.loc = new PosXY(chunk.getPos().x, chunk.getPos().z);
+		this.chunkRef = new WeakReference(chunk);
+		this.base = base;
+		this.currentWill = currentWill;
+	}
+
+	public boolean isModified()
+	{
+		return (this.chunkRef != null) && (this.chunkRef.get() != null) && this.chunkRef.get().isModified();
+	}
+
+	public PosXY getLoc()
+	{
+		return loc;
+	}
+
+	public void setLoc(PosXY loc)
+	{
+		this.loc = loc;
+	}
+
+	public short getBase()
+	{
+		return base;
+	}
+
+	public void setBase(short base)
+	{
+		this.base = base;
+	}
+
+	public DemonWillHolder getCurrentWill()
+	{
+		return currentWill;
+	}
+
+	public void setCurrentWill(DemonWillHolder currentWill)
+	{
+		this.currentWill = currentWill;
+	}
+
+	public WeakReference<Chunk> getChunkRef()
+	{
+		return chunkRef;
+	}
+
+	public void setChunkRef(WeakReference<Chunk> chunkRef)
+	{
+		this.chunkRef = chunkRef;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/demonaura/WillWorld.java b/src/main/java/wayoftime/bloodmagic/demonaura/WillWorld.java
new file mode 100644
index 00000000..b1f92d9a
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/demonaura/WillWorld.java
@@ -0,0 +1,49 @@
+package wayoftime.bloodmagic.demonaura;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.minecraft.util.ResourceLocation;
+
+public class WillWorld
+{
+	// TODO: It was noted I may need to use RegistryKey<World> instead.
+	ResourceLocation dim;
+	ConcurrentHashMap<PosXY, WillChunk> willChunks = new ConcurrentHashMap<>();
+
+//    private static ConcurrentHashMap<PosXY, AspectList> nodeTickets = new ConcurrentHashMap();
+
+	public WillWorld(ResourceLocation resourceLocation)
+	{
+		this.dim = resourceLocation;
+	}
+
+	public WillChunk getWillChunkAt(int x, int y)
+	{
+		return getWillChunkAt(new PosXY(x, y));
+	}
+
+	public WillChunk getWillChunkAt(PosXY loc)
+	{
+		return this.willChunks.get(loc);
+	}
+
+	public ConcurrentHashMap<PosXY, WillChunk> getWillChunks()
+	{
+		return willChunks;
+	}
+
+	public void setWillChunks(ConcurrentHashMap<PosXY, WillChunk> willChunks)
+	{
+		this.willChunks = willChunks;
+	}
+
+//    public static ConcurrentHashMap<PosXY, AspectList> getNodeTickets()
+//    {
+//        return nodeTickets;
+//    }
+//
+//    public static void setNodeTickets(ConcurrentHashMap<PosXY, AspectList> nodeTickets)
+//    {
+//        nodeTickets = nodeTickets;
+//    }
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/demonaura/WorldDemonWillHandler.java b/src/main/java/wayoftime/bloodmagic/demonaura/WorldDemonWillHandler.java
new file mode 100644
index 00000000..c26cf624
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/demonaura/WorldDemonWillHandler.java
@@ -0,0 +1,218 @@
+package wayoftime.bloodmagic.demonaura;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.Chunk;
+import wayoftime.bloodmagic.util.BMLog;
+import wayoftime.bloodmagic.will.DemonWillHolder;
+import wayoftime.bloodmagic.will.EnumDemonWillType;
+
+public class WorldDemonWillHandler
+{
+	public static ConcurrentHashMap<ResourceLocation, CopyOnWriteArrayList<PosXY>> dirtyChunks = new ConcurrentHashMap<>();
+	static ConcurrentHashMap<ResourceLocation, WillWorld> containedWills = new ConcurrentHashMap<>();
+
+	@Nullable
+	public static DemonWillHolder getWillHolder(ResourceLocation resourceLocation, int x, int y)
+	{
+		WillChunk chunk = getWillChunk(resourceLocation, x, y);
+		if (chunk != null)
+		{
+			return chunk.getCurrentWill();
+		}
+
+		return null;
+	}
+
+	public static DemonWillHolder getWillHolder(World world, BlockPos pos)
+	{
+		return getWillHolder(getDimensionResourceLocation(world), pos.getX() >> 4, pos.getZ() >> 4);
+	}
+
+	public static WillWorld getWillWorld(int dim)
+	{
+		return containedWills.get(dim);
+	}
+
+	@Nullable
+	public static WillChunk getWillChunk(ResourceLocation resourceLocation, int x, int y)
+	{
+		if (!containedWills.containsKey(resourceLocation))
+		{
+			addWillWorld(resourceLocation);
+		}
+
+		return (containedWills.get(resourceLocation)).getWillChunkAt(x, y);
+	}
+
+	public static void addWillWorld(ResourceLocation resourceLocation)
+	{
+		if (!containedWills.containsKey(resourceLocation))
+		{
+			containedWills.put(resourceLocation, new WillWorld(resourceLocation));
+			BMLog.DEBUG.info("Creating demon will cache for world {}", resourceLocation);
+		}
+	}
+
+	public static void removeWillWorld(int dim)
+	{
+		containedWills.remove(dim);
+		BMLog.DEBUG.info("Removing demon will cache for world {}", dim);
+	}
+
+	public static void addWillChunk(ResourceLocation resourceLocation, Chunk chunk, short base, DemonWillHolder currentWill)
+	{
+		WillWorld aw = containedWills.get(resourceLocation);
+		if (aw == null)
+		{
+			aw = new WillWorld(resourceLocation);
+		}
+		aw.getWillChunks().put(new PosXY(chunk.getPos().x, chunk.getPos().z), new WillChunk(chunk, base, currentWill));
+
+		containedWills.put(resourceLocation, aw);
+	}
+
+	public static void removeWillChunk(ResourceLocation resourceLocation, int x, int y)
+	{
+		WillWorld aw = containedWills.get(resourceLocation);
+		if (aw != null)
+		{
+			WillChunk chunk = aw.getWillChunks().remove(new PosXY(x, y));
+			if (chunk != null)
+			{
+				markChunkAsDirty(chunk, resourceLocation);
+			}
+		}
+	}
+
+	public static EnumDemonWillType getHighestDemonWillType(World world, BlockPos pos)
+	{
+		double currentMax = 0;
+		EnumDemonWillType currentHighest = EnumDemonWillType.DEFAULT;
+
+		WillChunk willChunk = getWillChunk(world, pos);
+
+		DemonWillHolder currentWill = willChunk.getCurrentWill();
+		for (EnumDemonWillType type : EnumDemonWillType.values())
+		{
+			if (currentWill.getWill(type) > currentMax)
+			{
+				currentMax = currentWill.getWill(type);
+				currentHighest = type;
+			}
+		}
+
+		return currentHighest;
+	}
+
+	public static double drainWill(World world, BlockPos pos, EnumDemonWillType type, double amount, boolean doDrain)
+	{
+		WillChunk willChunk = getWillChunk(world, pos);
+
+		DemonWillHolder currentWill = willChunk.getCurrentWill();
+		double drain = Math.min(currentWill.getWill(type), amount);
+		if (!doDrain)
+		{
+			return drain;
+		}
+
+		drain = currentWill.drainWill(type, drain);
+		markChunkAsDirty(willChunk, getDimensionResourceLocation(world));
+
+		return drain;
+	}
+
+	public static double fillWillToMaximum(World world, BlockPos pos, EnumDemonWillType type, double amount, double max, boolean doFill)
+	{
+		WillChunk willChunk = getWillChunk(world, pos);
+
+		DemonWillHolder currentWill = willChunk.getCurrentWill();
+		double fill = Math.min(amount, max - currentWill.getWill(type));
+		if (!doFill || fill <= 0)
+		{
+			return fill > 0 ? fill : 0;
+		}
+
+		fill = currentWill.addWill(type, amount, max);
+		markChunkAsDirty(willChunk, getDimensionResourceLocation(world));
+
+		return fill;
+	}
+
+	public static double fillWill(World world, BlockPos pos, EnumDemonWillType type, double amount, boolean doFill)
+	{
+		WillChunk willChunk = getWillChunk(world, pos);
+
+		DemonWillHolder currentWill = willChunk.getCurrentWill();
+		if (!doFill)
+		{
+			return amount;
+		}
+
+		currentWill.addWill(type, amount);
+		markChunkAsDirty(willChunk, getDimensionResourceLocation(world));
+
+		return amount;
+	}
+
+	public static WillChunk getWillChunk(World world, BlockPos pos)
+	{
+		WillChunk willChunk = getWillChunk(getDimensionResourceLocation(world), pos.getX() >> 4, pos.getZ() >> 4);
+		if (willChunk == null)
+		{
+			Chunk chunk = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4);
+			generateWill(chunk);
+
+			willChunk = getWillChunk(getDimensionResourceLocation(world), pos.getX() >> 4, pos.getZ() >> 4);
+		}
+
+		return willChunk;
+	}
+
+	public static double getCurrentWill(World world, BlockPos pos, EnumDemonWillType type)
+	{
+		WillChunk willChunk = getWillChunk(world, pos);
+
+		if (willChunk == null)
+		{
+			return 0;
+		}
+
+		DemonWillHolder currentWill = willChunk.getCurrentWill();
+		return currentWill.getWill(type);
+	}
+
+	private static void markChunkAsDirty(WillChunk chunk, ResourceLocation resourceLocation)
+	{
+		if (chunk.isModified())
+		{
+			return;
+		}
+		PosXY pos = new PosXY(chunk.loc.x, chunk.loc.y);
+		if (!dirtyChunks.containsKey(resourceLocation))
+		{
+			dirtyChunks.put(resourceLocation, new CopyOnWriteArrayList<>());
+		}
+		CopyOnWriteArrayList<PosXY> dc = dirtyChunks.get(resourceLocation);
+		if (!dc.contains(pos))
+		{
+			dc.add(pos);
+		}
+	}
+
+	public static void generateWill(Chunk chunk)
+	{
+		addWillChunk(chunk.getWorld().getDimensionKey().getLocation(), chunk, (short) 1, new DemonWillHolder());
+	}
+
+	private static ResourceLocation getDimensionResourceLocation(World world)
+	{
+		return world.getDimensionKey().getLocation();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/event/RitualEvent.java b/src/main/java/wayoftime/bloodmagic/event/RitualEvent.java
new file mode 100644
index 00000000..f5356ec8
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/event/RitualEvent.java
@@ -0,0 +1,151 @@
+package wayoftime.bloodmagic.event;
+
+import java.util.UUID;
+
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+import net.minecraftforge.eventbus.api.Cancelable;
+import net.minecraftforge.eventbus.api.Event;
+import wayoftime.bloodmagic.ritual.IMasterRitualStone;
+import wayoftime.bloodmagic.ritual.Ritual;
+import wayoftime.bloodmagic.ritual.imperfect.IImperfectRitualStone;
+import wayoftime.bloodmagic.ritual.imperfect.ImperfectRitual;
+
+public class RitualEvent extends Event
+{
+	private final IMasterRitualStone mrs;
+	private final UUID ownerId;
+	private final Ritual ritual;
+
+	private RitualEvent(IMasterRitualStone mrs, UUID ownerId, Ritual ritual)
+	{
+		this.mrs = mrs;
+		this.ownerId = ownerId;
+		this.ritual = ritual;
+	}
+
+	public IMasterRitualStone getRitualStone()
+	{
+		return mrs;
+	}
+
+	public UUID getOwnerId()
+	{
+		return ownerId;
+	}
+
+	public Ritual getRitual()
+	{
+		return ritual;
+	}
+
+	/**
+	 * This event is called when a ritual is activated. If cancelled, it will not
+	 * activate.
+	 * <p>
+	 * {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#activateRitual(ItemStack, PlayerEntity, Ritual)}
+	 */
+	@Cancelable
+	public static class RitualActivatedEvent extends RitualEvent
+	{
+
+		private final PlayerEntity player;
+		private final ItemStack crystalStack;
+		private final int crystalTier;
+
+		public RitualActivatedEvent(IMasterRitualStone mrs, UUID ownerId, Ritual ritual, PlayerEntity player, ItemStack activationCrystal, int crystalTier)
+		{
+			super(mrs, ownerId, ritual);
+
+			this.player = player;
+			this.crystalStack = activationCrystal;
+			this.crystalTier = crystalTier;
+		}
+
+		public PlayerEntity getPlayer()
+		{
+			return player;
+		}
+
+		public ItemStack getCrystalStack()
+		{
+			return crystalStack;
+		}
+
+		public int getCrystalTier()
+		{
+			return crystalTier;
+		}
+	}
+
+	/**
+	 * This event is called when a Ritual effect is performed. If cancelled, the
+	 * effect will not happen.
+	 * <p>
+	 * {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#performRitual(World, net.minecraft.util.math.BlockPos)}
+	 */
+	@Cancelable
+	public static class RitualRunEvent extends RitualEvent
+	{
+
+		public RitualRunEvent(IMasterRitualStone mrs, UUID ownerId, Ritual ritual)
+		{
+			super(mrs, ownerId, ritual);
+		}
+	}
+
+	/**
+	 * This event is called when a Ritual is stopped by a {@link Ritual.BreakType}.
+	 * <p>
+	 * {@link WayofTime.bloodmagic.tile.TileMasterRitualStone#stopRitual(Ritual.BreakType)}
+	 */
+	public static class RitualStopEvent extends RitualEvent
+	{
+
+		private final Ritual.BreakType method;
+
+		public RitualStopEvent(IMasterRitualStone mrs, UUID ownerId, Ritual ritual, Ritual.BreakType method)
+		{
+			super(mrs, ownerId, ritual);
+
+			this.method = method;
+		}
+
+		public Ritual.BreakType getMethod()
+		{
+			return method;
+		}
+	}
+
+	@Cancelable
+	public static class ImperfectRitualActivatedEvent extends Event
+	{
+
+		private final IImperfectRitualStone ims;
+		private final PlayerEntity activator;
+		private final ImperfectRitual imperfectRitual;
+
+		public ImperfectRitualActivatedEvent(IImperfectRitualStone ims, PlayerEntity activator, ImperfectRitual imperfectRitual)
+		{
+			this.ims = ims;
+			this.activator = activator;
+			this.imperfectRitual = imperfectRitual;
+		}
+
+		public IImperfectRitualStone getRitualStone()
+		{
+			return ims;
+		}
+
+		public PlayerEntity getActivator()
+		{
+			return activator;
+		}
+
+		public ImperfectRitual getImperfectRitual()
+		{
+			return imperfectRitual;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/AreaDescriptor.java b/src/main/java/wayoftime/bloodmagic/ritual/AreaDescriptor.java
new file mode 100644
index 00000000..b37eeb80
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/AreaDescriptor.java
@@ -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;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/CapabilityRuneType.java b/src/main/java/wayoftime/bloodmagic/ritual/CapabilityRuneType.java
new file mode 100644
index 00000000..258867c4
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/CapabilityRuneType.java
@@ -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();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/EnumReaderBoundaries.java b/src/main/java/wayoftime/bloodmagic/ritual/EnumReaderBoundaries.java
new file mode 100644
index 00000000..3e664671
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/EnumReaderBoundaries.java
@@ -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();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/EnumRuneType.java b/src/main/java/wayoftime/bloodmagic/ritual/EnumRuneType.java
new file mode 100644
index 00000000..959e771d
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/EnumRuneType.java
@@ -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];
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/IMasterRitualStone.java b/src/main/java/wayoftime/bloodmagic/ritual/IMasterRitualStone.java
new file mode 100644
index 00000000..e82e2deb
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/IMasterRitualStone.java
@@ -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();
+}
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/IRitualStone.java b/src/main/java/wayoftime/bloodmagic/ritual/IRitualStone.java
new file mode 100644
index 00000000..a4409fe9
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/IRitualStone.java
@@ -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);
+	}
+}
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/Ritual.java b/src/main/java/wayoftime/bloodmagic/ritual/Ritual.java
new file mode 100644
index 00000000..0ecd8c43
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/Ritual.java
@@ -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,
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/RitualComponent.java b/src/main/java/wayoftime/bloodmagic/ritual/RitualComponent.java
new file mode 100644
index 00000000..0f34813f
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/RitualComponent.java
@@ -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;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/RitualManager.java b/src/main/java/wayoftime/bloodmagic/ritual/RitualManager.java
new file mode 100644
index 00000000..92b29583
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/RitualManager.java
@@ -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);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/RitualRegister.java b/src/main/java/wayoftime/bloodmagic/ritual/RitualRegister.java
new file mode 100644
index 00000000..56527fa6
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/RitualRegister.java
@@ -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;
+			}
+		}
+	}
+}
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/RitualRenderer.java b/src/main/java/wayoftime/bloodmagic/ritual/RitualRenderer.java
new file mode 100644
index 00000000..a2172c45
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/RitualRenderer.java
@@ -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);
+	}
+}
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/imperfect/IImperfectRitualStone.java b/src/main/java/wayoftime/bloodmagic/ritual/imperfect/IImperfectRitualStone.java
new file mode 100644
index 00000000..856f5c60
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/imperfect/IImperfectRitualStone.java
@@ -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();
+}
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/imperfect/ImperfectRitual.java b/src/main/java/wayoftime/bloodmagic/ritual/imperfect/ImperfectRitual.java
new file mode 100644
index 00000000..6fe28e1f
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/imperfect/ImperfectRitual.java
@@ -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;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/ritual/types/RitualWater.java b/src/main/java/wayoftime/bloodmagic/ritual/types/RitualWater.java
new file mode 100644
index 00000000..aa7d230f
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/ritual/types/RitualWater.java
@@ -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();
+	}
+}
diff --git a/src/main/java/wayoftime/bloodmagic/tile/TileMasterRitualStone.java b/src/main/java/wayoftime/bloodmagic/tile/TileMasterRitualStone.java
new file mode 100644
index 00000000..5e6083b7
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/tile/TileMasterRitualStone.java
@@ -0,0 +1,566 @@
+package wayoftime.bloodmagic.tile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Strings;
+
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.tileentity.TileEntityType;
+import net.minecraft.util.Direction;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.TranslationTextComponent;
+import net.minecraft.world.World;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.registries.ObjectHolder;
+import wayoftime.bloodmagic.BloodMagic;
+import wayoftime.bloodmagic.common.item.ItemActivationCrystal;
+import wayoftime.bloodmagic.core.data.Binding;
+import wayoftime.bloodmagic.core.data.SoulNetwork;
+import wayoftime.bloodmagic.demonaura.WorldDemonWillHandler;
+import wayoftime.bloodmagic.event.RitualEvent;
+import wayoftime.bloodmagic.iface.IBindable;
+import wayoftime.bloodmagic.ritual.AreaDescriptor;
+import wayoftime.bloodmagic.ritual.EnumReaderBoundaries;
+import wayoftime.bloodmagic.ritual.IMasterRitualStone;
+import wayoftime.bloodmagic.ritual.Ritual;
+import wayoftime.bloodmagic.tile.base.TileTicking;
+import wayoftime.bloodmagic.util.ChatUtil;
+import wayoftime.bloodmagic.util.Constants;
+import wayoftime.bloodmagic.util.helper.BindableHelper;
+import wayoftime.bloodmagic.util.helper.NBTHelper;
+import wayoftime.bloodmagic.util.helper.NetworkHelper;
+import wayoftime.bloodmagic.util.helper.PlayerHelper;
+import wayoftime.bloodmagic.util.helper.RitualHelper;
+import wayoftime.bloodmagic.will.DemonWillHolder;
+import wayoftime.bloodmagic.will.EnumDemonWillType;
+
+public class TileMasterRitualStone extends TileTicking implements IMasterRitualStone
+{
+	@ObjectHolder("bloodmagic:masterritualstone")
+	public static TileEntityType<TileMasterRitualStone> TYPE;
+	protected final Map<String, AreaDescriptor> modableRangeMap = new HashMap<>();
+	private UUID owner;
+	private SoulNetwork cachedNetwork;
+	private boolean active;
+	private boolean redstoned;
+	private int activeTime;
+	private int cooldown;
+	private Ritual currentRitual;
+	private Direction direction = Direction.NORTH;
+	private boolean inverted;
+	private List<EnumDemonWillType> currentActiveWillConfig = new ArrayList<>();
+
+	public TileMasterRitualStone(TileEntityType<?> type)
+	{
+		super(type);
+	}
+
+	public TileMasterRitualStone()
+	{
+		this(TYPE);
+	}
+
+	@Override
+	public void onUpdate()
+	{
+		if (getWorld().isRemote)
+			return;
+
+		if (isPowered() && isActive())
+		{
+			active = false;
+			redstoned = true;
+			stopRitual(Ritual.BreakType.REDSTONE);
+			return;
+		}
+
+		if (!isActive() && !isPowered() && isRedstoned() && getCurrentRitual() != null)
+		{
+			active = true;
+			ItemStack crystalStack = NBTHelper.checkNBT(ItemActivationCrystal.CrystalType.getStack(getCurrentRitual().getCrystalLevel()));
+			BindableHelper.applyBinding(crystalStack, new Binding(owner, PlayerHelper.getUsernameFromUUID(owner)));
+			activateRitual(crystalStack, null, getCurrentRitual());
+			redstoned = false;
+		}
+
+		if (getCurrentRitual() != null && isActive())
+		{
+			if (activeTime % getCurrentRitual().getRefreshTime() == 0)
+				performRitual(getWorld(), getPos());
+
+			activeTime++;
+		}
+	}
+
+	@Override
+	public void deserialize(CompoundNBT tag)
+	{
+		owner = tag.hasUniqueId("owner") ? tag.getUniqueId("owner") : null;
+		if (owner != null)
+			cachedNetwork = NetworkHelper.getSoulNetwork(owner);
+		currentRitual = BloodMagic.RITUAL_MANAGER.getRitual(tag.getString(Constants.NBT.CURRENT_RITUAL));
+		if (currentRitual != null)
+		{
+			CompoundNBT ritualTag = tag.getCompound(Constants.NBT.CURRENT_RITUAL_TAG);
+			if (!ritualTag.isEmpty())
+			{
+				currentRitual.readFromNBT(ritualTag);
+			}
+		}
+		active = tag.getBoolean(Constants.NBT.IS_RUNNING);
+		activeTime = tag.getInt(Constants.NBT.RUNTIME);
+		direction = Direction.values()[tag.getInt(Constants.NBT.DIRECTION)];
+		redstoned = tag.getBoolean(Constants.NBT.IS_REDSTONED);
+
+		for (EnumDemonWillType type : EnumDemonWillType.values())
+		{
+			if (tag.getBoolean("EnumWill" + type))
+			{
+				currentActiveWillConfig.add(type);
+			}
+		}
+	}
+
+	@Override
+	public CompoundNBT serialize(CompoundNBT tag)
+	{
+		String ritualId = BloodMagic.RITUAL_MANAGER.getId(getCurrentRitual());
+		if (owner != null)
+			tag.putUniqueId("owner", owner);
+		tag.putString(Constants.NBT.CURRENT_RITUAL, Strings.isNullOrEmpty(ritualId) ? "" : ritualId);
+		if (currentRitual != null)
+		{
+			CompoundNBT ritualTag = new CompoundNBT();
+			currentRitual.writeToNBT(ritualTag);
+			tag.put(Constants.NBT.CURRENT_RITUAL_TAG, ritualTag);
+		}
+		tag.putBoolean(Constants.NBT.IS_RUNNING, isActive());
+		tag.putInt(Constants.NBT.RUNTIME, getActiveTime());
+		tag.putInt(Constants.NBT.DIRECTION, direction.getIndex());
+		tag.putBoolean(Constants.NBT.IS_REDSTONED, redstoned);
+
+		for (EnumDemonWillType type : currentActiveWillConfig)
+		{
+			tag.putBoolean("EnumWill" + type, true);
+		}
+
+		return tag;
+	}
+
+	@Override
+	public boolean activateRitual(ItemStack activationCrystal, @Nullable PlayerEntity activator, Ritual ritual)
+	{
+		if (PlayerHelper.isFakePlayer(activator))
+			return false;
+
+		Binding binding = ((IBindable) activationCrystal.getItem()).getBinding(activationCrystal);
+		if (binding != null && ritual != null)
+		{
+			if (activationCrystal.getItem() instanceof ItemActivationCrystal)
+			{
+				int crystalLevel = ((ItemActivationCrystal) activationCrystal.getItem()).getCrystalLevel(activationCrystal);
+				if (RitualHelper.canCrystalActivate(ritual, crystalLevel))
+				{
+					if (!getWorld().isRemote)
+					{
+						SoulNetwork network = NetworkHelper.getSoulNetwork(binding);
+
+						if (!isRedstoned() && network.getCurrentEssence() < ritual.getActivationCost()
+								&& (activator != null && !activator.isCreative()))
+						{
+							activator.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.weak"), true);
+							return false;
+						}
+
+						if (currentRitual != null)
+							currentRitual.stopRitual(this, Ritual.BreakType.ACTIVATE);
+
+						RitualEvent.RitualActivatedEvent event = new RitualEvent.RitualActivatedEvent(this, binding.getOwnerId(), ritual, activator, activationCrystal, crystalLevel);
+
+						if (MinecraftForge.EVENT_BUS.post(event))
+						{
+							if (activator != null)
+								activator.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.prevent"), true);
+							return false;
+						}
+
+						if (ritual.activateRitual(this, activator, binding.getOwnerId()))
+						{
+							if (!isRedstoned() && (activator != null && !activator.isCreative()))
+								network.syphon(ticket(ritual.getActivationCost()));
+
+							if (activator != null)
+								activator.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.activate"), true);
+
+							this.active = true;
+							this.owner = binding.getOwnerId();
+							this.cachedNetwork = network;
+							this.currentRitual = ritual;
+
+							if (!checkBlockRanges(ritual.getModableRangeMap()))
+								addBlockRanges(ritual.getModableRangeMap());
+
+							notifyUpdate();
+							return true;
+						}
+					}
+
+					notifyUpdate();
+					return true;
+				}
+			}
+		} else
+		{
+			if (activator != null)
+				activator.sendStatusMessage(new TranslationTextComponent("chat.bloodmagic.ritual.notValid"), true);
+		}
+
+		return false;
+	}
+
+	@Override
+	public void performRitual(World world, BlockPos pos)
+	{
+		if (!world.isRemote && getCurrentRitual() != null
+				&& BloodMagic.RITUAL_MANAGER.enabled(BloodMagic.RITUAL_MANAGER.getId(currentRitual), false))
+		{
+			if (RitualHelper.checkValidRitual(getWorld(), getPos(), currentRitual, getDirection()))
+			{
+				Ritual ritual = getCurrentRitual();
+				RitualEvent.RitualRunEvent event = new RitualEvent.RitualRunEvent(this, getOwner(), ritual);
+
+				if (MinecraftForge.EVENT_BUS.post(event))
+					return;
+
+				if (!checkBlockRanges(getCurrentRitual().getModableRangeMap()))
+					addBlockRanges(getCurrentRitual().getModableRangeMap());
+
+				getCurrentRitual().performRitual(this);
+			} else
+			{
+				stopRitual(Ritual.BreakType.BREAK_STONE);
+			}
+		}
+	}
+
+	@Override
+	public void stopRitual(Ritual.BreakType breakType)
+	{
+		if (!getWorld().isRemote && getCurrentRitual() != null)
+		{
+			RitualEvent.RitualStopEvent event = new RitualEvent.RitualStopEvent(this, getOwner(), getCurrentRitual(), breakType);
+
+			if (MinecraftForge.EVENT_BUS.post(event))
+				return;
+
+			getCurrentRitual().stopRitual(this, breakType);
+			if (breakType != Ritual.BreakType.REDSTONE)
+			{
+				this.currentRitual = null;
+				this.active = false;
+				this.activeTime = 0;
+			}
+			notifyUpdate();
+		}
+	}
+
+	@Override
+	public int getCooldown()
+	{
+		return cooldown;
+	}
+
+	@Override
+	public void setCooldown(int cooldown)
+	{
+		this.cooldown = cooldown;
+	}
+
+	@Override
+	public Direction getDirection()
+	{
+		return direction;
+	}
+
+	public void setDirection(Direction direction)
+	{
+		this.direction = direction;
+	}
+
+	@Override
+	public boolean areTanksEmpty()
+	{
+		return false;
+	}
+
+	@Override
+	public int getRunningTime()
+	{
+		return activeTime;
+	}
+
+	@Override
+	public UUID getOwner()
+	{
+		return owner;
+	}
+
+	public void setOwner(UUID owner)
+	{
+		this.owner = owner;
+	}
+
+	@Override
+	public SoulNetwork getOwnerNetwork()
+	{
+		return cachedNetwork;
+	}
+
+	@Override
+	public World getWorld()
+	{
+		return super.getWorld();
+	}
+
+	@Override
+	public BlockPos getPos()
+	{
+		return super.getPos();
+	}
+
+	@Override
+	public World getWorldObj()
+	{
+		return getWorld();
+	}
+
+	@Override
+	public BlockPos getBlockPos()
+	{
+		return getPos();
+	}
+
+	@Override
+	public String getNextBlockRange(String range)
+	{
+		if (this.currentRitual != null)
+		{
+			return this.currentRitual.getNextBlockRange(range);
+		}
+
+		return "";
+	}
+
+	@Override
+	public void provideInformationOfRitualToPlayer(PlayerEntity player)
+	{
+		if (this.currentRitual != null)
+		{
+			ChatUtil.sendNoSpam(player, this.currentRitual.provideInformationOfRitualToPlayer(player));
+		}
+	}
+
+	@Override
+	public void provideInformationOfRangeToPlayer(PlayerEntity player, String range)
+	{
+		if (this.currentRitual != null && this.currentRitual.getListOfRanges().contains(range))
+		{
+			ChatUtil.sendNoSpam(player, this.currentRitual.provideInformationOfRangeToPlayer(player, range));
+		}
+	}
+
+	@Override
+	public void setActiveWillConfig(PlayerEntity player, List<EnumDemonWillType> typeList)
+	{
+		this.currentActiveWillConfig = typeList;
+	}
+
+	@Override
+	public EnumReaderBoundaries setBlockRangeByBounds(PlayerEntity player, String range, BlockPos offset1, BlockPos offset2)
+	{
+		AreaDescriptor descriptor = this.getBlockRange(range);
+		DemonWillHolder holder = WorldDemonWillHandler.getWillHolder(world, getBlockPos());
+
+		EnumReaderBoundaries modificationType = currentRitual.canBlockRangeBeModified(range, descriptor, this, offset1, offset2, holder);
+		if (modificationType == EnumReaderBoundaries.SUCCESS)
+			descriptor.modifyAreaByBlockPositions(offset1, offset2);
+
+		return modificationType;
+	}
+
+	@Override
+	public List<EnumDemonWillType> getActiveWillConfig()
+	{
+		return new ArrayList<>(currentActiveWillConfig);
+	}
+
+	@Override
+	public void provideInformationOfWillConfigToPlayer(PlayerEntity player, List<EnumDemonWillType> typeList)
+	{
+		// There is probably an easier way to make expanded chat messages
+		if (typeList.size() >= 1)
+		{
+			Object[] translations = new TranslationTextComponent[typeList.size()];
+			StringBuilder constructedString = new StringBuilder("%s");
+
+			for (int i = 1; i < typeList.size(); i++)
+			{
+				constructedString.append(", %s");
+			}
+
+			for (int i = 0; i < typeList.size(); i++)
+			{
+				translations[i] = new TranslationTextComponent("tooltip.bloodmagic.currentBaseType." + typeList.get(i).name.toLowerCase());
+			}
+
+			ChatUtil.sendNoSpam(player, new TranslationTextComponent("ritual.bloodmagic.willConfig.set", new TranslationTextComponent(constructedString.toString(), translations)));
+		} else
+		{
+			ChatUtil.sendNoSpam(player, new TranslationTextComponent("ritual.bloodmagic.willConfig.void"));
+		}
+	}
+
+	public boolean isPowered()
+	{
+		if (inverted)
+			return !getWorld().isBlockPowered(getPos());
+
+		return getWorld().isBlockPowered(getPos());
+	}
+
+	public SoulNetwork getCachedNetwork()
+	{
+		return cachedNetwork;
+	}
+
+	public void setCachedNetwork(SoulNetwork cachedNetwork)
+	{
+		this.cachedNetwork = cachedNetwork;
+	}
+
+	public boolean isActive()
+	{
+		return active;
+	}
+
+	@Override
+	public void setActive(boolean active)
+	{
+		this.active = active;
+	}
+
+	public boolean isRedstoned()
+	{
+		return redstoned;
+	}
+
+	public void setRedstoned(boolean redstoned)
+	{
+		this.redstoned = redstoned;
+	}
+
+	public int getActiveTime()
+	{
+		return activeTime;
+	}
+
+	public void setActiveTime(int activeTime)
+	{
+		this.activeTime = activeTime;
+	}
+
+	public Ritual getCurrentRitual()
+	{
+		return currentRitual;
+	}
+
+	public void setCurrentRitual(Ritual currentRitual)
+	{
+		this.currentRitual = currentRitual;
+	}
+
+	public boolean isInverted()
+	{
+		return inverted;
+	}
+
+	public void setInverted(boolean inverted)
+	{
+		this.inverted = inverted;
+	}
+
+	public List<EnumDemonWillType> getCurrentActiveWillConfig()
+	{
+		return currentActiveWillConfig;
+	}
+
+	public void setCurrentActiveWillConfig(List<EnumDemonWillType> currentActiveWillConfig)
+	{
+		this.currentActiveWillConfig = currentActiveWillConfig;
+	}
+
+	/**
+	 * 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;
+	}
+
+	@Override
+	public void addBlockRange(String range, AreaDescriptor defaultRange)
+	{
+		modableRangeMap.putIfAbsent(range, defaultRange.copy());
+	}
+
+	@Override
+	public void addBlockRanges(Map<String, AreaDescriptor> blockRanges)
+	{
+		for (Map.Entry<String, AreaDescriptor> entry : blockRanges.entrySet())
+		{
+			modableRangeMap.putIfAbsent(entry.getKey(), entry.getValue().copy());
+		}
+	}
+
+	@Override
+	public void setBlockRange(String range, AreaDescriptor defaultRange)
+	{
+		modableRangeMap.put(range, defaultRange.copy());
+	}
+
+	@Override
+	public void setBlockRanges(Map<String, AreaDescriptor> blockRanges)
+	{
+		for (Map.Entry<String, AreaDescriptor> entry : blockRanges.entrySet())
+		{
+			modableRangeMap.put(entry.getKey(), entry.getValue().copy());
+		}
+	}
+
+	public boolean checkBlockRanges(Map<String, AreaDescriptor> blockRanges)
+	{
+		for (Map.Entry<String, AreaDescriptor> entry : blockRanges.entrySet())
+		{
+			if (modableRangeMap.get(entry.getKey()) == null)
+				return false;
+		}
+		return true;
+	}
+
+}
diff --git a/src/main/java/wayoftime/bloodmagic/tile/base/TileTicking.java b/src/main/java/wayoftime/bloodmagic/tile/base/TileTicking.java
new file mode 100644
index 00000000..545dec20
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/tile/base/TileTicking.java
@@ -0,0 +1,71 @@
+package wayoftime.bloodmagic.tile.base;
+
+import net.minecraft.client.renderer.texture.ITickable;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.tileentity.TileEntityType;
+
+/**
+ * Base class for tiles that tick. Allows disabling the ticking
+ * programmatically.
+ */
+// TODO - Move implementations that depend on existed ticks to new methods from here.
+public abstract class TileTicking extends TileBase implements ITickable
+{
+	private int ticksExisted;
+	private boolean shouldTick = true;
+
+	public TileTicking(TileEntityType<?> type)
+	{
+		super(type);
+	}
+
+	@Override
+	public final void tick()
+	{
+		if (shouldTick())
+		{
+			ticksExisted++;
+			onUpdate();
+		}
+	}
+
+	@Override
+	void deserializeBase(CompoundNBT tagCompound)
+	{
+		this.ticksExisted = tagCompound.getInt("ticksExisted");
+		this.shouldTick = tagCompound.getBoolean("shouldTick");
+	}
+
+	@Override
+	CompoundNBT serializeBase(CompoundNBT tagCompound)
+	{
+		tagCompound.putInt("ticksExisted", getTicksExisted());
+		tagCompound.putBoolean("shouldTick", shouldTick());
+		return tagCompound;
+	}
+
+	/**
+	 * Called every tick that {@link #shouldTick()} is true.
+	 */
+	public abstract void onUpdate();
+
+	public int getTicksExisted()
+	{
+		return ticksExisted;
+	}
+
+	public void resetLifetime()
+	{
+		ticksExisted = 0;
+	}
+
+	public boolean shouldTick()
+	{
+		return shouldTick;
+	}
+
+	public void setShouldTick(boolean shouldTick)
+	{
+		this.shouldTick = shouldTick;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/wayoftime/bloodmagic/util/helper/RitualHelper.java b/src/main/java/wayoftime/bloodmagic/util/helper/RitualHelper.java
new file mode 100644
index 00000000..3862d73a
--- /dev/null
+++ b/src/main/java/wayoftime/bloodmagic/util/helper/RitualHelper.java
@@ -0,0 +1,253 @@
+package wayoftime.bloodmagic.util.helper;
+
+import java.util.List;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.google.common.collect.Lists;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.capabilities.CapabilityInject;
+import net.minecraftforge.common.util.LazyOptional;
+import wayoftime.bloodmagic.BloodMagic;
+import wayoftime.bloodmagic.common.block.BlockRitualStone;
+import wayoftime.bloodmagic.common.block.BloodMagicBlocks;
+import wayoftime.bloodmagic.ritual.EnumRuneType;
+import wayoftime.bloodmagic.ritual.IRitualStone;
+import wayoftime.bloodmagic.ritual.IRitualStone.Tile;
+import wayoftime.bloodmagic.ritual.Ritual;
+import wayoftime.bloodmagic.ritual.RitualComponent;
+import wayoftime.bloodmagic.tile.TileMasterRitualStone;
+
+public class RitualHelper
+{
+	@CapabilityInject(IRitualStone.Tile.class)
+	static Capability<IRitualStone.Tile> RUNE_CAPABILITY = null;
+
+	public static boolean canCrystalActivate(Ritual ritual, int crystalLevel)
+	{
+		return ritual.getCrystalLevel() <= crystalLevel
+				&& BloodMagic.RITUAL_MANAGER.enabled(BloodMagic.RITUAL_MANAGER.getId(ritual), false);
+	}
+
+	/**
+	 * Checks the RitualRegistry to see if the configuration of the ritual stones in
+	 * the world is valid for the given Direction.
+	 *
+	 * @param world - The world
+	 * @param pos   - Location of the MasterRitualStone
+	 * @return The ID of the valid ritual
+	 */
+	public static String getValidRitual(World world, BlockPos pos)
+	{
+		for (Ritual ritual : BloodMagic.RITUAL_MANAGER.getRituals())
+		{
+			for (int i = 0; i < 4; i++)
+			{
+				Direction direction = Direction.byHorizontalIndex(i);
+
+				if (checkValidRitual(world, pos, ritual, direction))
+					return BloodMagic.RITUAL_MANAGER.getId(ritual);
+			}
+		}
+
+		return "";
+	}
+
+	public static Direction getDirectionOfRitual(World world, BlockPos pos, Ritual ritual)
+	{
+		for (int i = 0; i < 4; i++)
+		{
+			Direction direction = Direction.byHorizontalIndex(i);
+			if (checkValidRitual(world, pos, ritual, direction))
+				return direction;
+		}
+
+		return null;
+	}
+
+	public static boolean checkValidRitual(World world, BlockPos pos, Ritual ritual, Direction direction)
+	{
+		if (ritual == null)
+		{
+			return false;
+		}
+
+		List<RitualComponent> components = Lists.newArrayList();
+		ritual.gatherComponents(components::add);
+
+		for (RitualComponent component : components)
+		{
+			BlockPos newPos = pos.add(component.getOffset(direction));
+			if (!isRuneType(world, newPos, component.getRuneType()))
+				return false;
+		}
+
+		return true;
+	}
+
+	public static boolean isRuneType(World world, BlockPos pos, EnumRuneType type)
+	{
+		if (world == null)
+			return false;
+		Block block = world.getBlockState(pos).getBlock();
+		TileEntity tile = world.getTileEntity(pos);
+
+		if (block instanceof IRitualStone)
+			return ((IRitualStone) block).isRuneType(world, pos, type);
+		else if (tile instanceof IRitualStone.Tile)
+			return ((IRitualStone.Tile) tile).isRuneType(type);
+		else if (tile != null && tile.getCapability(RUNE_CAPABILITY, null).isPresent())
+			return tile.getCapability(RUNE_CAPABILITY, null).resolve().get().isRuneType(type);
+
+		return false;
+	}
+
+	public static boolean isRune(World world, BlockPos pos)
+	{
+		if (world == null)
+			return false;
+		Block block = world.getBlockState(pos).getBlock();
+		TileEntity tile = world.getTileEntity(pos);
+
+		if (block instanceof IRitualStone)
+			return true;
+		else if (tile instanceof IRitualStone.Tile)
+			return true;
+		else
+			return tile != null && tile.getCapability(RUNE_CAPABILITY, null).isPresent();
+
+	}
+
+	public static void setRuneType(World world, BlockPos pos, EnumRuneType type)
+	{
+		if (world == null)
+			return;
+		BlockState state = world.getBlockState(pos);
+		TileEntity tile = world.getTileEntity(pos);
+
+		if (state.getBlock() instanceof IRitualStone)
+			((IRitualStone) state.getBlock()).setRuneType(world, pos, type);
+		else if (tile instanceof IRitualStone.Tile)
+			((IRitualStone.Tile) tile).setRuneType(type);
+		else
+		{
+			LazyOptional<Tile> cap = tile.getCapability(RUNE_CAPABILITY, null);
+			if (cap.isPresent())
+			{
+				cap.resolve().get().setRuneType(type);
+				world.notifyBlockUpdate(pos, state, state, 3);
+			}
+
+		}
+	}
+
+	public static boolean createRitual(World world, BlockPos pos, Direction direction, Ritual ritual, boolean safe)
+	{
+
+		List<RitualComponent> components = Lists.newArrayList();
+		ritual.gatherComponents(components::add);
+
+		if (abortConstruction(world, pos, direction, safe, components))
+			return false;
+
+		BlockState mrs = BloodMagicBlocks.MASTER_RITUAL_STONE.get().getDefaultState();
+		world.setBlockState(pos, mrs);
+
+		setRitualStones(direction, world, pos, components);
+		return true;
+	}
+
+	public static boolean abortConstruction(World world, BlockPos pos, Direction direction, boolean safe, List<RitualComponent> components)
+	{
+		// TODO: can be optimized to check only for the first and last component if
+		// every ritual has those at the highest and lowest y-level respectivly.
+		for (RitualComponent component : components)
+		{
+			BlockPos offset = component.getOffset(direction);
+			BlockPos newPos = pos.add(offset);
+			if (world.isOutsideBuildHeight(newPos) || (safe && !world.isAirBlock(newPos)))
+				return true;
+		}
+		return false;
+	}
+
+	public static boolean repairRitualFromRuins(TileMasterRitualStone tile, boolean safe)
+	{
+		Ritual ritual = tile.getCurrentRitual();
+		Direction direction;
+		Pair<Ritual, Direction> pair;
+		if (ritual == null)
+		{
+			pair = getRitualFromRuins(tile);
+			ritual = pair.getKey();
+			direction = pair.getValue();
+		} else
+			direction = tile.getDirection();
+
+		World world = tile.getWorld();
+		BlockPos pos = tile.getPos();
+
+		List<RitualComponent> components = Lists.newArrayList();
+		ritual.gatherComponents(components::add);
+
+		if (abortConstruction(world, pos, direction, safe, components))
+			return false;
+
+		setRitualStones(direction, world, pos, components);
+		return true;
+	}
+
+	public static void setRitualStones(Direction direction, World world, BlockPos pos, List<RitualComponent> gatheredComponents)
+	{
+		for (RitualComponent component : gatheredComponents)
+		{
+			BlockPos offset = component.getOffset(direction);
+			BlockPos newPos = pos.add(offset);
+			((BlockRitualStone) BloodMagicBlocks.BLANK_RITUAL_STONE.get()).setRuneType(world, newPos, component.getRuneType());
+		}
+	}
+
+	public static Pair<Ritual, Direction> getRitualFromRuins(TileMasterRitualStone tile)
+	{
+		BlockPos pos = tile.getPos();
+		World world = tile.getWorld();
+		Ritual possibleRitual = tile.getCurrentRitual();
+		Direction possibleDirection = tile.getDirection();
+		int highestCount = 0;
+
+		if (possibleRitual == null || possibleDirection == null)
+			for (Ritual ritual : BloodMagic.RITUAL_MANAGER.getRituals())
+			{
+				for (int i = 0; i < 4; i++)
+				{
+					Direction direction = Direction.byHorizontalIndex(i);
+					List<RitualComponent> components = Lists.newArrayList();
+					ritual.gatherComponents(components::add);
+					int currentCount = 0;
+
+					for (RitualComponent component : components)
+					{
+						BlockPos newPos = pos.add(component.getOffset(direction));
+						if (isRuneType(world, newPos, component.getRuneType()))
+							currentCount += 1;
+					}
+					if (currentCount > highestCount)
+					{
+						highestCount = currentCount;
+						possibleRitual = ritual;
+						possibleDirection = direction;
+					}
+
+				}
+
+			}
+		return Pair.of(possibleRitual, possibleDirection);
+	}
+}