diff --git a/.gitignore b/.gitignore
index 939f55fc..d45b2340 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,9 @@ build
# other
eclipse
run
-misc
-libs
+logs
+/misc
+/libs
+libsfordeobf
+generated
Thumbs.db
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 775fd0d4..6bd1915b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,17 +11,39 @@ apply plugin: 'net.minecraftforge.gradle.forge'
//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
-version = " 4.1.4 - MC 1.12.2"
+version = "4.2.0" // There, it matches semver, happy now? (I am just gonna rename the file afterwards, you know that right?)
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
-archivesBaseName = "Electroblob's Wizardry "
+archivesBaseName = "ElectroblobsWizardry"
sourceCompatibility = targetCompatibility = "1.8" // Need this here so eclipse task generates correctly.
compileJava {
sourceCompatibility = targetCompatibility = "1.8"
}
+repositories {
+ maven {
+ // location of the maven that hosts JEI files
+ name = "Progwml6 maven"
+ url = "http://dvs1.progwml6.com/files/maven"
+ }
+ maven {
+ // location of a maven mirror for JEI files, as a fallback
+ name = "ModMaven"
+ url = "modmaven.k-4u.nl"
+ }
+ flatDir {
+ // evErYthINg hAS tO Be a mAVeN rEPoSiTOrY... (to use deobfProvided, for some reason)
+ // The lack of documentation is actually quite impressive, see the following threads:
+ // http://www.minecraftforge.net/forum/topic/44262-solved-how-to-build-mod-with-dependencies/
+ // https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/mapping-and-modding-tutorials/2866817-setting-up-dependencies-in-minecraft-forge-with
+ // http://www.minecraftforge.net/forum/topic/44534-adding-dependencies-to-a-forge-project/
+ dirs "libsfordeobf" // Don't use libs or forge will think you have duplicate mods
+ }
+
+}
+
minecraft {
- version = "1.12.2-14.23.2.2611"
+ version = "1.12.2-14.23.5.2814"
runDir = "run"
// the mappings can be changed at any time, and must be in the following format.
@@ -38,6 +60,17 @@ dependencies {
// or you may define them like so..
//compile "some.group:artifact:version:classifier"
//compile "some.group:artifact:version"
+
+ // compile against the JEI API but do not include it at runtime
+ deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}:api"
+ // at runtime, use the full JEI jar
+ runtime "mezz.jei:jei_${mc_version}:${jei_version}"
+
+ //deobfProvided "baubles:baubles-1.12:1.5.2:api" // #ihavenoideawhatimdoing #itjustworks
+ //runtime "baubles:baubles-1.12:1.5.2" // If in doubt, copy what already works
+
+ //deobfProvided "antiqueatlas:antiqueatlas-1.12.2:4.5.1:src"
+ //runtime "antiqueatlas:antiqueatlas-1.12.2:4.5.1"
// real examples
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
diff --git a/gradle.properties b/gradle.properties
index e9b9fd5a..b4ec0fb3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
+mc_version=1.12.2
+jei_version=4.13.1.225
\ No newline at end of file
diff --git a/gradlew b/gradlew
index 91a7e269..ac7df23f 100644
--- a/gradlew
+++ b/gradlew
@@ -79,14 +79,14 @@ if [ -n "$JAVA_HOME" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
+locations of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
+locations of your Java installation."
fi
# Increase the maximum file descriptors if we can.
diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282a..5bb5d8ea 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -27,7 +27,7 @@ echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo locations of your Java installation.
goto fail
@@ -41,7 +41,7 @@ echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo locations of your Java installation.
goto fail
diff --git a/src/main/java/electroblob/wizardry/CommonProxy.java b/src/main/java/electroblob/wizardry/CommonProxy.java
index 7e45ebe8..71165cc2 100644
--- a/src/main/java/electroblob/wizardry/CommonProxy.java
+++ b/src/main/java/electroblob/wizardry/CommonProxy.java
@@ -1,33 +1,32 @@
package electroblob.wizardry;
-import electroblob.wizardry.client.particle.ParticleWizardry;
import electroblob.wizardry.item.ItemSpectralBow;
-import electroblob.wizardry.packet.PacketCastContinuousSpell;
-import electroblob.wizardry.packet.PacketCastSpell;
-import electroblob.wizardry.packet.PacketClairvoyance;
-import electroblob.wizardry.packet.PacketGlyphData;
-import electroblob.wizardry.packet.PacketNPCCastSpell.Message;
-import electroblob.wizardry.packet.PacketPlayerSync;
-import electroblob.wizardry.packet.PacketTransportation;
-import electroblob.wizardry.registry.Spells;
+import electroblob.wizardry.packet.*;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
-import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.Style;
+import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World;
import net.minecraftforge.common.config.Property;
+import java.util.List;
+import java.util.Set;
+
/**
* The common proxy for wizardry, serving the usual purpose of dealing with all things that need to be handled
* differently on the client and the server. A lot of the methods here appear to do absolutely nothing; this is because
* they do client-only things which are only handled in the client proxy.
*
- * @see {@link electroblob.wizardry.client.ClientProxy}
+ * @see electroblob.wizardry.client.ClientProxy ClientProxy
* @author Electroblob
* @since Wizardry 1.0
*/
@@ -51,15 +50,19 @@ public class CommonProxy {
public void registerResourceReloadListeners(){}
+ public void registerSoundEventListener(){}
+
+ public void registerAtlasMarkers(){}
+
// SECTION Particles
// ===============================================================================================================
/** Called from init() in the main mod class to initialise the particle factories. */
- public void initParticleFactories(){} // Does nothing since particles are client-side only
+ public void registerParticles(){} // Does nothing since particles are client-side only
/** Creates a new particle of the specified type from the appropriate particle factory. Does not actually spawn the
* particle; use {@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} to spawn particles. */
- public ParticleWizardry createParticle(Type type, World world, double x, double y, double z){
+ public electroblob.wizardry.client.particle.ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
return null;
}
@@ -70,19 +73,23 @@ public class CommonProxy {
// SECTION Items
// ===============================================================================================================
+ public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
+ return false;
+ }
+
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return null;
}
/**
* Returns the translated name of the scroll, taking spell discovery into account. If, for some reason, this gets
- * called server-side, it uses the deprecated server version of I18n, with a warning. Since any likely server-side
- * use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since no
- * particular player instance can be accessed, spell discovery is ignored.
+ * called server-side, it uses the deprecated server version of I18n. Since any likely server-side
+ * use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since
+ * no particular player instance can be accessed, spell discovery is ignored.
*/
public String getScrollDisplayName(ItemStack scroll){
- Spell spell = Spell.get(scroll.getItemDamage());
+ Spell spell = Spell.byMetadata(scroll.getItemDamage());
return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name",
I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim();
@@ -92,29 +99,53 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
}
+ /** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */
+ public void addMultiLineDescription(List tooltip, String key){
+ this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY));
+ }
+
+ /**
+ * Adds a multi-line description to the given tooltip list. The description is first translated using the given
+ * translation key, then the formatting code for the given style is appended, and finally the string is word-wrapped
+ * to the standard width (100).
+ * @param tooltip The tooltip list to add to
+ * @param key The translation key for the description
+ * @param style A style to apply
+ */
+ public void addMultiLineDescription(List tooltip, String key, Style style){}
+
// SECTION Packet Handlers
// ===============================================================================================================
- public void handlePlayerSyncPacket(PacketPlayerSync.Message message){
- }
+ public void handlePlayerSyncPacket(PacketPlayerSync.Message message){}
- public void handleGlyphDataPacket(PacketGlyphData.Message message){
- }
+ public void handleGlyphDataPacket(PacketGlyphData.Message message){}
- public void handleCastSpellPacket(PacketCastSpell.Message message){
- }
+ public void handleEmitterDataPacket(PacketEmitterData.Message message){}
- public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){
- }
+ public void handleCastSpellPacket(PacketCastSpell.Message message){}
- public void handleNPCCastSpellPacket(Message message){
- }
+ public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){}
- public void handleTransportationPacket(PacketTransportation.Message message){
- }
+ public void handleNPCCastSpellPacket(PacketNPCCastSpell.Message message){}
- public void handleClairvoyancePacket(PacketClairvoyance.Message message){
- }
+ public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){}
+
+ public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){}
+
+ public void handleTransportationPacket(PacketTransportation.Message message){}
+
+ public void handleClairvoyancePacket(PacketClairvoyance.Message message){}
+
+ public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){}
+
+ public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){}
+
+ public void handleResurrectionPacket(PacketResurrection.Message message){}
+
+ public void handlePossessionPacket(PacketPossession.Message message){}
+
+ public void handleConquerShrinePacket(PacketConquerShrine.Message message){}
// SECTION Misc
// ===============================================================================================================
@@ -122,6 +153,8 @@ public class CommonProxy {
public void setToNumberSliderEntry(Property property){}
public void setToHUDChooserEntry(Property property){}
+
+ public void setToNamedBooleanEntry(Property property){}
// public void setToEntityNameEntry(Property property){}
@@ -137,6 +170,75 @@ public class CommonProxy {
*/
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){}
+ /**
+ * Plays a continuous spell sound which moves with the given entity.
+ *
+ * @param entity The source of the sound
+ * @param spell The spell this sound is associated with
+ * @param start The starting SoundEvent to play
+ * @param loop The main looped SoundEvent to play
+ * @param end The ending SoundEvent to play
+ * @param category The SoundCategory to use
+ * @param volume Volume relative to 1
+ * @param pitch Pitch relative to 1
+ */
+ public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){}
+
+ /**
+ * Plays a continuous spell sound which moves with the given entity.
+ *
+ * @param entity The source of the sound
+ * @param spell The spell this sound is associated with
+ * @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
+ * @param category The SoundCategory to use
+ * @param volume Volume relative to 1
+ * @param pitch Pitch relative to 1
+ * @throws IllegalArgumentException if the given array contains less than 3 sound events
+ */
+ public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch){
+ if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ + "of sound events, but the given array contained less than 3 sound events!");
+ playSpellSoundLoop(entity, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch);
+ }
+
+ /**
+ * Plays a continuous spell sound at the given position.
+ * @param world The world in which to play the sound
+ * @param x The x coordinate to play the sound at
+ * @param y The y coordinate to play the sound at
+ * @param z The z coordinate to play the sound at
+ * @param spell The spell this sound is associated with
+ * @param start The starting SoundEvent to play
+ * @param loop The main looped SoundEvent to play
+ * @param end The ending SoundEvent to play
+ * @param category The SoundCategory to use
+ * @param volume Volume relative to 1
+ * @param pitch Pitch relative to 1
+ * @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
+ */
+ public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){}
+
+ /**
+ * Plays a continuous spell sound at the given position.
+ *
+ * @param world The world in which to play the sound
+ * @param x The x coordinate to play the sound at
+ * @param y The y coordinate to play the sound at
+ * @param z The z coordinate to play the sound at
+ * @param spell The spell this sound is associated with
+ * @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
+ * @param category The SoundCategory to use
+ * @param volume Volume relative to 1
+ * @param pitch Pitch relative to 1
+ * @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
+ * @throws IllegalArgumentException if the given array contains less than 3 sound events
+ */
+ public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch, int duration){
+ if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ + "of sound events, but the given array contained less than 3 sound events!");
+ playSpellSoundLoop(world, x, y, z, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch, duration);
+ }
+
/**
* Gets the client side world using Minecraft.getMinecraft().world. Only to be called client side! Returns
* null on the server side.
diff --git a/src/main/java/electroblob/wizardry/Settings.java b/src/main/java/electroblob/wizardry/Settings.java
index c8bc5585..8692cc0b 100644
--- a/src/main/java/electroblob/wizardry/Settings.java
+++ b/src/main/java/electroblob/wizardry/Settings.java
@@ -1,13 +1,9 @@
package electroblob.wizardry;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
import electroblob.wizardry.packet.PacketSyncSettings;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
+import electroblob.wizardry.util.AllyDesignationSystem.FriendlyFire;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import net.minecraft.entity.EntityList;
@@ -19,14 +15,18 @@ import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.relauncher.Side;
+
+import java.util.*;
+import java.util.regex.Pattern;
/**
* Singleton class which deals with everything related to wizardry's config file. To access individual settings, use
* {@link Wizardry#settings}. Also stores a few string constants for easy access.
- *
- * As part of the 1.2 update and code overhaul, the way the config settings work in multiplayer has been tightened up.
+ *
+ * As part of the 2.1 update and code overhaul, the way the config settings work in multiplayer has been tightened up.
* Importantly, there are three different types of config options:
- *
+ *
*
Server-only settings. These only affect server-side code and hence are not synced. Changing these locally only
* has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world. Examples
* include worldgen, mob drops and commands.
@@ -38,7 +38,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
*
Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys its
* own values for these, and changing them on a dedicated server will have no effect. These are usually only display and
* controls settings.
- *
+ *
* Each of the settings fields in this class is marked with one of the above categories to indicate which it belongs to.
* This in turn dictates which logical side it should be called from: client, server or both. Do not access a config
* setting from the wrong side, because it may cause unexpected or strange behaviour.
@@ -46,13 +46,14 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
* @since Wizardry 1.2
* @author Electroblob
*/
-// TODO: New plan: convert to the @Config system BUT separate the spells config out so it doesn't break stuff. (Maybe even
-// make it a JSON instead? Either way, there's scope for then adding more options for adjusting the base attributes etc.)
+// For the time being, I'm sticking with the old config system because @Config doesn't support custom config entry classes
// @Config(modid = Wizardry.MODID)
-@SuppressWarnings("deprecation") // Used server I18n deliberately; we want to write the comments in english.
+@SuppressWarnings("deprecation") // Used server I18n deliberately; we want to write the comments in the actual config file in english.
public final class Settings {
// Category names
+ /** The unlocalised name of the gameplay config category. */
+ public static final String GAMEPLAY_CATEGORY = "gameplay";
/** The unlocalised name of the spells config category. */
public static final String SPELLS_CATEGORY = "spells";
/** The unlocalised name of the resistances config category. */
@@ -63,8 +64,14 @@ public final class Settings {
public static final String COMMANDS_CATEGORY = "commands";
/** The unlocalised name of the worldgen config category. */
public static final String WORLDGEN_CATEGORY = "worldgen";
- /** The unlocalised name of the gameplay config category. */
- public static final String GAMEPLAY_CATEGORY = "gameplay";
+ /** The unlocalised name of the compatibility config category. */
+ public static final String COMPATIBILITY_CATEGORY = "compatibility";
+
+ private static final String[] DEFAULT_LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
+ "minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
+ "minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
+ "minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
+ "minecraft:chests/end_city_treasure"};
/** The wizardry config file. */
private Configuration config;
@@ -73,34 +80,88 @@ public final class Settings {
// has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world.
// Worldgen
+ /** [Server-only] Whether to use faster worldgen at the cost of 'seamlessness'. */
+ public boolean fastWorldgen = false;
+ /** [Server-only] List of dimension ids in which to generate wizard towers. */
+ public int[] towerDimensions = {0};
/** [Server-only] The rarity of wizard towers, used by the world generator. Larger numbers are rarer. */
- public int towerRarity = 8;
+ public int towerRarity = 600;
+ /** [Server-only] List of structure file locations for wizard towers without loot chests. */
+ public ResourceLocation[] towerFiles = {new ResourceLocation(Wizardry.MODID, "wizard_tower_0"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_1"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_2"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_3")};
+ /** [Server-only] List of structure file locations for wizard towers with loot chests. */
+ public ResourceLocation[] towerWithChestFiles = {new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_0"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_1"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_2"),
+ new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_3")};
+ /** [Server-only] List of dimension ids in which to generate obelisks. */
+ public int[] obeliskDimensions = {0, -1};
+ /** [Server-only] The rarity of obelisks, used by the world generator. Larger numbers are rarer. */
+ public int obeliskRarity = 550;
+ /** [Server-only] List of structure file locations for obelisks. */
+ public ResourceLocation[] obeliskFiles = {new ResourceLocation(Wizardry.MODID, "obelisk_0"),
+ new ResourceLocation(Wizardry.MODID, "obelisk_1"),
+ new ResourceLocation(Wizardry.MODID, "obelisk_2"),
+ new ResourceLocation(Wizardry.MODID, "obelisk_3"),
+ new ResourceLocation(Wizardry.MODID, "obelisk_4")};
+ /** [Server-only] List of dimension ids in which to generate shrines. */
+ public int[] shrineDimensions = {0, -1};
+ /** [Server-only] The rarity of shrines, used by the world generator. Larger numbers are rarer. */
+ public int shrineRarity = 1000;
+ /** [Server-only] List of structure file locations for shrines. */
+ public ResourceLocation[] shrineFiles = {new ResourceLocation(Wizardry.MODID, "shrine_0"),
+ new ResourceLocation(Wizardry.MODID, "shrine_1"),
+ new ResourceLocation(Wizardry.MODID, "shrine_2"),
+ new ResourceLocation(Wizardry.MODID, "shrine_3"),
+ new ResourceLocation(Wizardry.MODID, "shrine_4"),
+ new ResourceLocation(Wizardry.MODID, "shrine_5"),
+ new ResourceLocation(Wizardry.MODID, "shrine_6"),
+ new ResourceLocation(Wizardry.MODID, "shrine_7")};
+ /** [Server-only] The chance for wizard towers to generate with an evil wizard and chest inside. */
+ public double evilWizardChance = 0.2;
/** [Server-only] List of dimension ids in which to generate crystal ore. */
public int[] oreDimensions = {0};
- /** [Server-only] List of dimension ids in which to generate crystal ore. */
+ /** [Server-only] List of dimension ids in which to generate crystal flowers. */
public int[] flowerDimensions = {0};
- /** [Server-only] List of dimension ids in which to generate crystal ore. */
- public int[] towerDimensions = {0};
/**
- * [Server-only] Whether or not wizardry loot should generate in dungeon chests. Note that this does not
- * affect the generation of loot in wizard towers.
+ * [Server-only] List of resource location strings for loot tables to inject wizardry loot into. Note that
+ * this does not affect the generation of loot in wizard towers.
*/
- public boolean generateLoot = true;
+ public ResourceLocation[] lootInjectionLocations = toResourceLocations(DEFAULT_LOOT_INJECTION_LOCATIONS);
// Entities' drops, targeting, damage, etc.
- /** [Server-only] Chance (out of 200) for mobs to drop spell books. */
- public int spellBookDropChance = 3;
+ /** [Server-only] Whitelist for loot tables to inject additional mob drops into. */
+ public ResourceLocation[] mobLootTableWhitelist = {};
+ /** [Server-only] Blacklist for loot tables to inject additional mob drops into. */
+ public ResourceLocation[] mobLootTableBlacklist = toResourceLocations("entities/vex", "entities/ender_dragon",
+ "entities/wither", "entities/silverfish", "entities/endermite",
+ Wizardry.MODID + "entities/evil_wizard");
/**
* [Server-only] Whether or not players can teleport through unbreakable blocks (e.g. bedrock) using the
* phase step spell.
*/
public boolean teleportThroughUnbreakableBlocks = false;
/** [Server-only] Whether to allow players to damage their designated allies using magic. */
- public boolean friendlyFire = true;
+ public FriendlyFire friendlyFire = FriendlyFire.ALL;
/** [Server-only] Whether to allow players to disarm other players using the telekinesis spell. */
public boolean telekineticDisarmament = true;
/** [Server-only] Whether summoned creatures can revenge attack their caster if their caster attacks them. */
public boolean minionRevengeTargeting = true;
+ /** [Server-only] Whether to allow players to change the world time using the speed time spell. */
+ public boolean worldTimeManipulation = true;
+ /** [Server-only] Whether to allow players to move other players around using magic. */
+ public boolean playersMoveEachOther = true;
+ /** [Server-only] Whether spells cast by players can destroy blocks in the world. */
+ public boolean playerBlockDamage = true;
+ /** [Server-only] Whether to revert to the old wand upgrade system, which only requires tomes of arcana. */
+ public boolean legacyWandLevelling = false;
+ /** [Server-only] Whether to replace Minecraft's own fireballs with wizardry fireballs. */
+ public boolean replaceVanillaFireballs = true;
+ /** [Server-only] Whether to replace Minecraft's distance-based fall damage calculation with an equivalent,
+ * velocity-based one. */
+ public boolean replaceVanillaFallDamage = true;
/**
* [Server-only] List of registry names of entities which summoned creatures are allowed to attack, in addition
* to the defaults.
@@ -110,18 +171,39 @@ public final class Settings {
* [Server-only] List of registry names of entities which summoned creatures are specifically not allowed to
* attack, overriding the defaults and the whitelist.
*/
- public ResourceLocation[] summonedCreatureTargetsBlacklist = {new ResourceLocation("creeper")};
+ public ResourceLocation[] summonedCreatureTargetsBlacklist = toResourceLocations("creeper");
/**
* [Server-only] List of registry names of entities which are immune to the mind control spell, in addition to
* the defaults.
*/
public ResourceLocation[] mindControlTargetsBlacklist = {};
+ /**
+ * [Server-only] List of registry names of items which cannot be smelted by the pocket furnace spell, in
+ * addition to armour, tools and weapons.
+ */
+ public ResourceLocation[] pocketFurnaceItemBlacklist = toResourceLocations("cobblestone", "netherrack");
+ /** [Server-only] List of registry names of blocks which can be detected by the divination spell. */
+ public ResourceLocation[] divinationOreWhitelist = {};
+ /** [Server-only] List of registry names of items which count as swords for imbuement spells. */
+ public ResourceLocation[] swordItemWhitelist = {};
+ /** [Server-only] List of registry names of items which count as bows for imbuement spells. */
+ public ResourceLocation[] bowItemWhitelist = {};
+ /** [Server-only] Map of items to values which wizard trades may use as currency. */
+ public Map currencyItems = new HashMap<>();
/** [Server-only] Global damage scaling factor for all player magic damage. */
- public double playerDamageScale = 1.0f;
+ public double playerDamageScale = 1.0;
/** [Server-only] Global damage scaling factor for all npc magic damage. */
- public double npcDamageScale = 1.0f;
- /** [Server-only] List of dimension ids in which evil wizards can spawn. */
- public int[] evilWizardDimensions = {0};
+ public double npcDamageScale = 1.0;
+ /** [Server-only] List of dimension ids in which wizardry's hostile mobs can spawn. */
+ public int[] mobSpawnDimensions = {0};
+ /** [Server-only] Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
+ public int evilWizardSpawnRate = 3;
+ /** [Server-only] Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
+ public int iceWraithSpawnRate = 3;
+ /** [Server-only] Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
+ public int lightningWraithSpawnRate = 1;
+ /** [Server-only] List of registry names of biomes in which wizardry's hostile mobs cannot spawn. */
+ public ResourceLocation[] mobSpawnBiomeBlacklist = toResourceLocations("mushroom_island", "mushroom_island_shore");
// Commands (these don't need synchronising since typing a command always queries the server).
/**
@@ -138,6 +220,25 @@ public final class Settings {
/** [Server-only] The name of the /allies command. */
public String alliesCommandName = "allies";
+ /**
+ * [Server-only] List of damage source string identifiers to be ignored when re-applying damage.
+ */
+ public String[] damageSourceBlacklist = {};
+ /** [Server-only] Whether to print compatibility warnings to the console. */
+ public boolean compatibilityWarnings = true;
+ /** [Server-only] Whether Baubles integration features are enabled. */
+ public boolean baublesIntegration = true;
+// /** [Server-only] Whether JEI integration features are enabled. */
+// public boolean jeiIntegration = true;
+ /** [Server-only] Whether Antique Atlas integration features are enabled. */
+ public boolean antiqueAtlasIntegration = true;
+ /** [Server-only] Whether global markers for wizard towers are added to antique atlases. */
+ public boolean autoTowerMarkers = true;
+ /** [Server-only] Whether global markers for obelisks are added to antique atlases. */
+ public boolean autoObeliskMarkers = true;
+ /** [Server-only] Whether global markers for shrines are added to antique atlases. */
+ public boolean autoShrineMarkers = true;
+
// Synchronised settings. These settings affect both client-side AND server-side code. Changing these locally
// only has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world.
@@ -148,6 +249,18 @@ public final class Settings {
* survival mode if this is false.
*/
public boolean discoveryMode = true;
+ /**
+ * [Synchronised] When set to true, players in creative mode can bypass arcane locks regardless of whether
+ * they are an op or not.
+ */
+ public boolean creativeBypassesArcaneLock = true;
+ /**
+ * [Synchronised] When set to true, players will be slowed when a nearby player or entity has the slow time
+ * effect.
+ */
+ public boolean slowTimeAffectsPlayers = true;
+ /** [Synchronised] Chance of 'misreading' an undiscovered spell and triggering a forfeit instead. */
+ public double forfeitChance = 0.2;
// Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys
// its own values for these, and changing them on a dedicated server will have no effect.
@@ -157,32 +270,46 @@ public final class Settings {
* [Client-only] Whether the player can switch between spells on a wand by scrolling with the mouse wheel
* while sneaking.
*/
- public boolean enableShiftScrolling = true;
- /** [Client-only] Whether to reverse the spell switching scroll direction.*/
+ public boolean shiftScrolling = true;
+ /** [Client-only] Whether to reverse the spell switching scroll direction. */
public boolean reverseScrollDirection = false;
// Display
/** [Client-only] Whether to show summoned creatures' names and owners above their heads. */
- public boolean showSummonedCreatureNames = true;
+ public boolean summonedCreatureNames = true;
+ /**
+ * [Client-only] When set to true, sections of The Wizard's Handbook are unlocked when a player
+ * gains the advancement that triggers them, and are hidden otherwise. When set to false, the entire handbook is
+ * readable regardless of advancement progress.
+ */
+ public boolean handbookProgression = true;
+ /** [Client-only] Whether the various book GUIs pause the game in singleplayer worlds. */
+ public boolean booksPauseGame = true;
+ /** [Client-only] Whether to use custom shaders for certain spells. */
+ public boolean useShaders = true;
/** [Client-only] The position of the spell HUD. */
public GuiPosition spellHUDPosition = GuiPosition.BOTTOM_LEFT;
-
+
public static final String DEFAULT_HUD_SKIN_KEY = "default"; // Defined here so it's not in a client-only class.
/** [Client-only] The string identifier of the skin used for the spell HUD. */
public String spellHUDSkin = DEFAULT_HUD_SKIN_KEY;
- /** Set of constants for each of the four positions that the spell HUD can be in. */
+ /** Set of constants for each of the eight positions that the spell HUD can be in. */
public enum GuiPosition {
- BOTTOM_LEFT("Bottom left", false, false),
- TOP_LEFT("Top left", false, true),
- TOP_RIGHT("Top right", true, true),
- BOTTOM_RIGHT("Bottom right", true, false);
+ BOTTOM_LEFT("Bottom left", false, false, false),
+ TOP_LEFT("Top left", false, true, false),
+ TOP_RIGHT("Top right", true, true, false),
+ BOTTOM_RIGHT("Bottom right", true, false, false),
+ FOLLOW_BOTTOM("Follow wand, bottom", false, false, true),
+ FOLLOW_TOP("Follow wand, top", false, true, true),
+ OPPOSITE_BOTTOM("Opposite wand, bottom", true, false, true),
+ OPPOSITE_TOP("Opposite wand, top", true, true, true);
/** Constant array storing the names of each of the constants, in the order they are declared. */
public static final String[] names;
- static{
+ static {
names = new String[values().length];
for(GuiPosition position : values()){
names[position.ordinal()] = position.name;
@@ -191,13 +318,15 @@ public final class Settings {
/** The readable name for this GUI position that will be displayed on the button in the config GUI. */
public final String name;
- public boolean flipX;
- public boolean flipY;
+ public final boolean flipX;
+ public final boolean flipY;
+ public final boolean dynamic;
- GuiPosition(String name, boolean flipX, boolean flipY){
+ GuiPosition(String name, boolean flipX, boolean flipY, boolean dynamic){
this.name = name;
this.flipX = flipX;
this.flipY = flipY;
+ this.dynamic = dynamic;
}
/**
@@ -236,8 +365,9 @@ public final class Settings {
setupGeneralConfig();
setupWorldgenConfig();
- setupClientConfig();
+ if(event.getSide() == Side.CLIENT) setupClientConfig(); // Server has no spell HUD skins so this would crash it
setupCommandsConfig();
+ setupCompatibilityConfig();
config.save();
}
@@ -252,9 +382,9 @@ public final class Settings {
Wizardry.logger.info("Setting up spells config for " + Spell.getTotalSpellCount() + " spells");
setupSpellsConfig();
-
+
Wizardry.logger.info("Setting up resistances config");
-
+
setupResistancesConfig();
config.save();
@@ -269,6 +399,7 @@ public final class Settings {
setupWorldgenConfig();
setupClientConfig();
setupCommandsConfig();
+ setupCompatibilityConfig();
setupSpellsConfig();
setupResistancesConfig();
@@ -298,6 +429,7 @@ public final class Settings {
I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc"));
// Uses the same config key as the spell name, because - well, that's what it's called!
property.setLanguageKey("spell." + spell.getUnlocalisedName());
+ Wizardry.proxy.setToNamedBooleanEntry(property);
spell.setEnabled(property.getBoolean());
}
@@ -306,7 +438,7 @@ public final class Settings {
private void setupGeneralConfig(){
// This trick is borrowed from forge; it sorts the config options into the order you want them.
- List propOrder = new ArrayList();
+ List propOrder = new ArrayList<>();
Property property;
@@ -315,108 +447,251 @@ public final class Settings {
property = config.get(GAMEPLAY_CATEGORY, "discoveryMode", true,
"For those who like a sense of mystery! When set to true, spells you haven't cast yet will be unreadable until you cast them (on a per-world basis). Has no effect when in creative mode. Spells of identification will be unobtainable in survival mode if this is false.");
property.setLanguageKey("config." + Wizardry.MODID + ".discovery_mode");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
property.setRequiresWorldRestart(true);
discoveryMode = property.getBoolean();
propOrder.add(property.getName());
- property = config.get(GAMEPLAY_CATEGORY, "friendlyFire", true,
- "Whether to allow players to damage their designated allies using magic.");
+ property = config.get(GAMEPLAY_CATEGORY, "legacyWandLevelling", false,
+ "Controls whether wands are required to gain progression before they can be upgraded to the next tier. Enable this option to revert to the pre-4.2 system, which only requires tomes of arcana. Wands will still gain progression even when this is enabled, so if you go back to the new system you won't lose any progress.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".legacy_wand_levelling");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ legacyWandLevelling = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "friendlyFire", FriendlyFire.ALL.name, "Controls which creatures may be damaged by your magic when allied to you. Your spells will not target your allies or creatures summoned/owned by them regardless of this setting, but this setting prevents all magic damage to allies.", FriendlyFire.names);
property.setLanguageKey("config." + Wizardry.MODID + ".friendly_fire");
- friendlyFire = property.getBoolean();
- propOrder.add(property.getName());
-
- property = config.get(GAMEPLAY_CATEGORY, "spellBookDropChance", 3,
- "The chance for mobs to drop a spell book when killed. The greater this number, the more often they will drop. Set to 0 to disable spell book drops. Set to 200 for guaranteed drops.",
- 0, 200);
- property.setLanguageKey("config." + Wizardry.MODID + ".spell_book_drop_chance");
- Wizardry.proxy.setToNumberSliderEntry(property);
- spellBookDropChance = property.getInt();
- propOrder.add(property.getName());
-
- property = config.get(GAMEPLAY_CATEGORY, "evilWizardDimensions", new int[]{0},
- "List of dimension ids in which evil wizards can spawn.");
- property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_dimensions");
- property.setRequiresMcRestart(true);
- evilWizardDimensions = property.getIntList();
- propOrder.add(property.getName());
-
- // These two aren't sliders because using a slider makes it difficult to fine-tune the numbers; the nature of a
- // scaling factor means that 0.5 is as big a change as 2.0, so whilst a slider is fine for increasing the
- // damage, it doesn't give fine enough control for values less than 1.
- property = config.get(GAMEPLAY_CATEGORY, "playerDamageScaling", 1.0,
- "Global damage scaling factor for the damage dealt by players casting spells, relative to 1.", 0, 20);
- property.setLanguageKey("config." + Wizardry.MODID + ".player_damage_scaling");
- playerDamageScale = property.getDouble();
- propOrder.add(property.getName());
-
- property = config.get(GAMEPLAY_CATEGORY, "npcDamageScaling", 1.0,
- "Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1.", 0, 20);
- property.setLanguageKey("config." + Wizardry.MODID + ".npc_damage_scaling");
- npcDamageScale = property.getDouble();
+ friendlyFire = FriendlyFire.fromName(property.getString());
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "minionRevengeTargeting", true,
"Whether summoned creatures can revenge attack their owner if their owner attacks them.");
property.setLanguageKey("config." + Wizardry.MODID + ".minion_revenge_targeting");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
property.setRequiresWorldRestart(false);
minionRevengeTargeting = property.getBoolean();
propOrder.add(property.getName());
- property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsWhitelist", new String[0],
- "List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
- property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_whitelist");
- property.setRequiresWorldRestart(true);
- // Converts all strings in the list to a ResourceLocation.
- summonedCreatureTargetsWhitelist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
+ property = config.get(GAMEPLAY_CATEGORY, "playersMoveEachOther", true,
+ "Whether to allow players to move other players around using magic.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".players_move_each_other");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ playersMoveEachOther = property.getBoolean();
propOrder.add(property.getName());
- property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsBlacklist",
- new String[]{"creeper"},
- "List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
- property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_blacklist");
- property.setRequiresWorldRestart(true);
- // Converts all strings in the list to a ResourceLocation.
- summonedCreatureTargetsBlacklist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
+ property = config.get(GAMEPLAY_CATEGORY, "playerBlockDamage", true,
+ "Whether spells cast by players can destroy blocks in the world. Set to false to prevent griefing. To prevent non-players from destroying blocks with magic, use the mobGriefing gamerule.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".player_block_damage");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ playerBlockDamage = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "telekineticDisarmament", true,
"Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items.");
property.setLanguageKey("config." + Wizardry.MODID + ".telekinetic_disarmament");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
telekineticDisarmament = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "teleportThroughUnbreakableBlocks", false,
"Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".teleport_through_unbreakable_blocks");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
teleportThroughUnbreakableBlocks = property.getBoolean();
propOrder.add(property.getName());
+ property = config.get(GAMEPLAY_CATEGORY, "worldTimeManipulation", true,
+ "Whether players are allowed to change the world time with the speed time spell. If this is false, the speed time spell will not change the world time but will still speed up nearby block, entity and tile entity ticks.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".world_time_manipulation");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ worldTimeManipulation = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "replaceVanillaFireballs", true,
+ "Whether to replace Minecraft's own fireballs with wizardry fireballs. If this is disabled, only wizardry spells will use the custom fireballs.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".replace_vanilla_fireballs");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ replaceVanillaFireballs = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "replaceVanillaFallDamage", true,
+ "Whether to replace Minecraft's distance-based fall damage calculation with an equivalent, velocity-based one. This is done such that mobs in freefall will take exactly the same damage as normal, so it will not break falling-based mob farms. Disable this if you experience falling-related weirdness! If this is disabled, some spells will use a more simplistic method of resetting the player's fall damage in certain cases.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".replace_vanilla_fall_damage");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ replaceVanillaFallDamage = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "creativeBypassesArcaneLock", true,
+ "Whether any player in creative mode can bypass arcane-locked blocks. If this is false, players must also be op in order to do so.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".creative_bypasses_arcane_lock");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ creativeBypassesArcaneLock = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "slowTimeAffectsPlayers", true,
+ "Whether players are slowed when another nearby player uses the slow time spell. If this is false, mobs and projectiles will still be affected but players will move at normal speed.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".slow_time_affects_players");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ slowTimeAffectsPlayers = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "mobLootTableWhitelist", new String[0], "Whitelist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually include them.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_whitelist");
+ property.setRequiresMcRestart(true);
+ mobLootTableWhitelist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "mobLootTableBlacklist", new String[]{"entities/vex", "entities/ender_dragon", "entities/wither", Wizardry.MODID + ":entities/evil_wizard"}, "Blacklist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually exclude them.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_blacklist");
+ property.setRequiresMcRestart(true);
+ mobLootTableBlacklist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "mobSpawnDimensions", new int[]{0},
+ "List of dimension ids in which wizardry's hostile mobs can spawn.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".mob_spawn_dimensions");
+ property.setRequiresMcRestart(true);
+ mobSpawnDimensions = property.getIntList();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "mobSpawnBiomeBlacklist", new String[]{"mushroom_island", "mushroom_island_shore"},
+ "List of names of biomes in which wizardry's hostile mobs cannot spawn. Biome names are not case-sensitive. For mod biomes, prefix with the mod ID (e.g. biomesoplenty:mystic_grove).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".mob_spawn_biome_blacklist");
+ property.setRequiresMcRestart(true);
+ mobSpawnBiomeBlacklist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "evilWizardSpawnRate", 3,
+ "Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable evil wizard spawning entirely.",
+ 0, 100);
+ property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_spawn_rate");
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ property.setRequiresMcRestart(true);
+ evilWizardSpawnRate = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "iceWraithSpawnRate", 3,
+ "Spawn rate for naturally-spawned ice wraiths; higher numbers mean more ice wraiths will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable ice wraith spawning entirely.",
+ 0, 100);
+ property.setLanguageKey("config." + Wizardry.MODID + ".ice_wraith_spawn_rate");
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ property.setRequiresMcRestart(true);
+ iceWraithSpawnRate = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "lightningWraithSpawnRate", 1,
+ "Spawn rate for naturally-spawned lightning wraiths; higher numbers mean more lightning wraiths will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable lightning wraith spawning entirely.",
+ 0, 100);
+ property.setLanguageKey("config." + Wizardry.MODID + ".lightning_wraith_spawn_rate");
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ property.setRequiresMcRestart(true);
+ lightningWraithSpawnRate = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "forfeitChance", 0.2,
+ "The chance to 'misread' an undiscovered spell and trigger a forfeit instead. Setting this to 0 effectively disables the forfeit mechanic. Has no effect if discovery mode is disabled.",
+ 0, 1);
+ property.setLanguageKey("config." + Wizardry.MODID + ".forfeit_chance");
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ forfeitChance = property.getDouble();
+ propOrder.add(property.getName());
+
+ // These two aren't sliders because using a slider makes it difficult to fine-tune the numbers; the nature of a
+ // scaling factor means that 0.5 is as big a change as 2.0, so whilst a slider is fine for increasing the
+ // damage, it doesn't give fine enough control for values less than 1.
+ property = config.get(GAMEPLAY_CATEGORY, "playerDamageScaling", 1.0,
+ "Global damage scaling factor for the damage dealt by players casting spells, relative to 1.", 0, 255);
+ property.setLanguageKey("config." + Wizardry.MODID + ".player_damage_scaling");
+ playerDamageScale = property.getDouble();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "npcDamageScaling", 1.0,
+ "Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1.", 0, 255);
+ property.setLanguageKey("config." + Wizardry.MODID + ".npc_damage_scaling");
+ npcDamageScale = property.getDouble();
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsWhitelist", new String[0],
+ "List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_whitelist");
+ property.setRequiresWorldRestart(true);
+ summonedCreatureTargetsWhitelist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsBlacklist", new String[]{"creeper"},
+ "List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_blacklist");
+ property.setRequiresWorldRestart(true);
+ summonedCreatureTargetsBlacklist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
property = config.get(GAMEPLAY_CATEGORY, "mindControlTargetsBlacklist", new String[]{},
- "List of names of entities which cannot be mind controlled, in addition to the defaults. Add creatures to this list if allowing them to be mind-controlled causes problems or could be exploited. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities which cannot be mind controlled, in addition to the defaults. Add creatures to this list if allowing them to be mind-controlled causes problems or could be exploited. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mind_control_targets_blacklist");
property.setRequiresWorldRestart(true);
- // Converts all strings in the list to a ResourceLocation.
- mindControlTargetsBlacklist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
+ mindControlTargetsBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
+ property = config.get(GAMEPLAY_CATEGORY, "pocketFurnaceItemBlacklist", new String[]{"cobblestone", "netherrack"},
+ "List of registry names of blocks or items which cannot be smelted by the pocket furnace spell, in addition to armour, tools and weapons. Block/item names are not case sensitive. For mod items, prefix with the mod ID (e.g. " + Wizardry.MODID + ":crystal_ore).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".pocket_furnace_item_blacklist");
+ property.setRequiresWorldRestart(true);
+ pocketFurnaceItemBlacklist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "divinationOreWhitelist", new String[0], "List of registry names of ore blocks which can be detected by the divination spell. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. " + Wizardry.MODID + ":crystal_ore).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".divination_ore_whitelist");
+ property.setRequiresWorldRestart(true);
+ divinationOreWhitelist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "swordItemWhitelist", new String[0], "List of registry names of items which should count as swords for imbuement spells. Most swords should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:broadsword).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".sword_item_whitelist");
+ property.setRequiresWorldRestart(true);
+ swordItemWhitelist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "bowItemWhitelist", new String[0], "List of registry names of items which should count as bows for imbuement spells. Most bows should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:shortbow).");
+ property.setLanguageKey("config." + Wizardry.MODID + ".bow_item_whitelist");
+ property.setRequiresWorldRestart(true);
+ bowItemWhitelist = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(GAMEPLAY_CATEGORY, "currencyItems", new String[]{"gold_ingot 3", "emerald 6"}, "List of registry names of items which wizard trades can use as currency (in the first slot; the second slot is unaffected). Each entry in this list should consist of an item registry name, followed by a single space, then an integer which defines the 'value' of the item. Higher values mean fewer of that currency item are required for a given trade.",
+ Pattern.compile("[A-Za-z:_]+ [0-9]+"));
+ property.setLanguageKey("config." + Wizardry.MODID + ".currency_items");
+ property.setRequiresWorldRestart(true);
+ propOrder.add(property.getName());
+ currencyItems = new HashMap<>();
+ for(String string : property.getStringList()){
+ String[] args = string.split(" ");
+ if(args.length != 2){
+ Wizardry.logger.warn("Invalid entry in currency items: {}", string);
+ continue; // Ignore invalid entries, the pattern above should ensure this never happens
+ }
+ try {
+ currencyItems.put(new ResourceLocation(args[0]), Integer.parseInt(args[1]));
+ }catch(NumberFormatException e){
+ Wizardry.logger.warn("Invalid integer in currency items: {}", args[1]);
+ }
+ }
+
config.setCategoryPropertyOrder(GAMEPLAY_CATEGORY, propOrder);
}
-
+
private void setupWorldgenConfig(){
- // This trick is borrowed from forge; it sorts the config options into the order you want them.
- List propOrder = new ArrayList();
+ List propOrder = new ArrayList<>();
Property property;
config.addCustomCategoryComment(WORLDGEN_CATEGORY, "Settings that affect world generation. In multiplayer, the server/LAN host settings will apply.");
-
- property = config.get(WORLDGEN_CATEGORY, "towerRarity", 8, "Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely.", 0, 50);
- property.setLanguageKey("config." + Wizardry.MODID + ".tower_rarity");
- property.setRequiresWorldRestart(true);
- Wizardry.proxy.setToNumberSliderEntry(property);
- towerRarity = property.getInt();
+
+ property = config.get(WORLDGEN_CATEGORY, "fastWorldgen", false, "Whether to use faster worldgen at the cost of 'seamlessness'. Enabling this option removes the checks for steep slopes and cleanup of floating trees that improve the look of worldgen. Performance improvement will vary depending on your setup. This option will affect randomisation; for any given seed, structures will not be the same as when it is turned off.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".fast_worldgen");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ fastWorldgen = property.getBoolean();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "towerDimensions", new int[]{0}, "List of dimension ids in which wizard towers will generate.");
@@ -425,6 +700,74 @@ public final class Settings {
towerDimensions = property.getIntList();
propOrder.add(property.getName());
+ property = config.get(WORLDGEN_CATEGORY, "towerRarity", 600, "Rarity of wizard towers. 1 in this many chunks will contain a wizard tower, meaning higher numbers are rarer.", 20, 5000);
+ property.setLanguageKey("config." + Wizardry.MODID + ".tower_rarity");
+ property.setRequiresWorldRestart(true);
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ towerRarity = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "evilWizardChance", 0.2, "The chance for wizard towers to generate with an evil wizard and chest inside, instead of a friendly wizard.", 0, 1);
+ property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_chance");
+ property.setRequiresWorldRestart(true);
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ evilWizardChance = property.getDouble();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "towerFiles", new String[]{Wizardry.MODID + ":wizard_tower_0", Wizardry.MODID + ":wizard_tower_1", Wizardry.MODID + ":wizard_tower_2", Wizardry.MODID + ":wizard_tower_3"},
+ "List of structure file locations for wizard towers without loot chests. One of these files will be randomly selected each time a wizard tower is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".tower_files");
+ property.setRequiresWorldRestart(true);
+ towerFiles = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "towerWithChestFiles", new String[]{Wizardry.MODID + ":wizard_tower_chest_0", Wizardry.MODID + ":wizard_tower_chest_1", Wizardry.MODID + ":wizard_tower_chest_2", Wizardry.MODID + ":wizard_tower_chest_3"},
+ "List of structure file locations for wizard towers with loot chests. One of these files will be randomly selected each time a wizard tower is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".tower_with_chest_files");
+ property.setRequiresWorldRestart(true);
+ towerWithChestFiles = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "obeliskDimensions", new int[]{0, -1}, "List of dimension ids in which obelisks will generate.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_dimensions");
+ property.setRequiresWorldRestart(true);
+ obeliskDimensions = property.getIntList();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "obeliskRarity", 550, "Rarity of obelisks. 1 in this many chunks will contain an obelisk, meaning higher numbers are rarer.", 20, 5000);
+ property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_rarity");
+ property.setRequiresWorldRestart(true);
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ obeliskRarity = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "obeliskFiles", new String[]{Wizardry.MODID + ":obelisk_0", Wizardry.MODID + ":obelisk_1", Wizardry.MODID + ":obelisk_2", Wizardry.MODID + ":obelisk_3", Wizardry.MODID + ":obelisk_4"},
+ "List of structure file locations for obelisks. One of these files will be randomly selected each time an obelisk is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_files");
+ property.setRequiresWorldRestart(true);
+ obeliskFiles = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "shrineDimensions", new int[]{0, -1}, "List of dimension ids in which shrines will generate.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".shrine_dimensions");
+ property.setRequiresWorldRestart(true);
+ shrineDimensions = property.getIntList();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "shrineRarity", 1000, "Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.", 20, 5000);
+ property.setLanguageKey("config." + Wizardry.MODID + ".shrine_rarity");
+ property.setRequiresWorldRestart(true);
+ Wizardry.proxy.setToNumberSliderEntry(property);
+ shrineRarity = property.getInt();
+ propOrder.add(property.getName());
+
+ property = config.get(WORLDGEN_CATEGORY, "shrineFiles", new String[]{Wizardry.MODID + ":shrine_0", Wizardry.MODID + ":shrine_1", Wizardry.MODID + ":shrine_2", Wizardry.MODID + ":shrine_3", Wizardry.MODID + ":shrine_4", Wizardry.MODID + ":shrine_5", Wizardry.MODID + ":shrine_6", Wizardry.MODID + ":shrine_7"},
+ "List of structure file locations for shrines. One of these files will be randomly selected each time a shrine is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".shrine_files");
+ property.setRequiresWorldRestart(true);
+ shrineFiles = getResourceLocationList(property);
+ propOrder.add(property.getName());
+
property = config.get(WORLDGEN_CATEGORY, "oreDimensions", new int[]{0}, "List of dimension ids in which crystal ore will generate. Note that removing the overworld (id 0) from this list will make the mod VERY difficult to play!");
property.setLanguageKey("config." + Wizardry.MODID + ".ore_dimensions");
property.setRequiresWorldRestart(true);
@@ -437,34 +780,35 @@ public final class Settings {
flowerDimensions = property.getIntList();
propOrder.add(property.getName());
- property = config.get(WORLDGEN_CATEGORY, "generateLoot", true, "Whether to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into the loot tables for vanilla dungeon chests.");
- property.setLanguageKey("config." + Wizardry.MODID + ".generate_loot");
- property.setRequiresWorldRestart(true);
- generateLoot = property.getBoolean();
+ property = config.get(WORLDGEN_CATEGORY, "lootInjectionLocations", DEFAULT_LOOT_INJECTION_LOCATIONS, "List of loot tables to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".loot_injection_locations");
+ property.setRequiresMcRestart(true);
+ lootInjectionLocations = getResourceLocationList(property);
propOrder.add(property.getName());
-
+
config.setCategoryPropertyOrder(WORLDGEN_CATEGORY, propOrder);
}
-
+
private void setupClientConfig(){
- // This trick is borrowed from forge; it sorts the config options into the order you want them.
List propOrder = new ArrayList();
Property property;
config.addCustomCategoryComment(CLIENT_CATEGORY, "Client-side settings that only affect the local minecraft game. If this file is on a dedicated server, these settings will have no effect; in multiplayer, each player obeys their own settings.");
- property = config.get(CLIENT_CATEGORY, "enableShiftScrolling", true,
+ property = config.get(CLIENT_CATEGORY, "shiftScrolling", true,
"Whether you can switch between spells on a wand by scrolling with the mouse wheel while sneaking. Note that this will only affect you; other players connected to the same server obey their own settings.");
- property.setLanguageKey("config." + Wizardry.MODID + ".enable_shift_scrolling");
+ property.setLanguageKey("config." + Wizardry.MODID + ".shift_scrolling");
property.setRequiresWorldRestart(false);
- enableShiftScrolling = property.getBoolean();
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ shiftScrolling = property.getBoolean();
propOrder.add(property.getName());
-
- property = config.get(CLIENT_CATEGORY, "reverseScrollDirection", false, "Whether to reverse the scroll direction used to switch between spells on a wand while sneaking.");
+
+ property = config.get(CLIENT_CATEGORY, "reverseScrollDirection", false, "The scroll direction used to switch between spells on a wand while sneaking.");
property.setLanguageKey("config." + Wizardry.MODID + ".reverse_scroll_direction");
property.setRequiresWorldRestart(false);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
reverseScrollDirection = property.getBoolean();
propOrder.add(property.getName());
@@ -472,35 +816,51 @@ public final class Settings {
property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_position");
spellHUDPosition = GuiPosition.fromName(property.getString());
propOrder.add(property.getName());
-
+
property = config.get(CLIENT_CATEGORY, "spellHUDSkin", DEFAULT_HUD_SKIN_KEY, "The skin used for the spell HUD.", Wizardry.proxy.getSpellHUDSkins().toArray(new String[0]));
property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_skin");
Wizardry.proxy.setToHUDChooserEntry(property);
spellHUDSkin = property.getString();
propOrder.add(property.getName());
- property = config.get(CLIENT_CATEGORY, "showSummonedCreatureNames", true, "Whether to show summoned creatures' names and owners above their heads.");
- property.setLanguageKey("config." + Wizardry.MODID + ".show_summoned_creature_names");
- showSummonedCreatureNames = property.getBoolean();
+ property = config.get(CLIENT_CATEGORY, "handbookProgression", true, "When set to true, sections of The Wizard's Handbook are unlocked when a player gains the advancement that triggers them, and are hidden otherwise. When set to false, the entire handbook is readable regardless of advancement progress.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".handbook_progression");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ handbookProgression = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(CLIENT_CATEGORY, "booksPauseGame", true, "Whether opening any of wizardry's books pauses the game in singleplayer. Has no effect on servers or LAN worlds.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".books_pause_game");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ booksPauseGame = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(CLIENT_CATEGORY, "summonedCreatureNames", true, "Whether to show summoned creatures' names and owners above their heads.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_names");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ summonedCreatureNames = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(CLIENT_CATEGORY, "useShaders", true, "Whether to use custom shaders for certain spells. These use the vanilla shader system (like mob spectating shaders) and shouldn't have much of an effect on performance in most cases, but they may conflict with other shaders.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".use_shaders");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ useShaders = property.getBoolean();
propOrder.add(property.getName());
config.setCategoryPropertyOrder(CLIENT_CATEGORY, propOrder);
}
-
+
private void setupCommandsConfig(){
- // This trick is borrowed from forge; it sorts the config options into the order you want them.
List propOrder = new ArrayList();
Property property;
config.addCustomCategoryComment(COMMANDS_CATEGORY, "Settings for the commands added by Wizardry. In multiplayer, the server/LAN host settings will apply.");
- // This one isn't a slider either because people are likely to want exact values ("it must be at most 50.3" is a
- // bit strange!).
property = config.get(COMMANDS_CATEGORY, "castCommandMultiplierLimit", 20.0,
"Upper limit for the multipliers passed into the /cast command. This is here to stop players from accidentally breaking a world/server. Large blast mutipliers can cause extreme lag - you have been warned!",
- 1, 255);
+ 1, Integer.MAX_VALUE); // Sure, I mean you COULD set it to 2^31-1... what could possibly go wrong?
property.setLanguageKey("config." + Wizardry.MODID + ".cast_command_multiplier_limit");
maxSpellCommandMultiplier = property.getDouble();
propOrder.add(property.getName());
@@ -532,7 +892,7 @@ public final class Settings {
property.setRequiresWorldRestart(true);
alliesCommandName = property.getString();
propOrder.add(property.getName());
-
+
config.setCategoryPropertyOrder(COMMANDS_CATEGORY, propOrder);
}
@@ -546,7 +906,7 @@ public final class Settings {
"Settings which allow entities to be made immune to certain types of magic. In multiplayer, the server/LAN host settings will apply.");
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToFire", new String[]{},
- "List of names of entities that are immune to fire, in addition to the defaults. Add mod creatures to this list if you want them to be immune to fire magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities that are immune to fire, in addition to the defaults. Add mod creatures to this list if you want them to be immune to fire magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_fire");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -559,7 +919,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToIce", new String[]{},
- "List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_ice");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -572,7 +932,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToLightning", new String[]{},
- "List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_lightning");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -585,7 +945,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToWither", new String[]{},
- "List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_wither");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -598,7 +958,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToPoison", new String[]{},
- "List of names of entities that are immune to poison, in addition to the defaults. Add mod creatures to this list if you want them to be immune to poison magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
+ "List of names of entities that are immune to poison, in addition to the defaults. Add mod creatures to this list if you want them to be immune to poison magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_poison");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -613,4 +973,87 @@ public final class Settings {
config.setCategoryPropertyOrder(RESISTANCES_CATEGORY, propOrder);
}
+ private void setupCompatibilityConfig(){
+
+ List propOrder = new ArrayList();
+
+ Property property;
+
+ config.addCustomCategoryComment(COMPATIBILITY_CATEGORY, "Settings that affect how wizardry interacts with other mods. In multiplayer, the server/LAN host settings will apply.");
+
+ property = config.get(COMPATIBILITY_CATEGORY, "damageSourceBlacklist", new String[]{},
+ "List of damage source string identifiers to be ignored when re-applying damage. Case-sensitive. A message will be logged if wizardry detects a damage source that should be added to this list. Otherwise, don't change unless instructed to do so.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".damage_source_blacklist");
+ property.setRequiresWorldRestart(true);
+ damageSourceBlacklist = property.getStringList();
+ propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "compatibilityWarnings", true,
+ "Whether to print compatibility warnings to the console. Set to false if excessive messages are being printed.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".compatibility_warnings");
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ compatibilityWarnings = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "baublesIntegration", true,
+ "If Baubles is installed, controls whether Baubles integration features are enabled. If this is disabled, wizardry will always behave as if Baubles is not installed.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".baubles_integration");
+ property.setRequiresMcRestart(true);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ baublesIntegration = property.getBoolean();
+ propOrder.add(property.getName());
+
+// property = config.get(COMPATIBILITY_CATEGORY, "jeiIntegration", true,
+// "If JEI (Just Enough Items) is installed, controls whether JEI integration features are enabled. If this is disabled, wizardry will always behave as if JEI is not installed.");
+// property.setLanguageKey("config." + Wizardry.MODID + ".jei_integration");
+// property.setRequiresMcRestart(true);
+// Wizardry.proxy.setToNamedBooleanEntry(property);
+// jeiIntegration = property.getBoolean();
+// propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "antiqueAtlasIntegration", true,
+ "If Antique Atlas is installed, controls whether Antique Atlas integration features are enabled. If this is disabled, wizardry will always behave as if Antique Atlas is not installed.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".antique_atlas_integration");
+ property.setRequiresMcRestart(true);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ antiqueAtlasIntegration = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceTowerMarkers", true,
+ "Controls whether wizardry automatically places antique atlas markers at the locations of wizard towers.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_tower_markers");
+ property.setRequiresMcRestart(true);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ autoTowerMarkers = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceObeliskMarkers", true,
+ "Controls whether wizardry automatically places antique atlas markers at the locations of obelisks.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_obelisk_markers");
+ property.setRequiresMcRestart(true);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ autoObeliskMarkers = property.getBoolean();
+ propOrder.add(property.getName());
+
+ property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceShrineMarkers", true,
+ "Controls whether wizardry automatically places antique atlas markers at the locations of shrines.");
+ property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_shrine_markers");
+ property.setRequiresMcRestart(true);
+ Wizardry.proxy.setToNamedBooleanEntry(property);
+ autoShrineMarkers = property.getBoolean();
+ propOrder.add(property.getName());
+
+ config.setCategoryPropertyOrder(COMPATIBILITY_CATEGORY, propOrder);
+
+ }
+
+ /** Retrieves a string list from the given property and converts it to an array of {@link ResourceLocation}s. */
+ public static ResourceLocation[] getResourceLocationList(Property property){
+ return toResourceLocations(property.getStringList());
+ }
+
+ /** Converts the given strings to an array of {@link ResourceLocation}s */
+ public static ResourceLocation[] toResourceLocations(String... strings){
+ return Arrays.stream(strings).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
+ }
}
diff --git a/src/main/java/electroblob/wizardry/Wizardry.java b/src/main/java/electroblob/wizardry/Wizardry.java
index 732b36ae..1eeb4806 100644
--- a/src/main/java/electroblob/wizardry/Wizardry.java
+++ b/src/main/java/electroblob/wizardry/Wizardry.java
@@ -1,23 +1,22 @@
package electroblob.wizardry;
-import org.apache.logging.log4j.Logger;
-
import electroblob.wizardry.command.CommandCastSpell;
import electroblob.wizardry.command.CommandDiscoverSpell;
import electroblob.wizardry.command.CommandSetAlly;
import electroblob.wizardry.command.CommandViewAllies;
+import electroblob.wizardry.data.DispenserCastingData;
+import electroblob.wizardry.data.WizardData;
+import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
+import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
+import electroblob.wizardry.misc.Forfeit;
import electroblob.wizardry.packet.WizardryPacketHandler;
-import electroblob.wizardry.registry.WizardryAdvancementTriggers;
-import electroblob.wizardry.registry.WizardryItems;
-import electroblob.wizardry.registry.WizardryRegistry;
-import electroblob.wizardry.registry.WizardryTabs;
+import electroblob.wizardry.registry.*;
+import electroblob.wizardry.spell.Spell;
+import electroblob.wizardry.util.CustomSoundCategory;
+import electroblob.wizardry.util.SpellProperties;
+import electroblob.wizardry.worldgen.*;
import net.minecraft.item.Item;
-import net.minecraft.nbt.NBTBase;
-import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.MinecraftForge;
-import net.minecraftforge.common.capabilities.Capability;
-import net.minecraftforge.common.capabilities.Capability.IStorage;
-import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -30,12 +29,13 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
+import org.apache.logging.log4j.Logger;
/**
* "Electroblob's Wizardry adds an RPG-like system of spells to Minecraft, with the aim of being as playable as
* possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master
* the arcane! - But you knew that, right?"
- *
+ *
* Main mod class for Wizardry. Contains the logger and settings instances, along with all the other stuff that's normally
* in a main mod class.
* @author Electroblob
@@ -50,9 +50,9 @@ public class Wizardry {
public static final String NAME = "Electroblob's Wizardry";
/**
* The version number for this version of wizardry. The following system is used for version numbers:
- *
+ *
*
[major Minecraft version].[major mod version].[minor mod version/patch]
- *
+ *
*
The major mod version is consistent across Minecraft versions, i.e. Wizardry 1.1 has the same
* features as Wizardry 2.1, but they are for different versions of Minecraft and have separate minor versioning.
* 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft
@@ -60,56 +60,28 @@ public class Wizardry {
*/
public static final String VERSION = "4.2.0";
- // IDEA: Improve the algorithm that finds a place to summon creatures to take walls into account.
- // IDEA: Replace all uses of Math.cos and Math.sin with MathHelper versions
// IDEA: Triggering of inbuilt Forge events in relevant places?
// IDEA: Abstract the vanilla particles behind the particle builder
- /* Minor bugs that need fixing at some point:
- * - Player skin hat layer shows through wizard hats - Wizard armour breaks rather than just running out of mana (I
- * can't seem to replicate this bug, but I have had it happen to me before...)
- * - Shift-clicking a stack of special upgrades when in the arcane workbench causes the whole stack to be
- * transferred when it should be just one (this is a bug with vanilla as well - try putting a stack of bottles into
- * a brewing stand). I have at least made it so only one gets used now, so it has no impact on the game.
- * - When a spell is on cooldown, you can't break blocks when holding a wand. */
-
- // TODO: So somehow I have managed to overlook the fact that health is actually a float. What this means is that
- // I can make the healing spells use damage multipliers - hurrah!
-
- // TODO: Switch from IInventory to IItemHandler (Or don't. It's only useful for automation really.)
// TODO: Have particles obey Minecraft's particle setting where appropriate
// (see https://github.com/RootsTeam/Embers/blob/master/src/main/java/teamroots/embers/particle/ParticleUtil.java)
- // TODO: Go over all the worldgen code, use IWorldGenerator
- // TODO: Implement a continuous sound system using MovingSoundEntity, allowing continuous spells to have a long sound
- // loop as well as a start and end sound
- // TODO: EntityMagicArrow needs attention
- // TODO: Go over particle spawning on projectile impact and make sure hitvec is used wherever appropriate
- // TODO: Replace spell IDs in packets with ResourceLocation strings
- // TODO: Forcefield needs looking at, esp. with regards to projectiles and explosions
// TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty
- // TODO: Fireskin somehow lost its particles
- // TODO: Convert settings over to the @Config system
- // TODO: Go through listeners of LivingHurtEvent and decide whether they should change to LivingDamageEvent
-
- // NOTE: Add melee upgrades to loot tables when they are added.
+ // TODO: See what can be done with the illager spell sounds (and go over sounds in general)
+ // TODO: Fix possession
+ // TODO: Fix charge
/** Static instance of the {@link Settings} object for Wizardry. */
public static final Settings settings = new Settings();
/** Static instance of the {@link Logger} object for Wizardry.
- *
+ *
* Logging conventions for wizardry (only these levels are used currently):
- *
+ *
* - ERROR: Anything that threw an exception; may or may not crash the game.
* - WARN: Anything that isn't supposed to happen during normal operation, but didn't throw an exception.
* - INFO: Anything that might happen during normal mod operation that the user needs to know about. */
public static Logger logger;
- // private static Pattern entityNamePattern;
-
- // EventManager
- WizardryWorldGenerator generator = new WizardryWorldGenerator();
-
// The instance of wizardry that Forge uses.
@Instance(Wizardry.MODID)
public static Wizardry instance;
@@ -123,40 +95,27 @@ public class Wizardry {
logger = event.getModLog();
- proxy.registerResourceReloadListener();
+ proxy.registerResourceReloadListeners();
settings.initConfig(event);
- // Yes - by the looks of it, having an interface is completely unnecessary in this case.
- CapabilityManager.INSTANCE.register(WizardData.class, new IStorage(){
- // These methods are only called by Capability.writeNBT() or Capability.readNBT(), which in turn are
- // NEVER CALLED. Unless I'm missing some reflective invocation, that means this entire class serves only
- // to allow capabilities to be saved and loaded manually. What that would be useful for I don't know.
- // (If an API forces most users to write redundant code for no reason, it's not user friendly, is it?)
- // ... well, that's my rant for today!
- @Override
- public NBTBase writeNBT(Capability capability, WizardData instance, EnumFacing side){
- return null;
- }
-
- @Override
- public void readNBT(Capability capability, WizardData instance, EnumFacing side, NBTBase nbt){
- }
- }, WizardData::new);
-
- WizardryRegistry.registerTileEntities();
-
- WizardryRegistry.registerEntities();
- // The check for the generateLoot setting is now done within this method.
- WizardryRegistry.registerLoot();
+ // Capabilities
+ WizardData.register();
+ DispenserCastingData.register();
+ // Register things that don't have registries
+ WizardryBlocks.registerTileEntities();
+ WizardryLoot.register();
WizardryAdvancementTriggers.register();
+ Forfeit.register();
- // Moved to preInit, because apparently it has to be here now.
+ // Client-side stuff (via proxies)
proxy.registerRenderers();
- // It seems this also has to be here
proxy.registerKeyBindings();
+ WizardryBaublesIntegration.init();
+ WizardryAntiqueAtlasIntegration.init();
+
}
@EventHandler
@@ -164,26 +123,41 @@ public class Wizardry {
settings.initConfigExtras();
- // Event Handlers
- GameRegistry.registerWorldGenerator(generator, 0);
- MinecraftForge.EVENT_BUS.register(instance);
+ // World generators
+ // Weight is a misnomer, it's actually the priority (where lower numbers get generated first)
+ // Literally nothing on typical 'weight' values here, there isn't even an upper limit
+ // Examples I've managed to find:
+ // - Tinker's construct slime islands use 25
+ GameRegistry.registerWorldGenerator(new WorldGenCrystalOre(), 0);
+ GameRegistry.registerWorldGenerator(new WorldGenCrystalFlower(), 50);
+ GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20);
+ GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20);
+ GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20);
+
+ // This is for the config change and missing mappings events
+ MinecraftForge.EVENT_BUS.register(instance); // Since there's already an instance we might as well use it
+
NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler());
WizardryPacketHandler.initPackets();
+ // Post-registry extras
+ WizardryItems.populateWandMap();
+ WizardryItems.populateArmourMap();
+ WizardryItems.registerDispenseBehaviours();
+ Spell.registry.forEach(Spell::init);
+ SpellProperties.init();
+
+ // Client-side stuff (via proxies)
proxy.initGuiBits();
- proxy.initParticleFactories();
+ proxy.registerParticles();
+ proxy.registerSoundEventListener();
+
+ WizardrySounds.SPELLS = CustomSoundCategory.add(Wizardry.MODID + ":spells");
}
@EventHandler
public void postInit(FMLPostInitializationEvent event){
- // This needs to be here or it won't necessarily include all the mods' entities.
- // TODO: Re-implement this when the Forge bug is fixed.
- /* Doesn't seem to be doing anything... String entityNames = ""; for(Object name :
- * EntityList.classToStringMapping.values()){ if(name instanceof String){ entityNames = entityNames + name +
- * '|'; } } // Cuts off the last '|' entityNames = entityNames.substring(0, entityNames.length()-1);
- * entityNamePattern = Pattern.compile(entityNames); */
proxy.initialiseLayers();
- WizardryTabs.sort();
}
@EventHandler
@@ -205,23 +179,30 @@ public class Wizardry {
// 2.1 changed some item ids, so this fixes them for existing worlds
// Nobody updates minecraft versions when using mods, but I may as well leave this here just in case.
@SubscribeEvent
- public static void onMissingMappingEvent(RegistryEvent.MissingMappings event){
+ public static void onMissingItemMappingEvent(RegistryEvent.MissingMappings event){
// Just get, not getAll, since the mod id didn't change!
for(RegistryEvent.MissingMappings.Mapping mapping : event.getAllMappings()){
- if(mapping.key.getResourceDomain().equals(Wizardry.MODID)){
+ if(mapping.key.getNamespace().equals(Wizardry.MODID)){
Item replacement;
- switch(mapping.key.getResourcePath()){
+ switch(mapping.key.getPath()){
case "wand_basic": replacement = WizardryItems.magic_wand; break;
- case "wand_basic_fire": replacement = WizardryItems.basic_fire_wand; break;
- case "wand_basic_ice": replacement = WizardryItems.basic_ice_wand; break;
- case "wand_basic_lightning": replacement = WizardryItems.basic_lightning_wand; break;
- case "wand_basic_necromancy": replacement = WizardryItems.basic_necromancy_wand; break;
- case "wand_basic_earth": replacement = WizardryItems.basic_earth_wand; break;
- case "wand_basic_sorcery": replacement = WizardryItems.basic_sorcery_wand; break;
- case "wand_basic_healing": replacement = WizardryItems.basic_healing_wand; break;
+ case "wand_basic_fire": replacement = WizardryItems.novice_fire_wand; break;
+ case "wand_basic_ice": replacement = WizardryItems.novice_ice_wand; break;
+ case "wand_basic_lightning": replacement = WizardryItems.novice_lightning_wand; break;
+ case "wand_basic_necromancy": replacement = WizardryItems.novice_necromancy_wand; break;
+ case "wand_basic_earth": replacement = WizardryItems.novice_earth_wand; break;
+ case "wand_basic_sorcery": replacement = WizardryItems.novice_sorcery_wand; break;
+ case "wand_basic_healing": replacement = WizardryItems.novice_healing_wand; break;
+ case "basic_fire_wand": replacement = WizardryItems.novice_fire_wand; break;
+ case "basic_ice_wand": replacement = WizardryItems.novice_ice_wand; break;
+ case "basic_lightning_wand": replacement = WizardryItems.novice_lightning_wand; break;
+ case "basic_necromancy_wand": replacement = WizardryItems.novice_necromancy_wand; break;
+ case "basic_earth_wand": replacement = WizardryItems.novice_earth_wand; break;
+ case "basic_sorcery_wand": replacement = WizardryItems.novice_sorcery_wand; break;
+ case "basic_healing_wand": replacement = WizardryItems.novice_healing_wand; break;
case "wand_apprentice": replacement = WizardryItems.apprentice_wand; break;
case "wand_apprentice_fire": replacement = WizardryItems.apprentice_fire_wand; break;
case "wand_apprentice_ice": replacement = WizardryItems.apprentice_ice_wand; break;
@@ -264,4 +245,13 @@ public class Wizardry {
}
}
+ @SubscribeEvent
+ public static void onMissingSpellMappingEvent(RegistryEvent.MissingMappings event){
+ for(RegistryEvent.MissingMappings.Mapping mapping : event.getAllMappings()){
+ if(mapping.key.getNamespace().equals(Wizardry.MODID)){
+ if(mapping.key.getPath().equals("firestorm")) mapping.remap(Spells.fire_breath);
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/electroblob/wizardry/WizardryEventHandler.java b/src/main/java/electroblob/wizardry/WizardryEventHandler.java
index ebebcb77..7a57ef9c 100644
--- a/src/main/java/electroblob/wizardry/WizardryEventHandler.java
+++ b/src/main/java/electroblob/wizardry/WizardryEventHandler.java
@@ -1,120 +1,137 @@
package electroblob.wizardry;
import electroblob.wizardry.constants.Constants;
-import electroblob.wizardry.entity.EntityArc;
-import electroblob.wizardry.entity.living.EntityEvilWizard;
+import electroblob.wizardry.data.SpellEmitterData;
+import electroblob.wizardry.data.SpellGlyphData;
+import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.ISpellCaster;
-import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.integration.DamageSafetyChecker;
-import electroblob.wizardry.item.ItemWand;
-import electroblob.wizardry.item.ItemWizardArmour;
-import electroblob.wizardry.registry.Spells;
-import electroblob.wizardry.registry.WizardryAdvancementTriggers;
-import electroblob.wizardry.registry.WizardryEnchantments;
-import electroblob.wizardry.registry.WizardryItems;
-import electroblob.wizardry.registry.WizardryPotions;
-import electroblob.wizardry.registry.WizardrySounds;
+import electroblob.wizardry.item.IManaStoringItem;
+import electroblob.wizardry.item.ItemArtefact;
+import electroblob.wizardry.packet.PacketSyncAdvancements;
+import electroblob.wizardry.packet.WizardryPacketHandler;
+import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.FreezingWeapon;
+import electroblob.wizardry.spell.ImbueWeapon;
import electroblob.wizardry.spell.Spell;
+import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
-import electroblob.wizardry.util.ParticleBuilder;
-import electroblob.wizardry.util.SpellModifiers;
-import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.ParticleBuilder.Type;
-import electroblob.wizardry.util.WizardryUtilities;
+import net.minecraft.advancements.Advancement;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
-import net.minecraft.entity.item.EntityItem;
-import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
-import net.minecraft.init.SoundEvents;
-import net.minecraft.inventory.ContainerPlayer;
-import net.minecraft.inventory.ContainerWorkbench;
-import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
-import net.minecraft.item.ItemSword;
import net.minecraft.potion.PotionEffect;
-import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
-import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
-import net.minecraft.world.storage.loot.LootEntry;
-import net.minecraft.world.storage.loot.LootEntryTable;
-import net.minecraft.world.storage.loot.LootPool;
-import net.minecraft.world.storage.loot.RandomValueRange;
-import net.minecraft.world.storage.loot.conditions.LootCondition;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer;
-import net.minecraftforge.event.LootTableLoadEvent;
-import net.minecraftforge.event.entity.living.LivingAttackEvent;
-import net.minecraftforge.event.entity.living.LivingDeathEvent;
-import net.minecraftforge.event.entity.living.LivingDropsEvent;
+import net.minecraftforge.event.entity.PlaySoundAtEntityEvent;
+import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
-import net.minecraftforge.event.entity.living.LivingHurtEvent;
+import net.minecraftforge.event.entity.player.AdvancementEvent;
+import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
+import java.util.ArrayList;
+
/**
- * As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only a few
- * miscellaneous things that don't make much sense anywhere else, or that are better kept together. Previously, this was
- * a gigantic class with about half of the entire mod's logic in it!
+ * General-purpose event handler for things that don't fit anywhere else or groups of related behaviours that are better
+ * kept together. As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only
+ * a few miscellaneous things that don't make much sense anywhere else, or that are better kept together (previously,
+ * this was a gigantic class with about half of the entire mod's logic in it!)
*
* @author Electroblob
* @since Wizardry 1.0
*/
+// The general rules for where event-based logic goes are:
+// - If the logic relates only to vanilla things or is general (e.g. gameplay settings) it lives in here
+// - If there is an obvious class for the thing relevant to the logic being performed, it goes in there for the sake
+// of modularity/separation-of-concerns (e.g. armour cost reductions go in ItemWizardArmour)
+// - If the thing has an instance but not a separate class (e.g. most custom potions), it goes in a related class
+// where applicable (e.g. a spell class), or if not it goes in here
+// - If several things share a significant amount of logic, to avoid duplicate code and potentially improve efficiency
+// they should be kept together either in a class relevant to all of them (probably a common superclass) or in here
+// - Client-side logic goes in WizardryClientEventHandler or another relevant client-side class
@Mod.EventBusSubscriber
public final class WizardryEventHandler {
- // IDEA: Config option allowing users to specify loot locations
- private static final String[] LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
- "minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
- "minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
- "minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
- "minecraft:chests/end_city_treasure"};
-
- @SubscribeEvent
- public static void onLootTableLoadEvent(LootTableLoadEvent event){
- if(Wizardry.settings.generateLoot){
- for(String location : LOOT_INJECTION_LOCATIONS){
- if(event.getName().toString().matches(location)){
- event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/dungeon_additions"));
- }
- }
- }
- }
-
- private static LootPool getAdditive(String entryName){
- return new LootPool(new LootEntry[]{getAdditiveEntry(entryName, 1)}, new LootCondition[0],
- new RandomValueRange(1), new RandomValueRange(0, 1), Wizardry.MODID + "_additive_pool");
- }
-
- private static LootEntryTable getAdditiveEntry(String name, int weight){
- return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0],
- Wizardry.MODID + "_additive_entry");
- }
+ private WizardryEventHandler(){} // No instances!
@SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
- // When a player logs in, they are sent the glyph data and the server's settings.
+ // When a player logs in, they are sent the glyph data, server settings and spell properties.
if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player);
+ SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player);
+ syncAdvancements((EntityPlayerMP)event.player, false);
+ }
+ Spell.syncProperties(event.player);
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ public static void onPlaySoundAtEntityEvent(PlaySoundAtEntityEvent event){
+ // Muffle (there's no spell class for it so it's here instead)
+ if(event.getEntity() instanceof EntityLivingBase
+ && ((EntityLivingBase)event.getEntity()).isPotionActive(WizardryPotions.muffle)){
+ event.setCanceled(true);
}
}
@SubscribeEvent
+ public static void onAdvancementEvent(AdvancementEvent event){
+ // Forge has no hook for revoked advancements :(
+ // Guess we'll just have to make do
+ // Also, this seems to get fired on player login, so to prevent the toasts from appearing every login the
+ // only way I can see to do it is by testing the player has been around long enough.
+ if(event.getEntityPlayer() instanceof EntityPlayerMP && event.getEntityPlayer().ticksExisted > 0){
+ syncAdvancements((EntityPlayerMP)event.getEntityPlayer(), true);
+ }
+ }
+
+ private static void syncAdvancements(EntityPlayerMP player, boolean showToasts){
+
+ Wizardry.logger.info("Synchronising advancements for " + player.getName());
+
+ ArrayList advancements = new ArrayList<>();
+
+ for(Advancement advancement : player.getServer().getAdvancementManager().getAdvancements()){
+ if(player.getAdvancements().getProgress(advancement).isDone()) advancements.add(advancement.getId());
+ }
+
+ WizardryPacketHandler.net.sendTo(new PacketSyncAdvancements.Message(showToasts, advancements.toArray(new ResourceLocation[0])), player);
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH) // Disabling of specific spells comes after arcane jammer but before everything else
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
+
+ boolean enabled = true;
+
+ switch(event.getSource()){
+ case WAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.WANDS); break;
+ case SCROLL: enabled = event.getSpell().isEnabled(SpellProperties.Context.SCROLL); break;
+ case COMMAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.COMMANDS); break;
+ case NPC: enabled = event.getSpell().isEnabled(SpellProperties.Context.NPCS); break;
+ case DISPENSER: enabled = event.getSpell().isEnabled(SpellProperties.Context.DISPENSERS); break;
+ case OTHER: enabled = event.getSpell().isEnabled(); break; // Any enabled context will do for this one
+ }
+
// If a spell is disabled in the config, it will not work.
- if(!event.getSpell().isEnabled()){
- if(!event.getEntityLiving().world.isRemote) event.getEntity().sendMessage(
+ if(!enabled){
+ if(event.getCaster() != null && !event.getCaster().world.isRemote) event.getCaster().sendMessage(
new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted()));
event.setCanceled(true);
}
@@ -123,32 +140,36 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Post event){
- // Spell discovery (only players can discover spells, obviously)
- if(event.getEntity() instanceof EntityPlayer){
+ if(event.getCaster() instanceof EntityPlayer){
- EntityPlayer player = (EntityPlayer)event.getEntity();
+ EntityPlayer player = (EntityPlayer)event.getCaster();
+ // Advancement triggers
+ if(player instanceof EntityPlayerMP){
+ WizardryAdvancementTriggers.cast_spell.trigger((EntityPlayerMP)player, event.getSpell(), player.getHeldItem(player.getActiveHand()));
+ }
+
+ // Spell discovery (only players can discover spells, obviously)
WizardData data = WizardData.get(player);
if(data != null){
// Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now
// it's in common code, which is nice!)
// Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled.
- if(!MinecraftForge.EVENT_BUS
- .post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
+ if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
&& data.discoverSpell(event.getSpell())){
// If the spell wasn't already discovered, other stuff happens:
if(event.getSource() == SpellCastEvent.Source.COMMAND){
// If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client.
- if(!event.getSpell().doesSpellRequirePacket()) data.sync();
+ if(!event.getSpell().requiresPacket()) data.sync();
- }else if(!event.getEntity().world.isRemote && !player.capabilities.isCreativeMode
+ }else if(!event.getCaster().world.isRemote && !player.isCreative()
&& Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on, and only when
// the spell wasn't cast using commands.
- WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
+ WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
player.sendMessage(new TextComponentTranslation("spell.discover",
event.getSpell().getNameForTranslationFormatted()));
}
@@ -157,33 +178,59 @@ public final class WizardryEventHandler {
}
}
+ @SubscribeEvent(priority = EventPriority.LOW)
+ public static void onDiscoverSpellEvent(DiscoverSpellEvent event){
+ if(event.getEntityPlayer() instanceof EntityPlayerMP){
+ WizardryAdvancementTriggers.discover_spell.trigger((EntityPlayerMP)event.getEntityPlayer(), event.getSpell(), event.getSource());
+ }
+ }
+
+ @SubscribeEvent
+ public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
+
+ if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving
+ && event.getTarget().isPotionActive(WizardryPotions.muffle)){
+
+ Vec3d vec = event.getTarget().getPositionEyes(1).subtract(event.getEntity().getPositionEyes(1));
+ // Find the angle between the direction the mob is looking and the direction the player is in
+ // Angle between a and b = acos((a.b) / (|a|*|b|))
+ double angle = Math.acos(vec.dotProduct(event.getEntity().getLookVec()) / vec.length());
+ System.out.println(angle);
+ // If the player is not within the 144-degree arc in front of the mob, it won't detect them
+ if(angle > 0.4 * Math.PI){
+ ((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
+ }
+ }
+ }
+
/* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent
* fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually
* succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel
- * the damage). Things are processed in the following order: * LivingAttackEvent * - Invulnerability -
- * Already-dead-ness - Fire resistance - Helmets vs. falling things - Hurt resistant time - Invulnerability (again)
- * * LivingHurtEvent * - Armour - Potions - Health is finally changed Of course, there are no guarantees that other
+ * the damage). Things are processed in the following order:
+ *
+ * * LivingAttackEvent *
+ * - Invulnerability
+ * - Already-dead-ness
+ * - Fire resistance
+ * - Helmets vs. falling things
+ * - Hurt resistant time
+ * - Invulnerability (again)
+ * * LivingHurtEvent *
+ * - Armour
+ * - Potions
+ * * LivingDamageEvent *
+ * - Health is finally changed
+ *
+ * Of course, there are no guarantees that other
* mods hooking into these two events will be called before or after yours, but you can have some degree of control
* by choosing which event to use. EDIT: Actually, there are. Firstly, you can set a priority in the @SubscribeEvent
* annotation which defines how early (higher priority) or late (lower priority) the method is called. Methods with
* the same priority are sorted alphabetically by mod id (so it's safe to assume wizardry would be fairly late on!).
* I wonder if there are any conventions for what sort of things take what priority...? */
- @SubscribeEvent
+ @SubscribeEvent(priority = EventPriority.LOW) // Low priority in case the event gets cancelled at default priority
public static void onLivingAttackEvent(LivingAttackEvent event){
- // Prevents any damage to allies from magic if friendly fire is disabled
- if(!Wizardry.settings.friendlyFire && event.getSource() != null
- && event.getSource().getTrueSource() instanceof EntityPlayer && event.getEntity() instanceof EntityPlayer
- && event.getSource() instanceof IElementalDamage){
- if(WizardryUtilities.isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(),
- (EntityPlayer)event.getEntity())){
- event.setCanceled(true);
- // This needs to be here, since if the event is cancelled nothing else needs to happen.
- return;
- }
- }
-
// Retaliatory effects
// These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy)
// if statement in one place.
@@ -194,38 +241,43 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
World world = event.getEntityLiving().world;
- // Fireskin
- if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
- && !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
- attacker.setFire(5);
+ if(attacker.getDistance(event.getEntityLiving()) < 10){
- // Ice Shroud
- if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
- && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving())
- && !(event.getEntityLiving() instanceof FakePlayer))
- attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
+ // Fireskin
+ if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
+ && !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
+ attacker.setFire(Spells.fire_breath.getProperty(Spell.BURN_DURATION).intValue());
- // Static Aura
- if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
+ // Ice Shroud
+ if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
+ && !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving())
+ && !(attacker instanceof FakePlayer)) // Fake players cause problems
+ attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost,
+ Spells.ice_shroud.getProperty(Spell.EFFECT_DURATION).intValue(),
+ Spells.ice_shroud.getProperty(Spell.EFFECT_STRENGTH).intValue()));
- if(world.isRemote){
-
- ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height/2, 0)
- .target(attacker).spawn(world);
-
- ParticleBuilder.spawnShockParticles(world, attacker.posX,
- attacker.getEntityBoundingBox().minY + attacker.height, attacker.posZ);
+ // Static Aura
+ if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
+
+ if(world.isRemote){
+
+ ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height / 2, 0)
+ .target(attacker).spawn(world);
+
+ ParticleBuilder.spawnShockParticles(world, attacker.posX,
+ attacker.getEntityBoundingBox().minY + attacker.height / 2, attacker.posZ);
+ }
+
+ DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
+ DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
+ attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
}
-
- DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
- DamageType.SHOCK, true), 4.0f, event.getSource().getDamageType());
- attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
}
}
}
- @SubscribeEvent
+ @SubscribeEvent(priority = EventPriority.LOW) // Again, we don't want these effects if the event is cancelled
public static void onLivingHurtEvent(LivingHurtEvent event){
// Flaming and freezing swords
@@ -234,7 +286,7 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
// Players can only ever attack with their main hand, so this is the right method to use here.
- if(!attacker.getHeldItemMainhand().isEmpty() && attacker.getHeldItemMainhand().getItem() instanceof ItemSword){
+ if(!attacker.getHeldItemMainhand().isEmpty() && ImbueWeapon.isSword(attacker.getHeldItemMainhand().getItem())){
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon,
attacker.getHeldItemMainhand());
@@ -275,30 +327,13 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
- if(event.getEntityLiving() instanceof EntityPlayer){
-
- EntityPlayer player = (EntityPlayer)event.getEntityLiving();
-
- if(player.world.isRemote) hackilyFixContinuousSpellCasting(player);
-
- // Mana flask crafting
- if(player.openContainer instanceof ContainerWorkbench){
-
- IInventory craftMatrix = ((ContainerWorkbench)player.openContainer).craftMatrix;
- ItemStack output = ((ContainerWorkbench)player.openContainer).craftResult.getStackInSlot(0);
- processManaFlaskCrafting(craftMatrix, output);
-
- }else if(player.openContainer instanceof ContainerPlayer){
-
- IInventory craftMatrix = ((ContainerPlayer)player.openContainer).craftMatrix;
- ItemStack output = ((ContainerPlayer)player.openContainer).craftResult.getStackInSlot(0);
- // Unfortunately I have no choice but to call this method every tick when the player isn't using another
- // inventory, since the only thing tracking whether the player is looking at their inventory is the GUI
- // itself, which is client-side only.
- processManaFlaskCrafting(craftMatrix, output);
- }
-
- }
+ // Experimental animation feature
+// if(event.getEntityLiving().isHandActive() && event.getEntityLiving().getActiveItemStack().getItemUseAction() == WizardryUtilities.POINT){
+// event.getEntityLiving().isSwingInProgress = true;
+// event.getEntityLiving().swingProgress = 1f;
+// event.getEntityLiving().prevSwingProgress = 1;
+// event.getEntityLiving().swingingHand = event.getEntityLiving().getActiveHand();
+// }
if(event.getEntityLiving().world.isRemote){
@@ -309,13 +344,14 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
- if(spell != null && spell != Spells.none){
+ if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check!
- if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(event.getEntityLiving(), spell, modifiers,
- SpellCastEvent.Source.NPC, 0))){
+ if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(),
+ modifiers, 0))){
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side.
+ // Right now that doesn't matter because NPCs don't use modifiers, but they might in future
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
}
}
@@ -323,7 +359,7 @@ public final class WizardryEventHandler {
}
}
- @SubscribeEvent
+ @SubscribeEvent(priority = EventPriority.LOWEST) // No siphoning if the event is cancelled, that could be exploited...
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getTrueSource() instanceof EntityPlayer){
@@ -332,108 +368,83 @@ public final class WizardryEventHandler {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
- if(stack.getItem() instanceof ItemWand && stack.isItemDamaged()
+ if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
- int damage = stack.getItemDamage()
- - Constants.SIPHON_MANA_PER_LEVEL
+
+ int mana = Constants.SIPHON_MANA_PER_LEVEL
* WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade)
- - player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
- if(damage < 0) damage = 0;
- stack.setItemDamage(damage);
- break;
+ + player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
+
+ if(ItemArtefact.isArtefactActive(player, WizardryItems.ring_siphoning)) mana *= 1.3f;
+
+ ((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana);
+
+ break; // Only recharge one item per kill
+ }
+ }
+ }
+ }
+
+ // These two are lifted from EntityLivingBase#travel
+ private static final double LIVING_ENTITY_GRAVITY = 0.08;
+ private static final double LIVING_ENTITY_DRAG = 0.98;
+
+ private static final double LIVING_ENTITY_TERMINAL_VELOCITY = 3.92; // From Minecraft Wiki!
+
+ private static final double LOG_LIVING_ENTITY_DRAG = Math.log(LIVING_ENTITY_DRAG);
+ private static final double FALL_TICKS_ERROR_CORRECTION = 0.500841776608447;
+
+ @SubscribeEvent // Priority doesn't matter here, we're only setting event fields so if it's cancelled it won't matter
+ public static void onLivingFallEvent(LivingFallEvent event){
+ // Why is fall damage based on distance fallen? Why? Who on earth came up with that? It makes no sense whatsoever!
+ if(Wizardry.settings.replaceVanillaFallDamage && !Loader.isModLoaded("speedbasedfalldamage")){
+ // We want to keep the fall damage EXACTLY THE SAME for free, uninterrupted falls, but fix the weirdness
+ // caused when something else changes the entity's velocity
+ // All living entities have a gravity of 0.08b/t^2
+ // Therefore it would be simple to say v^2 = u^2 + 2gs gives the equivalent fall distance as motionY^2 / 0.16
+ // However, Minecraft also has a drag of 0.02 * the velocity, so we actually expect a slightly different value
+ // Much maths later...
+
+ double v = event.getEntity().motionY;
+ // Players are weird, their velocity somehow resets on the server just before this event fires so
+ // instead we're storing the y velocity from the previous tick in WizardData and retrieving it here
+ // Of course, if another mod screws things up and sets a player's velocity client-side only then this won't
+ // work ...but it's better than having clients calculate their own fall damage
+ if(event.getEntity() instanceof EntityPlayer){
+ WizardData data = WizardData.get((EntityPlayer)event.getEntity());
+ if(data != null){
+ v = data.prevMotionY;
}
}
- if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
- WizardryAdvancementTriggers.self_destruct.triggerFor(player);
- }
- }
- }
+ // At terminal velocity, there's no way of finding fall distance from velocity, and the entity is probably dead anyway!
+ if(v > -3.9){
- @SubscribeEvent
- public static void onLivingDropsEvent(LivingDropsEvent event){
- // TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of
- // automatically adding it to all subclasses of IMob.
- // Evil wizards drop spell books themselves
- if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
- // TODO: Backport when you backport the new summoned creature system.
- && !(event.getEntityLiving() instanceof ISummonedCreature)
- && event.getSource().getTrueSource() instanceof EntityPlayer && Wizardry.settings.spellBookDropChance > 0){
+ // Just to make the code more readable, java will replace them all with numbers anyway
+ double g = LIVING_ENTITY_GRAVITY;
+ double f = LIVING_ENTITY_DRAG;
+ double lnf = LOG_LIVING_ENTITY_DRAG;
+ double tv = LIVING_ENTITY_TERMINAL_VELOCITY;
- // This does exactly what the entity drop method does, but with a different random number so that the
- // spell book doesn't always drop with other rare drops.
- int rareDropNumber = event.getEntity().world.rand.nextInt(200) - event.getLootingLevel();
- if(rareDropNumber < Wizardry.settings.spellBookDropChance){
- // Drops a spell book
- int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().world.rand);
+ // Work backwards from y velocity to get fall time
+ // logs are probably slow but it's not like this gets calculated every tick, and we only need one log
+ double t = Math.log(((-v - tv) * lnf) / g) / lnf; // Log the number over log the base
- event.getDrops()
- .add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX,
- event.getEntityLiving().posY, event.getEntityLiving().posZ,
- new ItemStack(WizardryItems.spell_book, 1, id)));
- }
- }
- }
+ // Because time is in discrete ticks, the above equation for t results in a constant error of
+ // +0.500841776608447, so I guess we can just subtract it... if it works it works I guess!
+ t -= FALL_TICKS_ERROR_CORRECTION; // Don't cast to int or perform any rounding
- // Private helper methods
- // ================================================================================================================
+ // Now work forwards from t to find the effective fall distance, i.e. the distance the entity would
+ // have to freefall to reach the same velocity
+ // Don't ask me where the 196 is from, again, it just works!
+ double y = (g * Math.pow(f, t)) / (lnf*lnf) + tv * (t) - 196;
- /**
- * Detects inconsistencies between player.getActiveItemStack and the actual itemstack and forces them to be equal.
- * Fixes issue #25.
- *
- * @param player
- */
- private static void hackilyFixContinuousSpellCasting(EntityPlayer player){
- if(player.isHandActive() && player.getHeldItem(player.getActiveHand()).getItem() instanceof ItemWand
- && WandHelper.getCurrentSpell(player.getHeldItem(player.getActiveHand())).isContinuous){
- if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())){
- player.setHeldItem(player.getActiveHand(), player.getActiveItemStack());
- }
- }
- }
+ // DEBUG
+// if(event.getEntity() instanceof EntityPlayer){
+// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
+// }
- private static void processManaFlaskCrafting(IInventory craftMatrix, ItemStack output){
-
- // Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
- // the proper damage before it is actually crafted.
-
- boolean flag = false;
- ItemStack wand = ItemStack.EMPTY;
- ItemStack armour = ItemStack.EMPTY;
-
- for(int i = 0; i < craftMatrix.getSizeInventory(); i++){
-
- ItemStack itemstack = craftMatrix.getStackInSlot(i);
-
- if(itemstack.getItem() == WizardryItems.mana_flask){
- flag = true;
- }
-
- if(itemstack.getItem() instanceof ItemWand){
- wand = itemstack;
- }
-
- if(itemstack.getItem() instanceof ItemWizardArmour){
- armour = itemstack;
- }
- }
-
- if(output.getItem() instanceof ItemWand && flag && !wand.isEmpty()){
- output.setTagCompound((wand.getTagCompound()));
- if(wand.getItemDamage() - Constants.MANA_PER_FLASK < 0){
- output.setItemDamage(0);
- }else{
- output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
- }
- }
-
- if(output.getItem() instanceof ItemWizardArmour && flag && !armour.isEmpty()){
- output.setTagCompound((armour.getTagCompound()));
- if(armour.getItemDamage() - Constants.MANA_PER_FLASK < 0){
- output.setItemDamage(0);
- }else{
- output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
+ event.setDistance((float)y);
}
}
}
diff --git a/src/main/java/electroblob/wizardry/WizardryGuiFactory.java b/src/main/java/electroblob/wizardry/WizardryGuiFactory.java
index 169c473e..a6ef1190 100644
--- a/src/main/java/electroblob/wizardry/WizardryGuiFactory.java
+++ b/src/main/java/electroblob/wizardry/WizardryGuiFactory.java
@@ -1,12 +1,12 @@
package electroblob.wizardry;
-import java.util.Set;
-
-import electroblob.wizardry.client.gui.GuiConfigWizardry;
+import electroblob.wizardry.client.gui.config.GuiConfigWizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.IModGuiFactory;
+import java.util.Set;
+
public class WizardryGuiFactory implements IModGuiFactory {
@Override
diff --git a/src/main/java/electroblob/wizardry/WizardryGuiHandler.java b/src/main/java/electroblob/wizardry/WizardryGuiHandler.java
index 5bd9364c..140ed107 100644
--- a/src/main/java/electroblob/wizardry/WizardryGuiHandler.java
+++ b/src/main/java/electroblob/wizardry/WizardryGuiHandler.java
@@ -1,6 +1,5 @@
package electroblob.wizardry;
-import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWizardHandbook;
import electroblob.wizardry.spell.Spell;
@@ -46,12 +45,12 @@ public class WizardryGuiHandler implements IGuiHandler {
}
}else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook
|| player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){
- return new GuiWizardHandbook();
+ return new electroblob.wizardry.client.gui.handbook.GuiWizardHandbook();
}else if(id == SPELL_BOOK){
if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){
- return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemMainhand().getItemDamage()));
+ return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemMainhand().getItemDamage()));
}else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){
- return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemOffhand().getItemDamage()));
+ return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemOffhand().getItemDamage()));
}
}else if(id == PORTABLE_CRAFTING){
return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z));
diff --git a/src/main/java/electroblob/wizardry/WizardryWorldGenerator.java b/src/main/java/electroblob/wizardry/WizardryWorldGenerator.java
deleted file mode 100644
index 4283a41e..00000000
--- a/src/main/java/electroblob/wizardry/WizardryWorldGenerator.java
+++ /dev/null
@@ -1,949 +0,0 @@
-package electroblob.wizardry;
-
-import java.util.HashSet;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.commons.lang3.ArrayUtils;
-
-import electroblob.wizardry.entity.living.EntityEvilWizard;
-import electroblob.wizardry.entity.living.EntityWizard;
-import electroblob.wizardry.registry.WizardryBlocks;
-import electroblob.wizardry.util.WizardryUtilities;
-import net.minecraft.block.BlockChest;
-import net.minecraft.block.BlockColored;
-import net.minecraft.block.BlockLeaves;
-import net.minecraft.block.BlockLiquid;
-import net.minecraft.block.BlockPlanks;
-import net.minecraft.block.BlockSlab;
-import net.minecraft.block.BlockSlab.EnumBlockHalf;
-import net.minecraft.block.BlockTorch;
-import net.minecraft.block.state.IBlockState;
-import net.minecraft.init.Biomes;
-import net.minecraft.init.Blocks;
-import net.minecraft.inventory.IInventory;
-import net.minecraft.item.EnumDyeColor;
-import net.minecraft.item.ItemDoor;
-import net.minecraft.util.EnumFacing;
-import net.minecraft.util.ResourceLocation;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
-import net.minecraft.world.WorldServer;
-import net.minecraft.world.biome.Biome;
-import net.minecraft.world.chunk.IChunkProvider;
-import net.minecraft.world.gen.IChunkGenerator;
-import net.minecraft.world.gen.feature.WorldGenMinable;
-import net.minecraft.world.storage.loot.LootContext;
-import net.minecraft.world.storage.loot.LootTable;
-import net.minecraftforge.common.BiomeDictionary;
-import net.minecraftforge.common.IPlantable;
-import net.minecraftforge.fml.common.IWorldGenerator;
-
-public class WizardryWorldGenerator implements IWorldGenerator {
-
- /** The string identifier for wizard tower chests, used in ChestGenHooks. */
- public static final String WIZARD_TOWER = Wizardry.MODID + "wizardTower";
-
- @Override
- public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator,
- IChunkProvider chunkProvider){
-
- for(int id : Wizardry.settings.oreDimensions){
- if(id == world.provider.getDimension()) this.addOreSpawn(WizardryBlocks.crystal_ore.getDefaultState(),
- world, random, chunkX * 16, chunkZ * 16, 16, 16, 5, 7, 5, 30);
- }
-
- for(int id : Wizardry.settings.flowerDimensions){
- if(id == world.provider.getDimension()) this.generatePlant(WizardryBlocks.crystal_flower.getDefaultState(),
- world, random, chunkX * 16, chunkZ * 16, 2, 20);
- }
-
- if(world.getWorldInfo().isMapFeaturesEnabled()){
- for(int id : Wizardry.settings.towerDimensions){
- if(id == world.provider.getDimension())
- this.generateWizardTower(world, random, chunkX * 16, chunkZ * 16);
- }
- }
- }
-
- /**
- * Adds an Ore Spawn to Minecraft. Simply register all Ores to spawn with this method in your Generation method in
- * your IWorldGeneration extending Class
- *
- * @param The Block to spawn
- * @param The World to spawn in
- * @param A Random object for retrieving random positions within the world to spawn the Block
- * @param An int for passing the X-Coordinate for the Generation method
- * @param An int for passing the Z-Coordinate for the Generation method
- * @param An int for setting the maximum X-Coordinate values for spawning on the X-Axis on a Per-Chunk basis
- * @param An int for setting the maximum Z-Coordinate values for spawning on the Z-Axis on a Per-Chunk basis
- * @param An int for setting the maximum size of a vein
- * @param An int for the Number of chances available for the Block to spawn per-chunk
- * @param An int for the minimum Y-Coordinate height at which this block may spawn
- * @param An int for the maximum Y-Coordinate height at which this block may spawn
- **/
- public void addOreSpawn(IBlockState state, World world, Random random, int blockXPos, int blockZPos, int maxX,
- int maxZ, int maxVeinSize, int chancesToSpawn, int minY, int maxY){
- // int maxPossY = minY + (maxY - 1);
- assert maxY > minY : "The maximum Y must be greater than the Minimum Y";
- assert maxX > 0 && maxX <= 16 : "addOreSpawn: The Maximum X must be greater than 0 and less than 16";
- assert minY > 0 : "addOreSpawn: The Minimum Y must be greater than 0";
- assert maxY < 256 && maxY > 0 : "addOreSpawn: The Maximum Y must be less than 256 but greater than 0";
- assert maxZ > 0 && maxZ <= 16 : "addOreSpawn: The Maximum Z must be greater than 0 and less than 16";
-
- int diffBtwnMinMaxY = maxY - minY;
- for(int x = 0; x < chancesToSpawn; x++){
- int posX = blockXPos + random.nextInt(maxX);
- int posY = minY + random.nextInt(diffBtwnMinMaxY);
- int posZ = blockZPos + random.nextInt(maxZ);
- (new WorldGenMinable(state, maxVeinSize)).generate(world, random, new BlockPos(posX, posY, posZ));
- }
- }
-
- /**
- * Generates the specified plant randomly throughout the world.
- *
- * @param block The plant block
- * @param world The world
- * @param random A random instance
- * @param x The x coord of the first block in the chunk
- * @param z The y coord of the first block in the chunk
- * @param chancesToSpawn Number of chances to spawn a flower patch
- * @param groupSize The number of times to try generating a flower per flower patch spawn
- */
- public void generatePlant(IBlockState state, World world, Random random, int x, int z, int chancesToSpawn,
- int groupSize){
-
- for(int i = 0; i < chancesToSpawn; i++){
- int randPosX = x + random.nextInt(16);
- int randPosY = random.nextInt(256);
- int randPosZ = z + random.nextInt(16);
- for(int l = 0; l < groupSize; ++l){
- int i1 = randPosX + random.nextInt(8) - random.nextInt(8);
- int j1 = randPosY + random.nextInt(4) - random.nextInt(4);
- int k1 = randPosZ + random.nextInt(8) - random.nextInt(8);
-
- BlockPos pos = new BlockPos(i1, j1, k1);
-
- if(world.isBlockLoaded(pos) && world.isAirBlock(pos) && (!world.provider.isNether() || j1 < 127)
- && state.getBlock().canPlaceBlockOnSide(world, pos, EnumFacing.UP)){
-
- world.setBlockState(pos, state, 2);
- }
- }
- }
- }
-
- /**
- * Generates wizard towers randomly throughout the world.
- */
- public void generateWizardTower(World world, Random random, int chunkX, int chunkZ){
-
- // Allows the config file to set the rarity value to 0 to disable tower generation completely.
- if(Wizardry.settings.towerRarity == 0) return;
-
- // Compensates for the lack of space in forests. Math.max is required since treeless biomes have treesPerChunk =
- // -999
- // double treeFactor = 70 - Math.max((double)world.getBiomeGenForCoords(chunkX,
- // chunkZ).theBiomeDecorator.treesPerChunk, 0) * 1.5d;
-
- // Multiplied by 70 to (roughly) retain the old rarity scale
- if(random.nextInt((int)(Wizardry.settings.towerRarity * 70)) == 0){
-
- BlockPos origin = new BlockPos(chunkX + random.nextInt(16), 0, chunkZ + random.nextInt(16));
-
- // Despite what its name suggests, this method does not return the position of a liquid. It is in fact
- // exactly what is needed here since it is used for placing villages and stuff, and doesn't include leaves
- // or other foliage.
- origin = origin.up(world.getTopSolidOrLiquidBlock(origin).getY() - 1);
-
- int[][][] towerBlueprint = towerBlueprintSmall;
-
- switch(random.nextInt(4)){
- case 0:
- towerBlueprint = towerBlueprintSmall;
- break;
- case 1:
- towerBlueprint = towerBlueprintMedium;
- break;
- case 2:
- towerBlueprint = towerBlueprintTall;
- break;
- case 3:
- towerBlueprint = towerBlueprintDouble;
- break;
- }
-
- // 0 = West, 1 = North, 2 = East, 3 = South (The way you would face when walking out of the door)
- EnumFacing orientation = EnumFacing.byHorizontalIndex(random.nextInt(4));
- boolean flip = random.nextBoolean();
-
- if(checkSpaceForTower(world, origin, towerBlueprint, orientation, flip)){
-
- // == Setup ==
-
- boolean evilWizard = random.nextInt(5) == 0;
-
- IBlockState wallMaterial = Blocks.COBBLESTONE.getDefaultState();
- BlockPlanks.EnumType woodType = BlockPlanks.EnumType.OAK;
-
- Biome biome = world.getBiome(origin);
-
- // The order of these is somewhat important in that biomes can be many types, so the last of these
- // checks
- // that is true gets priority. Generally speaking, the later the check, the more specific it is.
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DENSE))
- wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SWAMP))
- wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SANDY))
- wallMaterial = Blocks.SANDSTONE.getDefaultState();
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.NETHER))
- wallMaterial = Blocks.NETHER_BRICK.getDefaultState();
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MOUNTAIN))
- wallMaterial = Blocks.STONEBRICK.getDefaultState();
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MESA))
- wallMaterial = Blocks.HARDENED_CLAY.getDefaultState();
-
- // Unfortunately, I can't check all the wood types with the biome dictionary
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS))
- woodType = BlockPlanks.EnumType.SPRUCE;
- if(biome == Biomes.BIRCH_FOREST || biome == Biomes.BIRCH_FOREST_HILLS)
- woodType = BlockPlanks.EnumType.BIRCH;
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) woodType = BlockPlanks.EnumType.JUNGLE;
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SAVANNA)) woodType = BlockPlanks.EnumType.ACACIA;
- // Not technically a tree type, but I think it fits quite well anyway
- if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SPOOKY))
- woodType = BlockPlanks.EnumType.DARK_OAK;
-
- IBlockState[] blockStateList = new IBlockState[]{null, Blocks.AIR.getDefaultState(),
- Blocks.PLANKS.getDefaultState().withProperty(BlockPlanks.VARIANT, woodType),
- Blocks.BOOKSHELF.getDefaultState(),
- Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(BlockColored.COLOR,
- EnumDyeColor.values()[random.nextInt(EnumDyeColor.values().length)]),
- Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.BOTTOM),
- Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.TOP),
- wallMaterial, Blocks.GLASS_PANE.getDefaultState(), Blocks.OAK_DOOR.getDefaultState(),
- WizardryBlocks.arcane_workbench.getDefaultState(), Blocks.TORCH.getDefaultState(),
- evilWizard ? Blocks.CHEST.getDefaultState() : Blocks.BOOKSHELF.getDefaultState()};
-
- Set blocksPlaced = new HashSet();
-
- // == Foundations ==
-
- // Fills in foundations. This is done first so the door always has something to be placed on.
- boolean flag = true;
-
- // BlockPos is immutable, so I'm not sure if simply saying pos1 = pos will be sufficient.
- BlockPos layerCentre = new BlockPos(origin);
-
- // Stop when the bottom of the world is reached
- while(flag && layerCentre.getY() > 0){
-
- flag = false;
-
- for(BlockPos offset : foundationLayer){
- if(!world.isBlockNormalCube(layerCentre.add(offset), false)){
- world.setBlockState(layerCentre.add(offset), wallMaterial);
- blocksPlaced.add(layerCentre.add(offset));
- // Keeps going as long as something was filled in.
- flag = true;
- }
- }
-
- layerCentre = layerCentre.down();
- }
-
- // == Main Structure ==
-
- // It is assumed that the width of the blueprint is the same all the way up, and that the layers are
- // square.
- int width = towerBlueprint[0].length - 1;
-
- // x, y and z are the position the block is being put in.
- // x1, y and z1 are the position in the blueprint which determines which block is being placed.
-
- int x1 = 0, z1 = 0;
-
- for(int y = 0; y < towerBlueprint.length; y++){
- for(int z = 0; z < towerBlueprint[y].length; z++){
- for(int x = 0; x < towerBlueprint[y][z].length; x++){
-
- BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
-
- switch(orientation){
- case WEST:
- x1 = flip ? width - x : x;
- z1 = z;
- break;
- case NORTH:
- x1 = z;
- z1 = flip ? x : width - x;
- break;
- case EAST:
- x1 = flip ? x : width - x;
- z1 = width - z;
- break;
- case SOUTH:
- x1 = width - z;
- z1 = flip ? width - x : x;
- break;
- default:
- break;
- }
-
- if(blockStateList[towerBlueprint[y][z1][x1]] != null
- && blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.TORCH
- && blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.CHEST){
-
- if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.OAK_DOOR){
- // Rotates the door depending on whether flip is true.
- ItemDoor.placeDoor(
- world, pos, flip
- ? EnumFacing.byHorizontalIndex(3 - orientation.getHorizontalIndex())
- .getOpposite()
- : orientation.rotateYCCW(),
- Blocks.OAK_DOOR, false);
- }else{
- world.setBlockState(pos, blockStateList[towerBlueprint[y][z1][x1]], 2);
- }
-
- blocksPlaced.add(pos);
-
- }
- }
- }
- }
-
- // == Extras ==
-
- // Torches are done afterwards so they don't fall off
- // Chests are also done afterwards, because for some reason the chest decides which way round to face
- // itself, after it has been placed, based on the surrounding blocks... but if it was placed during
- // the rest of the tower generation, some of those blocks wouldn't exist, hence it must be done here.
- for(int y = 0; y < towerBlueprint.length; y++){
- for(int z = 0; z < towerBlueprint[y].length; z++){
- for(int x = 0; x < towerBlueprint[y][z].length; x++){
-
- BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
-
- switch(orientation){
- case WEST:
- x1 = flip ? width - x : x;
- z1 = z;
- break;
- case NORTH:
- x1 = z;
- z1 = flip ? x : width - x;
- break;
- case EAST:
- x1 = flip ? x : width - x;
- z1 = width - z;
- break;
- case SOUTH:
- x1 = width - z;
- z1 = flip ? width - x : x;
- break;
- default:
- break;
- }
-
- if(blockStateList[towerBlueprint[y][z1][x1]] != null){
-
- if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.TORCH){
- if(placeTorch(world, pos, true)){
- blocksPlaced.add(pos);
- }else{
- Wizardry.logger.info("Attempted to generate a torch at " + pos + " in " + world
- + ", but failed!");
- }
- }
-
- // World should always be a WorldServer, but it's worth checking anyway.
- if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.CHEST
- && world instanceof WorldServer){
- if(placeChest(world, pos)){
- blocksPlaced.add(pos);
- LootTable table = world.getLootTableManager().getLootTableFromLocation(
- new ResourceLocation(Wizardry.MODID, "chests/wizard_tower"));
- IInventory inventory = (IInventory)world.getTileEntity(pos);
- LootContext context = new LootContext.Builder((WorldServer)world).build();
- table.fillInventory(inventory, random, context);
- }else{
- Wizardry.logger.info("Attempted to generate a chest at " + pos + " in " + world
- + ", but failed!");
- }
- }
- }
- }
- }
- }
-
- if(evilWizard){
-
- EntityEvilWizard wizard = new EntityEvilWizard(world);
- wizard.hasTower = true;
- wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
- origin.getZ() + 1.5, 0, 0);
- wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
-
- world.spawnEntity(wizard);
-
- }else{
-
- EntityWizard wizard = new EntityWizard(world);
- wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
- origin.getZ() + 1.5, 0, 0);
- wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
- wizard.setTowerBlocks(blocksPlaced);
-
- world.spawnEntity(wizard);
- }
- }
- }
- }
-
- /**
- * Places a torch at the given position in the given world and automatically assigns an appropriate state. Order of
- * priority is U-S-W-N-E, or S-W-N-E-U if wallPriority is true.
- *
- * @param world The world to place the torch in.
- * @param pos The position to place the torch at.
- * @param wallPriority True to prioritise wall torches, false to prioritise floor torches.
- * @return True if the torch was placed, false if it is not possible.
- */
- private static boolean placeTorch(World world, BlockPos pos, boolean wallPriority){
-
- for(EnumFacing facing : ArrayUtils.add(EnumFacing.HORIZONTALS, wallPriority ? 4 : 0, EnumFacing.UP)){
- if(world.isSideSolid(pos.offset(facing.getOpposite()), facing)){
- world.setBlockState(pos, Blocks.TORCH.getDefaultState().withProperty(BlockTorch.FACING, facing));
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Places a chest at the given position in the given world and automatically assigns an appropriate state. Order of
- * priority is S-W-N-E.
- *
- * @param world The world to chest the torch in.
- * @param pos The position to chest the torch at.
- * @return True if the chest was placed, false if it is not possible.
- */
- private static boolean placeChest(World world, BlockPos pos){
-
- for(EnumFacing facing : EnumFacing.HORIZONTALS){
- if(world.isAirBlock(pos.offset(facing))){
- world.setBlockState(pos, Blocks.CHEST.getDefaultState().withProperty(BlockChest.FACING, facing));
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Checks whether a tower generated at the given coordinates will intersect any solid or liquid blocks. Only tests
- * for liquids (not solid blocks) for the first four layers to account for the floor and for sloping terrain.
- *
- * @return True if none of the blocks which the tower would replace are solid or liquid. Dirt, stone etc., water and
- * lava count, as do logs, but leaves and plants don't.
- */
- private static boolean checkSpaceForTower(World world, BlockPos pos, int[][][] towerBlueprint,
- EnumFacing orientation, boolean flip){
-
- // x, y and z are the position the block is being put in.
- // x1, y and z1 are the position in the blueprint which determines which block is being placed.
-
- int x1 = 0, z1 = 0;
-
- // It is assumed that the width of the blueprint is the same all the way up, and that the layers are square.
- int width = towerBlueprint[0].length - 1;
-
- for(int y = 0; y < towerBlueprint.length; y++){
- for(int z = 0; z < towerBlueprint[y].length; z++){
- for(int x = 0; x < towerBlueprint[y][z].length; x++){
-
- BlockPos pos1 = pos.add(x - width / 2, y, z - width / 2);
-
- switch(orientation){
- case WEST:
- x1 = flip ? width - x : x;
- z1 = z;
- break;
- case NORTH:
- x1 = z;
- z1 = flip ? x : width - x;
- break;
- case EAST:
- x1 = flip ? x : width - x;
- z1 = width - z;
- break;
- case SOUTH:
- x1 = width - z;
- z1 = flip ? width - x : x;
- break;
- default:
- break;
- }
-
- if(towerBlueprint[y][z1][x1] != 0 && !WizardryUtilities.canBlockBeReplacedB(world, pos1)
- // TODO: Is this a better replacement for the subsequent two lines?
- // && !world.getBlockState(pos1).getBlock().isFoliage(world, pos1)
- && !(world.getBlockState(pos1).getBlock() instanceof IPlantable)
- && !(world.getBlockState(pos1).getBlock() instanceof BlockLeaves)
- && (y > 3 || world.getBlockState(pos1).getBlock() instanceof BlockLiquid)){
- return false;
- }
- }
- }
- }
-
- return true;
- }
-
- /** Array of relative positions of each block in a layer of foundations. */
- private static final BlockPos[] foundationLayer = new BlockPos[]{new BlockPos(-2, 0, -1), new BlockPos(-2, 0, 0),
- new BlockPos(-2, 0, 1), new BlockPos(2, 0, -1), new BlockPos(2, 0, 0), new BlockPos(2, 0, 1),
- new BlockPos(-1, 0, -2), new BlockPos(0, 0, -2), new BlockPos(1, 0, -2), new BlockPos(-1, 0, 2),
- new BlockPos(0, 0, 2), new BlockPos(1, 0, 2),};
-
- /**
- * 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
- * to each integer are as follows (note that some blocks change depending on the biome):
- *