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 5c3b7f46..6bd1915b 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ 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" +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 = "ElectroblobsWizardry" @@ -20,8 +20,30 @@ 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 e477bca5..71165cc2 100644 --- a/src/main/java/electroblob/wizardry/CommonProxy.java +++ b/src/main/java/electroblob/wizardry/CommonProxy.java @@ -1,32 +1,32 @@ package electroblob.wizardry; 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.WizardryParticleType; 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 */ @@ -36,90 +36,34 @@ public class CommonProxy { // SECTION Registry // =============================================================================================================== - public void registerRenderers(){ - } + public void registerRenderers(){} - public void initialiseLayers(){ - } + public void initialiseLayers(){} - public void registerKeyBindings(){ - } - - public void registerSpellHUD(){} + public void registerKeyBindings(){} public net.minecraft.client.model.ModelBiped getWizardArmourModel(){ return null; } public void initGuiBits(){} + + public void registerResourceReloadListeners(){} + + public void registerSoundEventListener(){} + + public void registerAtlasMarkers(){} // SECTION Particles // =============================================================================================================== - /** - * Spawns a custom particle of the specified type. - * - * @param type EnumParticleType of the particle - * @param world Reference to the World object - * @param x Particle x position - * @param y Particle y position - * @param z Particle z position - * @param velX Particle x velocity - * @param velY Particle y velocity - * @param velZ Particle z velocity - * @param maxAge Lifetime of the particle in ticks - * @param r Red component of particle colour; will be clamped to between 0 and 1 - * @param g Red component of particle colour; will be clamped to between 0 and 1 - * @param b Red component of particle colour; will be clamped to between 0 and 1 - * @param doGravity Whether the particle is affected by gravity (only affects SPARKLE at the moment) - * @param radius The radius of the particle's motion, for cirular motion particles - */ - public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX, - double velY, double velZ, int maxAge, float r, float g, float b, boolean doGravity, double radius){ - // Does nothing since particles are client-side only - } + /** Called from init() in the main mod class to initialise the particle factories. */ + public void registerParticles(){} // Does nothing since particles are client-side only - /** - * Spawns a custom particle of the specified type. doGravity defaults to false and radius defaults to 0. Note that - * some of these settings may not affect the particle; some particles are always affected by gravity, for instance. - * - * @param type EnumParticleType of the particle - * @param world Reference to the World object - * @param x Particle x position - * @param y Particle y position - * @param z Particle z position - * @param velX Particle x velocity - * @param velY Particle y velocity - * @param velZ Particle z velocity - * @param maxAge Lifetime of the particle in ticks - * @param r Red component of particle colour; will be clamped to between 0 and 1 (unless this is a MAGIC_FIRE - * particle, in which case this is the scale) - * @param g Red component of particle colour; will be clamped to between 0 and 1 - * @param b Red component of particle colour; will be clamped to between 0 and 1 - */ - public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX, - double velY, double velZ, int maxAge, float r, float g, float b){ - this.spawnParticle(type, world, x, y, z, velX, velY, velZ, maxAge, r, g, b, false, 0); - } - - /** - * Spawns a custom particle of the specified type. Colour defaults to white, doGravity defaults to false and radius - * defaults to 0. Note that some of these settings may not affect the particle; some particles are always affected - * by gravity, for instance. - * - * @param type EnumParticleType of the particle - * @param world Reference to the World object - * @param x Particle x position - * @param y Particle y position - * @param z Particle z position - * @param velX Particle x velocity - * @param velY Particle y velocity - * @param velZ Particle z velocity - * @param maxAge Lifetime of the particle in ticks - */ - public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX, - double velY, double velZ, int maxAge){ - this.spawnParticle(type, world, x, y, z, velX, velY, velZ, maxAge, 1, 1, 1, false, 0); + /** 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 electroblob.wizardry.client.particle.ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){ + return null; } public void spawnTornadoParticle(World world, double x, double y, double z, double velX, double velZ, double radius, @@ -129,27 +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){ - // I have now learnt that the server side I18n always translates to the default en_US, so I could just return - // a hardcoded name in English instead. - // Commented because it's just really annoying. - //Wizardry.logger.info("A mod has called ItemScroll#getItemStackDisplayName from the server side. Using the" - // + "deprecated server-side translation methods as a fallback."); - - // Displays [Empty slot] if spell is continuous. - Spell spell = Spell.get(scroll.getItemDamage()); - if(spell.isContinuous) spell = Spells.none; + Spell spell = Spell.byMetadata(scroll.getItemDamage()); return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name", I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim(); @@ -159,35 +99,63 @@ 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 // =============================================================================================================== - public void setToNumberSliderEntry(Property property){ - } + public void setToNumberSliderEntry(Property property){} + + public void setToHUDChooserEntry(Property property){} + + public void setToNamedBooleanEntry(Property property){} + // public void setToEntityNameEntry(Property property){} /** @@ -195,13 +163,82 @@ public class CommonProxy { * * @param entity The source of the sound * @param sound The SoundEvent to play + * @param category The SoundCategory to use * @param volume Volume relative to 1 * @param pitch Pitch relative to 1 * @param repeat Whether to repeat the sound for as long as the entity is alive (or until stopped manually) */ - public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){ + 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. @@ -210,4 +247,8 @@ public class CommonProxy { return null; } + /** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */ + public Set getSpellHUDSkins(){ + return null; + } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/Settings.java b/src/main/java/electroblob/wizardry/Settings.java index 0c747916..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,16 +220,47 @@ 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. // Gamemodes /** * [Synchronised] When set to true, spells a player hasn't cast yet will be unreadable until they are cast - * (on a per-world basis). Has no effect when in creative mode. Spells of identification will be unobtainable in + * (on a per-world basis). Has no effect when in creative mode. Scrolls of identification will be unobtainable in * 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,23 +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; + 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; - /** Set of constants for each of the four positions that the spell HUD can be in. */ + 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 eight positions that the spell HUD can be in. */ public enum GuiPosition { - BOTTOM_LEFT("Bottom left"), TOP_LEFT("Top left"), TOP_RIGHT("Top right"), BOTTOM_RIGHT("Bottom right"); + 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; @@ -182,9 +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 final boolean flipX; + public final boolean flipY; + public final boolean dynamic; - GuiPosition(String name){ + GuiPosition(String name, boolean flipX, boolean flipY, boolean dynamic){ this.name = name; + this.flipX = flipX; + this.flipY = flipY; + this.dynamic = dynamic; } /** @@ -223,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(); } @@ -239,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(); @@ -256,6 +399,7 @@ public final class Settings { setupWorldgenConfig(); setupClientConfig(); setupCommandsConfig(); + setupCompatibilityConfig(); setupSpellsConfig(); setupResistancesConfig(); @@ -285,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()); } @@ -293,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; @@ -302,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."); @@ -412,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); @@ -424,29 +780,36 @@ 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, "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()); property = config.get(CLIENT_CATEGORY, "spellHUDPosition", GuiPosition.BOTTOM_LEFT.name, "The position of the spell HUD.", GuiPosition.names); @@ -454,28 +817,50 @@ public final class Settings { spellHUDPosition = GuiPosition.fromName(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, "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, "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()); @@ -507,7 +892,7 @@ public final class Settings { property.setRequiresWorldRestart(true); alliesCommandName = property.getString(); propOrder.add(property.getName()); - + config.setCategoryPropertyOrder(COMMANDS_CATEGORY, propOrder); } @@ -521,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); @@ -534,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); @@ -547,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); @@ -560,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); @@ -573,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); @@ -588,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/WizardData.java b/src/main/java/electroblob/wizardry/WizardData.java deleted file mode 100644 index 313822b4..00000000 --- a/src/main/java/electroblob/wizardry/WizardData.java +++ /dev/null @@ -1,715 +0,0 @@ -package electroblob.wizardry; - -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.enchantment.Imbuement; -import electroblob.wizardry.entity.EntityShield; -import electroblob.wizardry.entity.living.ISummonedCreature; -import electroblob.wizardry.event.SpellCastEvent; -import electroblob.wizardry.event.SpellCastEvent.Source; -import electroblob.wizardry.packet.PacketCastContinuousSpell; -import electroblob.wizardry.packet.PacketPlayerSync; -import electroblob.wizardry.packet.PacketTransportation; -import electroblob.wizardry.packet.WizardryPacketHandler; -import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; -import electroblob.wizardry.spell.None; -import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.MagicDamage; -import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.enchantment.Enchantment; -import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.init.Items; -import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.ItemEnchantedBook; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.*; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.BlockPos; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityInject; -import net.minecraftforge.common.capabilities.ICapabilitySerializable; -import net.minecraftforge.common.util.Constants.NBT; -import net.minecraftforge.common.util.INBTSerializable; -import net.minecraftforge.event.AttachCapabilitiesEvent; -import net.minecraftforge.event.entity.EntityJoinWorldEvent; -import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; -import net.minecraftforge.event.entity.player.PlayerEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.network.simpleimpl.IMessage; - -/** - * Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave minimum - * external changes (for my own sanity, mainly!). Turns out the only major difference between an internal capability and - * an IEEP is a couple of redundant classes and a different way of registering it. - *

    - * Forge seems to have separate classes to hold the Capability<...> instance ('key') and methods for getting the - * capability, but in my opinion there are already too many classes to deal with, so I'm not adding any more than are - * necessary, meaning those constants and values are kept here instead. - * - * @since Wizardry 2.1 - * @author Electroblob - */ -// On the plus side, having to rethink this class allowed me to clean it up a lot. -@Mod.EventBusSubscriber -public class WizardData implements INBTSerializable { - - /** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */ - // This annotation does some crazy Forge magic behind the scenes and assigns this field a value. - @CapabilityInject(WizardData.class) - private static final Capability WIZARD_DATA_CAPABILITY = null; - - /** The player this WizardData instance belongs to. */ - private final EntityPlayer player; - - // This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry. - private Map imbuementDurations; - - public boolean hasSpiritWolf; - public boolean hasSpiritHorse; - - /** - * Whether this player is currently casting a continuous spell via commands. Not saved over world reload and reset - * on player death. - */ - private Spell currentlyCasting; - /** - * The time for which this player has been casting a continuous spell via commands. Increments by 1 each tick. Not - * saved over world reload and reset on player death. - */ - private int castingTick; - /** - * SpellModifiers object for the current continuous spell cast via commands. Not saved over world reload and reset - * on player death. - */ - private SpellModifiers spellModifiers; - /** Coordinates for the saved transportation stone circle location. Will be null if no location is saved. */ - private BlockPos stoneCircleLocation; - /** Dimension id which the saved stone circle is in. */ - private int stoneCircleDimension; - /** Time left until the player teleports under the effect of transportation */ - private int tpCountdown; - - /** Coordinates for the saved clairvoyance location. Will be null if no location is saved. */ - private BlockPos clairvoyanceLocation; - /** Dimension id which the saved clairvoyance point is in. */ - private int clairvoyanceDimension; - - public EntityShield shield; - - public WeakReference selectedMinion; - - /** - * Set of this player's discovered spells. Do not write to this list directly, use - * {@link WizardData#discoverSpell(Spell)} instead. - */ - public Set spellsDiscovered; - - private Set allies; - /** - * List of usernames of this player's allies. May not be accurate 100% of the time. This is here so that a player - * can view the usernames of their allies even when those allies are not online. Do not use this for any other - * purpose than displaying the names! - */ - public Set allyNames; - - private Set soulboundCreatures; - - public WizardData(){ - this(null); // Nullary constructor for the registration method factory parameter - } - - public WizardData(EntityPlayer player){ - this.player = player; - this.imbuementDurations = new HashMap(); - this.spellsDiscovered = new HashSet(); - // All players can recognise magic missile. This is not done using discoverSpell because that seems to cause - // a crash on load occasionally (probably something to do with achievements being initalised) - this.spellsDiscovered.add(Spells.magic_missile); - this.hasSpiritWolf = false; - this.hasSpiritHorse = false; - this.currentlyCasting = Spells.none; - this.spellModifiers = new SpellModifiers(); - this.castingTick = 0; - this.stoneCircleDimension = 0; - this.clairvoyanceDimension = 0; - this.setTpCountdown(0); - this.allies = new HashSet(); - this.allyNames = new HashSet(); - this.soulboundCreatures = new HashSet(); - } - - public boolean hasSpellBeenDiscovered(Spell spell){ - return spellsDiscovered.contains(spell) || spell instanceof None; - } - - /** - * Adds the given spell to the list of discovered spells for this player. Automatically takes into account whether - * the spell has been discovered. Use this method rather than adding directly to the list because it handles - * achievements. - * - * @param spell The spell to be discovered - * @return True if the spell had not already been discovered; false otherwise. - */ - public boolean discoverSpell(Spell spell){ - - if(spellsDiscovered == null){ - spellsDiscovered = new HashSet(); - } - // The 'none' spell cannot be discovered - if(spell instanceof None) return false; - // Tries to add the spell to the list of discovered spells, and returns false if it was already present - if(!spellsDiscovered.add(spell)) return false; - // If the spell had not already been discovered, achievements can be triggered and the method returns true - if(spellsDiscovered.containsAll(Spell.getSpells(Spell::isEnabled))){ - WizardryAdvancementTriggers.all_spells.triggerFor(this.player); - } - - for(Element element : Element.values()){ - if(element != Element.MAGIC - && spellsDiscovered.containsAll(Spell.getSpells(new Spell.TierElementFilter(null, element)))){ - WizardryAdvancementTriggers.element_master.triggerFor(this.player); - } - } - - return true; - } - - /** Sets the player's saved transportation stone location and dimension. */ - public void setStoneCircleLocation(BlockPos pos, int dimensionID){ - this.stoneCircleLocation = pos; - this.stoneCircleDimension = dimensionID; - } - - /** Returns the coordinates of the associated player's saved transportation stone circle. */ - public BlockPos getStoneCircleLocation(){ - return stoneCircleLocation; - } - - /** Returns the dimension ID of the associated player's saved transportation stone circle. */ - public int getStoneCircleDimension(){ - return stoneCircleDimension; - } - - public int getTpCountdown(){ - return tpCountdown; - } - - public void setTpCountdown(int tpCountdown){ - this.tpCountdown = tpCountdown; - } - - /** Sets the player's saved clairvoyance location. */ - public void setClairvoyancePoint(BlockPos pos, int dimensionID){ - this.clairvoyanceLocation = pos; - this.clairvoyanceDimension = dimensionID; - } - - /** Returns the coordinates for the saved clairvoyance location. Will be null if no location is saved. */ - public BlockPos getClairvoyanceLocation(){ - return clairvoyanceLocation; - } - - /** Returns the dimension ID for the saved clairvoyance location. Will be null if no location is saved. */ - public int getClairvoyanceDimension(){ - return clairvoyanceDimension; - } - - /** - * Overwrites the imbuement duration associated with the given imubement for this player, or creates it if there was - * none previously. - * - * @throws IllegalArgumentException if the given {@link Enchantment} is not an {@link Imbuement}. - */ - public void setImbuementDuration(Enchantment enchantment, int duration){ - // It is best to throw an exception here, because otherwise the error would either go unnoticed (if - // non-imbuements - // were ignored) or cause a ClassCastException later (if non-imbuements were allowed to be added). - if(enchantment instanceof Imbuement){ - this.imbuementDurations.put((Imbuement)enchantment, duration); - }else{ - throw new IllegalArgumentException( - "Attempted to set an imbuement duration for something that isn't an Imbuement! (This exception has been thrown now to prevent a ClassCastException from occurring later.)"); - } - } - - /** - * Returns the imbuement duration associated with the given imbuement for this player, or 0 if it does not exist. - */ - @SuppressWarnings("unlikely-arg-type") - public int getImbuementDuration(Enchantment enchantment){ - // Need to check that i is not null, otherwise it throws an NPE when Java auto-unboxes it. - // What's nice here is that the map simply accepts objects as keys, so there's no need to cast or throw - // exceptions. - Integer i = this.imbuementDurations.get(enchantment); - // If i is null, returns 0; otherwise returns i, auto-unboxed to an int. - return i == null ? 0 : i; - } - - /** - * Decrements the duration for each conjured item by 1, and removes from the map any that are 0 or less or that the - * player no longer has. Also deletes the item from the player's inventory if it runs out of time. - */ - private void updateImbuedItems(){ - - Set activeImbuements = new HashSet(); - - // For each item in the player's inventory - for(ItemStack stack : player.inventory.mainInventory){ - if(stack.isItemEnchanted()){ - - NBTTagList enchantmentList = stack.getItem() == Items.ENCHANTED_BOOK ? - ItemEnchantedBook.getEnchantments(stack) : stack.getEnchantmentTagList(); - - Iterator iterator =enchantmentList.iterator(); - // For each of the item's enchantments - while(iterator.hasNext()){ - NBTTagCompound enchantmentTag = (NBTTagCompound) iterator.next(); - Enchantment enchantment = Enchantment.getEnchantmentByID(enchantmentTag.getShort("id")); - // Ignores the enchantment unless it is an imbuement - if(enchantment instanceof Imbuement){ - int duration = this.getImbuementDuration(enchantment); - // If the imbuement is still active: - if(duration > 0){ - // Decrements the timer - this.imbuementDurations.put((Imbuement)enchantment, duration - 1); - // Adds this imbuement to the set of imbuements that need to be kept - activeImbuements.add((Imbuement)enchantment); - // Otherwise: - }else{ - // Removes the enchantment from the item - iterator.remove(); - } - } - } - } - } - // Removes all imbuements from the map that are no longer active - this.imbuementDurations.keySet().retainAll(activeImbuements); - } - - /** - * Adds the given player to the list of allies belonging to the associated player, or removes the player if they are - * already in the list of allies. Returns true if the player was added, false if they were removed. - */ - public boolean toggleAlly(EntityPlayer player){ - if(this.isPlayerAlly(player)){ - this.allies.remove(player.getUniqueID()); - // The remove method uses .equals() rather than == so this will work fine. - this.allyNames.remove(player.getName()); - return false; - }else{ - this.allies.add(player.getUniqueID()); - this.allyNames.add(player.getName()); - return true; - } - } - - /** Returns whether the given player is in this player's list of allies, or is on the same team as this player. */ - public boolean isPlayerAlly(EntityPlayer player){ - return this.allies.contains(player.getUniqueID()) || this.player.isOnSameTeam(player); - } - - /** Adds the given entity to this player's list of soulbound creatures, and returns whether it succeeded. */ - public boolean soulbind(EntityLivingBase target){ - return this.soulboundCreatures.add(target.getUniqueID()); - } - - /** Returns whether the given entity has been soulbound to this player. */ - public boolean isCreatureSoulbound(EntityPlayer target){ - return this.soulboundCreatures.contains(target.getUniqueID()); - } - - /** - * Damages all creatures soulbound to this player by the given amount, and removes from the list any that no longer - * exist. - */ - public void damageAllSoulboundCreatures(float damage){ - - for(Iterator iterator = this.soulboundCreatures.iterator(); iterator.hasNext();){ - - Entity entity = WizardryUtilities.getEntityByUUID(this.player.world, iterator.next()); - - if(entity == null) iterator.remove(); - - if(entity instanceof EntityLivingBase){ - // Retaliatory effect - if(entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(this.player, DamageType.MAGIC, true), - damage)){ - // Sound only plays if the damage succeeds - player.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, player.world.rand.nextFloat() * 0.2F + 1.0F); - } - } - } - } - - /** Starts casting the given spell with the given modifiers. */ - public void startCastingContinuousSpell(Spell spell, SpellModifiers modifiers){ - - this.currentlyCasting = spell; - this.spellModifiers = modifiers; - - if(!this.player.world.isRemote){ - PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(), - spell.id(), this.spellModifiers); - WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension()); - } - } - - /** Stops casting the current spell. */ - public void stopCastingContinuousSpell(){ - - this.currentlyCasting = Spells.none; - this.castingTick = 0; - this.spellModifiers.reset(); - - if(!this.player.world.isRemote){ - PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(), - Spells.none.id(), this.spellModifiers); - WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension()); - } - } - - /** Casts the current continuous spell, fires relevant events and updates the castingTick field. */ - public void updateContinuousSpellCasting(){ - - if(this.currentlyCasting != null && this.currentlyCasting.isContinuous){ - - if(MinecraftForge.EVENT_BUS.post( - new SpellCastEvent.Tick(player, currentlyCasting, spellModifiers, Source.COMMAND, castingTick))){ - this.stopCastingContinuousSpell(); - return; - } - - if(this.currentlyCasting.cast(player.world, player, EnumHand.MAIN_HAND, castingTick, this.spellModifiers) - && this.castingTick == 0){ - // On the first tick casting a continuous spell via commands, SpellCastEvent.Post is fired. - MinecraftForge.EVENT_BUS - .post(new SpellCastEvent.Post(player, currentlyCasting, spellModifiers, Source.COMMAND)); - } - - castingTick++; - - }else{ - // Why is this here? Surely castingTick will always be 0 if currentlyCasting is null? - this.castingTick = 0; - } - } - - /** Returns whether this player is currently casting a continuous spell via commands. */ - public boolean isCasting(){ - return this.currentlyCasting != null && this.currentlyCasting != Spells.none; - } - - /** - * Returns the continuous spell this player is currently casting via commands, or the 'none' spell if they aren't - * casting anything. - */ - public Spell currentlyCasting(){ - return currentlyCasting; - } - - /** Called each time the associated player is updated. */ - private void update(){ - - if(this.selectedMinion != null && this.selectedMinion.get() == null) this.selectedMinion = null; - - // This new system removes a lot of repetitive event handler code and inflexible variables which had duplicate - // functions, just for different enchantments. - updateImbuedItems(); - - if(!player.world.isRemote){ - if(getTpCountdown() == 1){ - player.setPositionAndUpdate(this.stoneCircleLocation.getX() + 0.5, this.stoneCircleLocation.getY(), - this.stoneCircleLocation.getZ() + 0.5); - player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0)); - IMessage msg = new PacketTransportation.Message(player.getEntityId()); - WizardryPacketHandler.net.sendToDimension(msg, player.world.provider.getDimension()); - } - - if(getTpCountdown() > 0){ - setTpCountdown(getTpCountdown() - 1); - } - } - - updateContinuousSpellCasting(); - } - - /** - * Returns the WizardData instance for the specified player. - */ - public static final WizardData get(EntityPlayer player){ - return player.getCapability(WIZARD_DATA_CAPABILITY, null); - } - - /** - * Called from the event handler each time the associated player entity is cloned, i.e. on respawn or when - * travelling to a different dimension. Used to copy over any variables that should persist over player death. This - * is the inverse of the old onPlayerDeath method, which reset the variables that shouldn't persist. - * - * @param data The old WizardData whose variables are to be copied over. - * @param respawn True if the player died and is respawning, false if they are just travelling between dimensions. - */ - public void copyFrom(WizardData data, boolean respawn){ - // TODO: What happens with spirit wolf and spirit horse? - this.hasSpiritHorse = data.hasSpiritHorse; - this.hasSpiritWolf = data.hasSpiritWolf; - this.allies = data.allies; - this.allyNames = data.allyNames; - this.clairvoyanceDimension = data.clairvoyanceDimension; - this.clairvoyanceLocation = data.clairvoyanceLocation; - this.selectedMinion = data.selectedMinion; - // Curse of soulbinding is lifted when the caster dies, but not when they switch dimensions. - if(!respawn) this.soulboundCreatures = data.soulboundCreatures; - this.spellsDiscovered = data.spellsDiscovered; - this.stoneCircleDimension = data.stoneCircleDimension; - this.stoneCircleLocation = data.stoneCircleLocation; - - // Imbuements are lost on death so their durations do not persist. - // Command spell casting is reset on death so the associated variables do not persist. - // tpCountdown is reset both when the player dies and when they switch dimensions. - - } - - /** Sends a packet to this player's client to synchronise necessary information. Only called server side. */ - public void sync(){ - if(this.player instanceof EntityPlayerMP){ - int id = -1; - if(this.selectedMinion != null && this.selectedMinion.get() instanceof Entity) - id = ((Entity)this.selectedMinion.get()).getEntityId(); - IMessage msg = new PacketPlayerSync.Message(this.spellsDiscovered, id); - WizardryPacketHandler.net.sendTo(msg, (EntityPlayerMP)this.player); - } - } - - @Override - public NBTTagCompound serializeNBT(){ - - NBTTagCompound properties = new NBTTagCompound(); - - // ...so Java 8 allows you to do stuff like this: - properties.setTag("imbuements", WizardryUtilities.mapToNBT(this.imbuementDurations, - imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new)); - - properties.setBoolean("hasSpiritWolf", this.hasSpiritWolf); - properties.setBoolean("hasSpiritHorse", this.hasSpiritHorse); - - if(this.stoneCircleLocation != null) - properties.setLong("stoneCircleLocation", this.stoneCircleLocation.toLong()); - properties.setInteger("stoneCircleDimension", this.stoneCircleDimension); - properties.setInteger("tpCountdown", this.tpCountdown); - - if(this.clairvoyanceLocation != null) - properties.setLong("clairvoyanceLocation", this.clairvoyanceLocation.toLong()); - properties.setInteger("clairvoyanceDimension", this.getClairvoyanceDimension()); - - // THIS is why I wrote the list/map <-> NBT methods. Look how neat this is! - properties.setTag("allies", WizardryUtilities.listToNBT(this.allies, WizardryUtilities::UUIDtoTagCompound)); - properties.setTag("allyNames", WizardryUtilities.listToNBT(this.allyNames, NBTTagString::new)); - properties.setTag("soulboundCreatures", - WizardryUtilities.listToNBT(this.soulboundCreatures, WizardryUtilities::UUIDtoTagCompound)); - - // Might be worth converting this over to WizardryUtilities.listToNBT. - int[] spells = new int[this.spellsDiscovered.size()]; - int i = 0; - for(Spell spell : this.spellsDiscovered){ - spells[i] = spell.id(); - i++; - } - properties.setIntArray("discoveredSpells", spells); - - return properties; - } - - @Override - public void deserializeNBT(NBTTagCompound nbt){ - - if(nbt != null){ - - this.imbuementDurations = WizardryUtilities.NBTToMap(nbt.getTagList("imbuements", NBT.TAG_COMPOUND), - (NBTTagInt tag) -> (Imbuement)Enchantment.getEnchantmentByID(tag.getInt()), NBTTagInt::getInt); - - this.hasSpiritWolf = nbt.getBoolean("hasSpiritWolf"); - this.hasSpiritHorse = nbt.getBoolean("hasSpiritHorse"); - - this.stoneCircleLocation = BlockPos.fromLong(nbt.getLong("stoneCircleLocation")); - this.stoneCircleDimension = nbt.getInteger("stoneCircleDimension"); - this.tpCountdown = nbt.getInteger("tpCountdown"); - - this.clairvoyanceLocation = BlockPos.fromLong(nbt.getLong("clairvoyanceLocation")); - this.clairvoyanceDimension = nbt.getInteger("clairvoyanceDimension"); - - this.allies = new HashSet(WizardryUtilities.NBTToList(nbt.getTagList("allies", NBT.TAG_COMPOUND), - WizardryUtilities::tagCompoundToUUID)); - - this.allyNames = new HashSet( - WizardryUtilities.NBTToList(nbt.getTagList("allyNames", NBT.TAG_STRING), NBTTagString::getString)); - - this.soulboundCreatures = new HashSet(WizardryUtilities.NBTToList( - nbt.getTagList("soulboundCreatures", NBT.TAG_COMPOUND), WizardryUtilities::tagCompoundToUUID)); - - this.spellsDiscovered = new HashSet(); - for(int id : nbt.getIntArray("discoveredSpells")){ - spellsDiscovered.add(Spell.get(id)); - } - } - } - - // Event handlers - - @SubscribeEvent - // The type parameter here has to be Entity, not EntityPlayer, or the event won't get fired. - public static void onCapabilityLoad(AttachCapabilitiesEvent event){ - - if(event.getObject() instanceof EntityPlayer) - event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), - new WizardData.Provider((EntityPlayer)event.getObject())); - - // This demonstrates why capabilities are badly structured: The following code compiles, but what it does is put - // a player into a CapabilityDispatcher, which is in turn stored in that very same player, which makes no sense - // at all! - // event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), event.getObject()); - } - - @SubscribeEvent - public static void onPlayerCloneEvent(PlayerEvent.Clone event){ - - WizardData newData = WizardData.get(event.getEntityPlayer()); - WizardData oldData = WizardData.get(event.getOriginal()); - - newData.copyFrom(oldData, event.isWasDeath()); - } - - @SubscribeEvent - public static void onEntityJoinWorld(EntityJoinWorldEvent event){ - if(!event.getEntity().world.isRemote && event.getEntity() instanceof EntityPlayerMP){ - // Synchronises wizard data after loading. - WizardData data = WizardData.get((EntityPlayer)event.getEntity()); - if(data != null) data.sync(); - } - } - - @SubscribeEvent - public static void onLivingUpdateEvent(LivingUpdateEvent event){ - - if(event.getEntityLiving() instanceof EntityPlayer){ - - EntityPlayer player = (EntityPlayer)event.getEntityLiving(); - - if(WizardData.get(player) != null){ - WizardData.get(player).update(); - } - } - } - - /** - * This is a nested class for a few reasons: firstly, it makes sense because instances of this and WizardData go - * hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most importantly) it allows - * me to access WIZARD_DATA_CAPABILITY while keeping it private. - */ - public static class Provider implements ICapabilitySerializable { - - private final WizardData data; - - public Provider(EntityPlayer player){ - data = new WizardData(player); - } - - @Override - public boolean hasCapability(Capability capability, EnumFacing facing){ - return capability == WIZARD_DATA_CAPABILITY; - } - - @Override - public T getCapability(Capability capability, EnumFacing facing){ - - if(capability == WIZARD_DATA_CAPABILITY){ - return WIZARD_DATA_CAPABILITY.cast(data); - } - - return null; - } - - @Override - public NBTTagCompound serializeNBT(){ - return data.serializeNBT(); - } - - @Override - public void deserializeNBT(NBTTagCompound nbt){ - data.deserializeNBT(nbt); - } - - } - - // Ended up deleting IWizardData because it was unnecessary. This is the comment that was at the start of it: - - /* I'm not going to lie, I will never find the capabilities system even remotely intuitive so this is a bare-minimum - * approach just to get things working (four classes where one would have done?!) At one point I considered simply - * wrapping my old IEEP inside a single-field capability, but I eventually decided I would at least *try* to do it - * properly. - * - * "...without having to directly implement many interfaces." - Forge Docs. I still can't see what's wrong with - * implementing many interfaces; surely that's what Java interfaces are designed for? - * - * Other things I find annoying: - IStorage. It's completely redundant in the majority of cases, and I don't - * understand why we need yet another separate class. - Making an interface, only to implement it once and once - * only. This completely defeats the point of interfaces. - The EnumFacing parameter, which is again redundant for - * everything that isn't a tile entity. So much for a clean, neat system. - * - * What Forge has effectively done is conflated two different functions: attaching data to stuff and cross-mod - * integration/soft dependencies. I think this is bad design; it would have been better to keep the two features - * separate. - * - * Here's my current understanding of how the capability system works: - You make an interface which defines the - * things your capability can do (this class). I will call this the TEMPLATE. - You implement that interface with - * your default implementation (WizardData). This is the closest analog to your old IEEP implementation class. THIS - * CLASS STORES ALL THE VARIABLES, and hence has one instance for each instance of whatever it is attached to. I - * will call this the DATA. - The DATA class implements INBTSerializable (assuming you want it to be saved, which is - * nearly always the case) - Despite its name, Capability does NOT represent a capability itself. Instead, it - * acts as a sort of identifier/key, the idea being that you can access a particular instance of your DATA given the - * key (which tells forge that you want a capability of type TEMPLATE) and the object you want the DATA for. This is - * what Entity.getCapability(...) does. - * - * To really understand what's going on though, you need to sift through Forge's verbose data structures and find - * where capabilities are actually hooked into vanilla: - Anything that implements ICapabilityProvider will have a - * private CapabilityDispatcher field. This holds other ICapabilityProviders. (I know. This inheritance pattern DOES - * NOT MAKE SENSE, because these could, in theory, be OTHER ENTITIES!) - This field is assigned a value through - * Forge's event factory, which, as we are all familiar with, calls all the methods marked with @SubscribeEvent. - * These methods add individual ICapabilityProviders to a Map stored in the event, which the event factory then - * wraps in a CapabilityDispatcher (which is itself an ICapabilityProvider) for the object that called it. - In your - * event handler, you return a custom ICapabilityProvider which is effectively bolted on to the player, and - * duplicates the ICapabilityProvider methods so you can hook into them and return an instance of your DATA class. - - * Where before there was a simple collection of IEEPs stored in the player, there is now a tree of - * ICapabilityProviders: - * - * - Entity/TileEntity/ItemStack - Vanilla ICapabilityProviders, mostly IItemHandlers, stored as fields. - - * CapabilityDispatcher, stored as a field. - Custom ICapabilityProviders - Custom CapabilityDispatchers - ... - * - * Most importantly, EACH PLAYER HOLDS THEIR OWN INSTANCE OF THIS TREE. - * - * When a capability is retrieved, the following process happens: 1. For the Entity/TileEntity/ItemStack instance, - * ICapabilityProvider.getCapability(...) is called. 2. The request propogates through the tree and finds the - * requested capability. */ - -} diff --git a/src/main/java/electroblob/wizardry/Wizardry.java b/src/main/java/electroblob/wizardry/Wizardry.java index 2f6dbb6e..1eeb4806 100644 --- a/src/main/java/electroblob/wizardry/Wizardry.java +++ b/src/main/java/electroblob/wizardry/Wizardry.java @@ -4,18 +4,19 @@ 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,6 +31,16 @@ 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 + * @since Wizardry 1.0 + */ @Mod(modid = Wizardry.MODID, name = Wizardry.NAME, version = Wizardry.VERSION, guiFactory = "electroblob.wizardry.WizardryGuiFactory") public class Wizardry { @@ -39,48 +50,38 @@ 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 * 1.11.x versions, and so on. */ - public static final String VERSION = "4.1.4"; + 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) - - // NOTE: Add melee upgrades to loot tables when they are added. + // TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty + // 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. */ + /** 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; @@ -93,39 +94,28 @@ public class Wizardry { public void preInit(FMLPreInitializationEvent event){ logger = event.getModLog(); + + 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 @@ -133,26 +123,41 @@ public class Wizardry { settings.initConfigExtras(); - // Event Handlers - GameRegistry.registerWorldGenerator(generator, 0); - MinecraftForge.EVENT_BUS.register(instance); - proxy.registerSpellHUD(); // This can't easily be converted to use the new @Mod.EventBusSubscriber system + // 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.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 @@ -174,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.getNamespace().equals(Wizardry.MODID)){ - Item replacement = null; + Item replacement; 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; @@ -233,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 871787d6..08cb5ac9 100644 --- a/src/main/java/electroblob/wizardry/WizardryEventHandler.java +++ b/src/main/java/electroblob/wizardry/WizardryEventHandler.java @@ -1,115 +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.item.ItemWand; -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.integration.DamageSafetyChecker; +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.IElementalDamage; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; +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.item.ItemStack; -import net.minecraft.item.ItemSword; import net.minecraft.potion.PotionEffect; 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); } @@ -118,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())); } @@ -152,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. @@ -189,48 +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){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(event.getEntityLiving().posX, event.getEntityLiving().posY + 1, - event.getEntityLiving().posZ, attacker.posX, attacker.posY + attacker.height / 2, - attacker.posZ); - world.spawnEntity(arc); - }else{ - for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - attacker.posX + world.rand.nextFloat() - 0.5, attacker.getEntityBoundingBox().minY - + attacker.height / 2 + world.rand.nextFloat() * 2 - 1, - attacker.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, attacker.posX + world.rand.nextFloat() - 0.5, - attacker.getEntityBoundingBox().minY + attacker.height / 2 + world.rand.nextFloat() * 2 - - 1, - attacker.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); + // 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); } - } - attacker.attackEntityFrom( - MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.SHOCK, true), 4.0f); - attacker.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); + 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); + } } } } - @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 @@ -239,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()); @@ -280,12 +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); - } + // 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){ @@ -296,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); } } @@ -310,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){ @@ -319,64 +368,85 @@ 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){ + + // 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; + + // 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 + + // 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 + + // 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; + + // DEBUG +// if(event.getEntity() instanceof EntityPlayer){ +// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y); +// } + + event.setDistance((float)y); } } } - @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){ - - // 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); - - event.getDrops() - .add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX, - event.getEntityLiving().posY, event.getEntityLiving().posZ, - new ItemStack(WizardryItems.spell_book, 1, id))); - } - } - } - - // Private helper methods - // ================================================================================================================ - - /** - * 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()); - } - } - } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/WizardryGuiFactory.java b/src/main/java/electroblob/wizardry/WizardryGuiFactory.java index 19951840..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.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 f3dffaa6..140ed107 100644 --- a/src/main/java/electroblob/wizardry/WizardryGuiHandler.java +++ b/src/main/java/electroblob/wizardry/WizardryGuiHandler.java @@ -40,20 +40,20 @@ public class WizardryGuiHandler implements IGuiHandler { if(id == ARCANE_WORKBENCH){ TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); if(tileEntity instanceof TileEntityArcaneWorkbench){ - return new electroblob.wizardry.client.GuiArcaneWorkbench(player.inventory, + return new electroblob.wizardry.client.gui.GuiArcaneWorkbench(player.inventory, (TileEntityArcaneWorkbench)tileEntity); } }else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook || player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){ - return new electroblob.wizardry.client.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.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.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.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z)); + return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z)); } return null; } 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): - *

    - * 0 Nothing (keep existing block)
    - * 1 Air (remove existing block)
    - * 2 Floor (planks)
    - * 3 Bookshelf
    - * 4 Roof (stained clay)
    - * 5 Floor slab (lower half)
    - * 6 Floor slab (upper half)
    - * 7 Wall (cobblestone by default)
    - * 8 Glass pane
    - * 9 Door (metadata is handled by the built-in vanilla method)
    - * 10 Arcane workbench
    - * 11 Torch (metadata is handled separately depending on adjacent blocks)
    - * 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead) - */ - private static final int[][][] towerBlueprintSmall = { - // x is horizontal, z is vertical, y is layers - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0}, - {0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, - {0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0}, - {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4}, - {0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}}; - - /** - * 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): - *

    - * 0 Nothing (keep existing block)
    - * 1 Air (remove existing block)
    - * 2 Floor (planks)
    - * 3 Bookshelf
    - * 4 Roof (stained clay)
    - * 5 Floor slab (lower half)
    - * 6 Floor slab (upper half)
    - * 7 Wall (cobblestone by default)
    - * 8 Glass pane
    - * 9 Door (metadata is handled by the built-in vanilla method)
    - * 10 Arcane workbench
    - * 11 Torch (metadata is handled separately depending on adjacent blocks)
    - * 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead) - */ - private static final int[][][] towerBlueprintMedium = { - // x is horizontal, z is vertical, y is layers - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0}, - {0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, - {0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0}, - {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4}, - {0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}}; - - /** - * 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): - *

    - * 0 Nothing (keep existing block)
    - * 1 Air (remove existing block)
    - * 2 Floor (planks)
    - * 3 Bookshelf
    - * 4 Roof (stained clay)
    - * 5 Floor slab (lower half)
    - * 6 Floor slab (upper half)
    - * 7 Wall (cobblestone by default)
    - * 8 Glass pane
    - * 9 Door (metadata is handled by the built-in vanilla method)
    - * 10 Arcane workbench
    - * 11 Torch (metadata is handled separately depending on adjacent blocks)
    - * 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead) - */ - private static final int[][][] towerBlueprintTall = { - // x is horizontal, z is vertical, y is layers - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, - {0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, - {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0}, - {0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0}, - {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0}, - {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, - {0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0}, - {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4}, - {0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}}; - - /** - * 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): - *

    - * 0 Nothing (keep existing block)
    - * 1 Air (remove existing block)
    - * 2 Floor (planks)
    - * 3 Bookshelf
    - * 4 Roof (stained clay)
    - * 5 Floor slab (lower half)
    - * 6 Floor slab (upper half)
    - * 7 Wall (cobblestone by default)
    - * 8 Glass pane
    - * 9 Door (metadata is handled by the built-in vanilla method)
    - * 10 Arcane workbench
    - * 11 Torch (metadata is handled separately depending on adjacent blocks)
    - * 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead) - */ - private static final int[][][] towerBlueprintDouble = { - // x is horizontal, z is vertical, y is layers - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 9, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 6, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 0, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 11, 1, 11, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 1, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 1, 1, 6, 7, 7, 7, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 7, 7, 0, 0}, - {0, 0, 0, 0, 7, 11, 1, 1, 1, 1, 1, 7, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 0, 0}, - {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 7, 8, 7, 0}, - {0, 0, 0, 0, 7, 6, 1, 1, 1, 1, 1, 8, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 8, 7, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 8, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 7, 4}, - {0, 0, 0, 0, 7, 1, 1, 11, 7, 1, 1, 7, 4}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 7, 7, 7, 4}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 4, 4, 4, 0}, - {0, 0, 0, 0, 7, 1, 1, 6, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 4, 4, 4, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 4, 0, 0}, - {0, 0, 0, 0, 7, 11, 1, 1, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0}, - {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 2, 7, 0, 0, 0}, - {0, 0, 0, 7, 5, 2, 2, 6, 2, 7, 4, 0, 0}, {0, 0, 0, 7, 2, 2, 2, 2, 2, 7, 0, 0, 0}, - {0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 3, 3, 3, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, - {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 10, 1, 3, 7, 0, 0, 0}, - {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, - {0, 0, 0, 8, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, - {0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0}, - {0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0}, - {0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 12, 7, 4, 0, 0}, - {0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0}, - {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0}, - {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}, - {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}}; - -} diff --git a/src/main/java/electroblob/wizardry/advancement/AdvancementHelper.java b/src/main/java/electroblob/wizardry/advancement/AdvancementHelper.java deleted file mode 100644 index 1a725a97..00000000 --- a/src/main/java/electroblob/wizardry/advancement/AdvancementHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -package electroblob.wizardry.advancement; - -import electroblob.wizardry.Wizardry; -import net.minecraft.advancements.Advancement; -import net.minecraft.advancements.AdvancementManager; -import net.minecraft.advancements.AdvancementProgress; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.util.ResourceLocation; - -/** - * This is a provisory class for the transition to 1.12 - * It contains an enum with all the advancements of this mod - * and an helper to grant advancements on the server side only (like the command) - * @author Corail31 - * @since Wizardry 4.1 - */ -public class AdvancementHelper { - public enum EnumAdvancement { - crystal, - arcane_initiate, - apprentice, - master, - all_spells, - wizard_trade, - buy_master_spell, - freeze_blaze, - charge_creeper, - frankenstein, - special_upgrade, - craft_flask, - elemental, - armour_set, - legendary, - self_destruct, - pig_tornado, - jam_wizard, - slime_skeleton, - anger_wizard, - defeat_evil_wizard, - max_out_wand, - element_master, - identify_spell; - } - - public static boolean grantAdvancement(EntityPlayer player, EnumAdvancement advancementName) { - if (player == null) { return false; } - if (player.world.isRemote) { return true; } - EntityPlayerMP player_mp = player.getServer().getPlayerList().getPlayerByUUID(player.getUniqueID()); - AdvancementManager am = player_mp.getServerWorld().getAdvancementManager(); - Advancement advancement = am.getAdvancement(new ResourceLocation(Wizardry.MODID, advancementName.name())); - if (advancement == null) { return false; } - AdvancementProgress advancementprogress = player_mp.getAdvancements().getProgress(advancement); - if (!advancementprogress.isDone()) { - for (String criteria : advancementprogress.getRemaningCriteria()) { - player_mp.getAdvancements().grantCriterion(advancement, criteria); - } - } - return true; - } -} diff --git a/src/main/java/electroblob/wizardry/advancement/ArcaneWorkbenchTrigger.java b/src/main/java/electroblob/wizardry/advancement/ArcaneWorkbenchTrigger.java new file mode 100644 index 00000000..175f9cdf --- /dev/null +++ b/src/main/java/electroblob/wizardry/advancement/ArcaneWorkbenchTrigger.java @@ -0,0 +1,135 @@ +package electroblob.wizardry.advancement; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import net.minecraft.advancements.ICriterionTrigger; +import net.minecraft.advancements.PlayerAdvancements; +import net.minecraft.advancements.critereon.AbstractCriterionInstance; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Advancement trigger for things done in the arcane workbench. The majority of any + * ICriterionTrigger class is just boilerplate, and this is no exception. */ +public class ArcaneWorkbenchTrigger implements ICriterionTrigger { + + private final ResourceLocation id; + private final Map listeners = Maps.newHashMap(); + + public ArcaneWorkbenchTrigger(ResourceLocation id){ + this.id = id; + } + + public ResourceLocation getId(){ + return this.id; + } + + public void addListener(PlayerAdvancements advancements, Listener listener){ + + ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners == null){ + listeners = new ArcaneWorkbenchTrigger.Listeners(advancements); + this.listeners.put(advancements, listeners); + } + + listeners.add(listener); + } + + public void removeListener(PlayerAdvancements advancements, Listener listener){ + + ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners != null){ + listeners.remove(listener); + + if(listeners.isEmpty()){ + this.listeners.remove(advancements); + } + } + } + + public void removeAllListeners(PlayerAdvancements advancements){ + this.listeners.remove(advancements); + } + + public ArcaneWorkbenchTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){ + return new ArcaneWorkbenchTrigger.Instance(this.id, ItemPredicate.deserialize(json.get("item"))); + } + + public void trigger(EntityPlayerMP player, ItemStack stack){ + + ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(player.getAdvancements()); + + if(listeners != null){ + listeners.trigger(stack); + } + } + + public static class Instance extends AbstractCriterionInstance { + + private final ItemPredicate item; + + public Instance(ResourceLocation criterionIn, ItemPredicate item){ + super(criterionIn); + this.item = item; + } + + public boolean test(ItemStack stack){ + return this.item.test(stack); + } + } + + static class Listeners { + + private final PlayerAdvancements playerAdvancements; + private final Set> listeners = Sets.newHashSet(); + + public Listeners(PlayerAdvancements advancements){ + this.playerAdvancements = advancements; + } + + public boolean isEmpty(){ + return this.listeners.isEmpty(); + } + + public void add(Listener listener){ + this.listeners.add(listener); + } + + public void remove(Listener listener){ + this.listeners.remove(listener); + } + + public void trigger(ItemStack stack){ + + List> list = null; + + for(Listener listener : this.listeners){ + + if(listener.getCriterionInstance().test(stack)){ + + if(list == null){ + list = Lists.newArrayList(); + } + + list.add(listener); + } + } + + if(list != null){ + for(Listener listener : list){ + listener.grantCriterion(this.playerAdvancements); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/util/CustomAdvancementTrigger.java b/src/main/java/electroblob/wizardry/advancement/CustomAdvancementTrigger.java similarity index 96% rename from src/main/java/electroblob/wizardry/util/CustomAdvancementTrigger.java rename to src/main/java/electroblob/wizardry/advancement/CustomAdvancementTrigger.java index 8fb2693a..21a1cf99 100644 --- a/src/main/java/electroblob/wizardry/util/CustomAdvancementTrigger.java +++ b/src/main/java/electroblob/wizardry/advancement/CustomAdvancementTrigger.java @@ -1,10 +1,9 @@ -package electroblob.wizardry.util; +package electroblob.wizardry.advancement; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonObject; - import electroblob.wizardry.Wizardry; import net.minecraft.advancements.ICriterionInstance; import net.minecraft.advancements.ICriterionTrigger; @@ -34,7 +33,7 @@ public class CustomAdvancementTrigger implements ICriterionTrigger { + + private final ResourceLocation id; + private final Map listeners = Maps.newHashMap(); + + public SpellCastTrigger(ResourceLocation id){ + this.id = id; + } + + public ResourceLocation getId(){ + return this.id; + } + + public void addListener(PlayerAdvancements advancements, Listener listener){ + + SpellCastTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners == null){ + listeners = new SpellCastTrigger.Listeners(advancements); + this.listeners.put(advancements, listeners); + } + + listeners.add(listener); + } + + public void removeListener(PlayerAdvancements advancements, Listener listener){ + + SpellCastTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners != null){ + listeners.remove(listener); + + if(listeners.isEmpty()){ + this.listeners.remove(advancements); + } + } + } + + public void removeAllListeners(PlayerAdvancements advancements){ + this.listeners.remove(advancements); + } + + public SpellCastTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){ + return new SpellCastTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")), + ItemPredicate.deserialize(json.get("item"))); + } + + public void trigger(EntityPlayerMP player, Spell spell, ItemStack stack){ + + SpellCastTrigger.Listeners listeners = this.listeners.get(player.getAdvancements()); + + if(listeners != null){ + listeners.trigger(spell, stack); + } + } + + public static class Instance extends AbstractCriterionInstance { + + private final SpellPredicate spell; + private final ItemPredicate item; + + public Instance(ResourceLocation criterion, SpellPredicate spell, ItemPredicate item){ + super(criterion); + this.spell = spell; + this.item = item; + } + + public boolean test(Spell spell, ItemStack stack){ + return this.spell.test(spell) && item.test(stack); + } + } + + static class Listeners { + + private final PlayerAdvancements playerAdvancements; + private final Set> listeners = Sets.newHashSet(); + + public Listeners(PlayerAdvancements advancements){ + this.playerAdvancements = advancements; + } + + public boolean isEmpty(){ + return this.listeners.isEmpty(); + } + + public void add(Listener listener){ + this.listeners.add(listener); + } + + public void remove(Listener listener){ + this.listeners.remove(listener); + } + + public void trigger(Spell spell, ItemStack stack){ + + List> list = null; + + for(Listener listener : this.listeners){ + + if(listener.getCriterionInstance().test(spell, stack)){ + + if(list == null){ + list = Lists.newArrayList(); + } + + list.add(listener); + } + } + + if(list != null){ + for(Listener listener : list){ + listener.grantCriterion(this.playerAdvancements); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/advancement/SpellDiscoveryTrigger.java b/src/main/java/electroblob/wizardry/advancement/SpellDiscoveryTrigger.java new file mode 100644 index 00000000..f24fef36 --- /dev/null +++ b/src/main/java/electroblob/wizardry/advancement/SpellDiscoveryTrigger.java @@ -0,0 +1,143 @@ +package electroblob.wizardry.advancement; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.event.DiscoverSpellEvent; +import electroblob.wizardry.spell.Spell; +import net.minecraft.advancements.ICriterionTrigger; +import net.minecraft.advancements.PlayerAdvancements; +import net.minecraft.advancements.critereon.AbstractCriterionInstance; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Advancement trigger that is triggered when a spell is discovered. The majority of any + * ICriterionTrigger class is just boilerplate, and this is no exception. */ +public class SpellDiscoveryTrigger implements ICriterionTrigger { + + private final ResourceLocation id; + private final Map listeners = Maps.newHashMap(); + + public SpellDiscoveryTrigger(ResourceLocation id){ + this.id = id; + } + + public ResourceLocation getId(){ + return this.id; + } + + public void addListener(PlayerAdvancements advancements, Listener listener){ + + SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners == null){ + listeners = new SpellDiscoveryTrigger.Listeners(advancements); + this.listeners.put(advancements, listeners); + } + + listeners.add(listener); + } + + public void removeListener(PlayerAdvancements advancements, Listener listener){ + + SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners != null){ + listeners.remove(listener); + + if(listeners.isEmpty()){ + this.listeners.remove(advancements); + } + } + } + + public void removeAllListeners(PlayerAdvancements advancements){ + this.listeners.remove(advancements); + } + + public SpellDiscoveryTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){ + + String s = JsonUtils.getString(json, "source"); + DiscoverSpellEvent.Source source = DiscoverSpellEvent.Source.byName(s); + if(source == null) throw new JsonSyntaxException("No such spell discovery source: " + s); + return new SpellDiscoveryTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")), source); + } + + public void trigger(EntityPlayerMP player, Spell spell, DiscoverSpellEvent.Source source){ + + SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(player.getAdvancements()); + + if(listeners != null){ + listeners.trigger(spell, source); + } + } + + public static class Instance extends AbstractCriterionInstance { + + private final SpellPredicate spell; + private final DiscoverSpellEvent.Source source; + + public Instance(ResourceLocation criterion, SpellPredicate spell, DiscoverSpellEvent.Source source){ + super(criterion); + this.spell = spell; + this.source = source; + } + + public boolean test(Spell spell, DiscoverSpellEvent.Source source){ + return this.spell.test(spell) && source == this.source; + } + } + + static class Listeners { + + private final PlayerAdvancements playerAdvancements; + private final Set> listeners = Sets.newHashSet(); + + public Listeners(PlayerAdvancements advancements){ + this.playerAdvancements = advancements; + } + + public boolean isEmpty(){ + return this.listeners.isEmpty(); + } + + public void add(Listener listener){ + this.listeners.add(listener); + } + + public void remove(Listener listener){ + this.listeners.remove(listener); + } + + public void trigger(Spell spell, DiscoverSpellEvent.Source source){ + + List> list = null; + + for(Listener listener : this.listeners){ + + if(listener.getCriterionInstance().test(spell, source)){ + + if(list == null){ + list = Lists.newArrayList(); + } + + list.add(listener); + } + } + + if(list != null){ + for(Listener listener : list){ + listener.grantCriterion(this.playerAdvancements); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/advancement/SpellPredicate.java b/src/main/java/electroblob/wizardry/advancement/SpellPredicate.java new file mode 100644 index 00000000..24d6c694 --- /dev/null +++ b/src/main/java/electroblob/wizardry/advancement/SpellPredicate.java @@ -0,0 +1,117 @@ +package electroblob.wizardry.advancement; + +import com.google.common.collect.Streams; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.spell.Spell; +import net.minecraft.util.JsonUtils; + +import javax.annotation.Nullable; +import java.util.Arrays; + +/** Predicate used by advancement triggers to match spells. */ +public class SpellPredicate { + + public static final SpellPredicate ANY = new SpellPredicate(); + private final Spell spell; + private final Tier[] tiers; + private final Element[] elements; + + public SpellPredicate(){ + this.spell = null; + this.tiers = Tier.values(); + this.elements = Element.values(); + } + + public SpellPredicate(@Nullable Spell spell, Tier[] tiers, Element[] elements){ + this.spell = spell; + this.tiers = tiers; + this.elements = elements; + } + + public boolean test(Spell spell){ + + if(this.spell != null && spell != this.spell){ + return false; + }else if(!Arrays.asList(this.tiers).contains(spell.getTier())){ + return false; + }else if(!Arrays.asList(this.elements).contains(spell.getElement())){ + return false; + } + + return true; + } + + public static SpellPredicate deserialize(@Nullable JsonElement element){ + + if(element != null && !element.isJsonNull()){ + + JsonObject jsonobject = JsonUtils.getJsonObject(element, "spell"); + + Spell spell = null; + + if(jsonobject.has("spell")){ + + String s = JsonUtils.getString(jsonobject, "spell"); + spell = Spell.get(s); + + if(spell == null){ + throw new JsonSyntaxException("Unknown spell id '" + s + "'"); + } + } + + Tier[] tiers = Tier.values(); + + if(jsonobject.has("tiers")){ + try{ + JsonArray array = JsonUtils.getJsonArray(jsonobject, "tiers"); + tiers = Streams.stream(array) + .map(je -> Tier.fromName(JsonUtils.getString(je, "element of array tiers"))) + .toArray(Tier[]::new); + }catch(IllegalArgumentException e){ + throw new JsonSyntaxException("Incorrect spell predicate value", e); + } + } + + Element[] elements = Element.values(); + + if(jsonobject.has("elements")){ + try{ + JsonArray array = JsonUtils.getJsonArray(jsonobject, "elements"); + elements = Streams.stream(array) + .map(je -> Element.fromName(JsonUtils.getString(je, "element of array elements"))) + .toArray(Element[]::new); + }catch(IllegalArgumentException e){ + throw new JsonSyntaxException("Incorrect spell predicate value", e); + } + } + + return new SpellPredicate(spell, tiers, elements); + + }else{ + return ANY; + } + } + + public static SpellPredicate[] deserializeArray(@Nullable JsonElement element){ + + if(element != null && !element.isJsonNull()){ + + JsonArray jsonarray = JsonUtils.getJsonArray(element, "spells"); + SpellPredicate[] predicates = new SpellPredicate[jsonarray.size()]; + + for(int i = 0; i < predicates.length; ++i){ + predicates[i] = deserialize(jsonarray.get(i)); + } + + return predicates; + + }else{ + return new SpellPredicate[0]; + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/advancement/StructureTrigger.java b/src/main/java/electroblob/wizardry/advancement/StructureTrigger.java new file mode 100644 index 00000000..aedcd6a4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/advancement/StructureTrigger.java @@ -0,0 +1,136 @@ +package electroblob.wizardry.advancement; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import electroblob.wizardry.worldgen.WorldGenSurfaceStructure; +import net.minecraft.advancements.ICriterionTrigger; +import net.minecraft.advancements.PlayerAdvancements; +import net.minecraft.advancements.critereon.AbstractCriterionInstance; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.WorldServer; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Copied from PositionTrigger and modified to work with wizardry's structures. The majority of any + * ICriterionTrigger class is just boilerplate, and this is no exception. */ +public class StructureTrigger implements ICriterionTrigger { + + private final ResourceLocation id; + private final Map listeners = Maps.newHashMap(); + + public StructureTrigger(ResourceLocation id){ + this.id = id; + } + + public ResourceLocation getId(){ + return this.id; + } + + public void addListener(PlayerAdvancements advancements, Listener listener){ + + StructureTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners == null){ + listeners = new StructureTrigger.Listeners(advancements); + this.listeners.put(advancements, listeners); + } + + listeners.add(listener); + } + + public void removeListener(PlayerAdvancements advancements, Listener listener){ + + StructureTrigger.Listeners listeners = this.listeners.get(advancements); + + if(listeners != null){ + listeners.remove(listener); + + if(listeners.isEmpty()){ + this.listeners.remove(advancements); + } + } + } + + public void removeAllListeners(PlayerAdvancements advancements){ + this.listeners.remove(advancements); + } + + public StructureTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){ + return new StructureTrigger.Instance(this.id, JsonUtils.getString(json, "structure_type")); + } + + public void trigger(EntityPlayerMP player){ + + StructureTrigger.Listeners listeners = this.listeners.get(player.getAdvancements()); + + if(listeners != null){ + listeners.trigger(player.getServerWorld(), player.posX, player.posY, player.posZ); + } + } + + public static class Instance extends AbstractCriterionInstance { + + private final WorldGenSurfaceStructure structureType; + + public Instance(ResourceLocation criterionIn, String name){ + super(criterionIn); + this.structureType = WorldGenSurfaceStructure.byName(name); + } + + public boolean test(WorldServer world, double x, double y, double z){ + return structureType.isInsideStructure(world, x, y, z); + } + } + + static class Listeners { + + private final PlayerAdvancements playerAdvancements; + private final Set> listeners = Sets.newHashSet(); + + public Listeners(PlayerAdvancements advancements){ + this.playerAdvancements = advancements; + } + + public boolean isEmpty(){ + return this.listeners.isEmpty(); + } + + public void add(Listener listener){ + this.listeners.add(listener); + } + + public void remove(Listener listener){ + this.listeners.remove(listener); + } + + public void trigger(WorldServer world, double x, double y, double z){ + + List> list = null; + + for(Listener listener : this.listeners){ + + if(listener.getCriterionInstance().test(world, x, y, z)){ + + if(list == null){ + list = Lists.newArrayList(); + } + + list.add(listener); + } + } + + if(list != null){ + for(Listener listener : list){ + listener.grantCriterion(this.playerAdvancements); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/api/WizardryEnumHelper.java b/src/main/java/electroblob/wizardry/api/WizardryEnumHelper.java new file mode 100644 index 00000000..4c73f606 --- /dev/null +++ b/src/main/java/electroblob/wizardry/api/WizardryEnumHelper.java @@ -0,0 +1,96 @@ +package electroblob.wizardry.api; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.SpellType; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.util.SpellProperties; +import net.minecraft.util.text.Style; +import net.minecraftforge.common.util.EnumHelper; + +/** + * This class contains methods similar to those in {@link EnumHelper} specific to wizardry's enum types. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public final class WizardryEnumHelper { + + // Make sure these are updated if the relevant constructors are updated! + // Can't we do some kind of reflection to access them and generate these arrays? + private static final Class[] TIER_ARGUMENTS = new Class[]{Integer.class, Integer.class, Integer.class, Style.class, String.class}; + private static final Class[] ELEMENT_ARGUMENTS = new Class[]{Style.class, String.class, String.class}; + private static final Class[] SPELL_TYPE_ARGUMENTS = new Class[]{String.class}; + private static final Class[] SPELL_CONTEXT_ARGUMENTS = new Class[]{String.class}; + + /** + * Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is + * specifically for adding new tiers. Use this method in preference to the generic one in case the constructor + * parameters change for {@code Tier}. + *

    + * As of version 4.2, wizardry now has partial support for externally-added tiers; you'll need to do some of the + * legwork yourself though. + * + * @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the + * resulting enum constant; other than that it doesn't really make much difference. + * @param maxCharge The maximum charge for wands of this tier. + * @param upgradeLimit The maximum total number of special upgrades that can be applied to wands of this tier. + * @param weight The weight of this tier in the standard weighting. + * @param colour The colour of text associated with this tier, as a style object. + * @param name The unlocalised name of this tier, as used in translation keys. + * @return The resulting {@code Tier} enum constant. + */ + public static Tier addTier(String codeName, int maxCharge, int upgradeLimit, int weight, Style colour, String name){ + return EnumHelper.addEnum(Tier.class, codeName, TIER_ARGUMENTS, maxCharge, upgradeLimit, weight, colour, name); + } + + /** + * Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is + * specifically for adding new elements. Use this method in preference to the generic one in case the constructor + * parameters change for {@code Element}. + *

    + * As of version 4.2, wizardry now has full support for externally-added elements. + * + * @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the + * resulting enum constant; other than that it doesn't really make much difference. + * @param colour The colour of text associated with this element, as a style object. + * @param name The unlocalised name of this element, as used in translation keys. + * @param modID The mod ID of the mod that added this element, for icon rendering purposes. + * @return The resulting {@code Element} enum constant. + */ + // This is the only one that needs the mod ID argument because elements are the only ones that have icons + // For some reason Minecraft doesn't seem to care about the resource domain for lang files, it just pools them + public static Element addElement(String codeName, Style colour, String name, String modID){ + return EnumHelper.addEnum(Element.class, codeName, ELEMENT_ARGUMENTS, colour, name, modID); + } + + /** + * Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is + * specifically for adding new spell types. Use this method in preference to the generic one in case the constructor + * parameters change for {@code SpellType}. + *

    + * As of version 4.2, wizardry now has full support for externally-added spell types. + * + * @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the + * resulting enum constant; other than that it doesn't really make much difference. + * @param name The unlocalised name of this spell type, as used in translation keys. + * @return The resulting {@code SpellType} enum constant. + */ + public static SpellType addSpellType(String codeName, String name){ + return EnumHelper.addEnum(SpellType.class, codeName, SPELL_TYPE_ARGUMENTS, name); + } + + /** + * Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is + * specifically for adding new spell contexts (for use in spell property JSON files). Use this method in preference + * to the generic one in case the constructor parameters change for {@code Context}. + * + * @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the + * resulting enum constant; other than that it doesn't really make much difference. + * @param name The identifier for this spell context, as used in the JSON file. + * @return The resulting {@code Context} enum constant. + */ + public static SpellProperties.Context addSpellContext(String codeName, String name){ + return EnumHelper.addEnum(SpellProperties.Context.class, codeName, SPELL_CONTEXT_ARGUMENTS, name); + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockArcaneWorkbench.java b/src/main/java/electroblob/wizardry/block/BlockArcaneWorkbench.java index d2407cff..8e76c803 100644 --- a/src/main/java/electroblob/wizardry/block/BlockArcaneWorkbench.java +++ b/src/main/java/electroblob/wizardry/block/BlockArcaneWorkbench.java @@ -5,6 +5,7 @@ import electroblob.wizardry.WizardryGuiHandler; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import net.minecraft.block.BlockContainer; import net.minecraft.block.material.Material; +import net.minecraft.block.state.BlockFaceShape; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.InventoryHelper; @@ -51,6 +52,16 @@ public class BlockArcaneWorkbench extends BlockContainer { return false; } + @Override + public boolean isFullCube(IBlockState state){ + return false; + } + + @Override + public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){ + return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED; + } + @Override public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ){ diff --git a/src/main/java/electroblob/wizardry/block/BlockCrystal.java b/src/main/java/electroblob/wizardry/block/BlockCrystal.java new file mode 100644 index 00000000..2d0f4dd9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockCrystal.java @@ -0,0 +1,76 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.block.Block; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; + +import java.util.EnumMap; + +public class BlockCrystal extends Block { + + public static final PropertyEnum ELEMENT = PropertyEnum.create("element", Element.class); + + private static final EnumMap map_colours = new EnumMap<>(Element.class); + + static { + map_colours.put(Element.MAGIC, MapColor.PINK); + map_colours.put(Element.FIRE, MapColor.ORANGE_STAINED_HARDENED_CLAY); + map_colours.put(Element.ICE, MapColor.LIGHT_BLUE); + map_colours.put(Element.LIGHTNING, MapColor.CYAN); + map_colours.put(Element.NECROMANCY, MapColor.PURPLE); + map_colours.put(Element.EARTH, MapColor.GREEN); + map_colours.put(Element.SORCERY, MapColor.LIME); + map_colours.put(Element.HEALING, MapColor.YELLOW); + } + + public BlockCrystal(Material material){ + super(material); + this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.MAGIC)); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHarvestLevel("pickaxe", 2); + } + + @Override + public int damageDropped(IBlockState state){ + return (state.getValue(ELEMENT)).ordinal(); + } + + @Override + public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){ + return map_colours.get(state.getProperties().get(ELEMENT)); + } + + @Override + public void getSubBlocks(CreativeTabs tab, NonNullList items){ + if(this.getCreativeTab() == tab){ + for(Element element : Element.values()){ + items.add(new ItemStack(this, 1, element.ordinal())); + } + } + } + + @Override + public IBlockState getStateFromMeta(int metadata){ + return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]); + } + + @Override + public int getMetaFromState(IBlockState state){ + return (state.getValue(ELEMENT)).ordinal(); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, ELEMENT); + } +} diff --git a/src/main/java/electroblob/wizardry/block/BlockCrystalFlower.java b/src/main/java/electroblob/wizardry/block/BlockCrystalFlower.java index 1ca537ea..ed9a09d3 100644 --- a/src/main/java/electroblob/wizardry/block/BlockCrystalFlower.java +++ b/src/main/java/electroblob/wizardry/block/BlockCrystalFlower.java @@ -1,10 +1,8 @@ package electroblob.wizardry.block; -import java.util.Random; - -import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryBlocks; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.block.BlockBush; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; @@ -19,6 +17,8 @@ import net.minecraftforge.event.entity.player.BonemealEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.Random; + // Extending BlockBush allows me to remove nearly everything from this class. @Mod.EventBusSubscriber public class BlockCrystalFlower extends BlockBush { @@ -41,10 +41,10 @@ public class BlockCrystalFlower extends BlockBush { @Override public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){ if(world.isRemote && random.nextBoolean()){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, pos.getX() + random.nextDouble(), - pos.getY() + random.nextDouble() / 2 + 0.5, pos.getZ() + random.nextDouble(), 0d, 0.01, 0d, - 20 + random.nextInt(10), 0.5f + (random.nextFloat() / 2), 0.5f + (random.nextFloat() / 2), - 0.5f + (random.nextFloat() / 2)); + ParticleBuilder.create(Type.SPARKLE) + .pos(pos.getX() + random.nextDouble(), pos.getY() + random.nextDouble() / 2 + 0.5, pos.getZ() + random.nextDouble()).vel(0, 0.01, 0) + .time(20 + random.nextInt(10)).clr(0.5f + (random.nextFloat() / 2), 0.5f + (random.nextFloat() / 2), + 0.5f + (random.nextFloat() / 2)).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/block/BlockCrystalOre.java b/src/main/java/electroblob/wizardry/block/BlockCrystalOre.java index 916b6564..a96c9d1a 100644 --- a/src/main/java/electroblob/wizardry/block/BlockCrystalOre.java +++ b/src/main/java/electroblob/wizardry/block/BlockCrystalOre.java @@ -1,7 +1,5 @@ package electroblob.wizardry.block; -import java.util.Random; - import electroblob.wizardry.registry.WizardryItems; import net.minecraft.block.Block; import net.minecraft.block.SoundType; @@ -13,6 +11,8 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import java.util.Random; + public class BlockCrystalOre extends Block { public BlockCrystalOre(Material material){ diff --git a/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java b/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java new file mode 100644 index 00000000..8e80b5ae --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java @@ -0,0 +1,28 @@ +package electroblob.wizardry.block; + +import net.minecraft.block.BlockFrostedIce; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import java.util.Random; + +/** Like {@link BlockFrostedIce}, but melting does not depend on light level or neighbouring blocks, and it just + * disappears instead of turning to water. */ +public class BlockDryFrostedIce extends BlockFrostedIce { + + @Override + protected void turnIntoWater(World world, BlockPos pos){ + world.destroyBlock(pos, false); + } + + @Override + public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand){ + if(rand.nextInt(3) == 0){ + this.slightlyMelt(worldIn, pos, state, rand, true); + }else{ + worldIn.scheduleUpdate(pos, this, MathHelper.getInt(rand, 20, 40)); + } + } +} diff --git a/src/main/java/electroblob/wizardry/block/BlockMagicLight.java b/src/main/java/electroblob/wizardry/block/BlockMagicLight.java index 77974eec..72dd8a5e 100644 --- a/src/main/java/electroblob/wizardry/block/BlockMagicLight.java +++ b/src/main/java/electroblob/wizardry/block/BlockMagicLight.java @@ -1,23 +1,31 @@ package electroblob.wizardry.block; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityMagicLight; -import net.minecraft.block.BlockContainer; +import net.minecraft.block.Block; +import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumBlockRenderType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; -public class BlockMagicLight extends BlockContainer { +public class BlockMagicLight extends Block implements ITileEntityProvider { - private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0); + //private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0); - public BlockMagicLight(Material par2Material){ - super(par2Material); + public BlockMagicLight(Material material){ + super(material); this.setLightLevel(1.0f); + this.setBlockUnbreakable(); } @Override @@ -28,13 +36,38 @@ public class BlockMagicLight extends BlockContainer { } @Override - public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ - return AABB; + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){ + // Let the player dispel any lights if they have the lantern charm, not just the permanent ones because that would be annoying! + if(player.getHeldItem(hand).getItem() instanceof ISpellCastingItem && ItemArtefact.isArtefactActive(player, WizardryItems.charm_light)){ + + world.setBlockToAir(pos); + return true; + + }else{ + return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ); + } } +// @Override +// public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ +// return AABB; +// } + @Override public boolean isCollidable(){ - return false; + // This method has nothing to do with entity movement, it's just for raytracing + return true; + } + + @Override + public boolean addDestroyEffects(World world, BlockPos pos, net.minecraft.client.particle.ParticleManager manager){ + if(world.getBlockState(pos).getBlock() == this) return true; // No break particles! + else return super.addDestroyEffects(world, pos, manager); + } + + @Override + public boolean hasTileEntity(IBlockState state){ + return true; } @Override diff --git a/src/main/java/electroblob/wizardry/block/BlockObsidianCrust.java b/src/main/java/electroblob/wizardry/block/BlockObsidianCrust.java new file mode 100644 index 00000000..269459bc --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockObsidianCrust.java @@ -0,0 +1,116 @@ +package electroblob.wizardry.block; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockObsidian; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import java.util.Random; + +/** Like {@link net.minecraft.block.BlockFrostedIce}, but for lava instead of water. */ +// This is mostly copied from that class, with a few changes +public class BlockObsidianCrust extends BlockObsidian { + + public static final PropertyInteger AGE = PropertyInteger.create("age", 0, 3); + + public BlockObsidianCrust(){ + this.setDefaultState(this.blockState.getBaseState().withProperty(AGE, 0)); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(AGE); + } + + @Override + public IBlockState getStateFromMeta(int meta){ + return this.getDefaultState().withProperty(AGE, MathHelper.clamp(meta, 0, 3)); + } + + @Override + public void updateTick(World world, BlockPos pos, IBlockState state, Random random){ + if((random.nextInt(3) == 0 || this.countNeighbors(world, pos) < 4) && world.getLightFromNeighbors(pos) > 11 - state.getValue(AGE) - state.getLightOpacity()){ + this.slightlyMelt(world, pos, state, random, true); + }else{ + world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40)); + } + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos){ + if(block == this){ + int i = this.countNeighbors(world, pos); + + if(i < 2){ + this.melt(world, pos); + } + } + } + + private int countNeighbors(World world, BlockPos pos){ + + int i = 0; + + for(EnumFacing enumfacing : EnumFacing.values()){ + if(world.getBlockState(pos.offset(enumfacing)).getBlock() == this){ + ++i; + + if(i >= 4){ + return i; + } + } + } + + return i; + } + + protected void slightlyMelt(World world, BlockPos pos, IBlockState state, Random random, boolean meltNeighbours){ + + int i = state.getValue(AGE); + + if(i < 3){ + + world.setBlockState(pos, state.withProperty(AGE, i + 1), 2); + world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40)); + + }else{ + + this.melt(world, pos); + + if(meltNeighbours){ + + for(EnumFacing enumfacing : EnumFacing.values()){ + + BlockPos blockpos = pos.offset(enumfacing); + IBlockState iblockstate = world.getBlockState(blockpos); + + if(iblockstate.getBlock() == this){ + this.slightlyMelt(world, blockpos, iblockstate, random, false); + } + } + } + } + } + + protected void melt(World world, BlockPos pos){ + world.setBlockState(pos, Blocks.LAVA.getDefaultState()); + world.neighborChanged(pos, Blocks.LAVA, pos); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, AGE); + } + + @Override + public ItemStack getItem(World world, BlockPos pos, IBlockState state){ + return ItemStack.EMPTY; + } +} diff --git a/src/main/java/electroblob/wizardry/block/BlockPedestal.java b/src/main/java/electroblob/wizardry/block/BlockPedestal.java new file mode 100644 index 00000000..9da9c6f5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockPedestal.java @@ -0,0 +1,123 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.tileentity.TileEntityShrineCore; +import net.minecraft.block.Block; +import net.minecraft.block.ITileEntityProvider; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.Entity; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.NonNullList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Explosion; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.EnumMap; + +public class BlockPedestal extends Block implements ITileEntityProvider { + + public static final PropertyEnum ELEMENT = PropertyEnum.create("element", Element.class, + Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC + + // A 'natural' pedestal is one that was generated as part of a structure, is unbreakable and has a tileentity + public static final PropertyBool NATURAL = PropertyBool.create("natural"); + + private static final EnumMap map_colours = new EnumMap<>(Element.class); + + static { + map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY); + map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY); + map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY); + map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY); + map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY); + map_colours.put(Element.SORCERY, MapColor.GRAY); + map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY); + } + + public BlockPedestal(Material material){ + super(material); + this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE).withProperty(NATURAL, false)); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(1.5F); + this.setResistance(10.0F); + } + + @Override + public int damageDropped(IBlockState state){ + return state.getValue(ELEMENT).ordinal(); // Ignore the NATURAL state here, it's unobtainable + } + + @Override + public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){ + return map_colours.get(state.getProperties().get(ELEMENT)); + } + + @Override + public void getSubBlocks(CreativeTabs tab, NonNullList items){ + // Ignore the NATURAL state here, it's unobtainable + if(this.getCreativeTab() == tab){ + for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){ + items.add(new ItemStack(this, 1, element.ordinal())); + } + } + } + + @Override + public BlockRenderLayer getRenderLayer(){ + return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others + } + + @Override + public float getBlockHardness(IBlockState state, World world, BlockPos pos){ + return state.getValue(NATURAL) ? -1 : super.getBlockHardness(state, world, pos); + } + + @Override + public float getExplosionResistance(World world, BlockPos pos, @Nullable Entity exploder, Explosion explosion){ + return world.getBlockState(pos).getValue(NATURAL) ? 6000000.0F : super.getExplosionResistance(world, pos, exploder, explosion); + } + + @Override + public boolean hasTileEntity(IBlockState state){ + return state.getValue(NATURAL); // Only naturally-generated pedestals have a (shrine core) tile entity + } + + @Nullable + @Override + public TileEntity createNewTileEntity(World world, int meta){ + return new TileEntityShrineCore(); + } + + @Override + public IBlockState getStateFromMeta(int metadata){ + boolean natural = false; + if(metadata > ELEMENT.getAllowedValues().size()){ + natural = true; + metadata -= ELEMENT.getAllowedValues().size(); + } + return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]).withProperty(NATURAL, natural); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(ELEMENT).ordinal() + (state.getValue(NATURAL) ? ELEMENT.getAllowedValues().size() : 0); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, ELEMENT, NATURAL); + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockRunestone.java b/src/main/java/electroblob/wizardry/block/BlockRunestone.java new file mode 100644 index 00000000..996eaa52 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockRunestone.java @@ -0,0 +1,85 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.block.Block; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.ItemStack; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.NonNullList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; + +import java.util.Arrays; +import java.util.EnumMap; + +public class BlockRunestone extends Block { + + public static final PropertyEnum ELEMENT = PropertyEnum.create("element", Element.class, + Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC + + private static final EnumMap map_colours = new EnumMap<>(Element.class); + + static { + map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY); + map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY); + map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY); + map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY); + map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY); + map_colours.put(Element.SORCERY, MapColor.GRAY); + map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY); + } + + public BlockRunestone(Material material){ + super(material); + this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE)); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(1.5F); + this.setResistance(10.0F); + } + + @Override + public int damageDropped(IBlockState state){ + return state.getValue(ELEMENT).ordinal(); + } + + @Override + public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){ + return map_colours.get(state.getProperties().get(ELEMENT)); + } + + @Override + public void getSubBlocks(CreativeTabs tab, NonNullList items){ + if(this.getCreativeTab() == tab){ + for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){ + items.add(new ItemStack(this, 1, element.ordinal())); + } + } + } + + @Override + public BlockRenderLayer getRenderLayer(){ + return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others + } + + @Override + public IBlockState getStateFromMeta(int metadata){ + return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(ELEMENT).ordinal(); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, ELEMENT); + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockSnare.java b/src/main/java/electroblob/wizardry/block/BlockSnare.java index 878826df..a10df677 100644 --- a/src/main/java/electroblob/wizardry/block/BlockSnare.java +++ b/src/main/java/electroblob/wizardry/block/BlockSnare.java @@ -1,13 +1,13 @@ package electroblob.wizardry.block; -import java.util.Random; - +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.tileentity.TileEntityPlayerSave; +import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.Block; -import net.minecraft.block.BlockContainer; +import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; @@ -25,8 +25,9 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; -// TODO: Apparently you shouldn't extend BlockContainer. I feel like BlockArcaneWorkbench should, but what about the rest? -public class BlockSnare extends BlockContainer { +import java.util.Random; + +public class BlockSnare extends Block implements ITileEntityProvider { private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.0625f, 1.0f); @@ -58,10 +59,14 @@ public class BlockSnare extends BlockContainer { TileEntityPlayerSave tileentity = (TileEntityPlayerSave)world.getTileEntity(pos); - if(WizardryUtilities.isValidTarget(tileentity.getCaster(), entity)){ - ((EntityLivingBase)entity).attackEntityFrom( - MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC), 6); - ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 100, 2)); + if(AllyDesignationSystem.isValidTarget(tileentity.getCaster(), entity)){ + + entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC), + Spells.snare.getProperty(Spell.DAMAGE).floatValue()); + + ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, + Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue())); world.destroyBlock(pos, false); } diff --git a/src/main/java/electroblob/wizardry/block/BlockSpectral.java b/src/main/java/electroblob/wizardry/block/BlockSpectral.java index 300582dc..fe34473a 100644 --- a/src/main/java/electroblob/wizardry/block/BlockSpectral.java +++ b/src/main/java/electroblob/wizardry/block/BlockSpectral.java @@ -1,13 +1,11 @@ package electroblob.wizardry.block; -import java.util.Random; - -import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.tileentity.TileEntityTimer; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.block.Block; -import net.minecraft.block.BlockContainer; +import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; @@ -24,10 +22,10 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on -// block break. +import java.util.Random; + @Mod.EventBusSubscriber -public class BlockSpectral extends BlockContainer { +public class BlockSpectral extends Block implements ITileEntityProvider { public BlockSpectral(Material material){ super(material); @@ -45,10 +43,8 @@ public class BlockSpectral extends BlockContainer { return EnumBlockRenderType.MODEL; } - // Apparently it's OK to override this, despite it being deprecated. More - // importantly, it being deprecated is not - // Forge's doing, rather it is Mojang themselves misusing the @Deprecated - // annotation to mean 'internal, don't call'. + // Apparently it's OK to override this, despite it being deprecated. More importantly, it being deprecated is not + // Forge's doing, rather it is Mojang themselves misusing the @Deprecated annotation to mean 'internal, don't call'. @Override public boolean isOpaqueCube(IBlockState state){ return false; @@ -61,18 +57,14 @@ public class BlockSpectral extends BlockContainer { @Override public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){ - // Middle of block - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(), - pos.getY() + random.nextDouble(), pos.getZ() + random.nextDouble(), 0, 0, 0, - (int)(16.0D / (Math.random() * 0.8D + 0.2D)), 0.4f + random.nextFloat() * 0.2f, - 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f); - // Top surface - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(), pos.getY() + 1, - pos.getZ() + random.nextDouble(), 0, 0, 0, (int)(16.0D / (Math.random() * 0.8D + 0.2D)), - 0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f); - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, pos.getX() + random.nextDouble(), pos.getY() + 1, - pos.getZ() + random.nextDouble(), 0, 0, 0, (int)(16.0D / (Math.random() * 0.8D + 0.2D)), - 0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f); + + for(int i=0; i<2; i++){ + ParticleBuilder.create(Type.DUST) + .pos(pos.getX() + random.nextDouble(), pos.getY() + random.nextDouble(), pos.getZ() + random.nextDouble()) + .time((int)(16.0D / (Math.random() * 0.8D + 0.2D))) + .clr(0.4f + random.nextFloat() * 0.2f, 0.6f + random.nextFloat() * 0.4f, 0.6f + random.nextFloat() * 0.4f) + .shaded(true).spawn(world); + } } // Overriden to make the block always look full brightness despite not emitting @@ -82,6 +74,11 @@ public class BlockSpectral extends BlockContainer { return 15; } + @Override + public boolean hasTileEntity(IBlockState state){ + return true; + } + @Override public TileEntity createNewTileEntity(World world, int metadata){ return new TileEntityTimer(1200); diff --git a/src/main/java/electroblob/wizardry/block/BlockStatue.java b/src/main/java/electroblob/wizardry/block/BlockStatue.java index 4dce4a22..cf5b7dee 100644 --- a/src/main/java/electroblob/wizardry/block/BlockStatue.java +++ b/src/main/java/electroblob/wizardry/block/BlockStatue.java @@ -1,14 +1,13 @@ package electroblob.wizardry.block; -import java.util.Random; - -import electroblob.wizardry.spell.Petrify; import electroblob.wizardry.tileentity.TileEntityStatue; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.Block; -import net.minecraft.block.BlockContainer; +import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLiving; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumBlockRenderType; @@ -20,10 +19,17 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -public class BlockStatue extends BlockContainer { +import java.util.Random; + +public class BlockStatue extends Block implements ITileEntityProvider { private boolean isIce; + /** The NBT tag name for storing the petrified flag (used for rendering) in the target's tag compound. */ + public static final String PETRIFIED_NBT_KEY = "petrified"; + /** The NBT tag name for storing the frozen flag (used for rendering) in the target's tag compound. */ + public static final String FROZEN_NBT_KEY = "frozen"; + public BlockStatue(Material material){ super(material); this.isIce = material == Material.ICE; @@ -106,6 +112,11 @@ public class BlockStatue extends BlockContainer { return this.isIce ? EnumBlockRenderType.MODEL : EnumBlockRenderType.ENTITYBLOCK_ANIMATED; } + @Override + public boolean hasTileEntity(IBlockState state){ + return true; + } + @Override public TileEntity createNewTileEntity(World world, int metadata){ return new TileEntityStatue(this.isIce); @@ -146,7 +157,7 @@ public class BlockStatue extends BlockContainer { // This is only when position == 1 because world.destroyBlock calls this function for the other blocks. if(tileentity != null && tileentity.position == 1 && tileentity.creature != null){ - tileentity.creature.getEntityData().removeTag(Petrify.NBT_KEY); + tileentity.creature.getEntityData().removeTag(BlockStatue.PETRIFIED_NBT_KEY); tileentity.creature.isDead = false; world.spawnEntity(tileentity.creature); } @@ -166,4 +177,84 @@ public class BlockStatue extends BlockContainer { return this.isIce && block == this ? false : super.shouldSideBeRendered(blockState, blockAccess, pos, side); } + + /** + * Turns the given entity into a statue. The type of statue depends on the block instance this method was invoked on. + * @param entity The entity to turn into a statue. + * @param duration The time for which the entity should remain a statue. For petrified creatures, this is the minimum + * time it can stay as a statue. + * @return True if the entity was successfully turned into a statue, false if not (i.e. something was in the way). + */ + // Making this an instance method means it works equally well for both types of statue + public boolean convertToStatue(EntityLiving entity, int duration){ + + if(entity.deathTime > 0) return false; + + BlockPos pos = new BlockPos(entity); + World world = entity.world; + + entity.hurtTime = 0; // Stops the entity looking red while frozen and the resulting z-fighting + entity.extinguish(); + + // Short mobs such as spiders and pigs + if((entity.height < 1.2 || entity.isChild()) && WizardryUtilities.canBlockBeReplaced(world, pos)){ + + world.setBlockState(pos, this.getDefaultState()); + if(world.getTileEntity(pos) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 1); + ((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration); + } + + entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + entity.setDead(); + return true; + } + // Normal sized mobs like zombies and skeletons + else if(entity.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos) + && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ + + world.setBlockState(pos, this.getDefaultState()); + if(world.getTileEntity(pos) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 2); + ((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration); + } + + world.setBlockState(pos.up(), this.getDefaultState()); + if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 2); + } + + entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + entity.setDead(); + return true; + } + // Tall mobs like endermen + else if(WizardryUtilities.canBlockBeReplaced(world, pos) + && WizardryUtilities.canBlockBeReplaced(world, pos.up()) + && WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){ + + world.setBlockState(pos, this.getDefaultState()); + if(world.getTileEntity(pos) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(entity, 1, 3); + ((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration); + } + + world.setBlockState(pos.up(), this.getDefaultState()); + if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 3); + } + + world.setBlockState(pos.up(2), this.getDefaultState()); + if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){ + ((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(entity, 3, 3); + } + + entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + entity.setDead(); + return true; + } + + return false; + } + } diff --git a/src/main/java/electroblob/wizardry/block/BlockThorns.java b/src/main/java/electroblob/wizardry/block/BlockThorns.java new file mode 100644 index 00000000..0ceb6035 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockThorns.java @@ -0,0 +1,181 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.MagicDamage; +import net.minecraft.block.*; +import net.minecraft.block.BlockDoublePlant.EnumBlockHalf; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.DamageSource; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.Random; + +@Mod.EventBusSubscriber +public class BlockThorns extends BlockBush implements ITileEntityProvider { + + public static final int GROWTH_STAGES = 8; + + public static final PropertyInteger AGE = PropertyInteger.create("age", 0, GROWTH_STAGES-1); + public static final PropertyEnum HALF = PropertyEnum.create("half", EnumBlockHalf.class); + + public BlockThorns(){ + this.setDefaultState(this.blockState.getBaseState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 7)); + this.setHardness(4); + this.setSoundType(SoundType.PLANT); + this.setCreativeTab(null); + } + + @Override + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ + return FULL_BLOCK_AABB; + } + + @Override + public IBlockState getStateFromMeta(int meta){ + return this.getDefaultState().withProperty(HALF, EnumBlockHalf.values()[meta / GROWTH_STAGES]).withProperty(AGE, meta % GROWTH_STAGES); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(HALF).ordinal() * GROWTH_STAGES + state.getValue(AGE); + } + +// @Override +// public void updateTick(World world, BlockPos pos, IBlockState state, Random rand){ +// +// super.updateTick(world, pos, state, rand); +// +// // Update the state, including on the client, but don't do a block update since it's only visual +// if(state.getValue(AGE) < GROWTH_STAGES-1) world.setBlockState(pos, state.withProperty(AGE, state.getValue(AGE) + 1), 2); +// } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, HALF, AGE); + } + + public void placeAt(World world, BlockPos lowerPos, int flags){ + world.setBlockState(lowerPos, this.getDefaultState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 0), flags); + world.setBlockState(lowerPos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER).withProperty(AGE, 0), flags); + } + + @Override + public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack){ + world.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER), 2); + } + + @Override + public void breakBlock(World world, BlockPos pos, IBlockState state){ + super.breakBlock(world, pos, state); + if(state.getValue(HALF) == EnumBlockHalf.LOWER){ + if(world.getBlockState(pos.up()).getBlock() == this){ + world.destroyBlock(pos.up(), false); + } + }else{ + if(world.getBlockState(pos.down()).getBlock() == this){ + world.destroyBlock(pos.down(), false); + } + } + } + + public boolean canBlockStay(World worldIn, BlockPos pos, IBlockState state){ + if(state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER){ + return worldIn.getBlockState(pos.down()).getBlock() == this; + }else{ + IBlockState iblockstate = worldIn.getBlockState(pos.up()); + return iblockstate.getBlock() == this && this.canSustainBush(worldIn.getBlockState(pos.down())); + } + } + +// @Override +// public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos){ +// // Copied from BlockFlowerPot on authority of the Forge docs, which says this check is necessary +// SoundLoopSpellDispenser tileentity = world instanceof ChunkCache ? ((ChunkCache)world).getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK) : world.getTileEntity(pos); +// +// if(tileentity instanceof TileEntityPlayerSaveTimed){ +// state = state.withProperty(AGE, Math.min(7, ((TileEntityPlayerSaveTimed)tileentity).timer/2)); +// }else{ +// state = state.withProperty(AGE, 7); +// } +// +// return state; +// } + + @Override + public void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){ + if(!world.isRemote){ + if(applyThornDamage(world, pos, entity)){ + entity.setInWeb(); + } + } + } + + private static boolean applyThornDamage(World world, BlockPos pos, Entity target){ + + DamageSource source = DamageSource.CACTUS; + + TileEntity tileentity = world.getTileEntity(pos); + + if(tileentity instanceof TileEntityPlayerSaveTimed){ + if(AllyDesignationSystem.isValidTarget(((TileEntityPlayerSaveTimed)tileentity).getCaster(), target)){ + source = MagicDamage.causeDirectMagicDamage(((TileEntityPlayerSaveTimed)tileentity).getCaster(), + MagicDamage.DamageType.MAGIC); + }else{ + return false; // Don't attack or slow allies of the caster + } + } + + if(world.getTotalWorldTime() % 20 == 0) target.attackEntityFrom(source, Spells.forest_of_thorns.getProperty(Spell.DAMAGE).floatValue()); + + return true; + } + + @Override + public Block.EnumOffsetType getOffsetType(){ + return Block.EnumOffsetType.XZ; + } + + @Override + public TileEntity createNewTileEntity(World world, int metadata){ + return new TileEntityPlayerSaveTimed(600); + } + + @Override + public boolean hasTileEntity(IBlockState state){ + return true; + } + + @Override public boolean isReplaceable(IBlockAccess world, BlockPos pos){ return false; } + @Override protected boolean canSustainBush(IBlockState state){ return state.isNormalCube(); } + @Override public Item getItemDropped(IBlockState state, Random rand, int fortune){ return Items.AIR; } + @Override public boolean canSilkHarvest(World world, BlockPos pos, IBlockState state, EntityPlayer player){ return false; } + + @SubscribeEvent + public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){ + if(!event.getWorld().isRemote && event.getWorld().getTotalWorldTime() % 20 == 0 + && event.getWorld().getBlockState(event.getPos()).getBlock() == WizardryBlocks.thorns){ + applyThornDamage(event.getWorld(), event.getPos(), event.getEntity()); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java b/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java index 9d1684a4..ea0595a1 100644 --- a/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java +++ b/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java @@ -1,12 +1,16 @@ package electroblob.wizardry.block; -import java.util.Random; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.spell.Transportation; +import electroblob.wizardry.util.Location; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; @@ -20,6 +24,10 @@ import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + public class BlockTransportationStone extends Block { private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0625f * 5, 0, 0.0625f * 5, 0.0625f * 11, 0.0625f * 6, @@ -65,6 +73,11 @@ public class BlockTransportationStone extends Block { public boolean isOpaqueCube(IBlockState state){ return false; } + + @Override + public boolean isSideSolid(IBlockState base_state, IBlockAccess world, BlockPos pos, EnumFacing side){ + return side == EnumFacing.DOWN; + } @SuppressWarnings("deprecation") @Override @@ -98,7 +111,7 @@ public class BlockTransportationStone extends Block { ItemStack stack = player.getHeldItem(hand); - if(stack.getItem() instanceof ItemWand){ + if(stack.getItem() instanceof ISpellCastingItem){ if(WizardData.get(player) != null){ WizardData data = WizardData.get(player); @@ -107,17 +120,59 @@ public class BlockTransportationStone extends Block { for(int z = -1; z <= 1; z++){ BlockPos pos1 = pos.add(x, 0, z); if(testForCircle(world, pos1)){ - data.setStoneCircleLocation(pos1, world.provider.getDimension()); - if(!world.isRemote) player.sendMessage( - new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm", - Spells.transportation.getNameForTranslationFormatted())); + + Location here = new Location(pos1, player.dimension); + + List locations = data.getVariable(Transportation.LOCATIONS_KEY); + if(locations == null) data.setVariable(Transportation.LOCATIONS_KEY, locations = new ArrayList<>(Transportation.MAX_REMEMBERED_LOCATIONS)); + + if(ItemArtefact.isArtefactActive(player, WizardryItems.charm_transportation)){ + + if(locations.contains(here)){ + locations.remove(here); + if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true); + + }else{ + + locations.add(here); + if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.remember", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true); + + if(locations.size() > Transportation.MAX_REMEMBERED_LOCATIONS){ + Location removed = locations.remove(0); + if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", removed.pos.getX(), removed.pos.getY(), removed.pos.getZ(), removed.dimension), true); + } + } + + }else{ + if(locations.isEmpty()) locations.add(here); + else{ + locations.remove(here); // Prevents duplicates + locations.set(locations.size() - 1, here); + } + if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm", Spells.transportation.getNameForTranslationFormatted()), true); + } + return true; } } } - if(!world.isRemote) - player.sendMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid")); + if(!world.isRemote){ + player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"), true); + }else{ + + BlockPos centre = findMostLikelyCircle(world, pos); + // Displays particles in the required shape + for(int x = -1; x <= 1; x++){ + for(int z = -1; z <= 1; z++){ + if(x == 0 && z == 0) continue; + ParticleBuilder.create(ParticleBuilder.Type.PATH) + .pos(WizardryUtilities.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65) + .time(200).scale(2).spawn(world); + } + } + } + return true; } } @@ -131,12 +186,47 @@ public class BlockTransportationStone extends Block { for(int x = -1; x <= 1; x++){ for(int z = -1; z <= 1; z++){ + if(x == 0 && z == 0) continue; if(world.getBlockState(pos.add(x, 0, z)).getBlock() != WizardryBlocks.transportation_stone){ - if(x != 0 || z != 0) return false; + return false; } } } return true; } + + private static BlockPos findMostLikelyCircle(World world, BlockPos pos){ + + int bestSoFar = 0; + BlockPos result = null; + + for(int x = -1; x <= 1; x++){ + for(int z = -1; z <= 1; z++){ + if(x == 0 && z == 0) continue; + BlockPos pos1 = pos.add(x, 0, z); + int n = getCircleCompleteness(world, pos1); + if(n > bestSoFar){ + bestSoFar = n; + result = pos1; + } + } + } + + return result; + } + + private static int getCircleCompleteness(World world, BlockPos pos){ + + int n = 0; + + for(int x = -1; x <= 1; x++){ + for(int z = -1; z <= 1; z++){ + if(x == 0 && z == 0) continue; + if(world.getBlockState(pos.add(x, 0, z)).getBlock() == WizardryBlocks.transportation_stone) n++; + } + } + + return n; + } } diff --git a/src/main/java/electroblob/wizardry/block/BlockVanishingCobweb.java b/src/main/java/electroblob/wizardry/block/BlockVanishingCobweb.java index e37165a9..c8532ced 100644 --- a/src/main/java/electroblob/wizardry/block/BlockVanishingCobweb.java +++ b/src/main/java/electroblob/wizardry/block/BlockVanishingCobweb.java @@ -1,9 +1,8 @@ package electroblob.wizardry.block; -import java.util.Random; - import electroblob.wizardry.tileentity.TileEntityTimer; -import net.minecraft.block.BlockContainer; +import net.minecraft.block.Block; +import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; @@ -17,15 +16,16 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on block break. -public class BlockVanishingCobweb extends BlockContainer { +import java.util.Random; + +public class BlockVanishingCobweb extends Block implements ITileEntityProvider { public BlockVanishingCobweb(Material material){ super(material); } - @Override @SideOnly(Side.CLIENT) + @Override public BlockRenderLayer getRenderLayer(){ return BlockRenderLayer.CUTOUT; } @@ -50,6 +50,11 @@ public class BlockVanishingCobweb extends BlockContainer { return false; } + @Override + public boolean hasTileEntity(IBlockState state){ + return true; + } + @Override public TileEntity createNewTileEntity(World world, int metadata){ return new TileEntityTimer(400); diff --git a/src/main/java/electroblob/wizardry/client/ClientProxy.java b/src/main/java/electroblob/wizardry/client/ClientProxy.java index 4adcb7ac..ee3b2be0 100644 --- a/src/main/java/electroblob/wizardry/client/ClientProxy.java +++ b/src/main/java/electroblob/wizardry/client/ClientProxy.java @@ -1,147 +1,90 @@ package electroblob.wizardry.client; -import java.lang.ref.WeakReference; -import java.util.HashMap; - -import org.lwjgl.input.Keyboard; - import electroblob.wizardry.CommonProxy; -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.audio.MovingSoundEntity; +import electroblob.wizardry.client.audio.SoundLoop; +import electroblob.wizardry.client.audio.SoundLoopSpell; +import electroblob.wizardry.client.gui.GuiSpellDisplay; +import electroblob.wizardry.client.gui.config.NamedBooleanEntry; +import electroblob.wizardry.client.gui.config.SpellHUDSkinChooserEntry; +import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook; import electroblob.wizardry.client.model.ModelWizardArmour; -import electroblob.wizardry.client.particle.ParticleBlizzard; -import electroblob.wizardry.client.particle.ParticleDarkMagic; -import electroblob.wizardry.client.particle.ParticleDust; -import electroblob.wizardry.client.particle.ParticleGiantBubble; -import electroblob.wizardry.client.particle.ParticleIce; -import electroblob.wizardry.client.particle.ParticleLeaf; -import electroblob.wizardry.client.particle.ParticleMagicFlame; -import electroblob.wizardry.client.particle.ParticlePath; -import electroblob.wizardry.client.particle.ParticleRotatingSparkle; -import electroblob.wizardry.client.particle.ParticleSnow; -import electroblob.wizardry.client.particle.ParticleSpark; -import electroblob.wizardry.client.particle.ParticleSparkle; -import electroblob.wizardry.client.particle.ParticleTornado; -import electroblob.wizardry.client.renderer.LayerStone; -import electroblob.wizardry.client.renderer.RenderArc; -import electroblob.wizardry.client.renderer.RenderArcaneWorkbench; -import electroblob.wizardry.client.renderer.RenderBlackHole; -import electroblob.wizardry.client.renderer.RenderBlank; -import electroblob.wizardry.client.renderer.RenderBubble; -import electroblob.wizardry.client.renderer.RenderDecay; -import electroblob.wizardry.client.renderer.RenderDecoy; -import electroblob.wizardry.client.renderer.RenderEvilWizard; -import electroblob.wizardry.client.renderer.RenderFireRing; -import electroblob.wizardry.client.renderer.RenderForceArrow; -import electroblob.wizardry.client.renderer.RenderHammer; -import electroblob.wizardry.client.renderer.RenderIceGiant; -import electroblob.wizardry.client.renderer.RenderIceSpike; -import electroblob.wizardry.client.renderer.RenderLightningDisc; -import electroblob.wizardry.client.renderer.RenderLightningPulse; -import electroblob.wizardry.client.renderer.RenderMagicArrow; -import electroblob.wizardry.client.renderer.RenderMagicLight; -import electroblob.wizardry.client.renderer.RenderPhoenix; -import electroblob.wizardry.client.renderer.RenderProjectile; -import electroblob.wizardry.client.renderer.RenderSigil; -import electroblob.wizardry.client.renderer.RenderSpiritHorse; -import electroblob.wizardry.client.renderer.RenderSpiritWolf; -import electroblob.wizardry.client.renderer.RenderStatue; -import electroblob.wizardry.client.renderer.RenderWizard; -import electroblob.wizardry.entity.EntityArc; +import electroblob.wizardry.client.particle.*; +import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory; +import electroblob.wizardry.client.renderer.*; +import electroblob.wizardry.command.SpellEmitter; +import electroblob.wizardry.data.DispenserCastingData; +import electroblob.wizardry.data.SpellEmitterData; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.EntityShield; -import electroblob.wizardry.entity.construct.EntityArrowRain; -import electroblob.wizardry.entity.construct.EntityBlackHole; -import electroblob.wizardry.entity.construct.EntityBlizzard; -import electroblob.wizardry.entity.construct.EntityBubble; -import electroblob.wizardry.entity.construct.EntityDecay; -import electroblob.wizardry.entity.construct.EntityEarthquake; -import electroblob.wizardry.entity.construct.EntityFireRing; -import electroblob.wizardry.entity.construct.EntityFireSigil; -import electroblob.wizardry.entity.construct.EntityForcefield; -import electroblob.wizardry.entity.construct.EntityFrostSigil; -import electroblob.wizardry.entity.construct.EntityHailstorm; -import electroblob.wizardry.entity.construct.EntityHammer; -import electroblob.wizardry.entity.construct.EntityHealAura; -import electroblob.wizardry.entity.construct.EntityIceSpike; -import electroblob.wizardry.entity.construct.EntityLightningPulse; -import electroblob.wizardry.entity.construct.EntityLightningSigil; -import electroblob.wizardry.entity.construct.EntityTornado; -import electroblob.wizardry.entity.living.EntityDecoy; -import electroblob.wizardry.entity.living.EntityEvilWizard; -import electroblob.wizardry.entity.living.EntityIceGiant; -import electroblob.wizardry.entity.living.EntityIceWraith; -import electroblob.wizardry.entity.living.EntityLightningWraith; -import electroblob.wizardry.entity.living.EntityPhoenix; -import electroblob.wizardry.entity.living.EntityShadowWraith; -import electroblob.wizardry.entity.living.EntitySpiritHorse; -import electroblob.wizardry.entity.living.EntitySpiritWolf; -import electroblob.wizardry.entity.living.EntityStormElemental; -import electroblob.wizardry.entity.living.EntityWizard; -import electroblob.wizardry.entity.living.ISpellCaster; -import electroblob.wizardry.entity.living.ISummonedCreature; -import electroblob.wizardry.entity.projectile.EntityDarknessOrb; -import electroblob.wizardry.entity.projectile.EntityDart; -import electroblob.wizardry.entity.projectile.EntityFirebolt; -import electroblob.wizardry.entity.projectile.EntityFirebomb; -import electroblob.wizardry.entity.projectile.EntityForceArrow; -import electroblob.wizardry.entity.projectile.EntityForceOrb; -import electroblob.wizardry.entity.projectile.EntityIceCharge; -import electroblob.wizardry.entity.projectile.EntityIceLance; -import electroblob.wizardry.entity.projectile.EntityIceShard; -import electroblob.wizardry.entity.projectile.EntityLightningArrow; -import electroblob.wizardry.entity.projectile.EntityLightningDisc; -import electroblob.wizardry.entity.projectile.EntityMagicMissile; -import electroblob.wizardry.entity.projectile.EntityPoisonBomb; -import electroblob.wizardry.entity.projectile.EntitySmokeBomb; -import electroblob.wizardry.entity.projectile.EntitySpark; -import electroblob.wizardry.entity.projectile.EntitySparkBomb; -import electroblob.wizardry.entity.projectile.EntityThunderbolt; +import electroblob.wizardry.entity.construct.*; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.entity.projectile.*; import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent.Source; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; import electroblob.wizardry.item.ItemScroll; import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.packet.PacketCastContinuousSpell; -import electroblob.wizardry.packet.PacketCastSpell; -import electroblob.wizardry.packet.PacketNPCCastSpell; -import electroblob.wizardry.packet.PacketPlayerSync.Message; -import electroblob.wizardry.packet.PacketTransportation; +import electroblob.wizardry.packet.*; +import electroblob.wizardry.potion.PotionSlowTime; import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.spell.Clairvoyance; -import electroblob.wizardry.spell.None; -import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.*; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import electroblob.wizardry.tileentity.TileEntityMagicLight; +import electroblob.wizardry.tileentity.TileEntityShrineCore; import electroblob.wizardry.tileentity.TileEntityStatue; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryParticleType; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiMerchant; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.renderer.entity.RenderBlaze; +import net.minecraft.client.renderer.entity.RenderHusk; +import net.minecraft.client.renderer.entity.RenderSkeleton; import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.IReloadableResourceManager; +import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; +import net.minecraft.inventory.ContainerMerchant; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.EnumParticleTypes; 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.math.MathHelper; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.village.MerchantRecipeList; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Property; import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry; import net.minecraftforge.fml.client.registry.ClientRegistry; import net.minecraftforge.fml.client.registry.RenderingRegistry; +import org.lwjgl.input.Keyboard; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * The client proxy for wizardry. @@ -154,6 +97,9 @@ public class ClientProxy extends CommonProxy { /** Static instance of the mixed font renderer */ public static MixedFontRenderer mixedFontRenderer; + /** Static particle factory map */ + private static final Map factories = new HashMap<>(); + // Key Bindings public static final KeyBinding NEXT_SPELL = new KeyBinding("key." + Wizardry.MODID + ".next_spell", Keyboard.KEY_N, "key.categories." + Wizardry.MODID); public static final KeyBinding PREVIOUS_SPELL = new KeyBinding("key." + Wizardry.MODID + ".previous_spell", Keyboard.KEY_B, "key.categories." + Wizardry.MODID); @@ -161,6 +107,9 @@ public class ClientProxy extends CommonProxy { // Armour Model public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f); + /** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style)}). */ + private static final int TOOLTIP_WRAP_WIDTH = 140; + // SECTION Registry // =============================================================================================================== @@ -175,16 +124,28 @@ public class ClientProxy extends CommonProxy { ClientRegistry.registerKeyBinding(PREVIOUS_SPELL); } - @Override - public void registerSpellHUD(){ - MinecraftForge.EVENT_BUS.register(new GuiSpellDisplay(Minecraft.getMinecraft())); - } - @Override public void initGuiBits(){ mixedFontRenderer = new MixedFontRenderer(Minecraft.getMinecraft().gameSettings, new ResourceLocation("textures/font/ascii.png"), Minecraft.getMinecraft().renderEngine, false); - GuiWizardHandbook.initDisplayRecipes(); + } + + @Override + public void registerResourceReloadListeners(){ + IResourceManager manager = Minecraft.getMinecraft().getResourceManager(); + if(manager instanceof IReloadableResourceManager){ + ((IReloadableResourceManager)manager).registerReloadListener(GuiSpellDisplay::loadSkins); + ((IReloadableResourceManager)manager).registerReloadListener(GuiWizardHandbook::loadHandbookFile); + } + } + +// @Override +// public void registerSoundEventListener(){ +// Minecraft.getMinecraft().getSoundHandler().addListener(ContinuousSpellSoundEntity::soundPlayed); +// } + + public void registerAtlasMarkers(){ + WizardryAntiqueAtlasIntegration.registerMarkers(); } // SECTION Misc @@ -194,6 +155,16 @@ public class ClientProxy extends CommonProxy { public void setToNumberSliderEntry(Property property){ property.setConfigEntryClass(NumberSliderEntry.class); } + + @Override + public void setToHUDChooserEntry(Property property){ + property.setConfigEntryClass(SpellHUDSkinChooserEntry.class); + } + + @Override + public void setToNamedBooleanEntry(Property property){ + property.setConfigEntryClass(NamedBooleanEntry.class); + } @Override public World getTheWorld(){ @@ -201,13 +172,66 @@ public class ClientProxy extends CommonProxy { } @Override - public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){ - Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity(entity, sound, volume, pitch, repeat)); + public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){ + Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity<>(entity, sound, category, volume, pitch, repeat)); + } + + @Override + public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){ + SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellEntity(start, loop, end, spell, entity, volume, pitch)); + } + + @Override + 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){ + if(duration == -1){ + SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellDispenser(start, loop, end, spell, world, x, y, z, volume, pitch)); + }else{ + SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellPosTimed(start, loop, end, spell, duration, x, y, z, volume, pitch)); + } + } + + @Override + public Set getSpellHUDSkins(){ + return GuiSpellDisplay.getSkinKeys(); } // SECTION Items // =============================================================================================================== + @Override + public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){ + + EntityPlayerSP player = Minecraft.getMinecraft().player; + + if(player == null) return false; + + // Displayed recipe + if(Minecraft.getMinecraft().currentScreen instanceof GuiMerchant){ + // It doesn't actually matter if the recipe is selected or not, since the itemstack will only ever + // match one of them anyway - and we'd have to reflect into GuiMerchant to get the selected recipe + MerchantRecipeList recipes = ((GuiMerchant)Minecraft.getMinecraft().currentScreen).getMerchant().getRecipes(player); + if(recipes != null && recipes.stream().anyMatch(r -> r.getItemToSell() == stack)){ + // Spell books are always discovered when wizards are selling them + return true; + } + } + + // Recipe output slot + // Required or players would be able to find out what the spell is without actually completing the trade + if(player.openContainer instanceof ContainerMerchant){ + + if(((ContainerMerchant)player.openContainer).getMerchantInventory().getStackInSlot(2) == stack){ + return true; + } + } + + if(!Wizardry.settings.discoveryMode) return true; + if(player.isCreative()) return true; + if(WizardData.get(player) != null && WizardData.get(player).hasSpellBeenDiscovered(spell)) return true; + + return false; + } + @Override public FontRenderer getFontRenderer(ItemStack stack){ @@ -216,12 +240,10 @@ public class ClientProxy extends CommonProxy { if(stack.getItem() instanceof ItemWand){ spell = WandHelper.getCurrentSpell(stack); }else if(stack.getItem() instanceof ItemSpellBook || stack.getItem() instanceof ItemScroll){ - spell = Spell.get(stack.getItemDamage()); + spell = Spell.byMetadata(stack.getItemDamage()); } - if(Minecraft.getMinecraft().player != null && Wizardry.settings.discoveryMode && WizardData.get(Minecraft.getMinecraft().player) != null - && !Minecraft.getMinecraft().player.capabilities.isCreativeMode - && !WizardData.get(Minecraft.getMinecraft().player).hasSpellBeenDiscovered(spell)){ + if(!shouldDisplayDiscovered(spell, stack)){ return mixedFontRenderer; } @@ -231,16 +253,14 @@ public class ClientProxy extends CommonProxy { @Override public String getScrollDisplayName(ItemStack scroll){ - // Displays [Empty slot] if spell is continuous. - Spell spell = Spell.get(scroll.getItemDamage()); - if(spell.isContinuous) spell = Spells.none; + Spell spell = Spell.byMetadata(scroll.getItemDamage()); EntityPlayer player = Minecraft.getMinecraft().player; boolean discovered = true; // It seems that this method is called when the world is loading, before thePlayer has been initialised. // If the player is null, the spell is assumed to be discovered. - if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null + if(player != null && Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ discovered = false; } @@ -261,71 +281,59 @@ public class ClientProxy extends CommonProxy { return super.getConjuredBowDurability(stack); } + @Override + public void addMultiLineDescription(List tooltip, String key, Style style){ + String description = style.getFormattingCode() + I18n.format(key); + tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH)); + } + // SECTION Particles // =============================================================================================================== + /** Use {@link ParticleWizardry#registerParticle(ResourceLocation, IWizardryParticleFactory)}, this is internal. */ + // I mean, it does exactly the same thing but I might want to make it do something else in future... + public static void addParticleFactory(ResourceLocation name, IWizardryParticleFactory factory){ + factories.put(name, factory); + } + @Override - public void spawnParticle(WizardryParticleType type, World world, double x, double y, double z, double velX, double velY, double velZ, int maxAge, - float r, float g, float b, boolean doGravity, double radius){ - - // Colour values are now automatically clamped to between 0 and 1, as values outside this range seem to - // cause strange effects in 1.10 (or more specifically, particles that are bright pink!) - // TODO: This is a terrible dirty fix, but it'll do for now. Find a nicer way in future. - if(type != WizardryParticleType.MAGIC_FIRE) r = MathHelper.clamp(r, 0, 1); - g = MathHelper.clamp(g, 0, 1); - b = MathHelper.clamp(b, 0, 1); - - switch(type){ - - case BLIZZARD: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleBlizzard(world, maxAge, x, z, radius, y)); - break; - case BRIGHT_DUST: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDust(world, x, y, z, velX, velY, velZ, r, g, b, false)); - break; - case DARK_MAGIC: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDarkMagic(world, x, y, z, velX, velY, velZ, r, g, b)); - break; - case DUST: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleDust(world, x, y, z, velX, velY, velZ, r, g, b, true)); - break; - case ICE: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleIce(world, x, y, z, velX, velY, velZ, maxAge)); - break; - case LEAF: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleLeaf(world, x, y, z, velX, velY, velZ, maxAge)); - break; - case MAGIC_BUBBLE: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleGiantBubble(world, x, y, z, velX, velY, velZ)); - break; - case MAGIC_FIRE: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleMagicFlame(world, x, y, z, velX, velY, velZ, maxAge, r == 0 ? 1 + world.rand.nextFloat() : r)); - break; - case PATH: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticlePath(world, x, y, z, velX, velY, velZ, r, g, b, maxAge)); - break; - case SNOW: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSnow(world, x, y, z, velX, velY, velZ)); - break; - case SPARK: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSpark(world, x, y, z, velX, velY, velZ)); - break; - case SPARKLE: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleSparkle(world, x, y, z, velX, velY, velZ, r, g, b, maxAge, doGravity)); - break; - case SPARKLE_ROTATING: - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleRotatingSparkle(world, maxAge, x, z, radius, y, r, g, b)); - break; - default: - break; + public void registerParticles(){ + // I'll be a good programmer and use the API method rather than the one above. Lead by example, as they say... + ParticleWizardry.registerParticle(Type.BEAM, ParticleBeam::new); + ParticleWizardry.registerParticle(Type.BUFF, ParticleBuff::new); + ParticleWizardry.registerParticle(Type.DARK_MAGIC, ParticleDarkMagic::new); + ParticleWizardry.registerParticle(Type.DUST, ParticleDust::new); + ParticleWizardry.registerParticle(Type.FLASH, ParticleFlash::new); + ParticleWizardry.registerParticle(Type.ICE, ParticleIce::new); + ParticleWizardry.registerParticle(Type.LEAF, ParticleLeaf::new); + ParticleWizardry.registerParticle(Type.LIGHTNING, ParticleLightning::new); + ParticleWizardry.registerParticle(Type.LIGHTNING_PULSE, ParticleLightningPulse::new); + ParticleWizardry.registerParticle(Type.MAGIC_BUBBLE, ParticleMagicBubble::new); + ParticleWizardry.registerParticle(Type.MAGIC_FIRE, ParticleMagicFlame::new); + ParticleWizardry.registerParticle(Type.PATH, ParticlePath::new); + ParticleWizardry.registerParticle(Type.SCORCH, ParticleScorch::new); + ParticleWizardry.registerParticle(Type.SNOW, ParticleSnow::new); + ParticleWizardry.registerParticle(Type.SPARK, ParticleSpark::new); + ParticleWizardry.registerParticle(Type.SPARKLE, ParticleSparkle::new); + ParticleWizardry.registerParticle(Type.SPHERE, ParticleSphere::new); + ParticleWizardry.registerParticle(Type.SUMMON, ParticleSummon::new); + ParticleWizardry.registerParticle(Type.VINE, ParticleVine::new); + } + + @Override + public ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){ + IWizardryParticleFactory factory = factories.get(type); + if(factory == null){ + Wizardry.logger.warn("Unrecognised particle type {} ! Ensure the particle is properly registered.", type); + return null; } + return factory.createParticle(world, x, y, z); } @Override public void spawnTornadoParticle(World world, double x, double y, double z, double velX, double velZ, double radius, int maxAge, IBlockState block, BlockPos pos){ - Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleTornado(world, maxAge, x, z, radius, y, velX, velZ, block).setBlockPos(pos));// , - // world.rand.nextInt(6))); + Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleTornado(world, maxAge, x, z, radius, y, velX, velZ, block).setBlockPos(pos));// , world.rand.nextInt(6))); } // SECTION Packet Handlers @@ -336,14 +344,12 @@ public class ClientProxy extends CommonProxy { World world = Minecraft.getMinecraft().world; Entity caster = world.getEntityByID(message.casterID); - Spell spell = Spell.get(message.spellID); + Spell spell = Spell.byNetworkID(message.spellID); // Should always be true if(caster instanceof EntityPlayer){ ((EntityPlayer)caster).setActiveHand(message.hand); - // Duration isn't needed because it only ever affects things server-side, and anything that is - // seen client-side gets synced elsewhere. spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers); Source source = Source.OTHER; @@ -358,19 +364,34 @@ public class ClientProxy extends CommonProxy { // No need to check if the spell succeeded, because the packet is only ever sent when it succeeds. // The handler for this event now deals with discovery. - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityPlayer)caster, spell, message.modifiers, source)); + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(source, spell, (EntityPlayer)caster, message.modifiers)); }else{ Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player"); } } + @Override + public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){ + + World world = Minecraft.getMinecraft().world; + Spell spell = Spell.byNetworkID(message.spellID); + + spell.cast(world, message.position.x, message.position.y, message.position.z, message.direction, 0, message.duration, message.modifiers); + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.modifiers)); + + if(spell.isContinuous){ + SpellEmitter.add(spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.duration, message.modifiers); + } + } + @Override public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){ World world = Minecraft.getMinecraft().world; Entity caster = world.getEntityByID(message.casterID); - Spell spell = Spell.get(message.spellID); + Spell spell = Spell.byNetworkID(message.spellID); // Should always be true if(caster instanceof EntityPlayer){ @@ -380,7 +401,7 @@ public class ClientProxy extends CommonProxy { if(data.isCasting()){ WizardData.get((EntityPlayer)caster).stopCastingContinuousSpell(); }else{ - WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers); + WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers, message.duration); } } }else{ @@ -394,7 +415,7 @@ public class ClientProxy extends CommonProxy { World world = Minecraft.getMinecraft().world; Entity caster = world.getEntityByID(message.casterID); Entity target = message.targetID == -1 ? null : world.getEntityByID(message.targetID); - Spell spell = Spell.get(message.spellID); + Spell spell = Spell.byNetworkID(message.spellID); // Should always be true if(caster instanceof EntityLiving){ @@ -403,7 +424,7 @@ public class ClientProxy extends CommonProxy { spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers); // Again, no need to check if the spell succeeded, because the packet is only ever sent when it // succeeds. - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityLiving)caster, spell, message.modifiers, Source.NPC)); + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, (EntityLiving)caster, message.modifiers)); } if(caster instanceof ISpellCaster){ @@ -412,10 +433,41 @@ public class ClientProxy extends CommonProxy { ((EntityLiving)caster).setAttackTarget((EntityLivingBase)target); } } - }else{ + }else if(caster != null){ Wizardry.logger.warn("Recieved a PacketNPCCastSpell, but the caster ID was not the ID of an EntityLiving"); } } + + @Override + public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){ + + World world = Minecraft.getMinecraft().world; + + if(world.getTileEntity(message.pos) instanceof TileEntityDispenser){ // Should always be true + + Spell spell = Spell.byNetworkID(message.spellID); + + spell.cast(world, message.x, message.y, message.z, message.direction, 0, -1, message.modifiers); + // No need to check if the spell succeeded, because the packet is only ever sent when it succeeds. + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.DISPENSER, spell, world, message.x, message.y, + message.z, message.direction, message.modifiers)); + + if(spell.isContinuous || spell instanceof None){ + + DispenserCastingData data = DispenserCastingData.get((TileEntityDispenser)world.getTileEntity(message.pos)); + + if(spell.isContinuous){ + data.startCasting(spell, message.x, message.y, message.z, message.duration, message.modifiers); + }else{ + data.stopCasting(); + } + } + + }else{ + Wizardry.logger.warn("Recieved a PacketDispenserCastSpell, but no tileEntity was found at the supplied location."); + } + + } @Override public void handleTransportationPacket(PacketTransportation.Message message){ @@ -423,78 +475,150 @@ public class ClientProxy extends CommonProxy { World world = Minecraft.getMinecraft().world; Entity caster = world.getEntityByID(message.casterID); // Moved from when the packet is sent to when it is received; fixes the sound not playing in first person. - caster.playSound(SoundEvents.BLOCK_PORTAL_TRAVEL, 1, 1); + caster.playSound(WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, 1, 1); for(int i = 0; i < 20; i++){ double radius = 1; - double angle = world.rand.nextDouble() * Math.PI * 2; - double x = caster.posX + radius * Math.cos(angle); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + double x = caster.posX + radius * MathHelper.cos(angle); double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; - double z = caster.posZ + radius * Math.sin(angle); - Minecraft.getMinecraft().effectRenderer - .addEffect(new ParticleSparkle(world, x, y, z, 0, 0.02, 0, 0.6f, 1.0f, 0.6f, 80 + world.rand.nextInt(10))); + double z = caster.posZ + radius * MathHelper.sin(angle); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.02, 0).clr(0.6f, 1, 0.6f) + .time(80 + world.rand.nextInt(10)).spawn(world); } for(int i = 0; i < 20; i++){ double radius = 1; - double angle = world.rand.nextDouble() * Math.PI * 2; - double x = caster.posX + radius * Math.cos(angle); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + double x = caster.posX + radius * MathHelper.cos(angle); double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; - double z = caster.posZ + radius * Math.sin(angle); + double z = caster.posZ + radius * MathHelper.sin(angle); world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, x, y, z, 0, 0.02, 0); } for(int i = 0; i < 20; i++){ double radius = 1; - double angle = world.rand.nextDouble() * Math.PI * 2; - double x = caster.posX + radius * Math.cos(angle); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + double x = caster.posX + radius * MathHelper.cos(angle); double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; - double z = caster.posZ + radius * Math.sin(angle); + double z = caster.posZ + radius * MathHelper.sin(angle); world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0); } } @Override - public void handlePlayerSyncPacket(Message message){ + public void handlePlayerSyncPacket(PacketPlayerSync.Message message){ - WizardData properties = WizardData.get(Minecraft.getMinecraft().player); + WizardData data = WizardData.get(Minecraft.getMinecraft().player); - if(properties != null){ + if(data != null){ - properties.spellsDiscovered = message.spellsDiscovered; + data.synchronisedRandom.setSeed(message.seed); + data.spellsDiscovered = message.spellsDiscovered; + + message.spellData.forEach(data::setVariable); if(message.selectedMinionID == -1){ - properties.selectedMinion = null; + data.selectedMinion = null; }else{ Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.selectedMinionID); if(entity instanceof ISummonedCreature){ - properties.selectedMinion = new WeakReference((ISummonedCreature)entity); + data.selectedMinion = new WeakReference<>((ISummonedCreature)entity); }else{ - properties.selectedMinion = null; + data.selectedMinion = null; } } } } @Override - public void handleGlyphDataPacket(electroblob.wizardry.packet.PacketGlyphData.Message message){ + public void handleGlyphDataPacket(PacketGlyphData.Message message){ SpellGlyphData data = SpellGlyphData.get(Minecraft.getMinecraft().world); - data.randomNames = new HashMap(); - data.randomDescriptions = new HashMap(); + data.randomNames = new HashMap<>(); + data.randomDescriptions = new HashMap<>(); for(Spell spell : Spell.getSpells(Spell.allSpells)){ // -1 because the none spell isn't included - data.randomNames.put(spell, message.names.get(spell.id() - 1)); - data.randomDescriptions.put(spell, message.descriptions.get(spell.id() - 1)); + // This is a case where we must use the network ID, not the metadata + data.randomNames.put(spell, message.names.get(spell.networkID() - 1)); + data.randomDescriptions.put(spell, message.descriptions.get(spell.networkID() - 1)); } } @Override - public void handleClairvoyancePacket(electroblob.wizardry.packet.PacketClairvoyance.Message message){ + public void handleEmitterDataPacket(PacketEmitterData.Message message){ + message.emitters.forEach(e -> e.setWorld(Minecraft.getMinecraft().world)); // Do this as soon as possible! + SpellEmitterData data = SpellEmitterData.get(Minecraft.getMinecraft().world); + // We shouldn't need to clear the emitters because when a player logs in or changes dimension the client world + // is wiped anyway, so the call to get() above should result in a fresh SpellEmitterData instance + message.emitters.forEach(data::add); + } + + @Override + public void handleClairvoyancePacket(PacketClairvoyance.Message message){ Clairvoyance.spawnPathPaticles(Minecraft.getMinecraft().world, message.path, message.durationMultiplier); } + @Override + public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){ + GuiWizardHandbook.updateUnlockStatus(message.showToasts, message.completedAdvancements); + } + + @Override + public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){ + Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.hostID); + if(entity instanceof EntityLivingBase) PotionSlowTime.unblockNearbyEntities((EntityLivingBase)entity); + else Wizardry.logger.warn("Received a PacketEndSlowTime, but the entity ID did not match any living entity"); + } + + @Override + public void handleResurrectionPacket(PacketResurrection.Message message){ + Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID); + if(entity instanceof EntityPlayer){ + ((Resurrection)Spells.resurrection).resurrect((EntityPlayer)entity); + if(entity == Minecraft.getMinecraft().player){ + Minecraft.getMinecraft().world.spawnEntity(entity); + Minecraft.getMinecraft().displayGuiScreen(null); + } + } + else Wizardry.logger.warn("Received a PacketResurrection, but the entity ID did not match any player"); + } + + @Override + public void handlePossessionPacket(PacketPossession.Message message){ + + Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID); + + if(entity instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)entity; + + if(message.targetID == -1){ + ((Possession)Spells.possession).endPossession(player); + }else{ + Entity target = Minecraft.getMinecraft().world.getEntityByID(message.targetID); + if(target instanceof EntityLiving){ + ((Possession)Spells.possession).possess(player, (EntityLiving)target, message.duration); + player.sendStatusMessage(new TextComponentTranslation("spell." + Spells.possession.getRegistryName() + + ".success", Minecraft.getMinecraft().gameSettings.keyBindSneak.getDisplayName()), true); + } + else Wizardry.logger.warn("Received a PacketPossession, but the target ID did not match any living entity"); + } + } + else Wizardry.logger.warn("Received a PacketPossession, but the player ID did not match any player"); + } + + public void handleConquerShrinePacket(PacketConquerShrine.Message message){ + + TileEntity tileEntity = Minecraft.getMinecraft().world.getTileEntity(new BlockPos(message.x, message.y, message.z)); + + if(tileEntity instanceof TileEntityShrineCore){ + ((TileEntityShrineCore)tileEntity).conquer(); + + }else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent"); + } + // SECTION Rendering // =============================================================================================================== @@ -507,6 +631,7 @@ public class ClientProxy extends CommonProxy { @Override public void initialiseLayers(){ LayerStone.initialiseLayers(); + LayerFrost.initialiseLayers(); } @Override @@ -516,6 +641,12 @@ public class ClientProxy extends CommonProxy { // Yet another advantage to the new system: turns out you don't even need to register the renderer if you // just want the vanilla one for the mob you're extending. + // Luckily for us, the vanilla husk renderer is only parametrised to EntityZombie + RenderingRegistry.registerEntityRenderingHandler(EntityHuskMinion.class, RenderHusk::new); + // This now extends AbstractSkeleton so we need to bind the renderer ourselves + RenderingRegistry.registerEntityRenderingHandler(EntitySkeletonMinion.class, RenderSkeleton::new); + RenderingRegistry.registerEntityRenderingHandler(EntityStrayMinion.class, RenderStrayMinion::new); + // An anonymous class in a lambda expression! No point writing a separate class really, is there? RenderingRegistry.registerEntityRenderingHandler(EntityLightningWraith.class, manager -> new RenderBlaze(manager){ @Override @@ -549,15 +680,15 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityForceArrow.class, RenderForceArrow::new); // Creatures - RenderingRegistry.registerEntityRenderingHandler(EntitySpiritWolf.class, manager -> new RenderSpiritWolf(manager)); - RenderingRegistry.registerEntityRenderingHandler(EntitySpiritHorse.class, manager -> new RenderSpiritHorse(manager)); + RenderingRegistry.registerEntityRenderingHandler(EntitySpiritWolf.class, RenderSpiritWolf::new); + RenderingRegistry.registerEntityRenderingHandler(EntitySpiritHorse.class, RenderSpiritHorse::new); RenderingRegistry.registerEntityRenderingHandler(EntityWizard.class, RenderWizard::new); RenderingRegistry.registerEntityRenderingHandler(EntityEvilWizard.class, RenderEvilWizard::new); RenderingRegistry.registerEntityRenderingHandler(EntityDecoy.class, RenderDecoy::new); // Throwables RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class, - manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/entity/spark_bomb.png"), false)); + manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/spark_bomb.png"), false)); RenderingRegistry.registerEntityRenderingHandler(EntityFirebomb.class, manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/firebomb.png"), false)); RenderingRegistry.registerEntityRenderingHandler(EntityPoisonBomb.class, @@ -576,21 +707,29 @@ public class ClientProxy extends CommonProxy { manager -> new RenderLightningDisc(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f)); RenderingRegistry.registerEntityRenderingHandler(EntitySmokeBomb.class, manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/smoke_bomb.png"), false)); + RenderingRegistry.registerEntityRenderingHandler(EntityEmber.class, + manager -> new RenderProjectile(manager, 0.15f, new ResourceLocation(Wizardry.MODID, "textures/entity/ember.png"), false)); + RenderingRegistry.registerEntityRenderingHandler(EntityMagicFireball.class, + manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false)); + RenderingRegistry.registerEntityRenderingHandler(EntityLargeMagicFireball.class, + manager -> new RenderProjectile(manager, 1.5f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false)); + RenderingRegistry.registerEntityRenderingHandler(EntityIceball.class, + manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/iceball.png"), false)); // Effects and constructs - RenderingRegistry.registerEntityRenderingHandler(EntityArc.class, RenderArc::new); RenderingRegistry.registerEntityRenderingHandler(EntityBlackHole.class, RenderBlackHole::new); RenderingRegistry.registerEntityRenderingHandler(EntityShield.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityBubble.class, RenderBubble::new); RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new); RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new); + RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new); + //RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new); // Stuff that doesn't render RenderingRegistry.registerEntityRenderingHandler(EntityBlizzard.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityTornado.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityArrowRain.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityShadowWraith.class, RenderBlank::new); - RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityThunderbolt.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new); @@ -608,7 +747,8 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityFireRing.class, manager -> new RenderFireRing(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/ring_of_fire.png"), 5.0f)); RenderingRegistry.registerEntityRenderingHandler(EntityDecay.class, RenderDecay::new); - RenderingRegistry.registerEntityRenderingHandler(EntityLightningPulse.class, manager -> new RenderLightningPulse(manager, 8.0f)); + RenderingRegistry.registerEntityRenderingHandler(EntityCombustionRune.class, + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/combustion_rune.png"), 2.0f, true)); // TESRs ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench()); diff --git a/src/main/java/electroblob/wizardry/client/DrawingUtils.java b/src/main/java/electroblob/wizardry/client/DrawingUtils.java new file mode 100644 index 00000000..20b99a60 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/DrawingUtils.java @@ -0,0 +1,247 @@ +package electroblob.wizardry.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +/** + * Utility class containing some useful static methods for drawing GUIs. Previously these were spread across the main + * {@code WizardryUtilities} class and various individual GUI classes. + * + * @author Electroblob + * @since Wizardry 4.2 + * @see MixedFontRenderer + */ +//@SideOnly(Side.CLIENT) +public final class DrawingUtils { + + /** + * The integer colour for black passed into the font renderer methods. This used to be 0 but that's now white for + * some reason, so I've made a it a constant in case it changes again. + */ + // I think this is actually ever-so-slightly lighter than pure black, but the difference is unnoticeable. + public static final int BLACK = 1; + + /** + * Shorthand for {@link DrawingUtils#drawTexturedRect(int, int, int, int, int, int, int, int)} which draws the + * entire texture (u and v are set to 0 and textureWidth and textureHeight are the same as width and height). + */ + public static void drawTexturedRect(int x, int y, int width, int height){ + drawTexturedRect(x, y, 0, 0, width, height, width, height); + } + + /** + * Draws a textured rectangle, taking the size of the image and the bit needed into + * account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int) + * Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also handy + * for custom potion icons. + * + * @param x The x position of the rectangle + * @param y The y position of the rectangle + * @param u The x position of the top left corner of the section of the image wanted + * @param v The y position of the top left corner of the section of the image wanted + * @param width The width of the section + * @param height The height of the section + * @param textureWidth The width of the actual image. + * @param textureHeight The height of the actual image. + */ + public static void drawTexturedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight){ + DrawingUtils.drawTexturedFlippedRect(x, y, u, v, width, height, textureWidth, textureHeight, false, false); + } + + /** + * Draws a textured rectangle, taking the size of the image and the bit needed into + * account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int) + * Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also handy + * for custom potion icons. This version allows the texture to additionally be flipped in x and/or y. + * + * @param x The x position of the rectangle + * @param y The y position of the rectangle + * @param u The x position of the top left corner of the section of the image wanted + * @param v The y position of the top left corner of the section of the image wanted + * @param width The width of the section + * @param height The height of the section + * @param textureWidth The width of the actual image. + * @param textureHeight The height of the actual image. + * @param flipX Whether to flip the texture in the x direction. + * @param flipY Whether to flip the texture in the y direction. + */ + public static void drawTexturedFlippedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, boolean flipX, boolean flipY){ + + float f = 1F / (float)textureWidth; + float f1 = 1F / (float)textureHeight; + + int u1 = flipX ? u + width : u; + int u2 = flipX ? u : u + width; + int v1 = flipY ? v + height : v; + int v2 = flipY ? v : v + height; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(org.lwjgl.opengl.GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX); + + buffer.pos((double)(x), (double)(y + height), 0).tex((double)((float)(u1) * f), (double)((float)(v2) * f1)).endVertex(); + buffer.pos((double)(x + width), (double)(y + height), 0).tex((double)((float)(u2) * f), (double)((float)(v2) * f1)).endVertex(); + buffer.pos((double)(x + width), (double)(y), 0).tex((double)((float)(u2) * f), (double)((float)(v1) * f1)).endVertex(); + buffer.pos((double)(x), (double)(y), 0).tex((double)((float)(u1) * f), (double)((float)(v1) * f1)).endVertex(); + + tessellator.draw(); + } + + /** + * Draws a textured rectangle, stretching the section of the image to fit the size given. + * + * @param x The x position of the rectangle + * @param y The y position of the rectangle + * @param u The x position of the top left corner of the section of the image wanted, expressed as a fraction of the + * image width + * @param v The y position of the top left corner of the section of the image wanted, expressed as a fraction of the + * image width + * @param finalWidth The width as rendered + * @param finalHeight The height as rendered + * @param width The width of the section, expressed as a fraction of the image width + * @param height The height of the section, expressed as a fraction of the image width + */ + public static void drawTexturedStretchedRect(int x, int y, int u, int v, int finalWidth, int finalHeight, int width, + int height){ + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos((x), y + finalHeight, 0).tex(u, v + height).endVertex(); + buffer.pos(x + finalWidth, y + finalHeight, 0).tex(u + width, v + height).endVertex(); + buffer.pos(x + finalWidth, (y), 0).tex(u + width, v).endVertex(); + buffer.pos((x), (y), 0).tex(u, v).endVertex(); + + tessellator.draw(); + } + + /** + * Mixes the two given opaque colours in the proportion specified. + * @param colour1 The first colour to mix, as a 6-digit hexadecimal. + * @param colour2 The second colour to mix, as a 6-digit hexadecimal. + * @param proportion The proportion of the second colour; will be clamped to between 0 and 1. + * @return The resulting colour, as a 6-digit hexadecimal. + */ + public static int mix(int colour1, int colour2, float proportion){ + + proportion = MathHelper.clamp(proportion, 0, 1); + + int r1 = colour1 >> 16 & 255; + int g1 = colour1 >> 8 & 255; + int b1 = colour1 & 255; + int r2 = colour2 >> 16 & 255; + int g2 = colour2 >> 8 & 255; + int b2 = colour2 & 255; + + int r = (int)(r1 + (r2-r1) * proportion); + int g = (int)(g1 + (g2-g1) * proportion); + int b = (int)(b1 + (b2-b1) * proportion); + + return (r << 16) + (g << 8) + b; + } + + /** + * Makes the given opaque colour translucent with the given opacity. + * @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque). + * @param opacity The opacity to apply to the given colour, as a fraction between 0 and 1. + * @return The resulting integer colour code, which will be an 8-digit hexadecimal. + */ + public static int makeTranslucent(int colour, float opacity){ + return colour + ((int)(0xff * opacity * 0x01000000)); + } + + /** + * Draws the given string at the given position, scaling it if it does not fit within the given width. + * @param font A {@code FontRenderer} object. + * @param text The text to display. + * @param x The x position of the top-left corner of the text. + * @param y The y position of the top-left corner of the text. + * @param scale The scale that the text should normally be if it does not exceed the maximum width. + * @param colour The colour to render the text in, supports translucency. + * @param width The maximum width of the text. This is not scaled; you should pass in the width of the actual + * area of the screen in which the text needs to fit, regardless of the scale parameter. + * @param centre Whether to adjust the y position such that the centre of the text lines up with where its centre + * would be if it was not scaled (automatically or manually). + * @param alignR True to right-align the text, false for normal left alignment. + */ + public static void drawScaledStringToWidth(FontRenderer font, String text, float x, float y, float scale, int colour, float width, boolean centre, boolean alignR){ + + float textWidth = font.getStringWidth(text) * scale; + float textHeight = font.FONT_HEIGHT * scale; + + if(textWidth > width){ + scale *= width/textWidth; + }else if(alignR){ // Alignment makes no difference if the string fills the entire width + x += width - textWidth; + } + + if(centre) y += (font.FONT_HEIGHT - textHeight)/2; + + DrawingUtils.drawScaledTranslucentString(font, text, x, y, scale, colour); + } + + /** Draws the given string at the given position, scaling the text by the specified factor. Also enables blending to + * render text in semitransparent colours (e.g. 0x88ffffff). */ + public static void drawScaledTranslucentString(FontRenderer font, String text, float x, float y, float scale, int colour){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.scale(scale, scale, scale); + // Because we scaled the entire rendering space, the coordinates have to be scaled inversely + x /= scale; + y /= scale; + font.drawStringWithShadow(text, x, y, colour); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + + /** + * Draws an itemstack and (optionally) its tooltip, directly. Mainly intended for use outside of GUI classes, since + * most of the GL state changes done in this method (which are the main reason it exists at all) are already done + * when drawing a GUI. + * + * @param gui An instance of a GUI class. + * @param stack The itemstack to draw. + * @param x The x position of the left-hand edge of the itemstack. + * @param y The y position of the top edge of the itemstack. + * @param mouseX The x position of the mouse, used for tooltip positioning. + * @param mouseY The y position of the mouse, used for tooltip positioning. + * @param tooltip Whether to draw the tooltip. + */ + public static void drawItemAndTooltip(GuiContainer gui, ItemStack stack, int x, int y, int mouseX, int mouseY, boolean tooltip){ + + RenderItem renderItem = Minecraft.getMinecraft().getRenderItem(); + GlStateManager.pushMatrix(); + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableColorMaterial(); + GlStateManager.enableLighting(); + renderItem.zLevel = 100.0F; + + if(!stack.isEmpty()){ + renderItem.renderItemAndEffectIntoGUI(stack, x, y); + renderItem.renderItemOverlays(Minecraft.getMinecraft().fontRenderer, stack, x, y); + + if(tooltip){ + gui.drawHoveringText(gui.getItemToolTip(stack), mouseX + gui.getXSize()/2 - gui.width/2, + mouseY + gui.getYSize()/2 - gui.height/2); + } + } + + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + RenderHelper.enableStandardItemLighting(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/GuiArcaneWorkbench.java b/src/main/java/electroblob/wizardry/client/GuiArcaneWorkbench.java deleted file mode 100644 index edc27350..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiArcaneWorkbench.java +++ /dev/null @@ -1,251 +0,0 @@ -package electroblob.wizardry.client; - -import org.lwjgl.input.Keyboard; - -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.packet.PacketControlInput; -import electroblob.wizardry.packet.WizardryPacketHandler; -import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; -import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.inventory.GuiContainer; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.player.InventoryPlayer; -import net.minecraft.inventory.IInventory; -import net.minecraft.inventory.Slot; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.common.network.simpleimpl.IMessage; - -public class GuiArcaneWorkbench extends GuiContainer { - - private GuiButton applyBtn; - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, - "textures/gui/arcane_workbench.png"); - - private IInventory playerInventory; - private IInventory arcaneWorkbenchInventory; - - private final int tooltipWidth = 164; - - // We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it. - // For calculations, we use the size without the tooltip. - private final int xSizeNoTip = 176; - - public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){ - super(new ContainerArcaneWorkbench(invPlayer, entity)); - this.playerInventory = invPlayer; - this.arcaneWorkbenchInventory = entity; - xSize = xSizeNoTip; - ySize = 220; - } - - @Override - public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_){ - - this.drawDefaultBackground(); - - // Tests if there is a wand in the workbench and edits the positioning accordingly - if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots - .getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){ - xSize = xSizeNoTip + tooltipWidth; - guiLeft = (this.width - this.xSize) / 2; - this.applyBtn.x = (this.width - tooltipWidth) / 2 + 48; - }else{ - xSize = xSizeNoTip; - guiLeft = (this.width - this.xSize) / 2; - this.applyBtn.x = this.width / 2 + 48; - } - - if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack()){ - this.applyBtn.enabled = true; - }else{ - this.applyBtn.enabled = false; - } - - super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_); - - // Required now, or item mouseover tooltips won't render. - this.renderHoveredToolTip(p_73863_1_, p_73863_2_); - } - - @Override - public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY){ - - GlStateManager.color(1F, 1F, 1F, 1F); - Minecraft.getMinecraft().renderEngine.bindTexture(texture); - - // Main inventory - drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSizeNoTip, ySize); - - // Changing slots - for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){ - Slot slot = this.inventorySlots.getSlot(i); - if(slot.xPos >= 0 && slot.yPos >= 0) - this.drawTexturedModalRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36); - } - - // Tooltip only drawn if there is a wand - if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots - .getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){ - - // Tooltip box - drawTexturedModalRect(guiLeft + xSizeNoTip, guiTop, xSizeNoTip, 0, 256 - xSizeNoTip - 4, ySize); - drawTexturedModalRect(guiLeft + 252, guiTop, xSizeNoTip + 4, 0, tooltipWidth - 2 * (256 - xSizeNoTip - 4), ySize); - drawTexturedModalRect(guiLeft + xSize - (256 - xSizeNoTip - 4), guiTop, xSizeNoTip + 4, 0, - 256 - xSizeNoTip - 4, ySize); - - ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack(); - - Spell[] spells = WandHelper.getSpells(wand); - - int i = 0; - - for(Spell spell : spells){ - - boolean discovered = true; - - if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){ - discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell); - } - // As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on - // mods to add their own. - Minecraft.getMinecraft().renderEngine - .bindTexture(discovered ? spell.element.getIcon() : Element.MAGIC.getIcon()); - - // Renders the little element icon - WizardryUtilities.drawTexturedRect(guiLeft + xSizeNoTip + 5, guiTop + 34 + 10 * i++, 8, 8); - } - - int x = 0; - int y = guiTop + 50 + spells.length * 10; - - // Look how much shorter this is with the WandHelper class! - for(Item item : WandHelper.getSpecialUpgrades()){ - - int level = WandHelper.getUpgradeLevel(wand, item); - - if(level > 0){ - ItemStack stack = new ItemStack(item, level); - GlStateManager.enableDepth(); - this.itemRender.renderItemAndEffectIntoGUI(stack, guiLeft + xSizeNoTip + 6 + x, y); - this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack, guiLeft + xSizeNoTip + 6 + x, y, - null); - x += 18; - GlStateManager.disableDepth(); - } - } - } - - Minecraft.getMinecraft().renderEngine.bindTexture(texture); - - // Fixes the bug that caused the slot hightlight to render opaque. I don't know why it works, it just works! - GlStateManager.disableBlend(); - GlStateManager.enableAlpha(); - } - - @Override - protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){ - - this.fontRenderer - .drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName() - : I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752); - this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName() - : I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752); - - if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getHasStack() && this.inventorySlots - .getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack().getItem() instanceof ItemWand){ - - ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.WAND_SLOT).getStack(); - - this.fontRenderer.drawStringWithShadow("\u00A7f" + wand.getDisplayName(), xSizeNoTip + 6, 6, 0); - this.fontRenderer.drawStringWithShadow( - "\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana") + " " - + (wand.getMaxDamage() - wand.getItemDamage()) + "/" + wand.getMaxDamage(), - xSizeNoTip + 6, 20, 0); - - Spell[] spells = WandHelper.getSpells(wand); - - int y = 34; - - for(Spell spell : spells){ - - boolean discovered = true; - - if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){ - discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell); - } - - if(discovered){ - this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), xSizeNoTip + 16, y, 0); - }else{ - this.mc.standardGalacticFontRenderer.drawStringWithShadow( - "\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), xSizeNoTip + 16, y, 0); - } - y += 10; - } - - if(WandHelper.getTotalUpgrades(wand) > 0){ - - this.fontRenderer.drawStringWithShadow( - "\u00A7f" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.upgrades"), xSizeNoTip + 6, y + 6, 0); - - int x = 0; - y = 50 + spells.length * 10; - // Wand upgrade tooltips - for(Item item : WandHelper.getSpecialUpgrades()){ - - int level = WandHelper.getUpgradeLevel(wand, item); - - if(level > 0){ - // The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is - // relative to the GUI but the POINT isn't. - if(isPointInRegion(xSizeNoTip + 6 + x, y, 16, 16, mouseX, mouseY)){ - ItemStack stack = new ItemStack(item, level); - this.renderToolTip(stack, mouseX - guiLeft, mouseY - guiTop); - } - x += 18; - } - } - } - } - } - - @Override - public void initGui(){ - this.mc.player.openContainer = this.inventorySlots; - this.guiLeft = (this.width - this.xSize) / 2; - this.guiTop = (this.height - this.ySize) / 2; - Keyboard.enableRepeatEvents(true); - this.buttonList.clear(); - this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 48, this.height / 2 + 3)); - } - - @Override - public void onGuiClosed(){ - super.onGuiClosed(); - Keyboard.enableRepeatEvents(false); - } - - @Override - protected void actionPerformed(GuiButton button){ - if(button.enabled){ - if(button.id == 0){ - // Packet building - IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON); - WizardryPacketHandler.net.sendToServer(msg); - } - } - } - -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/GuiButtonApply.java b/src/main/java/electroblob/wizardry/client/GuiButtonApply.java deleted file mode 100644 index 195327e8..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiButtonApply.java +++ /dev/null @@ -1,42 +0,0 @@ -package electroblob.wizardry.client; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.resources.I18n; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -class GuiButtonApply extends GuiButton { - - public GuiButtonApply(int id, int x, int y){ - super(id, x, y, 32, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply")); - } - - @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ - - // Whether the button is highlighted - this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - - int k = 36; - int l = 220; - int colour = 14737632; - - if(this.enabled){ - if(this.hovered){ - k += this.width * 2; - colour = 16777120; - } - }else{ - k += this.width; - colour = 10526880; - } - - WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 256, 256); - this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2, - this.y + (this.height - 8) / 2, colour); - } -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/GuiButtonTurnPage.java b/src/main/java/electroblob/wizardry/client/GuiButtonTurnPage.java deleted file mode 100644 index 2bdeb44e..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiButtonTurnPage.java +++ /dev/null @@ -1,47 +0,0 @@ -package electroblob.wizardry.client; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -class GuiButtonTurnPage extends GuiButton { - - /** True for pointing right (next page), false for pointing left (previous page). */ - private final boolean nextPage; - - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); - - public GuiButtonTurnPage(int id, int x, int y, boolean isNextPage){ - super(id, x, y, 23, 13, ""); - this.nextPage = isNextPage; - } - - @Override - public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ - - if(this.visible){ - - boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - minecraft.getTextureManager().bindTexture(texture); - int k = 0; - int l = 192; - - if(flag){ - k += 23; - } - - if(!this.nextPage){ - l += 13; - } - - WizardryUtilities.drawTexturedRect(this.x, this.y, k, l, 23, 13, 288, 256); - } - } -} diff --git a/src/main/java/electroblob/wizardry/client/GuiSpellBook.java b/src/main/java/electroblob/wizardry/client/GuiSpellBook.java deleted file mode 100644 index fcfb2366..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiSpellBook.java +++ /dev/null @@ -1,112 +0,0 @@ -package electroblob.wizardry.client; - -import org.lwjgl.input.Keyboard; - -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.ResourceLocation; - -public class GuiSpellBook extends GuiScreen { - - private int xSize, ySize; - private Spell spell; - - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/spellbook.png"); - - public GuiSpellBook(Spell spell){ - super(); - xSize = 288; - ySize = 180; - this.spell = spell; - } - - /** - * Draws the screen and all the components in it. - */ - public void drawScreen(int par1, int par2, float par3){ - - int xPos = this.width / 2 - xSize / 2; - int yPos = this.height / 2 - this.ySize / 2; - - EntityPlayer player = Minecraft.getMinecraft().player; - - boolean discovered = true; - if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null - && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ - discovered = false; - } - - // Draws spell illustration on opposite page, underneath the book so it shows through the hole. - Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon()); - WizardryUtilities.drawTexturedRect(xPos + 145, yPos + 20, 0, 0, 128, 128, 128, 128); - - Minecraft.getMinecraft().renderEngine.bindTexture(texture); - WizardryUtilities.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256); - - super.drawScreen(par1, par2, par3); - - if(discovered){ - this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 14, 0); - this.fontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 25, 0x777777); - }else{ - this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17, - yPos + 14, 0); - this.mc.standardGalacticFontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 25, - 0x777777); - } - - this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 34, 0); - - if(spell.tier == Tier.BASIC){ - // Basic is usually white but this doesn't show up. - this.fontRenderer.drawString("Tier: \u00A77" + Tier.BASIC.getDisplayName(), xPos + 17, yPos + 44, 0); - }else{ - this.fontRenderer.drawString("Tier: " + spell.tier.getDisplayNameWithFormatting(), xPos + 17, yPos + 44, - 0); - } - - String element = "Element: " + spell.element.getFormattingCode() + spell.element.getDisplayName(); - if(!discovered) element = "Element: ?"; - this.fontRenderer.drawString(element, xPos + 17, yPos + 56, 0); - - String manaCost = "Mana Cost: " + spell.cost; - if(spell.isContinuous) manaCost = "Mana Cost: " + spell.cost + "/second"; - if(!discovered) manaCost = "Mana Cost: ?"; - this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 68, 0); - - if(discovered){ - this.fontRenderer.drawSplitString(spell.getDescription(), xPos + 17, yPos + 82, 118, 0); - }else{ - this.mc.standardGalacticFontRenderer.drawSplitString( - SpellGlyphData.getGlyphDescription(spell, player.world), xPos + 17, yPos + 82, 118, 0); - } - - /* // Word wrapping int charNumber = 0; int lineNumber = 0; - * - * while(charNumber < spell.desc.length()){ int lineLength = 0; String line; if(spell.desc.length() - charNumber - * > 22){ for(int i = charNumber; i < charNumber+23; i++){ if(spell.desc.charAt(i) == ' '){ lineLength = i - - * charNumber; } } line = spell.desc.substring(charNumber, charNumber + lineLength); }else{ line = - * spell.desc.substring(charNumber, spell.desc.length()); charNumber = spell.desc.length(); } - * this.fontRendererObj.drawString("\u00A7o" + line, xPos+17, yPos+82+10*lineNumber, 0); - * charNumber+=(lineLength+1); lineNumber++; } */ - } - - public void initGui(){ - super.initGui(); - Keyboard.enableRepeatEvents(true); - this.buttonList.clear(); - } - - public void onGuiClosed(){ - super.onGuiClosed(); - Keyboard.enableRepeatEvents(false); - } -} diff --git a/src/main/java/electroblob/wizardry/client/GuiSpellDisplay.java b/src/main/java/electroblob/wizardry/client/GuiSpellDisplay.java deleted file mode 100644 index 0f905882..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiSpellDisplay.java +++ /dev/null @@ -1,152 +0,0 @@ -package electroblob.wizardry.client; - -import java.util.List; - -import electroblob.wizardry.Settings.GuiPosition; -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.GlStateManager.DestFactor; -import net.minecraft.client.renderer.GlStateManager.SourceFactor; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemStack; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -public class GuiSpellDisplay extends Gui { - - private Minecraft mc; - - private static final ResourceLocation hudTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud.png"); - - public GuiSpellDisplay(Minecraft par1Minecraft){ - super(); - this.mc = par1Minecraft; - } - - @SubscribeEvent - public void draw(RenderGameOverlayEvent event){ - - EntityPlayer player = this.mc.player; - - // If the player has a wand in each hand, only displays for the one in the main hand. - - ItemStack wand = player.getHeldItemMainhand(); - - if(!(wand.getItem() instanceof ItemWand)){ - wand = player.getHeldItemOffhand(); - // If the player isn't holding a wand, then nothing else needs to be done. - if(!(wand.getItem() instanceof ItemWand)) return; - } - - int width = event.getResolution().getScaledWidth(); - int height = event.getResolution().getScaledHeight(); - - Spell spell = WandHelper.getCurrentSpell(wand); - int cooldown = WandHelper.getCurrentCooldown(wand); - - float cooldownMultiplier = 1.0f - WandHelper.getUpgradeLevel(wand, WizardryItems.cooldown_upgrade) * Constants.COOLDOWN_REDUCTION_PER_LEVEL; - - if(player.isPotionActive(WizardryPotions.font_of_mana)){ - // Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously - cooldownMultiplier /= 2 + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier(); - } - - // Coordinates of the top left corner of the HUD. - int left = 0; - int top = 0; - boolean mirror = false; - - if(Wizardry.settings.spellHUDPosition == GuiPosition.BOTTOM_LEFT){ - left = 0; - top = height - 36; - }else if(Wizardry.settings.spellHUDPosition == GuiPosition.TOP_LEFT){ - left = 0; - top = 0; - }else if(Wizardry.settings.spellHUDPosition == GuiPosition.TOP_RIGHT){ - left = width - 128; - top = 0; - mirror = true; - }else if(Wizardry.settings.spellHUDPosition == GuiPosition.BOTTOM_RIGHT){ - left = width - 128; - top = height - 36; - mirror = true; - } - - boolean discovered = true; - - if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){ - discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); - } - - if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){ - - // Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect - String colour = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.element.getFormattingCode(); - if(!discovered) colour = "\u00A79"; - String spellName = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world); - FontRenderer font = discovered ? this.mc.fontRenderer : this.mc.standardGalacticFontRenderer; - - int maxWidth = 90; - - if(font.getStringWidth(spellName) <= maxWidth){ - // Single line is rendered more centrally - font.drawStringWithShadow(colour + spellName, mirror ? left + 5 : left + 41, top + 13, 0xffffffff); - - }else{ - - int lineNumber = 0; - - List lines = font.listFormattedStringToWidth(spellName, maxWidth); - - for(Object line : lines){ - if(line instanceof String){ - font.drawStringWithShadow(colour + (String)line, mirror ? left + 5 : left + 41, top + 6 + 11 * lineNumber, 0xffffffff); - } - lineNumber++; - } - } - - }else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){ - - GlStateManager.enableBlend(); - GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - GlStateManager.color(1, 1, 1); - - this.mc.renderEngine.bindTexture(hudTexture); - - // Background of spell hud - this.drawTexturedModalRect(left, top, 0, mirror ? 36 : 0, 128, 36); - - // Cooldown bar - if(cooldown > 0){ - this.drawTexturedModalRect(mirror ? left + 5 : left + 41, top + 28, 128, 6, 82, 6); - - int l = (int)(((double)(spell.cooldown * cooldownMultiplier - cooldown) / (double)(spell.cooldown * cooldownMultiplier)) * 82); - - this.drawTexturedModalRect(mirror ? left + 5 : left + 41, top + 28, 128, 0, l, 6); - } - - // Spell illustration - this.mc.renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon()); - - WizardryUtilities.drawTexturedRect(mirror ? left + 94 : left + 2, top + 2, 0, 0, 32, 32, 32, 32); - - // Blend needs to be left enabled here because otherwise the hotbar becomes opaque - } - } - -} diff --git a/src/main/java/electroblob/wizardry/client/GuiWizardHandbook.java b/src/main/java/electroblob/wizardry/client/GuiWizardHandbook.java deleted file mode 100644 index 98fa241e..00000000 --- a/src/main/java/electroblob/wizardry/client/GuiWizardHandbook.java +++ /dev/null @@ -1,694 +0,0 @@ -package electroblob.wizardry.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryBlocks; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.init.Blocks; -import net.minecraft.init.Items; -import net.minecraft.item.ItemStack; -import net.minecraft.util.NonNullList; -import net.minecraft.util.ResourceLocation; - -public class GuiWizardHandbook extends GuiScreen { - - private int xSize, ySize; - private int pageNumber = 0; - - private static final int PAGE_WIDTH = 120; - /** - * The integer colour for black passed into the font renderer methods. This used to be 0 but that's now white for - * some reason, so I've made a it a constant in case it changes again. - */ - // I think this is actually ever-so-slightly lighter than pure black, but the difference is unnoticeable. - private static final int BLACK = 1; - - public static final ResourceLocation regularHandbook = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); - public static final ResourceLocation ore = new ResourceLocation(Wizardry.MODID, "textures/gui/ore_picture.png"); - public static final ResourceLocation crystal = new ResourceLocation(Wizardry.MODID, "textures/items/magic_crystal.png"); - public static final ResourceLocation workbenchGui = new ResourceLocation(Wizardry.MODID, "textures/gui/arcane_workbench.png"); - public static final ResourceLocation craftingGrids = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook_recipes.png"); - - private List> text; - private List
    sections; - - private static final List>>> RECIPES = new ArrayList<>(); - - private int guiPage, imagePage; - - public GuiWizardHandbook(){ - super(); - xSize = 288; - ySize = 180; - } - - @Override - public void drawScreen(int mouseX, int mouseY, float par3){ - - int xPos = this.width / 2 - xSize / 2; - int yPos = this.height / 2 - this.ySize / 2; - - // Tests for crafting recipes section - if(pageNumber >= (sections.get(sections.size() - 1).pageNumber - 1) / 2 - && pageNumber < (sections.get(sections.size() - 1).pageNumber - 1) / 2 + 4){ - Minecraft.getMinecraft().renderEngine.bindTexture(craftingGrids); - }else{ - Minecraft.getMinecraft().renderEngine.bindTexture(regularHandbook); - } - - WizardryUtilities.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256); - - // Arcane workbench gui picture - if(pageNumber == (this.guiPage - 1) / 2){ - Minecraft.getMinecraft().renderEngine.bindTexture(workbenchGui); - this.drawTexturedModalRect(this.guiPage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 14, 28, 12, 120, - 118); - } - - // Magic crystal and crystal ore images - if(pageNumber == (this.imagePage - 1) / 2){ - - Minecraft.getMinecraft().renderEngine.bindTexture(ore); - WizardryUtilities.drawTexturedRect(this.imagePage % 2 == 1 ? xPos + 17 : this.width / 2 + 7, yPos + 80, 0, - 0, 64, 64, 64, 64); - - Minecraft.getMinecraft().renderEngine.bindTexture(crystal); - drawTexturedStretchedRect(this.imagePage % 2 == 1 ? xPos + 17 + 64 : this.width / 2 + 7 + 62, yPos + 80, 0, - 0, 64, 64, 1, 1); - - } - - this.fontRenderer.drawString("" + (pageNumber * 2 + 1), xPos + xSize / 4 - 3, yPos + ySize - 20, 0); - this.fontRenderer.drawString("" + (pageNumber * 2 + 2), xPos + 3 * xSize / 4 - 5, yPos + ySize - 20, 0); - - super.drawScreen(mouseX, mouseY, par3); - - int lineNumber = 0; - - if(pageNumber == 1){ - for(Section s : sections){ - s.drawContents(); - } - }else{ - for(Section s : sections){ - s.hideButton(); - } - } - - for(String paragraph : text.get(pageNumber * 2)){ - - this.fontRenderer.drawSplitString(paragraph, xPos + 17, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - - List list = new ArrayList( - this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); - - lineNumber += list.size(); - } - - lineNumber = 0; - - // Prevents crash when the last page is blank (and hence is not in the list of pages) - if(text.size() > pageNumber * 2 + 1){ - for(String paragraph : text.get(pageNumber * 2 + 1)){ - - // First page is centred - if(pageNumber == 0){ - int startX = this.width / 2 + 7 + PAGE_WIDTH / 2 - - this.fontRenderer.getStringWidth(paragraph) / 2; - this.fontRenderer.drawSplitString(paragraph, startX, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - }else{ - this.fontRenderer.drawSplitString(paragraph, this.width / 2 + 7, - yPos + 14 + lineNumber * this.fontRenderer.FONT_HEIGHT, PAGE_WIDTH, BLACK); - } - - List list = new ArrayList( - this.fontRenderer.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); - - lineNumber += list.size(); - } - } - - // Which page of the recipes this is - int recipePage = pageNumber - (sections.get(sections.size() - 1).pageNumber - 1) / 2; - - if(recipePage >= 0 && recipePage < 4){ - // 4 recipes per page, hence the recipePage*4 - this.renderCraftingRecipe(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft()); - this.renderCraftingRecipe(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft()); - this.renderCraftingRecipe(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft()); - this.renderCraftingRecipe(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft()); - - // Tooltips are rendered after recipes to prevent tooltips on the left appearing behind items on the right. - this.renderCraftingTooltips(xPos + 23, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4).getRight(), RECIPES.get(recipePage*4).getLeft()); - this.renderCraftingTooltips(xPos + 23, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+1).getRight(), RECIPES.get(recipePage*4+1).getLeft()); - this.renderCraftingTooltips(xPos + 156, yPos + 39, mouseX, mouseY, RECIPES.get(recipePage*4+2).getRight(), RECIPES.get(recipePage*4+2).getLeft()); - this.renderCraftingTooltips(xPos + 156, yPos + 98, mouseX, mouseY, RECIPES.get(recipePage*4+3).getRight(), RECIPES.get(recipePage*4+3).getLeft()); - } - - } - - // TODO: In 1.12, this all needs redoing nicely. With the crafting system halfway through changing in 1.11.2, this - // isn't worth doing until then. - - private void renderCraftingRecipe(int xPos, int yPos, int mouseX, int mouseY, NonNullList> craftingGrid, - ItemStack craftingResult){ - - GlStateManager.pushMatrix(); - RenderHelper.enableGUIStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.enableRescaleNormal(); - GlStateManager.enableColorMaterial(); - GlStateManager.enableLighting(); - itemRender.zLevel = 100.0F; - - for(int i = 0; i < craftingGrid.size(); i++){ - for(int j = 0; j < craftingGrid.get(i).size(); j++){ - ItemStack stack = craftingGrid.get(i).get(j); - if(!stack.isEmpty()){ - itemRender.renderItemAndEffectIntoGUI(stack, xPos + 18 * i, yPos + 18 * j); - itemRender.renderItemOverlays(this.fontRenderer, stack, xPos + 18 * i, - yPos + 18 * j); - } - } - } - - if(!craftingResult.isEmpty()){ - itemRender.renderItemAndEffectIntoGUI(craftingResult, xPos + 86, yPos + 18); - itemRender.renderItemOverlays(this.fontRenderer, craftingResult, xPos + 86, yPos + 18); - } - - GlStateManager.popMatrix(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - RenderHelper.enableStandardItemLighting(); - - } - - private void renderCraftingTooltips(int xPos, int yPos, int mouseX, int mouseY, NonNullList> craftingGrid, - ItemStack craftingResult){ - - int guiLeft = this.width / 2 - xSize / 2; - int guiTop = this.height / 2 - this.ySize / 2; - - GlStateManager.pushMatrix(); - RenderHelper.enableGUIStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.enableRescaleNormal(); - GlStateManager.enableColorMaterial(); - itemRender.zLevel = 0.0F; - GlStateManager.disableLighting(); - - for(int i = 0; i < craftingGrid.size(); i++){ - for(int j = 0; j < craftingGrid.get(i).size(); j++){ - ItemStack stack = craftingGrid.get(i).get(j); - if(!stack.isEmpty() - && isPointInRegion(xPos + 18 * i, yPos + 18 * j, 16, 16, mouseX + guiLeft, mouseY + guiTop)){ - this.renderToolTip(stack, mouseX, mouseY); - } - } - } - - if(!craftingResult.isEmpty() && isPointInRegion(xPos + 86, yPos + 18, 16, 16, mouseX + guiLeft, mouseY + guiTop)){ - this.renderToolTip(craftingResult, mouseX, mouseY); - } - - GlStateManager.popMatrix(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - RenderHelper.enableStandardItemLighting(); - - } - - @Override - public void initGui(){ - - super.initGui(); - Keyboard.enableRepeatEvents(true); - - int nextButtonId = 0; - - this.buttonList.clear(); - this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 + this.xSize / 2 - 22 - 23, - this.height / 2 + this.ySize / 2 - 10 - 13, true)); - this.buttonList.add(new GuiButtonTurnPage(nextButtonId++, this.width / 2 - this.xSize / 2 + 21, - this.height / 2 + this.ySize / 2 - 10 - 13, false)); - - text = new ArrayList>(1); - sections = new ArrayList
    (1); - - BufferedReader bufferedreader = null; - - String textFilepath = Wizardry.MODID + ":texts/handbook_" - + Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".txt"; - - try{ - - bufferedreader = new BufferedReader(new InputStreamReader( - this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(), - StandardCharsets.UTF_8)); - - }catch (IOException e){ - - Wizardry.logger.info( - "Wizard handbook text file missing for the current language. Using default (English - US) instead."); - - textFilepath = Wizardry.MODID + ":texts/handbook_en_us.txt"; - - try { - - bufferedreader = new BufferedReader(new InputStreamReader( - this.mc.getResourceManager().getResource(new ResourceLocation(textFilepath)).getInputStream(), - StandardCharsets.UTF_8)); - - } catch (IOException x){ - Wizardry.logger.error("Couldn't find file: " + Wizardry.MODID + "/assets/texts/handbook_en_us.txt. The file may be" - + "missing; please try re-downloading and reinstalling Wizardry.", x); - } - } - - if(bufferedreader != null){ - - try{ - - String paragraph = bufferedreader.readLine(); - ArrayList page = new ArrayList(1); - - int linesPerPage = 16; - - int lineNumber = 0; - - while(paragraph != null){ - - // System.out.println(paragraph); - - if(paragraph.contains("PAGEBREAK") || lineNumber >= linesPerPage){ - - text.add(page); - - page = new ArrayList(1); - - lineNumber = 0; - - if(paragraph.contains("PAGEBREAK")) paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("LINEBREAK")){ - - lineNumber++; - - page.add(""); - - paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("SECTION")){ - - sections.add( - new Section(paragraph.replace("SECTION ", ""), text.size() + 1, this.width / 2 + 7, - this.height / 2 - this.ySize / 2 + 14 - + (sections.size() + 2) * this.fontRenderer.FONT_HEIGHT, - nextButtonId++)); - paragraph = bufferedreader.readLine(); - - }else if(paragraph.contains("IMAGE")){ - - if(paragraph.contains("WORKBENCH")){ - this.guiPage = text.size() + 1; - }else if(paragraph.contains("CRYSTAL")){ - this.imagePage = text.size() + 1; - } - - paragraph = bufferedreader.readLine(); - - }else{ - - paragraph = paragraph.replaceAll("NEXT_SPELL_KEY", ClientProxy.NEXT_SPELL.getDisplayName()); - paragraph = paragraph.replaceAll("PREVIOUS_SPELL_KEY", ClientProxy.PREVIOUS_SPELL.getDisplayName()); - paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL_MINUS_30", "" + (Constants.MANA_PER_CRYSTAL - 30)); - paragraph = paragraph.replaceAll("MANA_PER_CRYSTAL", "" + Constants.MANA_PER_CRYSTAL); - paragraph = paragraph.replaceAll("BASIC_MAX_CHARGE", "" + Tier.BASIC.maxCharge); - paragraph = paragraph.replaceAll("APPRENTICE_MAX_CHARGE", "" + Tier.APPRENTICE.maxCharge); - paragraph = paragraph.replaceAll("ADVANCED_MAX_CHARGE", "" + Tier.ADVANCED.maxCharge); - paragraph = paragraph.replaceAll("MASTER_MAX_CHARGE", "" + Tier.MASTER.maxCharge); - paragraph = paragraph.replaceAll("BASIC_COLOUR", "\u00A77"); - paragraph = paragraph.replaceAll("APPRENTICE_COLOUR", Tier.APPRENTICE.getFormattingCode()); - paragraph = paragraph.replaceAll("ADVANCED_COLOUR", Tier.ADVANCED.getFormattingCode()); - paragraph = paragraph.replaceAll("MASTER_COLOUR", Tier.MASTER.getFormattingCode()); - paragraph = paragraph.replaceAll("FIRE_COLOUR", Element.FIRE.getFormattingCode()); - paragraph = paragraph.replaceAll("ICE_COLOUR", Element.ICE.getFormattingCode()); - paragraph = paragraph.replaceAll("LIGHTNING_COLOUR", Element.LIGHTNING.getFormattingCode()); - paragraph = paragraph.replaceAll("NECROMANCY_COLOUR", Element.NECROMANCY.getFormattingCode()); - paragraph = paragraph.replaceAll("EARTH_COLOUR", Element.EARTH.getFormattingCode()); - paragraph = paragraph.replaceAll("SORCERY_COLOUR", Element.SORCERY.getFormattingCode()); - paragraph = paragraph.replaceAll("HEALING_COLOUR", Element.HEALING.getFormattingCode()); - paragraph = paragraph.replaceAll("RESET_COLOUR", "\u00A70"); - paragraph = paragraph.replaceAll("MCVERSION", "1.12.2"); - paragraph = paragraph.replaceAll("VERSION", Wizardry.VERSION); - - int linesInParagraph = this.fontRenderer - .listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH).size(); - - // Ignores empty lines at the top of a page. - if(paragraph.isEmpty() && lineNumber == 0){ - - paragraph = bufferedreader.readLine(); - - // Normal paragraph, all on one page - }else if(lineNumber + linesInParagraph <= linesPerPage){ - - page.add(paragraph); - - lineNumber += linesInParagraph; - - paragraph = bufferedreader.readLine(); - - // Paragraphs split across two pages (or more?) - }else{ - - int linesInFirstPart = linesPerPage - lineNumber; - - String paragraphFirstPart = ""; - String paragraphLastPart = ""; - - int i = 0; - - List strings = this.fontRenderer.listFormattedStringToWidth(paragraph, - GuiWizardHandbook.PAGE_WIDTH); - - for(Object s : strings){ - if(i < linesInFirstPart){ - paragraphFirstPart = paragraphFirstPart.concat((String)s + " "); - }else{ - paragraphLastPart = paragraphLastPart.concat((String)s + " "); - } - i++; - } - - // System.out.println("Paragraph crosses page boundary; string split into: \"" + - // paragraphFirstPart + "\" and \"" + paragraphLastPart + "\""); - - page.add(paragraphFirstPart); - - lineNumber += linesInFirstPart; - - paragraph = paragraphLastPart; - } - } - } - - text.add(page); - - }catch (IOException e){ - Wizardry.logger.error("Something went wrong reading file: " + textFilepath - + ". The file may be damaged; please try re-downloading and reinstalling wizardry.", e); - } - } - } - - private class Section { - - /** The integer text colour used for the section when it is moused over. Currently orange. */ - private static final int HIGHLIGHT_COLOUR = 0xdd4c1d; - - String name; - int pageNumber; - int x, y; - int buttonId; - - Section(String name, int pageNumber, int x, int y, int id){ - this.name = name; - this.pageNumber = pageNumber; - this.x = x; - this.y = y; - this.buttonId = id; - GuiWizardHandbook.this.buttonList.add(new GuiButtonInvisible(id, x, y, GuiWizardHandbook.PAGE_WIDTH, - GuiWizardHandbook.this.fontRenderer.FONT_HEIGHT)); - } - - void hideButton(){ - GuiWizardHandbook.this.buttonList.get(buttonId).visible = false; - } - - void drawContents(){ - - GuiWizardHandbook.this.buttonList.get(buttonId).visible = true; - - GuiWizardHandbook.this.fontRenderer.drawString(name, x, y, - GuiWizardHandbook.this.buttonList.get(buttonId).isMouseOver() ? HIGHLIGHT_COLOUR : BLACK); - - int nameWidth = GuiWizardHandbook.this.fontRenderer.getStringWidth(name); - - String dotsAndNumber = " " + this.pageNumber; - - while(GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - - nameWidth - 2){ - dotsAndNumber = "." + dotsAndNumber; - } - - GuiWizardHandbook.this.fontRenderer.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - - GuiWizardHandbook.this.fontRenderer.getStringWidth(dotsAndNumber), y, BLACK); - } - } - - @Override - public void onGuiClosed(){ - super.onGuiClosed(); - Keyboard.enableRepeatEvents(false); - } - - /** - * Fired when a control is clicked. This is the equivalent of ActionListener.actionPerformed(ActionEvent e). - */ - @Override - protected void actionPerformed(GuiButton par1GuiButton){ - - if(par1GuiButton.enabled){ - if(par1GuiButton.id == 0){ - if(pageNumber < (text.size() - 1) / 2) pageNumber++; - }else if(par1GuiButton.id == 1){ - if(pageNumber > 0) pageNumber--; - }else{ - if(pageNumber == 1) pageNumber = (sections.get(par1GuiButton.id - 2).pageNumber - 1) / 2; - } - } - } - - /** - * Args: left, top, width, height, pointX, pointY. Note: left, top are local to Gui, pointX, pointY are local to - * screen - */ - protected boolean isPointInRegion(int par1, int par2, int par3, int par4, int par5, int par6){ - int k1 = this.width / 2 - xSize / 2; - int l1 = this.height / 2 - this.ySize / 2; - par5 -= k1; - par6 -= l1; - return par5 >= par1 - 1 && par5 < par1 + par3 + 1 && par6 >= par2 - 1 && par6 < par2 + par4 + 1; - } - - /** - * Draws a textured rectangle, stretching the section of the image to fit the size given. - * - * @param x The x position of the rectangle - * @param y The y position of the rectangle - * @param u The x position of the top left corner of the section of the image wanted, expressed as a fraction of the - * image width - * @param v The y position of the top left corner of the section of the image wanted, expressed as a fraction of the - * image width - * @param finalWidth The width as rendered - * @param finalHeight The height as rendered - * @param width The width of the section, expressed as a fraction of the image width - * @param height The height of the section, expressed as a fraction of the image width - */ - public static void drawTexturedStretchedRect(int x, int y, int u, int v, int finalWidth, int finalHeight, int width, - int height){ - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos((x), y + finalHeight, 0).tex(u, v + height).endVertex(); - buffer.pos(x + finalWidth, y + finalHeight, 0).tex(u + width, v + height).endVertex(); - buffer.pos(x + finalWidth, (y), 0).tex(u + width, v).endVertex(); - buffer.pos((x), (y), 0).tex(u, v).endVertex(); - tessellator.draw(); - } - - private static NonNullList> createGrid(){ - NonNullList> grid = NonNullList.withSize(3, NonNullList.create()); - for(int i=0; i<3; i++){ - grid.set(i, NonNullList.withSize(3, ItemStack.EMPTY)); - } - return grid; - } - - /** Called from init() in the main mod class to initialise the recipes for display in the handbook. */ - public static void initDisplayRecipes(){ - - NonNullList> craftingGrid; - ItemStack craftingResult; - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.GOLD_NUGGET)); - craftingGrid.get(1).set(0, new ItemStack(Blocks.CARPET, 1, 10)); - craftingGrid.get(2).set(0, new ItemStack(Items.GOLD_NUGGET)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Blocks.LAPIS_BLOCK)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(2).set(2, new ItemStack(Blocks.STONE)); - craftingResult = new ItemStack(WizardryBlocks.arcane_workbench); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.STICK)); - craftingGrid.get(0).set(2, new ItemStack(Items.GOLD_NUGGET)); - craftingResult = new ItemStack(WizardryItems.magic_wand); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.BOOK)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.spell_book, 1, 1); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.BOOK)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.wizard_handbook); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryBlocks.crystal_flower)); - craftingResult = new ItemStack(WizardryItems.magic_crystal, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_crystal)); - craftingResult = new ItemStack(WizardryItems.mana_flask); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(Blocks.STONE)); - craftingGrid.get(0).set(1, new ItemStack(Blocks.STONE)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(Blocks.STONE)); - craftingGrid.get(2).set(1, new ItemStack(Blocks.STONE)); - craftingResult = new ItemStack(WizardryBlocks.transportation_stone, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(1).set(0, new ItemStack(Items.STRING)); - craftingGrid.get(0).set(1, new ItemStack(Items.STRING)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_crystal)); - craftingGrid.get(1).set(2, new ItemStack(Items.STRING)); - craftingGrid.get(2).set(1, new ItemStack(Items.STRING)); - craftingResult = new ItemStack(WizardryItems.magic_silk, 2); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_hat); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_robe); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(1).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(2, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_leggings); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(0, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(0).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingGrid.get(2).set(1, new ItemStack(WizardryItems.magic_silk)); - craftingResult = new ItemStack(WizardryItems.wizard_boots); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.PAPER)); - craftingGrid.get(1).set(0, new ItemStack(Items.STRING)); - craftingResult = new ItemStack(WizardryItems.blank_scroll); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.BLAZE_POWDER)); - craftingGrid.get(1).set(0, new ItemStack(Items.BLAZE_POWDER)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.firebomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.SPIDER_EYE)); - craftingGrid.get(1).set(0, new ItemStack(Items.SPIDER_EYE)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.poison_bomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - - craftingGrid = createGrid(); - craftingGrid.get(0).set(0, new ItemStack(Items.COAL)); - craftingGrid.get(1).set(0, new ItemStack(Items.COAL)); - craftingGrid.get(0).set(1, new ItemStack(Items.GLASS_BOTTLE)); - craftingGrid.get(1).set(1, new ItemStack(Items.GUNPOWDER)); - craftingResult = new ItemStack(WizardryItems.smoke_bomb, 3); - RECIPES.add(ImmutablePair.of(craftingResult, craftingGrid)); - } - -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/MixedFontRenderer.java b/src/main/java/electroblob/wizardry/client/MixedFontRenderer.java index ee098490..ff637a1c 100644 --- a/src/main/java/electroblob/wizardry/client/MixedFontRenderer.java +++ b/src/main/java/electroblob/wizardry/client/MixedFontRenderer.java @@ -5,15 +5,13 @@ import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.settings.GameSettings; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; /** * Font renderer that renders parts of strings surrounded by '#' (without quotes) in the SGA instead of normal text. * * @since Wizardry 1.1 */ -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class MixedFontRenderer extends FontRenderer { public MixedFontRenderer(GameSettings p_i1035_1_, ResourceLocation p_i1035_2_, TextureManager p_i1035_3_, diff --git a/src/main/java/electroblob/wizardry/client/MovingSoundEntity.java b/src/main/java/electroblob/wizardry/client/MovingSoundEntity.java deleted file mode 100644 index 0fdd1165..00000000 --- a/src/main/java/electroblob/wizardry/client/MovingSoundEntity.java +++ /dev/null @@ -1,52 +0,0 @@ -package electroblob.wizardry.client; - -import net.minecraft.client.audio.MovingSound; -import net.minecraft.entity.Entity; -import net.minecraft.util.SoundCategory; -import net.minecraft.util.SoundEvent; -import net.minecraft.util.math.MathHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that. -@SideOnly(Side.CLIENT) -public class MovingSoundEntity extends MovingSound { - private final Entity source; - private float distance = 0.0F; - - public MovingSoundEntity(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){ - // Uses BLOCKS because that's the closest thing to inanimate entities. Could use NEUTRAL like - // MovingSoundMinecart. - super(sound, SoundCategory.BLOCKS); - this.source = entity; - this.repeat = repeat; - this.volume = volume; - this.pitch = pitch; - this.repeatDelay = 0; - } - - /** - * Updates the JList with a new model. - */ - @Override - public void update(){ - if(this.source.isDead && repeat){ - this.donePlaying = true; - }else{ - this.xPosF = (float)this.source.posX; - this.yPosF = (float)this.source.posY; - this.zPosF = (float)this.source.posZ; - float f = MathHelper.sqrt(this.source.motionX * this.source.motionX - + this.source.motionY * this.source.motionY + this.source.motionZ * this.source.motionZ); - - // Is this something to do with the Doppler effect? - if((double)f >= 0.01D){ - this.distance = MathHelper.clamp(this.distance + 0.0025F, 0.0F, 1.0F); - this.volume = 0.0F + MathHelper.clamp(f, 0.0F, 0.5F) * 0.7F; - }else{ - // this.pitch = 0.0F; - // this.volume = 0.0F; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java index 52d561bb..a024f95e 100644 --- a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java +++ b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java @@ -1,108 +1,179 @@ package electroblob.wizardry.client; -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockMagicLight; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.data.DispenserCastingData; +import electroblob.wizardry.data.SpellEmitterData; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.item.ItemSpectralBow; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.packet.PacketControlInput; -import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.spell.Flight; -import electroblob.wizardry.spell.ShadowWard; -import electroblob.wizardry.spell.Shield; -import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; -import electroblob.wizardry.util.WandHelper; +import electroblob.wizardry.spell.*; +import electroblob.wizardry.util.RayTracer; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiMerchant; -import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.RenderItem; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.item.EntityArmorStand; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.village.MerchantRecipe; -import net.minecraftforge.client.event.FOVUpdateEvent; -import net.minecraftforge.client.event.GuiContainerEvent; -import net.minecraftforge.client.event.MouseEvent; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.client.event.RenderLivingEvent; -import net.minecraftforge.client.event.RenderPlayerEvent; -import net.minecraftforge.client.event.RenderWorldLastEvent; -import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraft.world.World; +import net.minecraftforge.client.event.*; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; + +import java.lang.reflect.Method; /** - * Event handler responsible for all client-side only events, mostly rendering. + * Event handler responsible for client-side only events, mostly rendering. * * @author Electroblob * @since Wizardry 1.0 */ -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT) public final class WizardryClientEventHandler { - private static final ResourceLocation shieldTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shield.png"); - private static final ResourceLocation wingTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/wing.png"); - private static final ResourceLocation shadowWardTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shadow_ward.png"); private static final ResourceLocation sixthSenseTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/sixth_sense.png"); private static final ResourceLocation sixthSenseOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/sixth_sense_overlay.png"); private static final ResourceLocation frostOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_overlay.png"); + private static final ResourceLocation blinkOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/blink_overlay.png"); private static final ResourceLocation pointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/pointer.png"); private static final ResourceLocation targetPointerTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/target_pointer.png"); + /** The remaining time for which the blink screen overlay effect will be displayed in first-person. Since this is + * only for the first-person player (the instance of which is itself stored in a static variable), this can simply + * be stored statically here, rather than needing to be in {@code WizardData}. */ + private static int blinkEffectTimer; + /** The number of ticks the blink effect lasts for. */ + private static final int BLINK_EFFECT_DURATION = 8; + + private static final Method unpressKey; + + static { + unpressKey = ObfuscationReflectionHelper.findMethod(KeyBinding.class, "func_74505_d", void.class); + } + + /** Starts the first person blink overlay effect. */ + public static void playBlinkEffect(){ + blinkEffectTimer = BLINK_EFFECT_DURATION; + } + @SubscribeEvent - public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ - event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL); - event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE); + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.player == Minecraft.getMinecraft().player){ + + if(blinkEffectTimer > 0) blinkEffectTimer--; + + // Only seems to work here... +// EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player); +// if(victim != null && victim.getHeldItemMainhand().isEmpty()){ +// Minecraft.getMinecraft().player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY); +// } + + // Reset shaders if their respective potions aren't active + // This is a player so the potion effects are synced by vanilla + if(Minecraft.getMinecraft().entityRenderer.getShaderGroup() != null){ + + String activeShader = Minecraft.getMinecraft().entityRenderer.getShaderGroup().getShaderGroupName(); + + if((activeShader.equals(SlowTime.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.slow_time)) + || (activeShader.equals(SixthSense.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense)) + || (activeShader.equals(Transience.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.transience))){ + + if(activeShader.equals(SixthSense.SHADER.toString()) + || activeShader.equals(Transience.SHADER.toString())) playBlinkEffect(); + + Minecraft.getMinecraft().entityRenderer.stopUseShader(); + } + } + } + } + + @SubscribeEvent + public static void onRenderHandEvent(RenderHandEvent event){ + + EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player); + + if(victim != null){ + + victim.rotationYawHead = Minecraft.getMinecraft().player.rotationYaw; + + if(Minecraft.getMinecraft().player.getHeldItemMainhand().isEmpty()){ + event.setCanceled(true); + } + } } - // Shift-scrolling to change spells + // This event is called every tick, not just when a movement key is pressed + @SubscribeEvent + public static void onInputUpdateEvent(InputUpdateEvent event){ + // Prevents the player moving when paralysed + if(event.getEntityPlayer().isPotionActive(WizardryPotions.paralysis)){ + event.getMovementInput().moveForward = 0; + event.getMovementInput().moveStrafe = 0; + event.getMovementInput().jump = false; + event.getMovementInput().sneak = false; + } + } + + @SubscribeEvent + public static void onClientTickEvent(TickEvent.ClientTickEvent event){ + + if(event.phase == TickEvent.Phase.END && !net.minecraft.client.Minecraft.getMinecraft().isGamePaused()){ + + World world = net.minecraft.client.Minecraft.getMinecraft().world; + + if(world == null) return; + + for(TileEntity tileentity : world.loadedTileEntityList){ + if(tileentity instanceof TileEntityDispenser){ + if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){ + DispenserCastingData.get((TileEntityDispenser)tileentity).update(); + } + } + } + + SpellEmitterData.update(world); + } + } + @SubscribeEvent public static void onMouseEvent(MouseEvent event){ - - EntityPlayer player = Minecraft.getMinecraft().player; - ItemStack wand = player.getHeldItemMainhand(); - - if(!(wand.getItem() instanceof ItemWand)){ - wand = player.getHeldItemOffhand(); - // If the player isn't holding a wand, then nothing else needs to be done. - if(!(wand.getItem() instanceof ItemWand)) return; - } - - if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking() - && Wizardry.settings.enableShiftScrolling){ - + + // Prevents the player looking around when paralysed + if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.paralysis) + && Minecraft.getMinecraft().inGameHasFocus){ event.setCanceled(true); - - if(event.getDwheel() > 0){ - // Packet building - IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY); - WizardryPacketHandler.net.sendToServer(msg); - - }else if(event.getDwheel() < 0){ - // Packet building - IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY); - WizardryPacketHandler.net.sendToServer(msg); - } + Minecraft.getMinecraft().player.prevRotationYaw = 0; + Minecraft.getMinecraft().player.prevRotationPitch = 0; + Minecraft.getMinecraft().player.rotationYaw = 0; + Minecraft.getMinecraft().player.rotationPitch = 0; } } @@ -124,12 +195,32 @@ public final class WizardryClientEventHandler { event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F); } + + if(blinkEffectTimer > 0){ + float f = ((float)Math.max(blinkEffectTimer - 2, 0))/BLINK_EFFECT_DURATION; + event.setNewfov(event.getFov() + f * f * 0.7f); + } } - + + @SubscribeEvent + public static void onDrawBlockHighlightEvent(DrawBlockHighlightEvent event){ + // Hide the block outline for magic light blocks unless the player can dispel them + if(event.getTarget().typeOfHit == RayTraceResult.Type.BLOCK + && event.getPlayer().world.getBlockState(event.getTarget().getBlockPos()).getBlock() instanceof BlockMagicLight){ + + if((!(event.getPlayer().getHeldItemMainhand().getItem() instanceof ISpellCastingItem) + && !(event.getPlayer().getHeldItemOffhand().getItem() instanceof ISpellCastingItem)) + || !ItemArtefact.isArtefactActive(event.getPlayer(), WizardryItems.charm_light)){ + + event.setCanceled(true); + } + } + } + // Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI. @SubscribeEvent public static void onGuiDrawForegroundEvent(GuiContainerEvent.DrawForeground event){ - + if(event.getGuiContainer() instanceof GuiMerchant){ GuiMerchant gui = (GuiMerchant)event.getGuiContainer(); @@ -142,120 +233,79 @@ public final class WizardryClientEventHandler { for(MerchantRecipe trade : gui.getMerchant().getRecipes(Minecraft.getMinecraft().player)){ if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){ Slot slot = gui.inventorySlots.getSlot(2); - // Uses reflection to draw the itemstack // It still doesn't look quite right because the slot highlight is behind the item, but it'll do // until/unless I find a better solution. - renderItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(), + DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(), gui.getSlotUnderMouse() == slot); } } } } } - - private static void renderItemAndTooltip(GuiContainer gui, ItemStack stack, int x, int y, int mouseX, int mouseY, boolean tooltip){ - - RenderItem renderItem = Minecraft.getMinecraft().getRenderItem(); - GlStateManager.pushMatrix(); - RenderHelper.enableGUIStandardItemLighting(); - GlStateManager.disableLighting(); - GlStateManager.enableRescaleNormal(); - GlStateManager.enableColorMaterial(); - GlStateManager.enableLighting(); - renderItem.zLevel = 100.0F; - - if(!stack.isEmpty()){ - renderItem.renderItemAndEffectIntoGUI(stack, x, y); - renderItem.renderItemOverlays(Minecraft.getMinecraft().fontRenderer, stack, x, y); - - if(tooltip){ - gui.drawHoveringText(gui.getItemToolTip(stack), mouseX + gui.getXSize()/2 - gui.width/2, - mouseY + gui.getYSize()/2 - gui.height/2); - } - } - - GlStateManager.popMatrix(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - RenderHelper.enableStandardItemLighting(); - } - - // Third person - @SubscribeEvent - public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){ - renderShieldIfActive(event.getEntityPlayer()); - renderWingsIfActive(event.getEntityPlayer(), event.getPartialRenderTick()); - renderShadowWardIfActive(event.getEntityPlayer()); - } - - // First person - @SubscribeEvent - public static void onRenderWorldLastEvent(RenderWorldLastEvent event){ - // Now only fires in first person. - if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){ - renderShieldFirstPerson(Minecraft.getMinecraft().player); - renderShadowWardFirstPerson(Minecraft.getMinecraft().player); - } - } @SubscribeEvent public static void onRenderLivingEvent(RenderLivingEvent.Post event){ Minecraft mc = Minecraft.getMinecraft(); - WizardData properties = WizardData.get(mc.player); - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16); + WizardData data = WizardData.get(mc.player); RenderManager renderManager = event.getRenderer().getRenderManager(); ItemStack wand = mc.player.getHeldItemMainhand(); - if(!(wand.getItem() instanceof ItemWand)){ + if(!(wand.getItem() instanceof ISpellCastingItem)){ wand = mc.player.getHeldItemOffhand(); } // Target selection pointer - if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && rayTrace != null && !(event.getEntity() instanceof EntityArmorStand) - && rayTrace.entityHit == event.getEntity() && properties != null && properties.selectedMinion != null){ + if(mc.player.isSneaking() && wand.getItem() instanceof ISpellCastingItem && WizardryUtilities.isLiving(event.getEntity()) + && data != null && data.selectedMinion != null){ + + // -> Moved this in here so it isn't called every tick + RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(mc.world, mc.player, 16, false); + + if(rayTrace != null && rayTrace.entityHit == event.getEntity()){ - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); - GlStateManager.pushMatrix(); + GlStateManager.pushMatrix(); - GlStateManager.disableCull(); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - // Disabling depth test allows it to be seen through everything. - GlStateManager.disableDepth(); - GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableCull(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + // Disabling depth test allows it to be seen through everything. + GlStateManager.disableDepth(); + GlStateManager.color(1, 1, 1, 1); - GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ()); + GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ()); - // This counteracts the reverse rotation behaviour when in front f5 view. - // Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example. - float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX; - GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F); - GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); + // This counteracts the reverse rotation behaviour when in front f5 view. + // Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example. + float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX; + GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - mc.renderEngine.bindTexture(targetPointerTexture); + mc.renderEngine.bindTexture(targetPointerTexture); - buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex(); - buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex(); - buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex(); - buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex(); + buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex(); + buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex(); + buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex(); + buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex(); - tessellator.draw(); + tessellator.draw(); - GlStateManager.enableCull(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); - GlStateManager.popMatrix(); + GlStateManager.popMatrix(); + } } // Summoned creature selection pointer - if(properties != null && properties.selectedMinion != null && properties.selectedMinion.get() == event.getEntity()){ + if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){ Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); @@ -296,8 +346,9 @@ public final class WizardryClientEventHandler { } // Sixth sense - if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand) && event.getEntity() != mc.player - && mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null && event.getEntity().getDistance(mc.player) < 20 + if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand) + && event.getEntity() != mc.player && mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null + && event.getEntity().getDistance(mc.player) < Spells.sixth_sense.getProperty(Spell.EFFECT_RADIUS).floatValue() * (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){ Tessellator tessellator = Tessellator.getInstance(); @@ -344,384 +395,72 @@ public final class WizardryClientEventHandler { @SubscribeEvent public static void onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){ - if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET - && Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense)){ - - GlStateManager.pushMatrix(); - - GlStateManager.disableDepth(); - GlStateManager.depthMask(false); - OpenGlHelper.glBlendFunc(770, 771, 1, 0); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.disableAlpha(); - Minecraft.getMinecraft().renderEngine.bindTexture(sixthSenseOverlayTexture); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex(); - buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D) - .endVertex(); - buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex(); - buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex(); - tessellator.draw(); - - GlStateManager.depthMask(true); - GlStateManager.enableDepth(); - GlStateManager.enableAlpha(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - - GlStateManager.popMatrix(); - } - - if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET && Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.frost)){ - - GlStateManager.pushMatrix(); - - GlStateManager.disableDepth(); - GlStateManager.depthMask(false); - OpenGlHelper.glBlendFunc(770, 771, 1, 0); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - GlStateManager.disableAlpha(); - Minecraft.getMinecraft().renderEngine.bindTexture(frostOverlayTexture); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex(); - buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D) - .endVertex(); - buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex(); - buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex(); - - tessellator.draw(); - GlStateManager.depthMask(true); - GlStateManager.enableDepth(); - GlStateManager.enableAlpha(); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - - GlStateManager.popMatrix(); + if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET){ + + if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense)){ + + OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableAlpha(); + + renderScreenOverlay(event, sixthSenseOverlayTexture); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } + + if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.frost)){ + + OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO); + GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableAlpha(); + + renderScreenOverlay(event, frostOverlayTexture); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } + + if(blinkEffectTimer > 0){ + + float alpha = ((float)blinkEffectTimer)/BLINK_EFFECT_DURATION; + + OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ZERO); + GlStateManager.color(1, 1, 1, alpha); + GlStateManager.disableAlpha(); + + renderScreenOverlay(event, blinkOverlayTexture); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } } } + + private static void renderScreenOverlay(RenderGameOverlayEvent.Post event, ResourceLocation texture){ + + GlStateManager.pushMatrix(); - // FIXME: Something in here is making the first person shadow ward rather translucent. - private static void renderShadowWardFirstPerson(EntityPlayer entityplayer){ - ItemStack wand = entityplayer.getActiveItemStack(); - if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard - || (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){ - - GlStateManager.pushMatrix(); - - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - GlStateManager.shadeModel(GL11.GL_SMOOTH); - GlStateManager.disableLighting(); - GlStateManager.disableAlpha(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - GlStateManager.translate(0, 1.2, 0); - GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0); - GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0); - - Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture); - - GlStateManager.pushMatrix(); - - GlStateManager.translate(0, 0, 1.2); - GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1); - GlStateManager.scale(1.1, 1.1, 1.1); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); - buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); - buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); - buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); - - tessellator.draw(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); - buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); - buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); - buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); - - tessellator.draw(); - - GlStateManager.popMatrix(); - - GlStateManager.shadeModel(GL11.GL_FLAT); - GlStateManager.enableLighting(); - GlStateManager.disableBlend(); - - GlStateManager.popMatrix(); - - } - } - - private static void renderShadowWardIfActive(EntityPlayer entityplayer){ - ItemStack wand = entityplayer.getActiveItemStack(); - if(WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard - || (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){ - - GlStateManager.pushMatrix(); - - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - GlStateManager.rotate(180, 0, 1, 0); - GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0); - - Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - GlStateManager.translate(0, 1.2, 0); - GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1); - GlStateManager.scale(1.1, 1.1, 1.1); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); - buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); - buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); - buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); - - tessellator.draw(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); - buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); - buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); - buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); - - tessellator.draw(); - - GlStateManager.enableLighting(); - GlStateManager.disableBlend(); - - GlStateManager.popMatrix(); - - } - } - - private static void renderWingsIfActive(EntityPlayer entityplayer, float partialTickTime){ - ItemStack wand = entityplayer.getActiveItemStack(); - if(WizardData.get(entityplayer).currentlyCasting() instanceof Flight - || (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof Flight)){ - - GlStateManager.pushMatrix(); - - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - // GlStateManager.rotate(-entityplayer.rotationYawHead, 0, 1, 0); - GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0); - // GlStateManager.rotate(180, 1, 0, 0); - - Minecraft.getMinecraft().renderEngine.bindTexture(wingTexture); - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - GlStateManager.pushMatrix(); - - GlStateManager.translate(0.1, 0.4, -0.15); - GlStateManager.rotate(20 + 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(0, 2, 0).tex(0, 0).endVertex(); - buffer.pos(2, 2, 0).tex(1, 0).endVertex(); - buffer.pos(2, 0, 0).tex(1, 1).endVertex(); - buffer.pos(0, 0, 0).tex(0, 1).endVertex(); - - tessellator.draw(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(0, 2, 0).tex(0, 0).endVertex(); - buffer.pos(0, 0, 0).tex(0, 1).endVertex(); - buffer.pos(2, 0, 0).tex(1, 1).endVertex(); - buffer.pos(2, 2, 0).tex(1, 0).endVertex(); - - tessellator.draw(); - - GlStateManager.popMatrix(); - - GlStateManager.pushMatrix(); - - GlStateManager.translate(-0.1, 0.4, -0.15); - GlStateManager.rotate(-200 - 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(0, 2, 0).tex(0, 0).endVertex(); - buffer.pos(2, 2, 0).tex(1, 0).endVertex(); - buffer.pos(2, 0, 0).tex(1, 1).endVertex(); - buffer.pos(0, 0, 0).tex(0, 1).endVertex(); - - tessellator.draw(); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - buffer.pos(0, 2, 0).tex(0, 0).endVertex(); - buffer.pos(0, 0, 0).tex(0, 1).endVertex(); - buffer.pos(2, 0, 0).tex(1, 1).endVertex(); - buffer.pos(2, 2, 0).tex(1, 0).endVertex(); - - tessellator.draw(); - - GlStateManager.popMatrix(); - - GlStateManager.enableLighting(); - GlStateManager.disableBlend(); - - GlStateManager.popMatrix(); - } - } - - private static void renderShieldFirstPerson(EntityPlayer entityplayer){ - ItemStack wand = entityplayer.getActiveItemStack(); - if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).shield != null - && (WizardData.get(entityplayer).currentlyCasting() instanceof Shield - || (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof Shield))){ - - GlStateManager.pushMatrix(); - - GlStateManager.disableCull(); - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA); - GlStateManager.shadeModel(GL11.GL_SMOOTH); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - GlStateManager.translate(0, 1.4, 0); - - GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0); - GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0); - - GlStateManager.translate(0, 0, 0.8); - - Tessellator tessellator = Tessellator.getInstance(); - - Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture); - - renderShield(tessellator); - - GlStateManager.enableLighting(); - - GlStateManager.shadeModel(GL11.GL_FLAT); - GlStateManager.enableCull(); - GlStateManager.disableBlend(); - // RenderHelper.enableStandardItemLighting(); - - GlStateManager.popMatrix(); - } - } - - private static void renderShieldIfActive(EntityPlayer entityplayer){ - ItemStack wand = entityplayer.getActiveItemStack(); - if(WizardData.get(entityplayer).shield != null && (WizardData.get(entityplayer).currentlyCasting() instanceof Shield - || (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof Shield))){ - - GlStateManager.pushMatrix(); - - GlStateManager.disableCull(); - GlStateManager.enableBlend(); - // For some reason, the old blend function (GL11.GL_SRC_ALPHA, GL11.GL_SRC_ALPHA) caused the inner - // edges to appear black, so I have changed it to this, which looks very slightly different. - GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA); - GlStateManager.shadeModel(GL11.GL_SMOOTH); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - GlStateManager.translate(0, 1.3, 0); - - // GlStateManager.rotate(180, 0, 1, 0); - GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0); - // GlStateManager.rotate(-entityplayer.rotationPitch, 1, 0, 0); - - GlStateManager.translate(0, 0, 0.8); - - Tessellator tessellator = Tessellator.getInstance(); - - Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture); - - renderShield(tessellator); - - GlStateManager.enableLighting(); - - GlStateManager.shadeModel(GL11.GL_FLAT); - GlStateManager.enableCull(); - GlStateManager.disableBlend(); - // RenderHelper.enableStandardItemLighting(); - - GlStateManager.popMatrix(); - } - } - - private static void renderShield(Tessellator tessellator){ + GlStateManager.disableDepth(); + GlStateManager.depthMask(false); + + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); - double widthOuter = 0.6d; - double heightOuter = 0.7d; - double widthInner = 0.3d; - double heightInner = 0.4d; - double depth = 0.2d; - - buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); - - buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex(); - buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); - buffer.pos(-widthInner, heightOuter, -depth).tex(0.2, 0).color(0, 0, 0, 255).endVertex(); - buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); - - buffer.pos(widthInner, heightOuter, -depth).tex(0.8, 0).color(0, 0, 0, 255).endVertex(); - buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); - buffer.pos(widthOuter, heightInner, -depth).tex(1, 0.2).color(0, 0, 0, 255).endVertex(); - buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); - - buffer.pos(widthOuter, -heightInner, -depth).tex(1, 0.8).color(0, 0, 0, 255).endVertex(); - buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); - buffer.pos(widthInner, -heightOuter, -depth).tex(0.8, 1).color(0, 0, 0, 255).endVertex(); - buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); - - buffer.pos(-widthInner, -heightOuter, -depth).tex(0.2, 1).color(0, 0, 0, 255).endVertex(); - buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); - buffer.pos(-widthOuter, -heightInner, -depth).tex(0, 0.8).color(0, 0, 0, 255).endVertex(); - buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); - - buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex(); - buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); - + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.pos(0.0D, (double)event.getResolution().getScaledHeight(), -90.0D).tex(0.0D, 1.0D).endVertex(); + buffer.pos((double)event.getResolution().getScaledWidth(), (double)event.getResolution().getScaledHeight(), -90.0D).tex(1.0D, 1.0D) + .endVertex(); + buffer.pos((double)event.getResolution().getScaledWidth(), 0.0D, -90.0D).tex(1.0D, 0.0D).endVertex(); + buffer.pos(0.0D, 0.0D, -90.0D).tex(0.0D, 0.0D).endVertex(); tessellator.draw(); + + GlStateManager.depthMask(true); + GlStateManager.enableDepth(); - buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); - - buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); - buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); - buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); - buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); - - tessellator.draw(); + GlStateManager.popMatrix(); } } diff --git a/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java b/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java new file mode 100644 index 00000000..7b98aca0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java @@ -0,0 +1,121 @@ +package electroblob.wizardry.client; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.gui.GuiSpellDisplay; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.packet.PacketControlInput; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraftforge.client.event.MouseEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.relauncher.Side; + +/** Event handler class responsible for handling wizardry's controls. */ +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class WizardryControlHandler { + + static boolean NkeyPressed = false; + static boolean BkeyPressed = false; + + // Changed to a tick event to allow mouse button keybinds + // The 'lag' that happened previously was actually because the code only fired when a keyboard key was pressed! + @SubscribeEvent + public static void onTickEvent(TickEvent.ClientTickEvent event){ + + if(event.phase == TickEvent.Phase.END) return; // Only really needs to be once per tick + + if(Wizardry.proxy instanceof ClientProxy){ + + EntityPlayer player = Minecraft.getMinecraft().player; + + if(player != null){ + + ItemStack wand = getWandInUse(player); + if(wand == null) return; + + if(ClientProxy.NEXT_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){ + if(!NkeyPressed){ + NkeyPressed = true; + selectNextSpell(wand); + } + }else{ + NkeyPressed = false; + } + + if(ClientProxy.PREVIOUS_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){ + if(!BkeyPressed){ + BkeyPressed = true; + // Packet building + selectPreviousSpell(wand); + } + }else{ + BkeyPressed = false; + } + } + } + } + + // Shift-scrolling to change spells + @SubscribeEvent + public static void onMouseEvent(MouseEvent event){ + + EntityPlayer player = Minecraft.getMinecraft().player; + ItemStack wand = getWandInUse(player); + if(wand == null) return; + + if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking() + && Wizardry.settings.shiftScrolling){ + + event.setCanceled(true); + + int d = Wizardry.settings.reverseScrollDirection ? -event.getDwheel() : event.getDwheel(); + + if(d > 0){ + selectNextSpell(wand); + }else if(d < 0){ + selectPreviousSpell(wand); + } + } + } + + private static ItemStack getWandInUse(EntityPlayer player){ + + ItemStack wand = player.getHeldItemMainhand(); + + // Only bother sending packets if the player is holding a spellcasting item with more than one spell slot + if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2){ + wand = player.getHeldItemOffhand(); + if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2) return null; + } + + return wand; + } + + private static void selectNextSpell(ItemStack wand){ + // Packet building + IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY); + WizardryPacketHandler.net.sendToServer(msg); + // GUI switch animation + ((ISpellCastingItem)wand.getItem()).selectNextSpell(wand); // Makes sure the spell is set immediately for the client + GuiSpellDisplay.playSpellSwitchAnimation(true); + Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1)); + } + + private static void selectPreviousSpell(ItemStack wand){ + // Packet building + IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY); + WizardryPacketHandler.net.sendToServer(msg); + // GUI switch animation + ((ISpellCastingItem)wand.getItem()).selectPreviousSpell(wand); // Makes sure the spell is set immediately for the client + GuiSpellDisplay.playSpellSwitchAnimation(false); + Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1)); + } +} diff --git a/src/main/java/electroblob/wizardry/client/WizardryKeyHandler.java b/src/main/java/electroblob/wizardry/client/WizardryKeyHandler.java deleted file mode 100644 index 09bcadc0..00000000 --- a/src/main/java/electroblob/wizardry/client/WizardryKeyHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.client; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.packet.PacketControlInput; -import electroblob.wizardry.packet.WizardryPacketHandler; -import net.minecraft.client.Minecraft; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemStack; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; -import net.minecraftforge.fml.common.network.simpleimpl.IMessage; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -@Mod.EventBusSubscriber(Side.CLIENT) -public class WizardryKeyHandler { - - static boolean NkeyPressed = false; - static boolean BkeyPressed = false; - - // Changed to a tick event to allow mouse button keybinds - // The 'lag' that happened previously was actually because the code only fired when a keyboard key was pressed! - @SubscribeEvent - public static void onTickEvent(TickEvent.ClientTickEvent event){ - - if(event.phase == TickEvent.Phase.END) return; // Only really needs to be once per tick - - if(Wizardry.proxy instanceof ClientProxy){ - - EntityPlayer player = Minecraft.getMinecraft().player; - - if(player != null){ - - ItemStack wand = player.getHeldItemMainhand(); - - if(!(wand.getItem() instanceof ItemWand)){ - wand = player.getHeldItemOffhand(); - // If the player isn't holding a wand, then nothing else needs to be done. - if(!(wand.getItem() instanceof ItemWand)) return; - } - } - - if(ClientProxy.NEXT_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){ - if(!NkeyPressed){ - NkeyPressed = true; - // Packet building - IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY); - WizardryPacketHandler.net.sendToServer(msg); - } - }else{ - NkeyPressed = false; - } - - if(ClientProxy.PREVIOUS_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){ - if(!BkeyPressed){ - BkeyPressed = true; - // Packet building - IMessage msg = new PacketControlInput.Message( - PacketControlInput.ControlType.PREVIOUS_SPELL_KEY); - WizardryPacketHandler.net.sendToServer(msg); - } - }else{ - BkeyPressed = false; - } - } - } -} diff --git a/src/main/java/electroblob/wizardry/client/audio/MovingSoundEntity.java b/src/main/java/electroblob/wizardry/client/audio/MovingSoundEntity.java new file mode 100644 index 00000000..11291f26 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/audio/MovingSoundEntity.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.client.audio; + +import net.minecraft.client.audio.MovingSound; +import net.minecraft.entity.Entity; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; + +// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that. +//@SideOnly(Side.CLIENT) +public class MovingSoundEntity extends MovingSound { + + protected final T source; + protected float distance = 0.0F; + + public MovingSoundEntity(T entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){ + super(sound, category); + this.source = entity; + this.repeat = repeat; + this.volume = volume; + this.pitch = pitch; + this.repeatDelay = 0; + } + + @Override + public void update(){ + + if(this.source.isDead){ + this.donePlaying = true; + }else{ + this.xPosF = (float)this.source.posX; + this.yPosF = (float)this.source.posY; + this.zPosF = (float)this.source.posZ; + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java b/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java new file mode 100644 index 00000000..651fb7fb --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java @@ -0,0 +1,94 @@ +package electroblob.wizardry.client.audio; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.ISound; +import net.minecraft.util.ITickable; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; + +import java.util.HashSet; +import java.util.Set; + +/** + * Instances of this class represent a set of sounds which together form a looped sound: start, loop and end. + * Currently this is only used for continuous spell sounds in wizardry itself, but feel free to make your own + * implementations - this class will take care of the internals. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +// See MusicTicker, this is the same idea of just storing the sounds statically client-side and ticking them +@Mod.EventBusSubscriber(Side.CLIENT) +public abstract class SoundLoop implements ITickable { + + private static final Set activeLoops = new HashSet<>(); + + private final ISound start; + private final ISound loop; + private final ISound end; + + private boolean looping = false; + private boolean needsRemoving = false; + + public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, ISoundFactory factory){ + // The reason I've gone to the effort of having a factory for these is that we need SoundLoop to have control + // over which sounds are repeated and which aren't whilst keeping them private. + this.start = factory.create(start, category, false); + this.loop = factory.create(loop, category, true); + this.end = factory.create(end, category, false); + } + + @Override + public void update(){ + // Check every tick if the start sound is done playing and if so, start the loop sound + if(!looping && !Minecraft.getMinecraft().getSoundHandler().isSoundPlaying(start)){ + Minecraft.getMinecraft().getSoundHandler().playSound(loop); + looping = true; + } + } + + /** Stops the loop part of the sound immediately and starts playing the end part. This may be called from subclasses + * or externally depending on the implementation. */ + // For continuous spell sounds it's internally + public void endLoop(){ + Minecraft.getMinecraft().getSoundHandler().stopSound(start); + Minecraft.getMinecraft().getSoundHandler().stopSound(loop); + Minecraft.getMinecraft().getSoundHandler().playSound(end); + // Can't modify activeLoops directly since we'll probably be calling this method from update(), which is + // during iteration of activeLoops so it could cause a ConcurrentModificationException + this.markForRemoval(); + } + + /** Marks this sound loop to be removed next tick. */ + protected void markForRemoval(){ + this.needsRemoving = true; + } + + // Static methods + + public static void addLoop(SoundLoop loop){ + activeLoops.add(loop); + // Do this here rather than in the constructor in case someone wants to play the loop later or reuse it + Minecraft.getMinecraft().getSoundHandler().playSound(loop.start); + } + + @SubscribeEvent + public static void tick(TickEvent.ClientTickEvent event){ + // Using the END phase means we can check for stopped sounds as soon as they are stopped (effectively), + // meaning we don't get the 'cut' between the start and loop sounds + // FIXME: Apparently this only works for dispensers. What the heck is the difference?! + if(event.phase == TickEvent.Phase.END){ + activeLoops.forEach(SoundLoop::update); + activeLoops.removeIf(s -> s.needsRemoving); + } + } + + @FunctionalInterface + public interface ISoundFactory { + ISound create(SoundEvent sound, SoundCategory category, boolean repeat); + } +} diff --git a/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java b/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java new file mode 100644 index 00000000..bb4d9b44 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java @@ -0,0 +1,117 @@ +package electroblob.wizardry.client.audio; + +import electroblob.wizardry.data.DispenserCastingData; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.audio.PositionedSound; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +/** Abstract base class for sound loops associated with spells; see subclasses below for implementations. */ +public abstract class SoundLoopSpell extends SoundLoop { + + private final Spell spell; + + public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, ISoundFactory factory, Spell spell){ + super(start, loop, end, WizardrySounds.SPELLS, factory); + this.spell = spell; + } + + @Override + public void update(){ + // This may be a bit overkill but I might as well put functionality in superclasses where possible + if(stillCasting(spell)){ + // This can't called in the same tick as endLoop because otherwise, if the spell casting stops during the + // same tick as the transition to the loop sound it will try and stop the loop immediately after it has + // started - and for whatever reason, this causes the 'Channel null in method stop' error. + super.update(); + }else{ + endLoop(); + } + } + + protected abstract boolean stillCasting(Spell spell); + + /** Implements a sound loop for continuous spells cast by entities. */ + public static class SoundLoopSpellEntity extends SoundLoopSpell { + + private final EntityLivingBase source; + + public SoundLoopSpellEntity(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, EntityLivingBase source, float volume, float pitch){ + super(start, loop, end, (sound, category, repeat) -> new MovingSoundEntity<>(source, sound, category, volume, pitch, repeat), spell); + this.source = source; + } + + @Override + protected boolean stillCasting(Spell spell){ + return WizardryUtilities.isCasting(source, spell); + } + } + + public static abstract class SoundLoopSpellPosition extends SoundLoopSpell { + + public SoundLoopSpellPosition (SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, + double x, double y, double z, float sndVolume, float sndPitch){ + // Huh, I actually found a use for a non-static initialiser block - hence the double curly brackets... + super(start, loop, end, (sound, category, r) -> new PositionedSound(sound, category){{ + // ...et voila, we can just set protected fields as we please using external variables + this.xPosF = (float)x; + this.yPosF = (float)y; + this.zPosF = (float)z; + this.repeat = r; + this.volume = sndVolume; + this.pitch = sndPitch; + }}, spell); + } + } + + /** Implements a sound loop for continuous spells cast by dispensers. */ + public static class SoundLoopSpellDispenser extends SoundLoopSpellPosition { + + private final TileEntityDispenser source; + + public SoundLoopSpellDispenser(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, World world, + double x, double y, double z, float sndVolume, float sndPitch){ + super(start, loop, end, spell, x, y, z, sndVolume, sndPitch); + + TileEntity tileentity = world.getTileEntity(new BlockPos(x, y, z)); + + if(tileentity instanceof TileEntityDispenser) this.source = (TileEntityDispenser)tileentity; + else throw new NullPointerException(String.format("Playing continuous spell sound: no dispenser found at %s, %s, %s", x, y, z)); + } + + @Override + protected boolean stillCasting(Spell spell){ + return DispenserCastingData.get(source).currentlyCasting() == spell; + } + + } + + /** Implements a sound loop for continuous spells cast at a position (via commands). */ + public static class SoundLoopSpellPosTimed extends SoundLoopSpellPosition { + + private int timeLeft; + + public SoundLoopSpellPosTimed(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, int duration, + double x, double y, double z, float sndVolume, float sndPitch){ + super(start, loop, end, spell, x, y, z, sndVolume, sndPitch); + this.timeLeft = duration; + } + + @Override + public void update(){ + super.update(); + timeLeft--; + } + + @Override + protected boolean stillCasting(Spell spell){ + return timeLeft > 0; + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java b/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java new file mode 100644 index 00000000..9fe07cb1 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java @@ -0,0 +1,449 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.IManaStoringItem; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.IWorkbenchItem; +import electroblob.wizardry.packet.PacketControlInput; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; +import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; +import electroblob.wizardry.util.WandHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.input.Keyboard; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class GuiArcaneWorkbench extends GuiContainer { + + private GuiButton applyBtn; + public static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, + "textures/gui/arcane_workbench.png"); + + private IInventory playerInventory; + private IInventory arcaneWorkbenchInventory; + + private static final int TOOLTIP_WIDTH = 164; + + /** We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it. + * For calculations, we use the size without the tooltip, which is stored in this constant. */ + private static final int MAIN_GUI_WIDTH = 176; + + private static final int RUNE_LEFT = 38; + private static final int RUNE_TOP = 22; + private static final int RUNE_WIDTH = 100; + private static final int RUNE_HEIGHT = 100; + + private static final int HALO_DIAMETER = 156; + + private static final int TEXTURE_WIDTH = 512; + private static final int TEXTURE_HEIGHT = 256; + + private int animationTimer = 0; + private static final int ANIMATION_DURATION = 20; + + public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){ + super(new ContainerArcaneWorkbench(invPlayer, entity)); + this.playerInventory = invPlayer; + this.arcaneWorkbenchInventory = entity; + xSize = MAIN_GUI_WIDTH; + ySize = 220; + } + + // Huh, didn't realise this method existed. Pretty neat. + @Override + public void updateScreen(){ + if(animationTimer > 0) animationTimer--; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + + this.drawDefaultBackground(); + + GlStateManager.color(1, 1, 1, 1); // Just in case + + Slot slot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT); + + // Tests if there is a wand in the workbench and edits the positioning accordingly + if(slot.getHasStack() && slot.getStack().getItem() instanceof IWorkbenchItem + && ((IWorkbenchItem)slot.getStack().getItem()).showTooltip(slot.getStack())){ + xSize = MAIN_GUI_WIDTH + TOOLTIP_WIDTH; + guiLeft = (this.width - this.xSize) / 2; + this.applyBtn.x = (this.width - TOOLTIP_WIDTH) / 2 + 64; + }else{ + xSize = MAIN_GUI_WIDTH; + guiLeft = (this.width - this.xSize) / 2; + this.applyBtn.x = this.width / 2 + 64; + } + + this.applyBtn.enabled = slot.getHasStack(); + + super.drawScreen(mouseX, mouseY, partialTicks); + + // Required now, or item mouseover tooltips won't render. + this.renderHoveredToolTip(mouseX, mouseY); + } + + @Override + public void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){ + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + // Animation + + // Grey background + DrawingUtils.drawTexturedRect(guiLeft + RUNE_LEFT, guiTop + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0, + RUNE_WIDTH, RUNE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + // Yellow 'halo' + if(animationTimer > 0){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + + int x = guiLeft + RUNE_LEFT + RUNE_WIDTH/2; + int y = guiTop + RUNE_TOP + RUNE_HEIGHT/2; + + float scale = (animationTimer + partialTicks)/ANIMATION_DURATION; + scale = (float)(1 - Math.pow(1-scale, 1.4f)); // Makes it slower at the start and speed up + GlStateManager.scale(scale, scale, 1); + GlStateManager.translate(x/scale, y/scale, 0); + + DrawingUtils.drawTexturedRect(-HALO_DIAMETER /2, -HALO_DIAMETER /2, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, RUNE_HEIGHT, + HALO_DIAMETER, HALO_DIAMETER, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + + // Main inventory + DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + float opacity = (animationTimer + partialTicks)/ANIMATION_DURATION; + + // Changing slots + for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){ + + Slot slot = this.inventorySlots.getSlot(i); + + if(slot.xPos >= 0 && slot.yPos >= 0){ + // Slot background + DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + // Slot animation + // IDEA: Somehow replace with intelligent check for whether the spell actually got applied + if(animationTimer > 0 && slot.getHasStack()){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.color(1, 1, 1, opacity); + + DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 36, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + } + } + + // Crystal + upgrade slot animations + if(animationTimer > 0){ + + Slot crystals = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CRYSTAL_SLOT); + Slot upgrades = this.inventorySlots.getSlot(ContainerArcaneWorkbench.UPGRADE_SLOT); + + if(crystals.getHasStack()){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.color(1, 1, 1, opacity); + + DrawingUtils.drawTexturedRect(guiLeft + crystals.xPos - 8, guiTop + crystals.yPos - 8, + MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + + if(upgrades.getHasStack()){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.color(1, 1, 1, opacity); + + DrawingUtils.drawTexturedRect(guiLeft + upgrades.xPos - 8, guiTop + upgrades.yPos - 8, + MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + } + + // Tooltip only drawn if there is a wand + if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){ + + ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack(); + + if(!(stack.getItem() instanceof IWorkbenchItem)){ + Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!"); + return; + } + + if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){ + + // Tooltip box + DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH, guiTop, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + int y = guiTop + 20; + + if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){ + y += 14; + } + + if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){ + + Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack); + + GlStateManager.enableBlend(); + + for(Spell spell : spells){ + + boolean discovered = true; + + if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){ + discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell); + } + // As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on + // mods to add their own. + Minecraft.getMinecraft().renderEngine + .bindTexture(discovered ? spell.getElement().getIcon() : Element.MAGIC.getIcon()); + + // Renders the little element icon + DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, y, 8, 8); + + y += 10; + } + } + + GlStateManager.disableBlend(); + + int x = 0; + y += 16; + + // Look how much shorter this is with the WandHelper class! + for(Item item : WandHelper.getSpecialUpgrades()){ + + int level = WandHelper.getUpgradeLevel(stack, item); + + if(level > 0){ + ItemStack stack1 = new ItemStack(item, level); + GlStateManager.enableDepth(); + this.itemRender.renderItemAndEffectIntoGUI(stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y); + this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y, + null); + x += 18; + GlStateManager.disableDepth(); + } + } + } + } + + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + // Fixes the bug that caused the slot highlight to render opaque. I don't know why it works, it just works! + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + } + + @Override + protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){ + + GlStateManager.color(1, 1, 1, 1); // Just in case + + this.fontRenderer + .drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName() + : I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752); + this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName() + : I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752); + + if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){ + + ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack(); + + if(!(stack.getItem() instanceof IWorkbenchItem)){ + Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!"); + return; + } + + if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){ + + int y = 6; + + this.fontRenderer.drawStringWithShadow("\u00A7f" + stack.getDisplayName(), MAIN_GUI_WIDTH + 6, y, 0); + + if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){ + y += 14; + this.fontRenderer.drawStringWithShadow( + "\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana") + + " " + ((IManaStoringItem)stack.getItem()).getMana(stack) + "/" + + ((IManaStoringItem)stack.getItem()).getManaCapacity(stack), + MAIN_GUI_WIDTH + 6, y, 0); + } + + y += 14; + + if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){ + + Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack); + + for(Spell spell : spells){ + + boolean discovered = true; + + if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){ + discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell); + } + + if(discovered){ + this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), MAIN_GUI_WIDTH + 16, y, 0); + }else{ + this.mc.standardGalacticFontRenderer.drawStringWithShadow( + "\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), MAIN_GUI_WIDTH + 16, y, 0); + } + y += 10; + } + } + + if(WandHelper.getTotalUpgrades(stack) > 0){ + + y += 6; + + this.fontRenderer.drawStringWithShadow("\u00A7f" + I18n.format("container." + + Wizardry.MODID + ":arcane_workbench.upgrades"), MAIN_GUI_WIDTH + 6, y, 0); + + int x = 0; + y += 10; + + // Wand upgrade tooltips + for(Item item : WandHelper.getSpecialUpgrades()){ + + int level = WandHelper.getUpgradeLevel(stack, item); + + if(level > 0){ + // The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is + // relative to the GUI but the POINT isn't. + if(isPointInRegion(MAIN_GUI_WIDTH + 6 + x, y, 16, 16, mouseX, mouseY)){ + ItemStack stack1 = new ItemStack(item, level); + this.renderToolTip(stack1, mouseX - guiLeft, mouseY - guiTop); + } + x += 18; + } + } + } + } + } + } + + @Override + public void initGui(){ + this.mc.player.openContainer = this.inventorySlots; + this.guiLeft = (this.width - this.xSize) / 2; + this.guiTop = (this.height - this.ySize) / 2; + Keyboard.enableRepeatEvents(true); + this.buttonList.clear(); + this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 64, this.height / 2 + 3)); + } + + @Override + public void onGuiClosed(){ + super.onGuiClosed(); + Keyboard.enableRepeatEvents(false); + } + + @Override + protected void actionPerformed(GuiButton button){ + if(button.enabled){ + if(button.id == 0){ + // Packet building + IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON); + WizardryPacketHandler.net.sendToServer(msg); + // Sound + Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord( + WizardrySounds.BLOCK_ARCANE_WORKBENCH_SPELLBIND, 1)); + // Animation + animationTimer = 20; + } + } + } + + private class GuiButtonApply extends GuiButton { + + public GuiButtonApply(int id, int x, int y){ + super(id, x, y, 16, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply")); + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + // Whether the button is highlighted + this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + + int k = 72; + int l = 220; + //int colour = 14737632; + + if(this.enabled){ + if(this.hovered){ + k += this.width * 2; + //colour = 16777120; + } + }else{ + k += this.width; + //colour = 10526880; + } + + DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 512, 256); + //this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2, + // this.y + (this.height - 8) / 2, colour); + } + } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL); + event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/GuiButtonInvisible.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java similarity index 67% rename from src/main/java/electroblob/wizardry/client/GuiButtonInvisible.java rename to src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java index 70e9c2e4..7156bb36 100644 --- a/src/main/java/electroblob/wizardry/client/GuiButtonInvisible.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonInvisible.java @@ -1,12 +1,10 @@ -package electroblob.wizardry.client; +package electroblob.wizardry.client.gui; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -class GuiButtonInvisible extends GuiButton { +//@SideOnly(Side.CLIENT) +public class GuiButtonInvisible extends GuiButton { public GuiButtonInvisible(int id, int x, int y, int width, int height){ super(id, x, y, width, height, ""); diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java new file mode 100644 index 00000000..bf50192c --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java @@ -0,0 +1,90 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.packet.PacketControlInput; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.spell.Resurrection; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiGameOver; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class GuiButtonResurrect extends GuiButton { + + private static int timeSinceDeath = -1; + + private final String translationKey; + + public GuiButtonResurrect(int id, int x, int y, String translationKey){ + super(id, x, y, I18n.format(translationKey + "_wait", Resurrection.getRemainingWaitTime(timeSinceDeath))); + this.translationKey = translationKey; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks){ + int waitTime = Resurrection.getRemainingWaitTime(timeSinceDeath); + this.enabled = waitTime == 0; + this.displayString = I18n.format(translationKey + (waitTime == 0 ? "_ready" : "_wait"), waitTime); + super.drawButton(mc, mouseX, mouseY, partialTicks); + } + + // Event handlers + + @SubscribeEvent + public static void onClientTickEvent(TickEvent.ClientTickEvent event){ + if(event.phase == TickEvent.Phase.START && timeSinceDeath >= 0) timeSinceDeath++; + } + + @SubscribeEvent + public static void onGuiScreenInitEvent(GuiScreenEvent.InitGuiEvent event){ + + if(event.getGui() instanceof GuiGameOver && ItemArtefact.isArtefactActive(Minecraft.getMinecraft().player, WizardryItems.amulet_resurrection) + && WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream().anyMatch(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player))){ + + event.getButtonList().add(new GuiButtonResurrect(event.getButtonList().size(), event.getGui().width / 2 - 100, + event.getGui().height / 4 + 120, "spell." + Spells.resurrection.getRegistryName() + ".button")); + timeSinceDeath = 0; + } + } + + @SubscribeEvent + public static void onGuiScreenActionPerformedEvent(GuiScreenEvent.ActionPerformedEvent event){ + + if(event.getGui() instanceof GuiGameOver){ + + ItemStack stack = WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream() + .filter(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player)).findFirst().orElse(null); + + if(stack != null){ + + if(event.getButton() instanceof GuiButtonResurrect && timeSinceDeath >= 0){ + // Cast resurrection on the client player and notify the server to do the same + // ISpellCastingItem#canCast already checked in Resurrection#canStackResurrect + ((ISpellCastingItem)stack.getItem()).cast(stack, Spells.resurrection, Minecraft.getMinecraft().player, EnumHand.MAIN_HAND, 0, new SpellModifiers()); + WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.RESURRECT_BUTTON)); + + }else if(!Minecraft.getMinecraft().world.getGameRules().getBoolean("keepInventory")){ + // Any other button drops the wand (N.B. this should be inside the stack != null check or it'll send + // packets unnecessarily and generate incorrect warnings + WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.CANCEL_RESURRECT)); + } + + timeSinceDeath = -1; + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/GuiPortableCrafting.java b/src/main/java/electroblob/wizardry/client/gui/GuiPortableCrafting.java similarity index 97% rename from src/main/java/electroblob/wizardry/client/GuiPortableCrafting.java rename to src/main/java/electroblob/wizardry/client/gui/GuiPortableCrafting.java index 5fc05f3a..88162dbf 100644 --- a/src/main/java/electroblob/wizardry/client/GuiPortableCrafting.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiPortableCrafting.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client; +package electroblob.wizardry.client.gui; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.GlStateManager; diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java b/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java new file mode 100644 index 00000000..306abffb --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java @@ -0,0 +1,121 @@ +package electroblob.wizardry.client.gui; + +import com.google.common.collect.ImmutableMap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; + +import java.util.Map; + +public class GuiSpellBook extends GuiScreen { + + private int xSize, ySize; + private Spell spell; + + private static final Map textures = ImmutableMap.of( + Tier.NOVICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_novice.png"), + Tier.APPRENTICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_apprentice.png"), + Tier.ADVANCED, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_advanced.png"), + Tier.MASTER, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_master.png")); + + public GuiSpellBook(Spell spell){ + super(); + xSize = 288; + ySize = 180; + this.spell = spell; + } + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3){ + + int xPos = this.width / 2 - xSize / 2; + int yPos = this.height / 2 - this.ySize / 2; + + EntityPlayer player = Minecraft.getMinecraft().player; + + boolean discovered = true; + if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null + && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ + discovered = false; + } + + GlStateManager.color(1, 1, 1, 1); // Just in case + + // Draws spell illustration on opposite page, underneath the book so it shows through the hole. + Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon()); + DrawingUtils.drawTexturedRect(xPos + 146, yPos + 20, 0, 0, 128, 128, 128, 128); + + Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.getTier())); + DrawingUtils.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256); + + super.drawScreen(par1, par2, par3); + + if(discovered){ + this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 15, 0); + this.fontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26, 0x777777); + }else{ + this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17, + yPos + 15, 0); + this.mc.standardGalacticFontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26, + 0x777777); + } + + //this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 35, 0); + + if(spell.getTier() == Tier.NOVICE){ + // Basic is usually white but this doesn't show up. + this.fontRenderer.drawString("Tier: \u00A77" + Tier.NOVICE.getDisplayName(), xPos + 17, yPos + 45, 0); + }else{ + this.fontRenderer.drawString("Tier: " + spell.getTier().getDisplayNameWithFormatting(), xPos + 17, yPos + 45, 0); + } + + String element = "Element: " + spell.getElement().getFormattingCode() + spell.getElement().getDisplayName(); + if(!discovered) element = "Element: ?"; + this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0); + + String manaCost = "Mana Cost: " + spell.getCost(); + if(spell.isContinuous) manaCost = "Mana Cost: " + spell.getCost() + "/second"; + if(!discovered) manaCost = "Mana Cost: ?"; + this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 69, 0); + + if(discovered){ + this.fontRenderer.drawSplitString(spell.getDescription(), xPos + 17, yPos + 83, 118, 0); + }else{ + this.mc.standardGalacticFontRenderer.drawSplitString( + SpellGlyphData.getGlyphDescription(spell, player.world), xPos + 17, yPos + 83, 118, 0); + } + } + + public void initGui(){ + super.initGui(); + Keyboard.enableRepeatEvents(true); + this.buttonList.clear(); + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1)); + } + + public void onGuiClosed(){ + super.onGuiClosed(); + Keyboard.enableRepeatEvents(false); + } + + @Override + public boolean doesGuiPauseGame(){ + return Wizardry.settings.booksPauseGame; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java b/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java new file mode 100644 index 00000000..85e2aa61 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java @@ -0,0 +1,589 @@ +package electroblob.wizardry.client.gui; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import electroblob.wizardry.Settings; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.ClientProxy; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.MixedFontRenderer; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.WandHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHandSide; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; +import java.util.Map.Entry; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class GuiSpellDisplay { + + private static final ResourceLocation INDEX = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud/_index.json"); + + /** A map which stores all loaded HUD skin objects. This gets wiped on resource pack reload and repopulated with + * mappings as specified by {@code _index.json} (these stack between resource packs). The keys in the map correspond + * to the keys in {@code _index.json}, and are sorted in that order, with skins belonging to resource packs sorted + * from lowest to highest priority. The skins in the base mod will therefore always be first. (It should be noted, + * however, that in the gui itself the skins are always sorted in alphabetical order for some reason.) */ + private static final Map skins = new LinkedHashMap<>(14); // 14 is the number of skins packaged with the mod + + private static final Gson gson = new Gson(); + + /** Width and height of the spell icon (very unlikely to change!) */ + private static final int SPELL_ICON_SIZE = 32; + /** Number of ticks the spell switching animation plays for. */ + private static final int SPELL_SWITCH_TIME = 4; + /** Scale of the next/previous spell names. */ + private static final float SPELL_NAME_SCALE = 0.5f; + /** Opacity of the next/previous spell names, as a fraction. */ + private static final float SPELL_NAME_OPACITY = 0.3f; + + private static final int HALF_HOTBAR_WIDTH = 97; // Half the width of the hotbar, plus a bit for clearance + private static final int OFFHAND_SLOT_WIDTH = 29; // Width of the offhand slot plus the gap between it and the hotbar + + /** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to + * the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */ + private static int switchTimer = 0; + + /** + * Starts the spell switching animation. + * @param next True to switch to the next spell, false for the previous spell. + */ + public static void playSpellSwitchAnimation(boolean next){ + switchTimer = next ? SPELL_SWITCH_TIME : -SPELL_SWITCH_TIME; + } + + /** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */ + public static Set getSkinKeys(){ + return Collections.unmodifiableSet(skins.keySet()); + } + + /** Returns an unmodifiable view of the loaded spell HUD skins map. */ + public static Map getSkins(){ + return Collections.unmodifiableMap(skins); + } + + /** Returns the skin that corresponds to the given key. */ + public static Skin getSkin(String key){ + return skins.get(key); + } + + // Normally when extending Gui, you'd have to have an instance to access its methods. However, we're not actually + // using any of them, so this class may as well not bother and just be a static event handler. Neat! + @SubscribeEvent + public static void draw(RenderGameOverlayEvent event){ + + Minecraft mc = Minecraft.getMinecraft(); + + EntityPlayer player = mc.player; + + if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD! + + // If the player has a wand in each hand, only displays for the one in the main hand. + + ItemStack wand = player.getHeldItemMainhand(); + boolean mainHand = true; + + if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))){ + wand = player.getHeldItemOffhand(); + mainHand = false; + // If the player isn't holding a spellcasting item that shows the HUD, then nothing else needs to be done. + if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) return; + } + + int width = event.getResolution().getScaledWidth(); + int height = event.getResolution().getScaledHeight(); + + boolean flipX = Wizardry.settings.spellHUDPosition.flipX; + boolean flipY = Wizardry.settings.spellHUDPosition.flipY; + + if(Wizardry.settings.spellHUDPosition.dynamic){ + // ............. | This bit is true if the wand is on the left, false if it is on the right + flipX = flipX == ((mainHand ? player.getPrimaryHand() : player.getPrimaryHand().opposite()) == EnumHandSide.LEFT); + } + + Skin skin = skins.get(Wizardry.settings.spellHUDSkin); + + if(skin == null){ + + Wizardry.logger.info("The spell HUD skin '" + Wizardry.settings.spellHUDSkin + "' specified in the config" + + " did not match any of the loaded skins; using the default skin as a fallback."); + + skin = skins.get(Settings.DEFAULT_HUD_SKIN_KEY); + + if(skin == null){ + Wizardry.logger.warn("The default spell HUD skin is missing! A resource pack must have overridden it" + + " with an invalid JSON file (default.json), please try again without any resource packs."); + return; + } + } + + GlStateManager.pushMatrix(); + + // 'Origin' of the spell hud (bottom left corner of the actual texture, always in the corner of the screen) + int x = flipX ? width : 0; + int y = flipY ? 0: height; + + // The space available to render the spell HUD + float xSpace = (float)(width/2 - HALF_HOTBAR_WIDTH); + if(!player.getHeldItemOffhand().isEmpty() + // Tests whether the offhand slot is rendered on the same side of the hotbar as the spell HUD + && (player.getPrimaryHand() == EnumHandSide.LEFT) == flipX){ + xSpace -= OFFHAND_SLOT_WIDTH; + } + + // If the skin is at the bottom and the screen width is too small, scale it to avoid the hotbar and offhand + if(!flipY && skin.getWidth() > xSpace){ // width/2 - 91 - 29 taken from GuiInGame line 547 + float scale = xSpace / skin.getWidth(); + GlStateManager.scale(scale, scale, 1); + x = MathHelper.ceil(x/scale); + y = MathHelper.ceil(y/scale); + } + + Spell spell = WandHelper.getCurrentSpell(wand); + int cooldown = WandHelper.getCurrentCooldown(wand); + int maxCooldown = WandHelper.getCurrentMaxCooldown(wand); + + if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){ + + float animationProgress = Math.signum(switchTimer) * ((SPELL_SWITCH_TIME - Math.abs(switchTimer) + + event.getPartialTicks()) / SPELL_SWITCH_TIME); + + String prevSpellName = getFormattedSpellName(WandHelper.getPreviousSpell(wand), player, WandHelper.getPreviousCooldown(wand)); + String spellName = getFormattedSpellName(spell, player, cooldown); + String nextSpellName = getFormattedSpellName(WandHelper.getNextSpell(wand), player, WandHelper.getNextCooldown(wand)); + + skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress); + + }else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){ + + boolean discovered = true; + + if(!player.isCreative() && WizardData.get(player) != null){ + discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); + } + + ResourceLocation icon = discovered ? spell.getIcon() : Spells.none.getIcon(); + + float progress = 1; + // Doesn't really matter what progress is when in creative, but we might as well avoid the calculation. + if(!player.isCreative() && !spell.isContinuous){ + // Subtracted partial tick time to make it smoother + progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + event.getPartialTicks()) / maxCooldown; + } + + skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative()); + + } + + GlStateManager.popMatrix(); + } + + /** + * Gets the name of the given spell, with formatting added according to its cooldown and whether the given player + * has discovered it. + * @param spell The spell to get the name of. + * @param player The player to test for having discovered the given spell. + * @param cooldown The spell's current cooldown. + * @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}. + */ + private static String getFormattedSpellName(Spell spell, EntityPlayer player, int cooldown){ + + boolean discovered = true; + + if(!player.isCreative() && WizardData.get(player) != null){ + discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); + } + + // Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect + String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.getElement().getFormattingCode(); + if(!discovered) format = "\u00A79"; + + String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world); + name = format + name; + if(!discovered) name = "#" + name + "#"; + + return name; + } + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + if(event.getEntity() == Minecraft.getMinecraft().player){ // Makes sure this only gets called once each tick. + if(switchTimer > 0) switchTimer--; + else if(switchTimer < 0) switchTimer++; + } + } + + /** Called from preInit in the main mod class (via the proxies) to initialise the HUD skins, and again on each + * resource reload. */ + public static void loadSkins(IResourceManager manager){ + + try { + + List indexFiles = manager.getAllResources(INDEX); + + skins.clear(); // Wipes the skins map before repopulating it + + for(IResource indexFile : indexFiles){ + + BufferedReader reader = new BufferedReader(new InputStreamReader(indexFile.getInputStream())); + + JsonElement je = gson.fromJson(reader, JsonElement.class); + JsonObject json = je.getAsJsonObject(); + + // Need to iterate over these since we don't know what they're called or how many there are + for(Entry entry : json.entrySet()){ + + String key = entry.getKey(); // Find out what each element is called, this will be the skins map key + + // It's a good idea to use JsonUtils because it produces more helpful error messages (that pack + // makers should understand). + + JsonObject skinData = JsonUtils.getJsonObject(json, key); + + String[] splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "texture")); + ResourceLocation texture = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".png"); + + splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "metadata")); + ResourceLocation metadata = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".json"); + + // The nice thing about this is it overwrites the existing mapping, and since the index files are in + // ascending order of resource pack priority, this means resource packs can override existing skins + // by specifying one with the same key. + skins.put(key, new Skin(texture, metadata)); + } + } + + } catch (IOException e){ + // If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing + // else needs to be done. + Wizardry.logger.error("Error reading spell HUD skin index file: ", e); + } + } + + /** + * Instances of this class represent individual HUD skins, complete with texture and all necessary metadata. This + * class serves to separate the logic behind the spell HUD from its actual rendering. + * All information and processing done within this class relates only to the actual drawing; spells and such like + * must be queried outside of this class and fed into the methods as appropriate. + * + * @author Electroblob + * @since Wizardry 4.2 + */ + public static class Skin { + + /** The texture file for this skin. */ + private final ResourceLocation texture; + + /** The display name of the skin in the config menu. */ + private String name; + /** The description of the skin shown when its button is hovered over in the config menu. */ + private String description; + + /** Width of the entire spell HUD. */ + private int width; + /** Height of the entire spell HUD. */ + private int height; + + /** Whether the entire HUD is flipped when on the right-hand side of the screen. If this is false, the HUD will + * still appear on the right-hand side of the screen, but in the same orientation as on the left-hand side. */ + private boolean mirrorX; + /** Whether the entire HUD is flipped when at the top of the screen. If this is false, the HUD will + * still appear at the top of the screen, but in the same orientation as at the bottom. */ + private boolean mirrorY; + + /** Distance of the spell icon from the left edge of the screen (or right edge when flipped). */ + private int spellIconInsetX; + /** Distance of the spell icon from the bottom edge of the screen (or top edge when flipped). */ + private int spellIconInsetY; + + /** Distance of the spell name from the left edge of the screen (or right edge when flipped). */ + private int textInsetX; + /** Distance of the spell name from the bottom edge of the screen (or the top edge when flipped). */ + private int textInsetY; + + /** Horizontal distance between the start of adjacent spell names. */ + private int cascadeOffsetX; + /** Vertical distance between the start of adjacent spell names. */ + private int cascadeOffsetY; + + /** Distance of the cooldown bar from the left edge of the screen (or right edge when flipped). */ + private int cooldownBarX; + /** Distance of the cooldown bar from the bottom edge of the screen (or top edge when flipped). */ + private int cooldownBarY; + /** Length of the cooldown bar. */ + private int cooldownBarLength; + /** Height of the cooldown bar. */ + private int cooldownBarHeight; + + /** Whether the cooldown bar is flipped horizontally when the HUD is on the right-hand side of the screen. */ + private boolean cooldownBarMirrorX; + /** Whether the cooldown bar is flipped vertically when the HUD is at the top of the screen. */ + private boolean cooldownBarMirrorY; + + /** Whether the cooldown bar progress overlay is shown when the cooldown bar is full (i.e. when progress = 1). */ + private boolean showCooldownWhenFull; + + private final Minecraft mc; + + /** Creates a new skin with the given texture and reads its values from the given metadata json file. */ + public Skin(ResourceLocation texture, ResourceLocation metadata){ + + mc = Minecraft.getMinecraft(); + + this.texture = texture; + + try { + // This time we only want the highest priority file + IResource metadataFile = Minecraft.getMinecraft().getResourceManager().getResource(metadata); + BufferedReader reader = new BufferedReader(new InputStreamReader(metadataFile.getInputStream())); + + JsonElement je = gson.fromJson(reader, JsonElement.class); + + parseJson(je.getAsJsonObject()); + + } catch (IOException e){ + // If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing + // else needs to be done. + Wizardry.logger.error("Error reading spell HUD skin metadata file: ", e); + } + } + + /** Returns the display name of this HUD skin, which is shown in the config GUI. */ + public String getName(){ + return name; + } + + /** Returns the description of this HUD skin, which is shown in its tooltip in the config GUI. */ + public String getDescription(){ + return description; + } + + /** Returns the overall width of this spell HUD skin. */ + public int getWidth(){ + return width; + } + + /** Returns the overall height of this spell HUD skin. */ + public int getHeight(){ + return height; + } + + /** Actually reads the metadata values for this skin from the json file. */ + private void parseJson(JsonObject json){ + + // For now, all the keys must be present for the metadata file to work (the only ones that could reasonably + // have a default anyway are the mirror values). + + name = JsonUtils.getString(json, "name"); + description = JsonUtils.getString(json, "description"); + + width = JsonUtils.getInt(json, "width"); + if(width > 128) Wizardry.logger.warn("The width of the spell HUD skin " + name + " exceeds 128, this may cause it to render strangely."); + height = JsonUtils.getInt(json, "height"); + + JsonObject mirror = JsonUtils.getJsonObject(json, "mirror"); + mirrorX = JsonUtils.getBoolean(mirror, "x"); + mirrorY = JsonUtils.getBoolean(mirror, "y"); + + JsonObject spellIconInset = JsonUtils.getJsonObject(json, "spell_icon_inset"); + spellIconInsetX = JsonUtils.getInt(spellIconInset, "x"); + spellIconInsetY = JsonUtils.getInt(spellIconInset, "y"); + + JsonObject textInset = JsonUtils.getJsonObject(json, "text_inset"); + textInsetX = JsonUtils.getInt(textInset, "x"); + textInsetY = JsonUtils.getInt(textInset, "y"); + + JsonObject cascadeOffset = JsonUtils.getJsonObject(json, "spell_cascade_offset"); + cascadeOffsetX = JsonUtils.getInt(cascadeOffset, "x"); + cascadeOffsetY = JsonUtils.getInt(cascadeOffset, "y"); + + JsonObject cooldownBar = JsonUtils.getJsonObject(json, "cooldown_bar"); + cooldownBarX = JsonUtils.getInt(cooldownBar, "x"); + cooldownBarY = JsonUtils.getInt(cooldownBar, "y"); + cooldownBarLength = JsonUtils.getInt(cooldownBar, "length"); + cooldownBarHeight = JsonUtils.getInt(cooldownBar, "height"); + + JsonObject cooldownBarMirror = JsonUtils.getJsonObject(cooldownBar, "mirror"); + cooldownBarMirrorX = JsonUtils.getBoolean(cooldownBarMirror, "x"); + cooldownBarMirrorY = JsonUtils.getBoolean(cooldownBarMirror, "y"); + + showCooldownWhenFull = JsonUtils.getBoolean(cooldownBar, "show_when_full"); + + } + + // The idea of these methods is that everything in here relates only to the actual drawing of the HUD. In other + // words, all processing of which spells to draw and so on is done outside of here. This means that the config + // GUI can easily display its preview without having a player or wand stack object to query. + + /** + * Draws the background layer of this HUD skin at the given position with the given orientations, with the given + * spell icon and cooldown bar progress. + * + * @param x The x-coordinate of the corner of the spell HUD. The bottom left corner of the actual texture + * will always be at this position unless mirrorX/Y is false, so for example if flipX is false and flipY is true, + * this will be the corner of the HUD that is closest to the top left corner of the screen. + * @param y The y-coordinate of the corner of the spell HUD; see above. + * @param flipX Whether to flip the HUD horizontally. + * @param flipY Whether to flip the HUD vertically. + * @param icon A {@code ResourceLocation} corresponding to the icon of the selected spell. + * @param cooldownBarProgress The fraction of the cooldown bar to draw; must be between 0 and 1 (inclusive). + * @param creativeMode True to draw the creative mode HUD, false for the survival mode version. + */ + public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode){ + + // Moves the origin if the HUD does not mirror; neatens the rest of the code. + if(flipX && !mirrorX) x -= width; + if(flipY && !mirrorY) y += height; + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.color(1, 1, 1); + + // Spell illustration - this is now done first so it is behind the HUD texture + mc.renderEngine.bindTexture(icon); + + int x1 = flipX && mirrorX ? x - spellIconInsetX - SPELL_ICON_SIZE : x + spellIconInsetX; + // y is upside-down so this is the other way round + int y1 = flipY && mirrorY ? y + spellIconInsetY : y - spellIconInsetY - SPELL_ICON_SIZE; + + DrawingUtils.drawTexturedRect(x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE); + + // Background of spell hud + mc.renderEngine.bindTexture(texture); + + x1 = flipX && mirrorX ? x - width : x; + y1 = flipY && mirrorY ? y : y - height; + // The 128 here is a uv value, not a dimension, and hence is left as a hardcoded number. + // TODO: Since the HUD is wider than it is tall, perhaps the creative mode texture should be in the bottom half instead of the right half? + DrawingUtils.drawTexturedFlippedRect(x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY); + + // Cooldown bar + if(!creativeMode && cooldownBarProgress > 0 && (showCooldownWhenFull || cooldownBarProgress < 1)){ + + int l = (int)(cooldownBarProgress * cooldownBarLength); + + x1 = flipX && mirrorX ? x - cooldownBarX - (cooldownBarMirrorX ? l : cooldownBarLength) : x + cooldownBarX; + y1 = flipY && mirrorY ? y + cooldownBarY : y - cooldownBarY - cooldownBarHeight; + + int u = cooldownBarX; // This doesn't change, even when cooldownBarMirrorX is true, because it should + int v = height; // always start with the left-hand in the actual texture file + + DrawingUtils.drawTexturedFlippedRect(x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY); + } + + GlStateManager.popMatrix(); + + // Blend needs to be left enabled here because otherwise the hotbar becomes opaque + } + + /** + * Draws the text layer of this HUD skin at the given position with the given orientations, with the given + * spell name strings. + * + * @param x The x-coordinate of the corner of the spell HUD. The bottom left corner of the actual texture + * will always be at this position, so for example if flipX is false and flipY is true, this will be the corner + * of the HUD that is closest to the top left corner of the screen. + * @param y The y-coordinate of the corner of the spell HUD; see above. + * @param flipX Whether to flip the HUD horizontally. + * @param flipY Whether to flip the HUD vertically. + * @param prevSpellName The name of the previous spell. This string will be drawn directly using the + * {@link MixedFontRenderer}; as such it should be supplied with formatting codes and # characters already + * appended. + * @param spellName The name of the currently selected spell; see above. + * @param nextSpellName The name of the next spell; see above. + * @param animationProgress The progress of the spell switching animation, as a fraction between 0 and 1 + * (inclusive). Positive values indicate switching forwards, negative values indicate switching backwards, and + * a value of zero indicates that the spell is not currently being switched. + */ + public void drawText(int x, int y, boolean flipX, boolean flipY, String prevSpellName, String spellName, String nextSpellName, float animationProgress){ + + // Moves the origin if the HUD does not mirror; neatens the rest of the code. + if(flipX && !mirrorX) x -= width; + if(flipY && !mirrorY) y += height; + + FontRenderer font = ClientProxy.mixedFontRenderer; // On this occasion we're client-side so this is OK + + // Position of the selected spell name in normal display, also used for interpolation when animating + int x1 = flipX && mirrorX ? x - width : x + textInsetX; + // The text is an odd number of pixels high so we need to subtract an extra 1 when not flipped + int y1 = flipY && mirrorY ? y + textInsetY - font.FONT_HEIGHT/2 + 2 : y - textInsetY - font.FONT_HEIGHT/2 - 1; + + int maxWidth = width - textInsetX; // Maximum width of the text + + if(animationProgress == 0){ // Normal display + + float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX; + float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX; + // Don't ask me why adding 1 to this makes it look more even, it just does! + float yPrev = y1 - (cascadeOffsetY + 1); // No need to account for flipY because previous is always above. + float yNext = y1 + cascadeOffsetY; // No need to account for flipY because next is always below. + float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX; + float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX; + int nextPrevClr = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY); + + DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, SPELL_NAME_SCALE, nextPrevClr, maxWidthPrev, true, flipX && mirrorX); + DrawingUtils.drawScaledStringToWidth(font, spellName, x1, y1, 1, 0xffffffff, maxWidth, true, flipX && mirrorX); + DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, SPELL_NAME_SCALE, nextPrevClr, maxWidthNext, true, flipX && mirrorX); + + }else{ // Switching spells + + boolean reverse = animationProgress < 0; + if(reverse) animationProgress = 1 - Math.abs(animationProgress); // Simplest way of reversing the animation + + float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX * animationProgress; + float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress); + float yPrev = y1 - (cascadeOffsetY + 1) * animationProgress; // No need to account for flipY because previous is always above. + float yNext = y1 + cascadeOffsetY * (1 - animationProgress); // No need to account for flipY because next is always below. + float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX * animationProgress; + float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress); + float scalePrev = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (1 - animationProgress); + float scaleNext = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (animationProgress); + int clrPrev = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * (1 - animationProgress)); + int clrNext = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * animationProgress); + + if(reverse){ // Switching to previous spell + + // Only renders the next spell and the current one + DrawingUtils.drawScaledStringToWidth(font, spellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX); + DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX); + + }else{ // Switching to next spell + + // Only renders the previous spell and the current one + DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX); + DrawingUtils.drawScaledStringToWidth(font, spellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX); + + } + } + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/EntityNameEntry.java b/src/main/java/electroblob/wizardry/client/gui/config/EntityNameEntry.java similarity index 90% rename from src/main/java/electroblob/wizardry/client/EntityNameEntry.java rename to src/main/java/electroblob/wizardry/client/gui/config/EntityNameEntry.java index e6b647f5..483c23cb 100644 --- a/src/main/java/electroblob/wizardry/client/EntityNameEntry.java +++ b/src/main/java/electroblob/wizardry/client/gui/config/EntityNameEntry.java @@ -1,21 +1,17 @@ -package electroblob.wizardry.client; - -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; +package electroblob.wizardry.client.gui.config; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.I18n; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.client.config.GuiButtonExt; -import net.minecraftforge.fml.client.config.GuiEditArray; -import net.minecraftforge.fml.client.config.GuiEditArrayEntries; +import net.minecraftforge.fml.client.config.*; import net.minecraftforge.fml.client.config.GuiEditArrayEntries.StringEntry; -import net.minecraftforge.fml.client.config.GuiSelectString; -import net.minecraftforge.fml.client.config.IConfigElement; import net.minecraftforge.fml.common.registry.EntityEntry; import net.minecraftforge.fml.common.registry.ForgeRegistries; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + /** * [NYI] Intended as a way of choosing entities by name from all those currently registered, within the config file, so * that users don't have to look up the entity IDs. I can't get this to work correctly at the moment. diff --git a/src/main/java/electroblob/wizardry/client/GuiConfigWizardry.java b/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java similarity index 90% rename from src/main/java/electroblob/wizardry/client/GuiConfigWizardry.java rename to src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java index 4da979bf..7f76ad1d 100644 --- a/src/main/java/electroblob/wizardry/client/GuiConfigWizardry.java +++ b/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java @@ -1,7 +1,4 @@ -package electroblob.wizardry.client; - -import java.util.ArrayList; -import java.util.List; +package electroblob.wizardry.client.gui.config; import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; @@ -15,6 +12,9 @@ import net.minecraftforge.fml.client.config.GuiConfigEntries; import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry; import net.minecraftforge.fml.client.config.IConfigElement; +import java.util.ArrayList; +import java.util.List; + public class GuiConfigWizardry extends GuiConfig { public GuiConfigWizardry(GuiScreen parent){ @@ -33,7 +33,8 @@ public class GuiConfigWizardry extends GuiConfig { configList.add(new DummyCategoryElement("clientConfig", "config." + Wizardry.MODID + ".category." + Settings.CLIENT_CATEGORY, ClientCategory.class)); configList.add(new DummyCategoryElement("spellsConfig", "config." + Wizardry.MODID + ".category." + Settings.SPELLS_CATEGORY, SpellsCategory.class)); configList.add(new DummyCategoryElement("resistancesConfig", "config." + Wizardry.MODID + ".category." + Settings.RESISTANCES_CATEGORY, ResistancesCategory.class)); - + configList.add(new DummyCategoryElement("compatibilityConfig", "config." + Wizardry.MODID + ".category." + Settings.COMPATIBILITY_CATEGORY, CompatibilityCategory.class)); + configList.addAll(new ConfigElement(Wizardry.settings.getConfigCategory(Configuration.CATEGORY_GENERAL)).getChildElements()); return configList; @@ -41,7 +42,7 @@ public class GuiConfigWizardry extends GuiConfig { // The reason this system is so convoluted is that it's designed for use with the @Config annotation. The problem is, // I'm not sure whether that will play well with the load phases. Hmmm... - + public static abstract class CategoryBase extends CategoryEntry { public CategoryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ @@ -77,6 +78,36 @@ public class GuiConfigWizardry extends GuiConfig { @Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; } } + + /** Worldgen category of the config gui. */ + public static class WorldgenCategory extends CategoryBase { + + public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; } + } + + /** Commands category of the config gui. */ + public static class CommandsCategory extends CategoryBase { + + public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; } + } + + /** Client category of the config gui. */ + public static class ClientCategory extends CategoryBase { + + public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.CLIENT_CATEGORY; } + } /** Spells category of the config gui. */ public static class SpellsCategory extends CategoryBase { @@ -97,34 +128,14 @@ public class GuiConfigWizardry extends GuiConfig { @Override protected String getCategory() { return Settings.RESISTANCES_CATEGORY; } } - - /** Worldgen category of the config gui. */ - public static class WorldgenCategory extends CategoryBase { - - public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ - super(owningScreen, owningEntryList, prop); - } - - @Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; } - } - - /** Client category of the config gui. */ - public static class ClientCategory extends CategoryBase { - - public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ - super(owningScreen, owningEntryList, prop); - } - - @Override protected String getCategory() { return Settings.CLIENT_CATEGORY; } - } - + /** Commands category of the config gui. */ - public static class CommandsCategory extends CategoryBase { - - public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + public static class CompatibilityCategory extends CategoryBase { + + public CompatibilityCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ super(owningScreen, owningEntryList, prop); } - - @Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; } + + @Override protected String getCategory() { return Settings.COMPATIBILITY_CATEGORY; } } } diff --git a/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java b/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java new file mode 100644 index 00000000..985d1594 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java @@ -0,0 +1,119 @@ +package electroblob.wizardry.client.gui.config; + +import com.google.common.collect.Lists; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.gui.GuiSpellDisplay; +import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin; +import electroblob.wizardry.registry.Spells; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.fml.client.config.GuiSelectString; +import net.minecraftforge.fml.client.config.IConfigElement; + +import javax.annotation.Nullable; +import java.util.Map; + +public class GuiSelectHUDSkin extends GuiSelectString { + + public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map selectableValues, Object currentValue, boolean enabled){ + super(parentScreen, configElement, slotIndex, selectableValues, currentValue, enabled); + } + + @Override + public void initGui(){ + super.initGui(); + setEntryListDimensions(); + } + + @Override + protected void actionPerformed(GuiButton button){ + super.actionPerformed(button); + setEntryListDimensions(); // Stops the entry list from resizing when a button is pressed + } + + private void setEntryListDimensions(){ + this.entryList.setDimensions(150, height, 43, height-43); + this.entryList.left = 10; + this.entryList.maxEntryWidth = 120; + this.entryList.headerPadding = 5; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + + super.drawScreen(mouseX, mouseY, partialTicks); + + GlStateManager.disableLighting(); + + if(this.currentValue instanceof String){ + + this.drawString(this.fontRenderer, I18n.format("config." + Wizardry.MODID + ".spell_hud_skin.preview"), 170, 44, 0xffffff); + + int previewLeft = 170; + int previewRight = width-10; + int previewTop = 60; + int previewBottom = height-43; + + this.drawGradientRect(previewLeft, previewTop, previewRight, previewBottom, 0x88000000, 0x88000000); + + int previewBorder = 10; + + Skin skin = GuiSpellDisplay.getSkin((String)this.currentValue); + + float scale = Math.min((previewRight - previewLeft - 2*previewBorder)/(float)skin.getWidth(), + (previewBottom - previewTop - 2*previewBorder)/(float)skin.getHeight()); + + float x = (previewLeft + previewRight)/2 - (skin.getWidth()*scale)/2; + float y = (previewBottom + previewTop)/2 + (skin.getHeight()*scale)/2; + + GlStateManager.pushMatrix(); + + GlStateManager.scale(scale, scale, scale); + + skin.drawBackground((int)(x/scale), (int)(y/scale), false, false, + Spells.magic_missile.getIcon(), 0.6f, false); + + skin.drawText((int)(x/scale), (int)(y/scale), false, false, + Spells.none.getDisplayNameWithFormatting(), + Spells.magic_missile.getDisplayNameWithFormatting(), + Spells.none.getDisplayNameWithFormatting(), 0); + + GlStateManager.popMatrix(); + + Skin hovered = getHoveredSkin(mouseX, mouseY); + + if(hovered != null){ + this.drawToolTip(Lists.newArrayList("\u00A7a" + hovered.getName(), "\u00A7e" + hovered.getDescription()), + mouseX, mouseY); + } + } + + GlStateManager.enableLighting(); + } + + /** Returns the skin corresponding to the list entry being hovered over, or null if there is none. */ + @Nullable + private Skin getHoveredSkin(int mouseX, int mouseY){ + + int index = this.entryList.getSlotIndexFromScreenCoords(mouseX, mouseY); + + if(index >= 0 && index <= this.entryList.listEntries.size() && mouseY <= this.entryList.bottom){ + + Object object = entryList.getListEntry(index).getValue(); + + if(object instanceof String){ + return GuiSpellDisplay.getSkin((String)object); + } + } + + return null; + } + + @Override // Stops the world being visible behind the GUI when configuring from within a world + public void drawWorldBackground(int tint){ + this.drawBackground(tint); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/config/NamedBooleanEntry.java b/src/main/java/electroblob/wizardry/client/gui/config/NamedBooleanEntry.java new file mode 100644 index 00000000..0e9fd5ed --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/config/NamedBooleanEntry.java @@ -0,0 +1,93 @@ +package electroblob.wizardry.client.gui.config; + +import net.minecraft.client.resources.I18n; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiConfigEntries; +import net.minecraftforge.fml.client.config.GuiUtils; +import net.minecraftforge.fml.client.config.IConfigElement; + +/** + * Same as {@link net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry}, but instead of simply + * displaying 'true' or 'false', allows the two display strings to be specified in the lang file. + */ +// BooleanEntry's constructors are private, so I had to copy the whole goddamn class to change one method. Thanks Forge. +public class NamedBooleanEntry extends GuiConfigEntries.ButtonEntry { + + protected final boolean beforeValue; + protected boolean currentValue; + + private static final String DEFAULT_KEY = "config.ebwizardry.generic"; + + public NamedBooleanEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement){ + super(owningScreen, owningEntryList, configElement); + this.beforeValue = Boolean.valueOf(configElement.get().toString()); + this.currentValue = beforeValue; + this.btnValue.enabled = enabled(); + updateValueButtonText(); + } + + // This is the only method that's any different + @Override + public void updateValueButtonText(){ + + String langKey = configElement.getLanguageKey() + "." + currentValue; + this.btnValue.displayString = I18n.format(langKey); + // If the key is unspecified, it defaults to the generic 'Enabled'/'Disabled' keys and adds a red/green colour + if(this.btnValue.displayString.equals(langKey)){ + this.btnValue.displayString = I18n.format(DEFAULT_KEY + "." + currentValue); + btnValue.packedFGColour = currentValue ? GuiUtils.getColorCode('a', true) : GuiUtils.getColorCode('c', true); + } + } + + // Everything from here down is the same as BooleanEntry + + @Override + public void valueButtonPressed(int slotIndex){ + if(enabled()) currentValue = !currentValue; + } + + @Override + public boolean isDefault(){ + return currentValue == Boolean.valueOf(configElement.getDefault().toString()); + } + + @Override + public void setToDefault(){ + if(enabled()){ + currentValue = Boolean.valueOf(configElement.getDefault().toString()); + updateValueButtonText(); + } + } + + @Override + public boolean isChanged(){ + return currentValue != beforeValue; + } + + @Override + public void undoChanges(){ + if(enabled()){ + currentValue = beforeValue; + updateValueButtonText(); + } + } + + @Override + public boolean saveConfigElement(){ + if(enabled() && isChanged()){ + configElement.set(currentValue); + return configElement.requiresMcRestart(); + } + return false; + } + + @Override + public Boolean getCurrentValue(){ + return currentValue; + } + + @Override + public Boolean[] getCurrentValues(){ + return new Boolean[]{getCurrentValue()}; + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/config/SpellHUDSkinChooserEntry.java b/src/main/java/electroblob/wizardry/client/gui/config/SpellHUDSkinChooserEntry.java new file mode 100644 index 00000000..e2ddb3ed --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/config/SpellHUDSkinChooserEntry.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.client.gui.config; + +import electroblob.wizardry.client.gui.GuiSpellDisplay; +import net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiConfigEntries; +import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry; +import net.minecraftforge.fml.client.config.IConfigElement; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +/** + * Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently + * selected skin. based off of {@link ModIDEntry} from Forge. + */ +public class SpellHUDSkinChooserEntry extends SelectValueEntry { + + public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop, getSelectableValues()); + if(this.selectableValues.size() == 0) this.btnValue.enabled = false; + } + + private static Map getSelectableValues(){ + return GuiSpellDisplay.getSkins().entrySet().stream().collect(Collectors.toMap(Entry::getKey, + e -> e.getValue().getName())); + } + + @Override // Copied from superclass to use custom child screen GUI class + public void valueButtonPressed(int slotIndex){ + mc.displayGuiScreen(new GuiSelectHUDSkin(this.owningScreen, configElement, slotIndex, selectableValues, currentValue, enabled())); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java new file mode 100644 index 00000000..1853e5a1 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java @@ -0,0 +1,194 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.JsonUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Instances of this class represent tables of contents in the wizard's handbook. Each {@link Section} can have a + * single table of contents, which can reference any other sections in the handbook (though it is normal to list + * top-level sections in a main contents and have subsections listed in their respective parent sections' contents). + * + * This class handles JSON parsing, formatting and drawing of the contents itself, working on a line-by-line basis + * (as opposed to sections, which work on a page-by-page basis). It also stores its own list of buttons. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +class Contents { + + // Final fields are mandatory, the rest are optional + final String id; + final Section section; + private boolean hyperlinks = true; + private boolean pageNumbers = true; + private String separator = "."; + // Derived fields, not specifically defined in JSON + private int startPage; + private int startLine; + private final List> buttons; + + private final List
    entries; + + private List
    visibleEntries; + + private Contents(String id, Section section){ + this.id = id; + this.section = section; + this.entries = new ArrayList<>(); + this.buttons = new ArrayList<>(); + this.visibleEntries = new ArrayList<>(); + } + + /** Returns an unmodifiable, flattened collection of all the buttons in this contents. */ + Collection getButtons(){ + return WizardryUtilities.flatten(buttons); + } + + void addEntry(Section section){ + entries.add(section); + } + + /** + * Draws this contents for the given double-page spread and shows/hides buttons accordingly. Will draw nothing + * if the given page is outside of this contents. + * + * @param font The font renderer object. + * @param doublePage The index of the double-page to be drawn. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + */ + void draw(FontRenderer font, int doublePage, int left, int top){ + + // Show/hide buttons + + int i = 0; + + for(List list : buttons){ + final int i1 = i++; + list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage); + } + + if(!pageNumbers) return; // No page numbers means only the buttons are drawn + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false); + // Relative indices of the pages to be rendered - often these will be outside the section entirely + int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1}; + + for(int page : visiblePages){ + + if(page >= 0 && page < visibleEntries.size() / maxLineNumber + 1){ + + int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X); + int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; + + for(Section entry : this.visibleEntries){ + + if(entry.isUnlocked()){ + + int nameWidth = font.getStringWidth(entry.title); + + String dotsAndNumber = " " + entry.startPage; + + while(font.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - nameWidth - 2){ + dotsAndNumber = separator + dotsAndNumber; + } + + font.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - font.getStringWidth(dotsAndNumber), y, DrawingUtils.BLACK, false); + + if(!hyperlinks) font.drawString(entry.title, x, y, DrawingUtils.BLACK, false); + + y += font.FONT_HEIGHT; + } + } + } + } + } + + /** + * Called on GUI load to format the section and all subsections, contents tables and other elements. Does not + * perform any actual drawing. + * + * @param font The font renderer object, for measurement purposes. + * @param startPage The index of the first page (single side, not double-page) of this section. + * @param startLine The index of the first line of this contents. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + * @return The number of lines this contents takes up. + * @throws JsonSyntaxException if at any point the formatting is found to be invalid. + */ + int format(FontRenderer font, int startPage, int startLine, int left, int top){ + + this.buttons.clear(); + + this.visibleEntries = new ArrayList<>(entries); // Need to copy the collection first! + + this.visibleEntries.removeIf(s -> !s.isUnlocked()); + + if(hyperlinks){ + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + this.startPage = startPage; + this.startLine = startLine; + + List list = new ArrayList<>(maxLineNumber); + + for(Section entry : this.visibleEntries){ + + int x = GuiWizardHandbook.isRightPage(startPage) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; + int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; + + list.add(new GuiButtonHyperlink.Internal(0, x, y, font, entry.title, entry, 0, "", maxLineNumber-startLine, GuiWizardHandbook.isRightPage(startPage))); + + startLine++; + + if(startLine == maxLineNumber){ + startLine = 0; + startPage++; + buttons.add(list); + list = new ArrayList<>(maxLineNumber); // If there are no more entries this will be discarded anyway + } + } + + buttons.add(list); + } + + // Returning this is kind of trivial at the moment but if we ever wanted to add a header or something, + // it would be more useful. + return visibleEntries.size(); + } + + /** + * Parses the given JSON object and constructs a new {@code Contents} from it, setting all the relevant fields + * and references. + * + * @param parent The parent section for this contents. + * @param json A JSON object representing the contents to be constructed. This must contain at least an "id" + * string. + * @return The resulting {@code Contents} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Contents fromJson(Section parent, JsonObject json){ + + Contents contents = new Contents(JsonUtils.getString(json, "id"), parent); + + contents.hyperlinks = JsonUtils.getBoolean(json, "hyperlinks", true); + contents.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true); + contents.separator = JsonUtils.getString(json, "separator", "."); + + return contents; + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java b/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java new file mode 100644 index 00000000..a7b32212 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/CraftingRecipe.java @@ -0,0 +1,226 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.common.collect.Streams; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; + +import java.util.*; + +class CraftingRecipe { + + static final int BORDER = 7; + static final int TEXTURE_INSET_X = 40, TEXTURE_INSET_Y = 190; + static final int WIDTH = 121, HEIGHT = 66; + + // Final fields are mandatory, the rest are optional + private final ResourceLocation[] locations; + // Derived fields, not specifically defined in JSON + private List recipes; + private final Set instances = new HashSet<>(); + + private CraftingRecipe(ResourceLocation[] locations){ + this.locations = locations; + } + + /** + * Adds an instance of this recipe to the list. + * + * @param page The index of the single page this image is on. + * @param x The x-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. + * @param y The y-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. + */ + void addInstance(int page, int x, int y){ + instances.add(new int[]{page, x, y}); + } + + /** Removes all instances of this recipe from the list. */ + void clearInstances(){ + instances.clear(); + } + + /** Called on GUI open to load the actual recipe object from the registry. This cannot be done on JSON load since + * the recipes aren't necessarily loaded at that point. */ + void load(){ + + recipes = new ArrayList<>(locations.length); + + for(ResourceLocation location : locations){ + + IRecipe recipe = CraftingManager.getRecipe(location); + if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location); + recipes.add(recipe); + } + } + + /** + * Draws all instances of this recipe that are located on the given double-page spread. + * + * @param font The font renderer object. + * @param itemRenderer The item renderer object. + * @param doublePage The double-page index of the page to be drawn. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + */ + void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){ + + int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; + + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], recipes.get(index % recipes.size())); + } + } + } + + /** + * Draws the tooltips for all instances of this recipe that are located on the given double-page spread. This has to + * be done separately so that the tooltips are on top of everything else. + * + * @param itemRenderer The item renderer object. + * @param doublePage The double-page index of the page to be drawn. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + */ + void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){ + + int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; + + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, recipes.get(index % recipes.size())); + } + } + } + + /** + * Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields + * and references. + * + * @param json A JSON object representing the image to be constructed. This must contain at least a "locations" + * string. + * @return The resulting {@code Image} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static CraftingRecipe fromJson(JsonObject json){ + + ResourceLocation[] locations = Streams.stream(JsonUtils.getJsonArray(json, "locations")) + .map(je -> new ResourceLocation(je.getAsString())).toArray(ResourceLocation[]::new); + return new CraftingRecipe(locations); + } + + static void populate(Map map, JsonObject json){ + + JsonObject sectionsObject = JsonUtils.getJsonObject(json, "recipes"); + + // Need to iterate over these since we don't know what they're called or how many there are + for(Map.Entry entry : sectionsObject.entrySet()){ + + String key = entry.getKey(); // Find out what each element is called, this will be the sections map key + + CraftingRecipe recipe = fromJson(entry.getValue().getAsJsonObject()); + map.put(key, recipe); + } + } + + private static void renderCraftingRecipe(FontRenderer font, RenderItem itemRenderer, int x, int y, IRecipe recipe){ + + ItemStack result = recipe.getRecipeOutput(); + + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); + + DrawingUtils.drawTexturedRect(x, y, TEXTURE_INSET_X, TEXTURE_INSET_Y, WIDTH, HEIGHT, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT); + + GlStateManager.pushMatrix(); + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableColorMaterial(); + itemRenderer.zLevel = 100.0F; + + int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; + + int i = 0; + + for(Ingredient ingredient : recipe.getIngredients()){ + + if(ingredient != Ingredient.EMPTY){ + ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; + if(!stack.isEmpty()){ + itemRenderer.renderItemAndEffectIntoGUI(stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3)); + itemRenderer.renderItemOverlays(font, stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3)); + } + } + + i++; + } + + if(!result.isEmpty()){ + itemRenderer.renderItemAndEffectIntoGUI(result, x + BORDER + 86, y + BORDER + 18); + itemRenderer.renderItemOverlays(font, result, x + BORDER + 86, y + BORDER + 18); + } + + GlStateManager.popMatrix(); + GlStateManager.enableDepth(); + GlStateManager.disableColorMaterial(); + itemRenderer.zLevel = 0.0F; + RenderHelper.disableStandardItemLighting(); + + } + + private static void renderCraftingTooltips(GuiWizardHandbook gui, RenderItem itemRenderer, int x, int y, int mouseX, int mouseY, IRecipe recipe){ + + ItemStack result = recipe.getRecipeOutput(); + + GlStateManager.pushMatrix(); + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableColorMaterial(); + itemRenderer.zLevel = 0.0F; + + int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; + + int i = 0; + + for(Ingredient ingredient : recipe.getIngredients()){ + + if(ingredient != Ingredient.EMPTY){ + ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; + if(!stack.isEmpty() && isPointInRegion(x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3), 16, 16, mouseX, mouseY)){ + gui.renderToolTip(stack, mouseX, mouseY); + } + } + + i++; + } + + if(!result.isEmpty() && isPointInRegion(x + BORDER + 86, y + BORDER + 18, 16, 16, mouseX, mouseY)){ + gui.renderToolTip(result, mouseX, mouseY); + } + + GlStateManager.popMatrix(); + GlStateManager.enableDepth(); + GlStateManager.disableColorMaterial(); + RenderHelper.disableStandardItemLighting(); + + } + + private static boolean isPointInRegion(int left, int top, int width, int height, int mouseX, int mouseY){ + return mouseX >= left - 1 && mouseX < left + width + 1 && mouseY >= top - 1 && mouseY < top + height + 1; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java new file mode 100644 index 00000000..d5a48d64 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonHyperlink.java @@ -0,0 +1,227 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.event.ClickEvent; + +import java.util.ArrayList; +import java.util.List; + +public abstract class GuiButtonHyperlink extends GuiButton { + + public static final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$"; + + /** Pulse period of links to new sections, in milliseconds. */ + private static final float PULSATION_PERIOD = 1500; + + final int indent; + final List lines; + final int linesLeft; + + GuiButtonHyperlink(int id, int x, int y, FontRenderer font, String text, int indent, String suffix, int linesLeft, boolean rightPage){ + + super(id, x, y, font.getStringWidth(text), font.FONT_HEIGHT, text); + + // Sometimes a link has punctuation or something after it that causes it to wrap onto a new line + String linkWithSuffix = text + suffix; + + // If the string won't fit any words at the end of the current line, treat it as if we started a new line + if(font.getStringWidth(linkWithSuffix.split("\\s")[0]) > GuiWizardHandbook.PAGE_WIDTH - indent){ + indent = 0; + this.y += font.FONT_HEIGHT; + } + + this.indent = indent; // Assigned here in case it was corrected above + this.linesLeft = linesLeft; + + String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0); + // Without trim(), there will be at least 1 leading space due to the custom wrapping + String remainder = linkWithSuffix.substring(line1.length()).trim(); + + // ... then wrap the rest to the normal width. + lines = new ArrayList<>(); + lines.add(line1); + // Some links are only one line, if this wasn't checked they would cause a StackOverflowError + if(!remainder.isEmpty()) lines.addAll(font.listFormattedStringToWidth(remainder, GuiWizardHandbook.PAGE_WIDTH)); + + // Removes the suffix if it exists (ugly as heck, but it works) + if(!suffix.isEmpty()){ + for(int i=lines.size()-1; i>=0; i--){ + String line = lines.get(i); + if(suffix.endsWith(line)){ + lines.remove(i); + }else if(line.endsWith(suffix)){ + lines.set(i, line.substring(0, line.length() - suffix.length())); + break; + } + } + } + + // Remove any lines that overflowed onto the next double-page + if(rightPage){ + while(lines.size() > linesLeft) lines.remove(lines.size() - 1); + } + } + + public boolean isHovered(net.minecraft.client.gui.FontRenderer font, int mouseX, int mouseY){ + + int i = 0; + + for(String line : lines){ + + int l = x; + if(i == 0) l += indent; + + int t = y + font.FONT_HEIGHT * i; + + if(i > linesLeft){ + l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH; + t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % font.FONT_HEIGHT); + } + + if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){ + return true; + } + + i++; + } + + return false; + } + + @Override + public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){ + return this.enabled && this.visible && isHovered(minecraft.fontRenderer, mouseX, mouseY); + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + if(this.visible){ + + this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY); + + int i = 0; + + for(String line : lines){ + + int l = x; + if(i == 0) l += indent; + + int t = y + minecraft.fontRenderer.FONT_HEIGHT * i; + + if(i > linesLeft){ + l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH; + t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % minecraft.fontRenderer.FONT_HEIGHT); + } + + minecraft.fontRenderer.drawString(line, l, t, getColour()); + + i++; + } + } + } + + protected int getColour(){ + return hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink"); + } + + /** + * Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections. + * @param x The x position of the button + * @param y The y position of the button + * @param font A reference to the FontRenderer object + * @param upToLink The paragraph (as a list of lines) up to the link, used to determine positioning and word wrap + * @param arguments The link arguments - that is, everything between the two @ signs, split by spaces + * @param suffix The text directly after the link, up to the first whitespace; used for word wrap. Usually this is + * either empty or contains a single punctuation mark. + * @return The resulting button + * @throws IllegalArgumentException if the given argument array is empty or contains more than 2 arguments + * @throws JsonSyntaxException if the specified link target is not a URL or a valid section ID + */ + public static GuiButtonHyperlink create(int x, int y, FontRenderer font, List upToLink, String[] arguments, String suffix, int linesLeft, boolean rightPage){ + + if(arguments.length == 0 || arguments.length > 2) throw new IllegalArgumentException("Incorrect array length!"); + + GuiButtonHyperlink button; + + if(arguments[0].matches(URL_REGEX)){ + + button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0], + font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage); + + }else{ + + Section target = GuiWizardHandbook.sections.get(arguments[0]); + + if(target == null) throw new JsonSyntaxException("Hyperlink points to nonexistent section id " + arguments[0]); + + button = new GuiButtonHyperlink.Internal(0, x, y, font, arguments[arguments.length - 1], + target, font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage); + } + + return button; + } + + static class Internal extends GuiButtonHyperlink { + + final Section target; + + Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix, int linesLeft, boolean rightPage){ + super(id, x, y, font, text, indent, suffix, linesLeft, rightPage); + this.target = target; + } + + @Override + public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){ + if(!target.isUnlocked()) return false; + return super.mousePressed(minecraft, mouseX, mouseY); + } + + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + + @Override + protected int getColour(){ + + if(!target.isUnlocked()) return GuiWizardHandbook.colours.get("text"); + + if(!hovered && target.isNew() && !Minecraft.getMinecraft().player.isCreative()){ + + int c = GuiWizardHandbook.colours.get("new_section"); + int d = GuiWizardHandbook.colours.get("hyperlink"); + float f = (MathHelper.sin((Minecraft.getSystemTime() % PULSATION_PERIOD) / PULSATION_PERIOD * 2 * (float)Math.PI) + 1) / 2f; + + return DrawingUtils.mix(c, d, f); + } + + return super.getColour(); + } + + } + + static class External extends GuiButtonHyperlink { + + final ITextComponent link; + + External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix, int linesLeft, boolean rightPage){ + super(id, x, y, font, text, indent, suffix, linesLeft, rightPage); + this.link = new TextComponentString(text); + link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)).setColor(TextFormatting.DARK_BLUE); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java new file mode 100644 index 00000000..662e71a1 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java @@ -0,0 +1,61 @@ +package electroblob.wizardry.client.gui.handbook; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; + +//@SideOnly(Side.CLIENT) +class GuiButtonTurnPage extends GuiButton { + + static final int WIDTH = 20; + static final int HEIGHT = 12; + + enum Type { + + NEXT_PAGE(0, 196), + PREVIOUS_PAGE(0, 208), + NEXT_SECTION(0, 220), + PREVIOUS_SECTION(0, 232), + CONTENTS(0, 244); + + private final int u, v; + + Type(int u, int v){ + this.u = u; + this.v = v; + } + } + + public final Type type; + + private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); + + public GuiButtonTurnPage(int id, int x, int y, Type type){ + super(id, x, y, WIDTH, HEIGHT, ""); + this.type = type; + } + + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + if(this.visible){ + + boolean flag = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + minecraft.getTextureManager().bindTexture(texture); + + DrawingUtils.drawTexturedRect(this.x, this.y, flag ? type.u + width : type.u, type.v, width, height, 512, 256); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java new file mode 100644 index 00000000..dcb54387 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java @@ -0,0 +1,564 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.ClientProxy; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.gui.GuiButtonInvisible; +import electroblob.wizardry.client.gui.handbook.GuiButtonTurnPage.Type; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.packet.PacketRequestAdvancementSync; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.audio.SoundHandler; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.*; + +/** + * GUI class for the wizard's handbook. Like any GUI class, this is instantiated each time the book is opened. As of + * Wizardry 4.2, the handbook text is defined as a JSON file rather than a plain text file, and is loaded only on + * resource pack reload, rather than every time the book is opened. This means all the data structures (sections, images, + * etc.) are built before the GUI instance exists at all. However, since some things depend on positioning, these have to + * be initialised on GUI creation. (Previously, everything was done on GUI load) + * + * @author Electroblob + * @since Wizardry 1.0 + * @see Section + * @see Contents + * @see Image + * @see CraftingRecipe + */ +public class GuiWizardHandbook extends GuiScreen { + + private static final ResourceLocation DEFAULT = new ResourceLocation(Wizardry.MODID, "texts/handbook_en_us.json"); + + static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); + + /** Global Gson instance for the handbook. */ + private static final Gson gson = new Gson(); + + // Formatting markup + + static final char FORMAT_MARKER = '#'; + static final char HYPERLINK_MARKER = '@'; + + static final String IMAGE_TAG = "image"; + static final String RECIPE_TAG = "recipe"; + static final String RULER_TAG = "ruler"; + + static final Map FORMAT_TAGS = new HashMap<>(); + + // Dimension constants + // Private constants are not relevant to book elements, package-protected ones are + + /** The dimensions of the rendered GUI area. */ + static final int GUI_WIDTH = 288, GUI_HEIGHT = 180; + /** The dimensions of the GUI texture itself. */ + static final int TEXTURE_WIDTH = 512, TEXTURE_HEIGHT = 256; + /** The dimensions of the area of a single page in which text can be drawn. */ + static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140; + /** The distance of the text from the top outside corner of each page. */ + static final int TEXT_INSET_X = 17, TEXT_INSET_Y = 16; + /** The distance of the buttons from the bottom outside corners of the GUI. */ + private static final int BUTTON_INSET_X = 22, BUTTON_INSET_Y = 13; + /** The distance between adjacent buttons. */ + private static final int BUTTON_SPACING = 20; + /** The distance of the page numbers from the bottom of the GUI. */ + private static final int PAGE_NUMBER_INSET = 22; + + // IDEA: Constant dimensions could be converted to JSON like the spell HUD ones + + // Global variables + + /** + * The double-page currently being viewed. Each double-page spread counts as a single page, with the inside + * of the front cover being page 0. + */ + private int currentPage = 0; + /** + * The number of single pages currently in the book. This is calculated on GUI load based on visible sections. + */ + private int pageCount = 1; // Starts at 1 because the first single-page is the inside of the cover + /** + * The double-page number where the bookmark is currently set, relative to the section stored in + * {@link GuiWizardHandbook#bookmarkSection}. Static because it persists when the book is closed. + */ + private static int bookmarkPage = 0; + /** + * The key corresponding to the section in which the bookmark is currently set. Static because it persists when the + * book is closed. Storing a section means the bookmark doesn't change location when new sections are unlocked. + */ + private static String bookmarkSection; + + // Buttons + private GuiButton bookmark, next, previous, nextSection, previousSection, menu; + + // Handbook content + + // As a general rule, I prefer to make static final fields lowercase if they're collections that change, because even + // though the collection itself is constant, the stuff in it is not, so being lowercase highlights this difference. + + /** + * A map which stores all loaded section objects, including subsections. This gets wiped on resource pack reload and + * repopulated with mappings as specified by the handbook JSON file for the current language. The keys in the map + * correspond to the keys in the sections object in that file, and are sorted in that order. + */ + static final Map sections = new LinkedHashMap<>(); + + /** + * A list which stores all loaded section objects, including subsections. This is an unmodifiable list view of the + * values in {@link GuiWizardHandbook#sections}, sorted in the same (page number) order. This exists only to allow + * sections to be accessed by ordinal index for the various navigation buttons, hence why it is private. + */ + private static List
    sectionList; + + /** + * A map which stores all loaded contents objects. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * id strings for the contents objects in that file. This map is not sorted. + */ + static final Map contentsList = new HashMap<>(); + + /** + * A map which stores all loaded hex colour values. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * keys in the colours object in that file. This map is not sorted. + */ + static final Map colours = new HashMap<>(); + + /** + * A map which stores all loaded image objects. This gets wiped on resource pack reload and repopulated with + * mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to the + * keys in the images object in that file. This map is not sorted. + */ + static final Map images = new HashMap<>(); + + /** + * A map which stores all loaded crafting recipe objects. This gets wiped on resource pack reload and repopulated + * with mappings as specified by the handbook JSON file for the current language. The keys in the map correspond to + * the keys in the recipes object in that file. This map is not sorted. + */ + static final Map recipes = new HashMap<>(); + + /** + * Adds a format tag to the handbook. All occurrences of the given tag string preceded by a # will be replaced with + * the result of the given value string on GUI load. The value string, therefore, can be anything that should be + * input dynamically, as long as it does not change while the GUI is open. Examples include wizardry's version, + * the various element colours and the keys assigned to wizardry's controls. + * @param tag The tag string, as defined in the handbook JSON file, excluding the # character. Cannot include spaces. + * @param value The string to replace occurrences of the given format tag with. Can include spaces but not the # character. + */ + public static void addFormatTag(String tag, String value){ + FORMAT_TAGS.put(tag, value); + } + + private static void initFormatTags(){ + + addFormatTag("next_spell_key", ClientProxy.NEXT_SPELL.getDisplayName()); + addFormatTag("previous_spell_key", ClientProxy.PREVIOUS_SPELL.getDisplayName()); + addFormatTag("example_charging_loss", "" + (Constants.MANA_PER_CRYSTAL - 30)); + addFormatTag("mana_per_crystal", "" + Constants.MANA_PER_CRYSTAL); + addFormatTag("novice_max_charge", "" + Tier.NOVICE.maxCharge); + addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge); + addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge); + addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge); + addFormatTag("version", Wizardry.VERSION); + addFormatTag("mcversion", Minecraft.getMinecraft().getVersion()); + + addFormatTag("colour_novice", "\u00A77"); + addFormatTag("colour_apprentice", Tier.APPRENTICE.getFormattingCode()); + addFormatTag("colour_advanced", Tier.ADVANCED.getFormattingCode()); + addFormatTag("colour_master", Tier.MASTER.getFormattingCode()); + + addFormatTag("colour_fire", Element.FIRE.getFormattingCode()); + addFormatTag("colour_ice", Element.ICE.getFormattingCode()); + addFormatTag("colour_lightning", Element.LIGHTNING.getFormattingCode()); + addFormatTag("colour_necromancy", Element.NECROMANCY.getFormattingCode()); + addFormatTag("colour_earth", Element.EARTH.getFormattingCode()); + addFormatTag("colour_sorcery", Element.SORCERY.getFormattingCode()); + addFormatTag("colour_healing", Element.HEALING.getFormattingCode()); + + addFormatTag("colour_reset", "\u00A70"); + } + + // Helper methods + + /** + * Converts the given single page index to a double-page index. Inverse of + * {@link GuiWizardHandbook#doubleToSinglePage(int, boolean)}. + * + * @param singlePageIndex The single-page index, which is the same as the page numbers actually displayed. + * @return The corresponding double-page index. + */ + static int singleToDoublePage(int singlePageIndex){ + // Yes, this is trivial, but if I ever change the numbering it'll be useful. It's also more descriptive. + return singlePageIndex / 2; + } + + /** + * Converts the given double-page index to a single-page index. Inverse of + * {@link GuiWizardHandbook#singleToDoublePage(int)}. + * + * @param doublePageIndex The double-page index, as stored in {@link GuiWizardHandbook#currentPage}. + * @param rightHandPage True to return the page on the right (1 greater), false for the left-hand page. + * @return The corresponding single-page index. + */ + static int doubleToSinglePage(int doublePageIndex, boolean rightHandPage){ + return rightHandPage ? doublePageIndex * 2 + 1 : doublePageIndex * 2; + } + + /** + * Returns whether the given page index refers to a right-hand page or a left-hand page. + * + * @param page The single-page index, which is the same as the page number actually displayed. + * @return True if the given page index refers to a right-hand page, false if it is a left-hand page. + */ + static boolean isRightPage(int page){ + return page % 2 == 1; + } + + // Drawing + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + + int left = this.width / 2 - GUI_WIDTH / 2; + int top = this.height / 2 - GUI_HEIGHT / 2; + + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + GlStateManager.color(1, 1, 1, 1); + + // Main background + DrawingUtils.drawTexturedRect(left, top, 0, 0, GUI_WIDTH, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + // First page background + if(currentPage == 0){ + DrawingUtils.drawTexturedRect(left, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + previous.visible = false; + previousSection.visible = false; // Not worth testing if we're in the first section every frame + menu.visible = false; + }else{ + previous.visible = true; + previousSection.visible = true; + menu.visible = true; + } + + // Last page background + if(currentPage == singleToDoublePage(pageCount)){ + DrawingUtils.drawTexturedFlippedRect(left + GUI_WIDTH / 2, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT, true, false); + next.visible = false; + nextSection.visible = false; + }else{ + next.visible = true; + nextSection.visible = true; + } + + // Page numbers + if(currentPage > 0){ + String pageNumber = "" + doubleToSinglePage(currentPage, false); + this.fontRenderer.drawString(pageNumber, left + TEXT_INSET_X + PAGE_WIDTH / 2 + - fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK); + } + if(currentPage < singleToDoublePage(pageCount)){ + String pageNumber = "" + doubleToSinglePage(currentPage, true); + this.fontRenderer.drawString(pageNumber, left + GUI_WIDTH - TEXT_INSET_X - PAGE_WIDTH / 2 + - fontRenderer.getStringWidth(pageNumber)/2, top + GUI_HEIGHT - PAGE_NUMBER_INSET, DrawingUtils.BLACK); + } + + // Main content + contentsList.values().forEach(c -> { if(c.section.isUnlocked()) c.draw(fontRenderer, currentPage, left, top); } ); + sections.values().forEach(s -> { if(s.isUnlocked()) s.draw(fontRenderer, currentPage, left, top); } ); + // These only get populated if the sections are unlocked so no checks are necessary + images.values().forEach(i -> i.draw(fontRenderer, currentPage, left, top)); + recipes.values().forEach(r -> r.draw(fontRenderer, itemRender, currentPage, left, top)); + + // Buttons + super.drawScreen(mouseX, mouseY, partialTicks); + + // Bookmark + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + + if(currentPage == singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage){ + // If the current page is the bookmarked page, the (invisible) bookmark button is disabled + bookmark.visible = false; + DrawingUtils.drawTexturedRect(left + 138, top, 299, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT); + }else{ + bookmark.visible = true; + bookmark.x = left + (currentPage > singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage ? 130 : 147); + DrawingUtils.drawTexturedRect(bookmark.x, top, + bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT); + } + + // Recipe tooltips + recipes.values().forEach(r -> r.drawTooltips(this, fontRenderer, itemRender, currentPage, left, top, mouseX, mouseY)); + + } + + // GUI Initialisation / Close + + @Override + public void onResize(Minecraft minecraft, int width, int height){ + initGui(); + } + + @Override + public void onGuiClosed(){ + super.onGuiClosed(); + Keyboard.enableRepeatEvents(false); + } + + @Override + public void initGui(){ + + super.initGui(); + Keyboard.enableRepeatEvents(true); + + initFormatTags(); + + final int left = this.width / 2 - GUI_WIDTH / 2; + final int top = this.height / 2 - GUI_HEIGHT / 2; + + recipes.values().forEach(CraftingRecipe::load); + + int nextButtonId = 0; + + this.buttonList.clear(); + + this.buttonList.add(next = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE)); + + this.buttonList.add(previous = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE)); + + this.buttonList.add(nextSection = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH - BUTTON_INSET_X - GuiButtonTurnPage.WIDTH - BUTTON_SPACING, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION)); + + this.buttonList.add(previousSection = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X + BUTTON_SPACING, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION)); + + this.buttonList.add(menu = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH/2 - 28, + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS)); + + this.buttonList.add(bookmark = new GuiButtonInvisible(nextButtonId++, left + 130, top + 172, 11, 19) { + @Override + public void playPressSound(SoundHandler soundHandler){ + soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); + } + }); + + pageCount = 1; + + // Clears instances of all images and recipes + images.values().forEach(Image::clearInstances); + recipes.values().forEach(CraftingRecipe::clearInstances); + + // Formats all the unlocked sections in order + for(Section section : sections.values()){ + if(section.isUnlocked()){ + pageCount = section.format(this.fontRenderer, pageCount, left, top); + buttonList.addAll(section.getButtons()); + } + } + + contentsList.values().forEach(c -> buttonList.addAll(c.getButtons())); + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1)); + } + + // JSON Parsing / Data Construction + + /** + * Called from preInit in the main mod class (via the proxies) to initialise the handbook (parses the JSON file + * and constructs the relevant data structures), and again on each resource reload (changing the language triggers + * a resource reload). + */ + public static void loadHandbookFile(IResourceManager manager){ + + IResource handbookFile = getHandbookResource(manager); + + if(handbookFile != null){ + + // Wipes all the maps before repopulating them + images.clear(); + sections.clear(); + contentsList.clear(); + colours.clear(); + + bookmarkSection = null; // Also need to wipe the reference to the old bookmarked section + + BufferedReader reader = new BufferedReader(new InputStreamReader(handbookFile.getInputStream())); + + JsonElement je = gson.fromJson(reader, JsonElement.class); + JsonObject json = je.getAsJsonObject(); + + JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(), + Color.decode(e.getValue().getAsString()).getRGB())); + + // Repopulates the remaining maps + Image.populate(images, json); + CraftingRecipe.populate(recipes, json); + Section.populate(sections, json); + + sectionList = Collections.unmodifiableList(new ArrayList<>(sections.values())); + + if(sections.isEmpty()){ + Wizardry.logger.warn("Handbook has no sections! Aborting loading..."); + return; + } + + bookmarkSection = JsonUtils.getString(json, "bookmark_start_section"); + if(!sections.containsKey(bookmarkSection)) throw new JsonSyntaxException("Section with id " + bookmarkSection + " is undefined"); + } + + // The first resource load on startup is done before the packet handler is loaded + if(WizardryPacketHandler.net != null) WizardryPacketHandler.net.sendToServer(new PacketRequestAdvancementSync.Message()); + } + + /** + * Retrieves the handbook JSON file for the current language and returns its IResource object. If a handbook file + * cannot be found for the current language, a message is printed to the console and the method attempts to retrieve + * the default file instead (English-US). If this file cannot be found, the resulting error is printed to the + * console and the method returns null. + * + * @param manager The resource manager instance to use. + * @return The handbook JSON file, as an IResource, or null if it was not found. + */ + private static IResource getHandbookResource(IResourceManager manager){ + + // TODO: Implement resource pack stacking to allow addon mods and texture packs to add/overwrite content + + IResource handbookFile = null; + + try{ + handbookFile = manager.getResource(new ResourceLocation(Wizardry.MODID, "texts/handbook_" + + Minecraft.getMinecraft().getLanguageManager().getCurrentLanguage().getLanguageCode() + ".json")); + }catch(IOException e){ + + Wizardry.logger.info("Wizard handbook JSON file missing for the current language (" + Minecraft.getMinecraft() + .getLanguageManager().getCurrentLanguage() + "). Using default (English-US) instead."); + + try{ + handbookFile = manager.getResource(DEFAULT); + }catch(IOException x){ + Wizardry.logger.error("Couldn't find file: " + DEFAULT + ". The file may be missing; please try re-downloading and reinstalling Wizardry.", x); + } + } + + return handbookFile; + } + + // Controls + + @Override + protected void actionPerformed(GuiButton button){ + + if(button.enabled){ + + if(button == next){ + if(currentPage < singleToDoublePage(pageCount)) currentPage++; + + }else if(button == previous){ + if(currentPage > 0) currentPage--; + + }else if(button == nextSection || button == previousSection){ + + Section currentSection = null; + + for(Section section : sections.values()){ + // We always want this button to do something, and taking the right-hand page means it always does + if(section.containsPage(doubleToSinglePage(currentPage, true))){ + currentSection = section; + break; + } + } + + if(currentSection != null){ + + List
    visibleSections = new ArrayList<>(sectionList); + visibleSections.removeIf(s -> !s.isUnlocked()); + + int index = visibleSections.indexOf(currentSection); + + if(button == nextSection && index + 1 < visibleSections.size()){ + currentPage = singleToDoublePage(visibleSections.get(index + 1).startPage); + }else if(index > 0){ + currentPage = singleToDoublePage(visibleSections.get(index - 1).startPage); + } + } + + }else if(button == menu){ + currentPage = singleToDoublePage(sections.get("main_contents").startPage); + + }else if(button == bookmark && bookmarkSection != null){ + currentPage = singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage; + + }else{ + if(button instanceof GuiButtonHyperlink.Internal){ + currentPage = singleToDoublePage(((GuiButtonHyperlink.Internal)button).target.startPage); + }else if(button instanceof GuiButtonHyperlink.External){ + this.handleComponentClick(((GuiButtonHyperlink.External)button).link); + } + } + } + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException{ + if(mouseButton == 1){ + // Right-clicking of bookmark + if(bookmark.mousePressed(this.mc, mouseX, mouseY)){ + + this.selectedButton = bookmark; + + for(String key : sections.keySet()){ + // The bookmark is assumed to bookmark the left-hand page + if(sections.get(key).containsPage(doubleToSinglePage(currentPage, false))) bookmarkSection = key; + } + + bookmarkPage = currentPage - singleToDoublePage(sections.get(bookmarkSection).startPage); + } + }else{ + super.mouseClicked(mouseX, mouseY, mouseButton); + } + } + + // Overridden to make it public + @Override + public void renderToolTip(ItemStack stack, int x, int y){ + super.renderToolTip(stack, x, y); + } + + @Override + public boolean doesGuiPauseGame(){ + return Wizardry.settings.booksPauseGame; + } + + + public static void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){ + sections.values().forEach(s -> s.updateUnlockStatus(showToasts, completedAdvancements)); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/HandbookToast.java b/src/main/java/electroblob/wizardry/client/gui/handbook/HandbookToast.java new file mode 100644 index 00000000..2db6e666 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/HandbookToast.java @@ -0,0 +1,54 @@ +package electroblob.wizardry.client.gui.handbook; + +import electroblob.wizardry.registry.WizardryItems; +import net.minecraft.client.gui.toasts.GuiToast; +import net.minecraft.client.gui.toasts.IToast; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; + +import java.util.List; + +//@SideOnly(Side.CLIENT) +public class HandbookToast implements IToast { + + private final Section section; + + public HandbookToast(Section section){ + this.section = section; + } + + public IToast.Visibility draw(GuiToast toastGui, long delta){ + + toastGui.getMinecraft().getTextureManager().bindTexture(TEXTURE_TOASTS); + + GlStateManager.color(1.0F, 1.0F, 1.0F); + toastGui.drawTexturedModalRect(0, 0, 0, 32, 160, 32); + + boolean firstPart = delta < 1500L; + + int a = firstPart ? MathHelper.floor(MathHelper.clamp((float)(1500L - delta) / 300.0F, 0.0F, 1.0F) * 255.0F) << 24 | 67108864 + : MathHelper.floor(MathHelper.clamp((float)(delta - 1500L) / 300.0F, 0.0F, 1.0F) * 252.0F) << 24 | 67108864; + + String s = firstPart ? I18n.format("handbook.toast.title") : section.title; + + int c = firstPart ? -11534256 : -16777216; + + List list = toastGui.getMinecraft().fontRenderer.listFormattedStringToWidth(s, 125); + + int h = 16 - list.size() * toastGui.getMinecraft().fontRenderer.FONT_HEIGHT / 2; + + for(String line : list){ + toastGui.getMinecraft().fontRenderer.drawString(line, 30, h, c | a); + h += toastGui.getMinecraft().fontRenderer.FONT_HEIGHT; + } + + RenderHelper.enableGUIStandardItemLighting(); + toastGui.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(null, new ItemStack(WizardryItems.wizard_handbook), 8, 8); + + return delta >= 5000L ? IToast.Visibility.HIDE : IToast.Visibility.SHOW; + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java new file mode 100644 index 00000000..732bb10b --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Image.java @@ -0,0 +1,148 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.client.DrawingUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class Image { + + // Final fields are mandatory, the rest are optional + private final ResourceLocation location; + private final int width, height; + private int textureWidth, textureHeight; + private int u = 0, v = 0; + private String caption = ""; + private boolean border = true; + // Derived fields, not specifically defined in JSON + private final Set instances = new HashSet<>(); + + private static final int CAPTION_OFFSET = 4; + + private static final int TEXTURE_INSET_X = 180; + private static final int BORDER = 1; + + private Image(ResourceLocation location, int width, int height){ + this.location = location; + this.width = width; + this.height = height; + } + + /** Returns the width of the image. */ + int getWidth(){ + return width; + } + + /** Returns the total height of the image, including caption if it has one. */ + int getHeight(FontRenderer font){ + return caption.isEmpty() ? height : height + CAPTION_OFFSET + font.FONT_HEIGHT; + } + + /** + * Adds an instance of this image to the list. + * + * @param page The index of the single page this image is on. + * @param x The x-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. + * @param y The y-coordinate of the top-left corner of the image, relative to the top-left corner of the GUI. + */ + void addInstance(int page, int x, int y){ + instances.add(new int[]{page, x, y}); + } + + /** Removes all instances of this image from the list. */ + void clearInstances(){ + instances.clear(); + } + + /** + * Draws all instances of this image that are located on the given double-page spread. + * + * @param font The font renderer object. + * @param doublePage The double-page index of the page to be drawn. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + */ + void draw(FontRenderer font, int doublePage, int left, int top){ + // Images + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + Minecraft.getMinecraft().renderEngine.bindTexture(location); + GlStateManager.color(1, 1, 1, 1); + DrawingUtils.drawTexturedRect(left + instance[1], top + instance[2], u, v, width, height, textureWidth, textureHeight); + font.drawString("\u00A7o" + caption, left + instance[1] + width/ 2 - font.getStringWidth(caption)/2, + top + instance[2] + height + CAPTION_OFFSET, GuiWizardHandbook.colours.get("caption")); + } + } + + if(border){ + // Borders - do this after all the images are drawn so we only have to bind the handbook texture again once + Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); + GlStateManager.color(1, 1, 1, 1); + for(int[] instance : instances){ + if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ + // Math.ceil accounts for odd-numbered image dimensions + DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] - BORDER, + TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, height / 2 + BORDER, + GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, false); + DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] - BORDER, + TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, height / 2 + BORDER, + GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, false); + DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] + height / 2, + TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, MathHelper.ceil(height / 2f) + BORDER, + GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, true); + DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] + height / 2, + TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, MathHelper.ceil(height / 2f) + BORDER, + GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, true); + } + } + } + } + + /** + * Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields + * and references. + * + * @param json A JSON object representing the image to be constructed. This must contain at least a "location" + * string. + * @return The resulting {@code Image} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Image fromJson(JsonObject json){ + + Image image = new Image(new ResourceLocation(JsonUtils.getString(json, "location")), + JsonUtils.getInt(json, "width"), JsonUtils.getInt(json, "height")); + + image.u = JsonUtils.getInt(json, "u", 0); + image.v = JsonUtils.getInt(json, "v", 0); + image.textureWidth = JsonUtils.getInt(json, "texture_width", image.width); + image.textureHeight = JsonUtils.getInt(json, "texture_height", image.height); + image.caption = JsonUtils.getString(json, "caption", ""); + image.border = JsonUtils.getBoolean(json, "border", true); + + return image; + } + + static void populate(Map map, JsonObject json){ + + JsonObject sectionsObject = JsonUtils.getJsonObject(json, "images"); + + // Need to iterate over these since we don't know what they're called or how many there are + for(Map.Entry entry : sectionsObject.entrySet()){ + + String key = entry.getKey(); // Find out what each element is called, this will be the sections map key + + Image image = fromJson(entry.getValue().getAsJsonObject()); + map.put(key, image); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java new file mode 100644 index 00000000..c057d5e5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java @@ -0,0 +1,451 @@ +package electroblob.wizardry.client.gui.handbook; + +import com.google.common.collect.Streams; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +/** + * Instances of this class represent sections in the wizard's handbook. As of wizardry 4.2, this class handles + * everything within the section itself, including JSON parsing, unlock triggers and drawing the actual rawText. + * Sections may now also be nested and have other elements within them, such as images and a table of contents, a + * behaviour which is also handled within this class. + *

    + * The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of + * sections, images and so on are determined on GUI load and depend on which of the previous sections have been + * unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI + * load, so that each section knows the previous section's length and therefore where to start. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +// Because these are now generated on resource pack reload (not on handbook open, as before), this class can no longer +// be a non-static inner class +class Section { + + // Final fields are mandatory (none here though), the rest are optional + String title; + private String[] rawText; + private Contents contents; + private ResourceLocation[] triggers; + private Map subsections; + private boolean centreX, centreY; + + // Derived fields, not explicitly defined in JSON + /** The single-page index of the first page of this section. */ + int startPage; + private final List> buttons; + /** + * A list of single pages, which are themselves lists of paragraphs (each paragraph is a single + * string which may include line breaks and other escape characters). + */ + private final List> pages; + + private boolean unlocked = false; + private boolean isNew = false; + + private Section(){ + this.buttons = new ArrayList<>(); + this.pages = new ArrayList<>(); + this.subsections = new LinkedHashMap<>(); + } + + Collection getButtons(){ + return WizardryUtilities.flatten(buttons); + } + + /** + * Returns true if the given page is within this section, false if not (or if the section is locked). + */ + boolean containsPage(int page){ + return this.isUnlocked() && startPage <= page && startPage + pages.size() > page; + } + + /** + * Returns true if this section is unlocked for the client player, false if not. Always returns true if + * handbook progression is disabled in the config. + */ + boolean isUnlocked(){ + + if(Minecraft.getMinecraft().player.isCreative()) return true; + if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off + if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start + + // A section is automatically unlocked if one of its subsections is unlocked + for(Section subsection : subsections.values()){ + if(subsection.isUnlocked()) return true; + } + + return unlocked; + } + + /** + * Returns true if this section has been unlocked and not read yet. Also returns true if any subsections are new. + */ + boolean isNew(){ + if(!Wizardry.settings.handbookProgression) return false; + return isNew || this.subsections.values().stream().anyMatch(Section::isNew); + } + + /** + * Actually draws the contents of the given section for the given double-page spread. Will do nothing if the + * given page is outside of this section. + * + * @param font The font renderer object. + * @param doublePage The index of the double-page to be drawn. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + */ + // This method is supposed to be 'idiot-proof' in the sense that the code calling it need not check whether the + // section actually needs drawing, so it can just dumbly call draw(...) for all the sections in order. + void draw(FontRenderer font, int doublePage, int left, int top){ + + // Show/hide buttons + + int i = 0; + + for(List list : buttons){ + final int i1 = i++; + list.forEach(b -> b.visible = GuiWizardHandbook.singleToDoublePage(startPage + i1) == doublePage); + } + + int leftIndex = GuiWizardHandbook.doubleToSinglePage(doublePage, false); + // Relative indices of the pages to be rendered - often these will be outside the section entirely + int[] visiblePages = {leftIndex - startPage, leftIndex - startPage + 1}; + + for(int page : visiblePages){ + + if(page >= 0 && page < pages.size()){ + + List lines = pages.get(page); + + int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X); + int y = top + GuiWizardHandbook.TEXT_INSET_Y; + if(centreY) y += GuiWizardHandbook.PAGE_HEIGHT / 2 - lines.size() / 2 * font.FONT_HEIGHT; + + for(String line : lines){ + + if(line.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG)){ + GlStateManager.color(1, 1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); + DrawingUtils.drawTexturedRect(x-1, y-1, 0, GuiWizardHandbook.GUI_HEIGHT, GuiWizardHandbook.PAGE_WIDTH + 2, 9, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT); + }else{ + int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x; + font.drawString(line, lx, y, DrawingUtils.BLACK, false); + } + + y += font.FONT_HEIGHT; + } + + isNew = false; // Now a page has been drawn, the player must have seen it so it's not new any more + } + } + } + + /** + * Called on GUI load to format the section, contents tables and other elements, excluding subsections. + * Does not perform any actual drawing. + * + * @param font The font renderer object, for measurement purposes. + * @param startPage The index of the first page (single side, not double-page) of this section. + * @param left The x coordinate of the left side of the GUI. + * @param top The y coordinate of the top of the GUI. + * @return The single-page index of the next blank page after the end of this section. + * @throws JsonSyntaxException if at any point the formatting is found to be invalid. + */ + int format(FontRenderer font, int startPage, int left, int top){ + + this.buttons.clear(); + this.pages.clear(); + + // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. + final int maxLineNumber = GuiWizardHandbook.PAGE_HEIGHT / font.FONT_HEIGHT; + + this.startPage = startPage; + + // First everything is added to a single list of lines, then it is split into pages. + List lines = new ArrayList<>(); + + // Adds the header if present + if(!this.title.isEmpty()){ + lines.add(this.title); + lines.add(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG); + } + + // Adds space for the contents if it exists + if(this.contents != null){ + lines.addAll(Collections.nCopies(this.contents.format(font, startPage, lines.size(), left, top), "")); + // Line break between contents and first paragraph + if((lines.size() % maxLineNumber) != 0) lines.add(""); + } + + if(this.rawText != null){ + // Paragraphs are defined as a JSON list because it makes it easier to arrange them properly across pages + // - using multiple line breaks would mean having to find and remove them when at the top of a page. + for(String paragraph : this.rawText){ + + // (lines.size() % maxLineNumber) gives the number of lines on the current page + // (lines.size() / maxLineNumber) gives the index of the current page minus the value of startPage + + // Formats the paragraph + + String raw = paragraph; // For error messages + + // Images (images must be separate paragraphs) + + if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.IMAGE_TAG)){ + + String[] arguments = paragraph.split("\\s", 2); + + if(arguments.length < 2) throw new JsonSyntaxException("Missing image name in string " + + StringUtils.abbreviate(raw, 50)); + + Image image = GuiWizardHandbook.images.get(arguments[1]); + if(image == null) throw new JsonSyntaxException("Image with id " + arguments[1] + " is undefined"); + + // Starts a new page if the image will not fit on the current one + if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){ + // Remaining number of lines on the page + lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), "")); + } + + if(image.getWidth() > GuiWizardHandbook.PAGE_WIDTH) Wizardry.logger.warn("Image with id " + arguments[1] + + "has a width (" + image.getWidth() + ") greater than the maximum page width (" + GuiWizardHandbook.PAGE_WIDTH + + "), it will extend beyond the page area."); + + if(image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT) Wizardry.logger.warn("Image with id " + arguments[1] + + "has a height (" + image.getHeight(font) + ") greater than the maximum page height (" + GuiWizardHandbook.PAGE_HEIGHT + + "), it will extend beyond the page area."); + + int page = startPage + (lines.size() / maxLineNumber); + + image.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - image.getWidth() / 2 + + (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X), + GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT); + + // Height of the image in lines, rounded up + // Uses a single space instead of an empty string so that the page trimming doesn't remove them + lines.addAll(Collections.nCopies(image.getHeight(font) / font.FONT_HEIGHT, " ")); + lines.add(""); // The last one is removable though, since it's actually extra space + + // Recipes (recipes must be separate paragraphs) + }else if(paragraph.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RECIPE_TAG)){ + + String[] arguments = paragraph.split("\\s", 2); + + if(arguments.length < 2) throw new JsonSyntaxException("Missing recipe name in string " + + StringUtils.abbreviate(raw, 50)); + + CraftingRecipe recipe = GuiWizardHandbook.recipes.get(arguments[1]); + if(recipe == null) throw new JsonSyntaxException("Recipe with id " + arguments[1] + " is undefined"); + + // Starts a new page if the recipe will not fit on the current one + if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){ + // Remaining number of lines on the page, plus the first blank one on the new page + lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), " ")); + } + + int page = startPage + (lines.size() / maxLineNumber); + + if(lines.size() % maxLineNumber == 0) lines.add(" "); + int startLine = lines.size() % maxLineNumber - 1; + + recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2 + + (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X), + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT); + + // Height of the recipe in lines, rounded up + // Uses a single space instead of an empty string so that the page trimming doesn't remove them + lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT - 1, " ")); + // This time we're not adding an extra space because it's not really needed + + }else{ // All other paragraphs + + // Formatting + for(Map.Entry entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){ + paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue()); + } + + // Hyperlinks + + int linkStart; + + while((linkStart = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER)) > -1){ // Ooh an assignment and a comparison in one... + + int linkEnd = paragraph.indexOf(GuiWizardHandbook.HYPERLINK_MARKER, linkStart + 1); + + if(linkEnd < 0) throw new JsonSyntaxException("Un-closed hyperlink marker in string " + + StringUtils.abbreviate(raw, 50)); + + List upToLink = font.listFormattedStringToWidth(paragraph.substring(0, linkStart), GuiWizardHandbook.PAGE_WIDTH); + + String linkRaw = paragraph.substring(linkStart, linkEnd + 1); + String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2); + String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0].substring(1); // substring(1) to remove the @ + + // The index of the single page currently being formatted, relative to the section + int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber; + // The overall index of the single page currently being formatted + int page = startPage + pageRelative; + // The line number on this page + int lineNumber = (lines.size() + upToLink.size() - 1) % maxLineNumber; + + int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; + int y = top + GuiWizardHandbook.TEXT_INSET_Y + lineNumber * font.FONT_HEIGHT; + + // Adds any missing sub-lists + while(this.buttons.size() <= pageRelative){ + this.buttons.add(new ArrayList<>()); + } + + // The button id only does what you use it for, so we're just not using it at all. + this.buttons.get(pageRelative).add(GuiButtonHyperlink.create(x, y, font, upToLink, arguments, suffix, maxLineNumber - lineNumber - 1, GuiWizardHandbook.isRightPage(page))); + + // The link button should exactly overlay the display rawText in the main string + // If the link has no display rawText specified, it displays the unformatted target string + paragraph = paragraph.replace(linkRaw, arguments[arguments.length - 1]); + } + + lines.addAll(font.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); + } + + // Line break between paragraphs (the last one will just be deleted later) + if((lines.size() % maxLineNumber) != 0) lines.add(""); + } + } + + // Splits lines into pages + + List page = new ArrayList<>(); + pages.add(page); + + while(!lines.isEmpty()){ + + if(page.size() == maxLineNumber){ + // Removes blank lines at the end of the page + while(page.get(page.size() - 1).isEmpty()) page.remove(page.size() - 1); + // Adds a new page + pages.add(page = new ArrayList<>()); + } + + String line = lines.remove(0); + + // Prevents blank lines at the start of the page + if(!page.isEmpty() || !line.isEmpty()) page.add(line); + } + + return startPage + pages.size(); + } + + /** + * Parses the given JSON object and constructs a new {@code Section} from it, setting all the relevant fields + * and references. This method converts the JSON object to a {@code Section} object and retrieves any resources; + * the section is not formatted in any way until GUI load, in {@link Section#format(FontRenderer, int, int, int)}. + * + * @param json A JSON object representing the section to be constructed. This must contain at least a "title" + * string. + * @return The resulting {@code Section} object. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + static Section fromJson(JsonObject json){ + + Section section = new Section(); + + section.title = JsonUtils.getString(json, "title", ""); + + if(JsonUtils.hasField(json, "include_in_contents")){ + + String id = JsonUtils.getString(json, "include_in_contents"); + + Contents belongsTo = GuiWizardHandbook.contentsList.get(id); + + if(belongsTo == null){ + throw new JsonSyntaxException("Expected include_in_contents to be the id of a previously defined contents, but no contents with the id " + id + " exists yet."); + }else{ + belongsTo.addEntry(section); + } + } + + if(JsonUtils.hasField(json, "contents")){ + section.contents = Contents.fromJson(section, JsonUtils.getJsonObject(json, "contents")); + GuiWizardHandbook.contentsList.put(section.contents.id, section.contents); + } + + if(JsonUtils.hasField(json, "text")){ + section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text")) + .map(e -> JsonUtils.getString(e, "element of array rawText")) + .toArray(String[]::new); + } + + if(JsonUtils.hasField(json, "triggers")){ + section.triggers = Streams.stream(JsonUtils.getJsonArray(json, "triggers")) + .map(e -> new ResourceLocation(JsonUtils.getString(e, "element of array triggers"))) + .toArray(ResourceLocation[]::new); + // TODO: Can we validate this and throw a JSON exception if no such advancement exists? + } + + if(JsonUtils.hasField(json, "centre")){ + JsonObject centre = JsonUtils.getJsonObject(json,"centre"); + section.centreX = JsonUtils.getBoolean(centre, "x", false); + section.centreY = JsonUtils.getBoolean(centre, "y", false); + } + + // The only benefit of having subsections (other than logical grouping) is that the parent section can + // automatically be unlocked if one of the subsections is. + if(JsonUtils.hasField(json, "sections")){ + populate(section.subsections, json); + } + + return section; + } + + static void populate(Map map, JsonObject json){ + + JsonObject sectionsObject = JsonUtils.getJsonObject(json, "sections"); + + // Need to iterate over these since we don't know what they're called or how many there are + for(Map.Entry entry : sectionsObject.entrySet()){ + + String key = entry.getKey(); // Find out what each element is called, this will be the sections map key + + Section section = fromJson(entry.getValue().getAsJsonObject()); + map.put(key, section); + map.putAll(section.subsections); + } + } + + /** + * Called on login and advancement completion to update this section's unlock status and display toast + * notifications if applicable. + */ + public void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){ + + if(triggers == null) return; + + List completed = new ArrayList<>(Arrays.asList(completedAdvancements)); + completed.retainAll(Arrays.asList(triggers)); + + // Only shows the toast when the section was locked before and is now unlocked + if(!this.unlocked && !completed.isEmpty() && showToasts && Wizardry.settings.handbookProgression){ + // Mmmmm toast... + Minecraft minecraft = Minecraft.getMinecraft(); + minecraft.getToastGui().add(new HandbookToast(this)); + this.isNew = true; + } + + // Currently, this will not take subsections into account + this.unlocked = !completed.isEmpty(); + } +} diff --git a/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java b/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java new file mode 100644 index 00000000..c3324830 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java @@ -0,0 +1,234 @@ +package electroblob.wizardry.client.model; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemOverrideList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; +import net.minecraftforge.client.model.pipeline.VertexLighterFlat; +import net.minecraftforge.common.ForgeModContainer; +import net.minecraftforge.fml.client.FMLClientHandler; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nullable; +import javax.vecmath.Matrix4f; +import java.util.ArrayList; +import java.util.List; + +/** + * Custom baked model that stores a list of texture names and sets the lighting to full brightness for any quads + * with one of those textures. Most of this code was copied and adapted from refined storage, which is licensed under + * the MIT license. https://github.com/raoulvdberge/refinedstorage
    + *
    + * N.B. This doesn't cover item models, and all of the code/model solutions to this that I have tried haven't worked. + * However, I noticed that the shade tag seems to work perfectly for items, so instead I've simply duplicated the block + * models into the item models folder and add {@code "shade":false} where appropriate. (If it works, it works, right?) + * + * @author Electroblob + * @author raoulvdberge + */ +public class BakedModelGlowingOverlay implements IBakedModel { + + // Something something something cache, says Forge + // This breaks randomised block models (because it's cached, duh...) but simply including the rand parameter will + // completely defeat the point of the cache so I need some way of storing the randomised model variants... hmmm... + // See WeightedBakedModel for more on randomisation (it's pretty similar to my 1.7 implementation from years ago) + +// private class CacheKey { +// +// private IBakedModel base; +// private String suffix; +// private IBlockState state; +// private EnumFacing side; +// +// public CacheKey(IBakedModel base, String suffix, IBlockState state, EnumFacing side){ +// this.base = base; +// this.suffix = suffix; +// this.state = state; +// this.side = side; +// } +// +// @Override +// public boolean equals(Object o){ +// +// if(this == o) return true; +// if(o == null || getClass() != o.getClass()) return false; +// +// CacheKey cacheKey = (CacheKey)o; +// +// if(cacheKey.side != side) return false; +// if(!state.equals(cacheKey.state)) return false; +// +// return true; +// } +// +// @Override +// public int hashCode() { +// return state.hashCode() + (31 * (side != null ? side.hashCode() : 0)); +// } +// } +// +// private static final LoadingCache> CACHE = CacheBuilder.newBuilder().build(new CacheLoader>() { +// @Override +// public List load(CacheKey key) { +// return transformQuads(key.base.getQuads(key.state, key.side, 0), key.suffix); +// } +// }); + + private final IBakedModel delegate; + private String suffix; + + public BakedModelGlowingOverlay(IBakedModel delegate, String suffix){ + this.delegate = delegate; + this.suffix = suffix; + } + + @Override + public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){ + if(state == null) return delegate.getQuads(state, side, rand); + return transformQuads(delegate.getQuads(state, side, rand), suffix); + //return CACHE.getUnchecked(new CacheKey(delegate, suffix, state instanceof IExtendedBlockState ? ((IExtendedBlockState) state).getClean() : state, side)); + } + + // I would write these myself but I'd end up with almost the exact same thing anyway + // They replace the quads from the original (delegate) model with full-brightness quads if their texture has the given suffix + + private static List transformQuads(List oldQuads, String suffix){ + + List quads = new ArrayList<>(oldQuads); + + for(int i = 0; i < quads.size(); ++i){ + BakedQuad quad = quads.get(i); + + if(quad.getSprite().getIconName().endsWith(suffix)){ + quads.set(i, transformQuad(quad, 0.007F)); // What's the significance of 0.007? + } + } + + return quads; + } + + private static BakedQuad transformQuad(BakedQuad quad, float light){ + + if(isLightMapDisabled()){ + return quad; + } + + VertexFormat newFormat = getFormatWithLightMap(quad.getFormat()); + + UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(newFormat); + + VertexLighterFlat trans = new VertexLighterFlat(Minecraft.getMinecraft().getBlockColors()) { + @Override + protected void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z){ + lightmap[0] = light; + lightmap[1] = light; + } + + @Override + public void setQuadTint(int tint){ + // NO OP + } + }; + + trans.setParent(builder); + + quad.pipe(trans); + + builder.setQuadTint(quad.getTintIndex()); + builder.setQuadOrientation(quad.getFace()); + builder.setTexture(quad.getSprite()); + builder.setApplyDiffuseLighting(false); + + return builder.build(); + } + + @Override + public boolean isAmbientOcclusion(){ + return delegate.isAmbientOcclusion(); + } + + @Override + public boolean isGui3d(){ + return delegate.isGui3d(); + } + + @Override + public boolean isBuiltInRenderer(){ + return delegate.isBuiltInRenderer(); + } + + @Override + public TextureAtlasSprite getParticleTexture(){ + return delegate.getParticleTexture(); + } + + @Override + public ItemCameraTransforms getItemCameraTransforms(){ + return delegate.getItemCameraTransforms(); + } + + @Override + public ItemOverrideList getOverrides(){ + return delegate.getOverrides();//BakedModelItemOverride.instance; + } + + @Override + public boolean isAmbientOcclusion(IBlockState state){ + return delegate.isAmbientOcclusion(state); + } + + @Override + public Pair handlePerspective(ItemCameraTransforms.TransformType cameraTransformType){ + return delegate.handlePerspective(cameraTransformType); + } + + // Utilities + + private static boolean isLightMapDisabled(){ + return FMLClientHandler.instance().hasOptifine() || !ForgeModContainer.forgeLightPipelineEnabled; + } + + private static final VertexFormat ITEM_FORMAT_WITH_LIGHTMAP = new VertexFormat(DefaultVertexFormats.ITEM).addElement(DefaultVertexFormats.TEX_2S); + + private static VertexFormat getFormatWithLightMap(VertexFormat format){ + + if(isLightMapDisabled()){ + return format; + } + + if(format == DefaultVertexFormats.BLOCK){ + return DefaultVertexFormats.BLOCK; + }else if(format == DefaultVertexFormats.ITEM){ + return ITEM_FORMAT_WITH_LIGHTMAP; + }else if(!format.hasUvOffset(1)){ + VertexFormat result = new VertexFormat(format); + result.addElement(DefaultVertexFormats.TEX_2S); + return result; + } + + return format; + } + + // Bit of a weird way of doing things if you ask me, but as far as I can tell it's how you're supposed to do it +// public static final class BakedModelItemOverride extends ItemOverrideList { +// +// // This class doesn't contain any data of its own so it can be a singleton +// public static final BakedModelItemOverride instance = new BakedModelItemOverride(); +// +// private BakedModelItemOverride(){ +// super(ImmutableList.of()); // We're not using the list functionality +// } +// +// @Override +// public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){ +// return new BakedModelGlowingOverlay(originalModel, "overlay"); // Bish bash bosh +// } +// } +} diff --git a/src/main/java/electroblob/wizardry/client/model/ModelWtfMojang.java b/src/main/java/electroblob/wizardry/client/model/ModelArmourFixer.java similarity index 90% rename from src/main/java/electroblob/wizardry/client/model/ModelWtfMojang.java rename to src/main/java/electroblob/wizardry/client/model/ModelArmourFixer.java index ced31285..793f21e8 100644 --- a/src/main/java/electroblob/wizardry/client/model/ModelWtfMojang.java +++ b/src/main/java/electroblob/wizardry/client/model/ModelArmourFixer.java @@ -4,9 +4,14 @@ import net.minecraft.client.model.ModelBiped; import net.minecraft.entity.Entity; import net.minecraft.entity.item.EntityArmorStand; -public class ModelWtfMojang extends ModelBiped { +/** + * Fixes custom armour models 'breathing' on the stand and rotates the helmet properly. + * @author Shadows-of-Fire + * @since Wizardry 4.1.2 + */ +public class ModelArmourFixer extends ModelBiped { - public ModelWtfMojang(float modelSize, float rotationYOffset, int textureWidth, int textureHeight) { + public ModelArmourFixer(float modelSize, float rotationYOffset, int textureWidth, int textureHeight) { super(modelSize, rotationYOffset, textureWidth, textureHeight); } diff --git a/src/main/java/electroblob/wizardry/client/model/ModelHammer.java b/src/main/java/electroblob/wizardry/client/model/ModelHammer.java index 85a164f7..c28c4d6e 100644 --- a/src/main/java/electroblob/wizardry/client/model/ModelHammer.java +++ b/src/main/java/electroblob/wizardry/client/model/ModelHammer.java @@ -5,64 +5,71 @@ import net.minecraft.client.model.ModelRenderer; import net.minecraft.entity.Entity; public class ModelHammer extends ModelBase { - ModelRenderer Shape1; - ModelRenderer Shape2; - ModelRenderer Shape3; - ModelRenderer Shape4; - ModelRenderer Shape5; - ModelRenderer Shape6; + + ModelRenderer hammerHead; + ModelRenderer handle; + ModelRenderer handleEnd; + ModelRenderer handleBase; + ModelRenderer ring1; + ModelRenderer ring2; public ModelHammer(){ + textureWidth = 64; textureHeight = 64; - Shape1 = new ModelRenderer(this, 0, 0); - Shape1.addBox(0F, 0F, 0F, 20, 12, 12); - Shape1.setRotationPoint(-10F, 12F, -6F); - Shape1.setTextureSize(64, 64); - Shape1.mirror = true; - setRotation(Shape1, 0F, 0F, 0F); - Shape2 = new ModelRenderer(this, 0, 24); - Shape2.addBox(0F, 0F, 0F, 4, 14, 4); - Shape2.setRotationPoint(-2F, -2F, -2F); - Shape2.setTextureSize(64, 64); - Shape2.mirror = true; - setRotation(Shape2, 0F, 0F, 0F); - Shape3 = new ModelRenderer(this, 0, 49); - Shape3.addBox(0F, 0F, 0F, 5, 5, 5); - Shape3.setRotationPoint(-2.5F, -7F, -2.5F); - Shape3.setTextureSize(64, 64); - Shape3.mirror = true; - setRotation(Shape3, 0F, 0F, 0F); - Shape4 = new ModelRenderer(this, 0, 42); - Shape4.addBox(0F, 0F, 0F, 5, 2, 5); - Shape4.setRotationPoint(-2.5F, 10F, -2.5F); - Shape4.setTextureSize(64, 64); - Shape4.mirror = true; - setRotation(Shape4, 0F, 0F, 0F); - Shape5 = new ModelRenderer(this, 20, 24); - Shape5.addBox(0F, 0F, 0F, 2, 14, 14); - Shape5.setRotationPoint(-8F, 11F, -7F); - Shape5.setTextureSize(64, 64); - Shape5.mirror = true; - setRotation(Shape5, 0F, 0F, 0F); - Shape6 = new ModelRenderer(this, 20, 24); - Shape6.addBox(0F, 0F, 0F, 2, 14, 14); - Shape6.setRotationPoint(6F, 11F, -7F); - Shape6.setTextureSize(64, 64); - Shape6.mirror = true; - setRotation(Shape6, 0F, 0F, 0F); + hammerHead = new ModelRenderer(this, 0, 0); + hammerHead.addBox(0F, 0F, 0F, 20, 12, 12); + hammerHead.setRotationPoint(-10F, 12F, -6F); + hammerHead.setTextureSize(64, 64); + hammerHead.mirror = true; + setRotation(hammerHead, 0F, 0F, 0F); + + handle = new ModelRenderer(this, 0, 24); + handle.addBox(0F, 0F, 0F, 4, 14, 4); + handle.setRotationPoint(-2F, -2F, -2F); + handle.setTextureSize(64, 64); + handle.mirror = true; + setRotation(handle, 0F, 0F, 0F); + + handleEnd = new ModelRenderer(this, 0, 49); + handleEnd.addBox(0F, 0F, 0F, 5, 5, 5); + handleEnd.setRotationPoint(-2.5F, -7F, -2.5F); + handleEnd.setTextureSize(64, 64); + handleEnd.mirror = true; + setRotation(handleEnd, 0F, 0F, 0F); + + handleBase = new ModelRenderer(this, 0, 42); + handleBase.addBox(0F, 0F, 0F, 5, 2, 5); + handleBase.setRotationPoint(-2.5F, 10F, -2.5F); + handleBase.setTextureSize(64, 64); + handleBase.mirror = true; + setRotation(handleBase, 0F, 0F, 0F); + + ring1 = new ModelRenderer(this, 20, 24); + ring1.addBox(0F, 0F, 0F, 2, 14, 14); + ring1.setRotationPoint(-8F, 11F, -7F); + ring1.setTextureSize(64, 64); + ring1.mirror = true; + setRotation(ring1, 0F, 0F, 0F); + + ring2 = new ModelRenderer(this, 20, 24); + ring2.addBox(0F, 0F, 0F, 2, 14, 14); + ring2.setRotationPoint(6F, 11F, -7F); + ring2.setTextureSize(64, 64); + ring2.mirror = true; + setRotation(ring2, 0F, 0F, 0F); } public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){ super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity); - Shape1.render(f5); - Shape2.render(f5); - Shape3.render(f5); - Shape4.render(f5); - Shape5.render(f5); - Shape6.render(f5); + hammerHead.render(f5); + handle.render(f5); + handleEnd.render(f5); + handleBase.render(f5); + ring1.render(f5); + ring2.render(f5); } private void setRotation(ModelRenderer model, float x, float y, float z){ diff --git a/src/main/java/electroblob/wizardry/client/model/ModelIceGiant.java b/src/main/java/electroblob/wizardry/client/model/ModelIceGiant.java index 798cfcb4..592f0208 100644 --- a/src/main/java/electroblob/wizardry/client/model/ModelIceGiant.java +++ b/src/main/java/electroblob/wizardry/client/model/ModelIceGiant.java @@ -1,18 +1,16 @@ package electroblob.wizardry.client.model; -import javax.vecmath.Matrix4f; -import javax.vecmath.Vector3f; - import electroblob.wizardry.entity.living.EntityIceGiant; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelRenderer; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +import javax.vecmath.Matrix4f; +import javax.vecmath.Vector3f; + +//@SideOnly(Side.CLIENT) public class ModelIceGiant extends ModelBase { /** The head model for the iron golem. */ public ModelRenderer iceGiantHead; diff --git a/src/main/java/electroblob/wizardry/client/model/ModelWizard.java b/src/main/java/electroblob/wizardry/client/model/ModelWizard.java index 0204c86a..8e7a469d 100644 --- a/src/main/java/electroblob/wizardry/client/model/ModelWizard.java +++ b/src/main/java/electroblob/wizardry/client/model/ModelWizard.java @@ -97,9 +97,9 @@ public class ModelWizard extends ModelBiped { setRotation(Shape13, 0F, 0F, 0F); // Makes head bits move with head - // bipedHead.addChild(Shape5); + // bipedHead.addChild(hatSegment4); bipedHead.addChild(beard); - // bipedHead.addChild(Shape7); + // bipedHead.addChild(hatSegment6); // bipedHead.addChild(Shape8); // bipedHead.addChild(Shape9); // bipedHead.addChild(Shape10); @@ -116,8 +116,8 @@ public class ModelWizard extends ModelBiped { /* public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { * super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity); * bipedRightLeg.render(f5); bipedLeftLeg.render(f5); bipedBody.render(f5); bipedLeftArm.render(f5); - * bipedRightArm.render(f5); bipedHead.render(f5); Shape5.render(f5); Shape8.render(f5); Shape9.render(f5); - * Shape10.render(f5); Shape7.render(f5); Shape11.render(f5); Shape12.render(f5); Shape6.render(f5); + * bipedRightArm.render(f5); bipedHead.render(f5); hatSegment4.render(f5); Shape8.render(f5); Shape9.render(f5); + * Shape10.render(f5); hatSegment6.render(f5); Shape11.render(f5); Shape12.render(f5); hatSegment5.render(f5); * Shape13.render(f5); } */ private void setRotation(ModelRenderer model, float x, float y, float z){ model.rotateAngleX = x; diff --git a/src/main/java/electroblob/wizardry/client/model/ModelWizardArmour.java b/src/main/java/electroblob/wizardry/client/model/ModelWizardArmour.java index ee371d3c..2bebd9c0 100644 --- a/src/main/java/electroblob/wizardry/client/model/ModelWizardArmour.java +++ b/src/main/java/electroblob/wizardry/client/model/ModelWizardArmour.java @@ -1,99 +1,110 @@ package electroblob.wizardry.client.model; +import electroblob.wizardry.block.BlockStatue; import net.minecraft.client.model.ModelRenderer; import net.minecraft.entity.Entity; -public class ModelWizardArmour extends ModelWtfMojang { - ModelRenderer Shape1; - ModelRenderer Shape2; - ModelRenderer Shape3; - ModelRenderer Shape4; - ModelRenderer Shape5; - ModelRenderer Shape6; - ModelRenderer Shape7; +public class ModelWizardArmour extends ModelArmourFixer { + + ModelRenderer hatBrim; + ModelRenderer hatSegment1; + ModelRenderer hatSegment2; + ModelRenderer hatSegment3; + ModelRenderer hatSegment4; + ModelRenderer hatSegment5; + ModelRenderer hatSegment6; ModelRenderer robe; - public ModelWizardArmour(float scale){ + public ModelWizardArmour(float delta){ - super(scale, 0, 64, 64); + super(delta, 0, 64, 64); // This is necessary to stop the head from scaling. this.bipedHead = new ModelRenderer(this, 0, 0); - this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.1f); + // The hat layer has an offset of 0.5, so 0.6 is about the smallest we can get away with + this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.6f); this.bipedHead.setRotationPoint(0.0F, 0.0F + 0, 0.0F); - Shape1 = new ModelRenderer(this, -16, 32); - Shape1.addBox(-8F, -7F, -8F, 16, 0, 16); - Shape1.setRotationPoint(0F, 0F, 0F); - Shape1.setTextureSize(64, 64); - Shape1.mirror = true; - setRotation(Shape1, 0F, 0F, 0F); + hatBrim = new ModelRenderer(this, 0, 47); + // Making the height 1 stops the top and bottom z-fighting when the hat is enchanted + hatBrim.addBox(-8F, -6.85F, -8F, 16, 1, 16, 0.6f); + hatBrim.setRotationPoint(0F, 0F, 0F); + hatBrim.setTextureSize(64, 64); + hatBrim.mirror = true; + setRotation(hatBrim, 0F, 0F, 0F); - Shape2 = new ModelRenderer(this, 0, 48); - Shape2.addBox(0F, 0F, 0F, 6, 2, 6); - Shape2.setRotationPoint(-3F, -10F, -3F); - Shape2.setTextureSize(64, 64); - Shape2.mirror = true; - setRotation(Shape2, -0.1396263F, 0F, 0F); + hatSegment1 = new ModelRenderer(this, 0, 32); + hatSegment1.addBox(0F, 0F, 0F, 6, 2, 6, 0.2f); + hatSegment1.setRotationPoint(-3F, -10.6F, -3F); + hatSegment1.setTextureSize(64, 64); + hatSegment1.mirror = true; + setRotation(hatSegment1, -0.1396263F, 0F, 0F); - Shape3 = new ModelRenderer(this, 0, 56); - Shape3.addBox(0F, 0F, 0F, 5, 2, 5); - Shape3.setRotationPoint(-2.5F, -11.53333F, -2F); - Shape3.setTextureSize(64, 64); - Shape3.mirror = true; - setRotation(Shape3, -0.2443461F, 0F, 0F); + hatSegment2 = new ModelRenderer(this, 0, 40); + hatSegment2.addBox(0F, 0F, 0F, 5, 2, 5, 0.1f); + hatSegment2.setRotationPoint(-2.5F, -12.13333F, -2F); + hatSegment2.setTextureSize(64, 64); + hatSegment2.mirror = true; + setRotation(hatSegment2, -0.2443461F, 0F, 0F); - Shape4 = new ModelRenderer(this, 24, 48); - Shape4.addBox(0F, 0F, 0F, 4, 2, 4); - Shape4.setRotationPoint(-2F, -13F, -1F); - Shape4.setTextureSize(64, 64); - Shape4.mirror = true; - setRotation(Shape4, -0.4014257F, 0F, 0F); + hatSegment3 = new ModelRenderer(this, 24, 32); + hatSegment3.addBox(0F, 0F, 0F, 4, 2, 4); + hatSegment3.setRotationPoint(-2F, -13.6F, -1F); + hatSegment3.setTextureSize(64, 64); + hatSegment3.mirror = true; + setRotation(hatSegment3, -0.4014257F, 0F, 0F); - Shape5 = new ModelRenderer(this, 24, 54); - Shape5.addBox(0F, 0F, 0F, 3, 2, 3); - Shape5.setRotationPoint(-1.5F, -14F, 0F); - Shape5.setTextureSize(64, 64); - Shape5.mirror = true; - setRotation(Shape5, -0.5759587F, 0F, 0F); + hatSegment4 = new ModelRenderer(this, 24, 38); + hatSegment4.addBox(0F, 0F, 0F, 3, 2, 3); + hatSegment4.setRotationPoint(-1.5F, -14.6F, 0F); + hatSegment4.setTextureSize(64, 64); + hatSegment4.mirror = true; + setRotation(hatSegment4, -0.5759587F, 0F, 0F); - Shape6 = new ModelRenderer(this, 20, 59); - Shape6.addBox(0F, 0F, 0F, 2, 2, 2); - Shape6.setRotationPoint(-1F, -14F, 0F); - Shape6.setTextureSize(64, 64); - Shape6.mirror = true; - setRotation(Shape6, 0.3316126F, 0F, 0F); + hatSegment5 = new ModelRenderer(this, 20, 43); + hatSegment5.addBox(0F, 0F, 0F, 2, 2, 2); + hatSegment5.setRotationPoint(-1F, -14.6F, 0F); + hatSegment5.setTextureSize(64, 64); + hatSegment5.mirror = true; + setRotation(hatSegment5, 0.3316126F, 0F, 0F); - Shape7 = new ModelRenderer(this, 28, 59); - Shape7.addBox(0F, 0F, 0F, 1, 1, 3); - Shape7.setRotationPoint(-0.5F, -14.5F, 2F); - Shape7.setTextureSize(64, 64); - Shape7.mirror = true; - setRotation(Shape7, -0.5585054F, 0F, 0F); + hatSegment6 = new ModelRenderer(this, 28, 43); + hatSegment6.addBox(0F, 0F, 0F, 1, 1, 3); + hatSegment6.setRotationPoint(-0.5F, -15.1F, 2F); + hatSegment6.setTextureSize(64, 64); + hatSegment6.mirror = true; + setRotation(hatSegment6, -0.5585054F, 0F, 0F); - // The robe is now the body - bipedBody = new ModelRenderer(this, 40, 42); - bipedBody.addBox(-4F, 0F, -2F, 8, 18, 4, scale); + bipedBody = new ModelRenderer(this, 16, 16); + bipedBody.addBox(-4F, 0F, -2F, 8, 11, 4, delta); bipedBody.setRotationPoint(0F, 0F, 0F); bipedBody.setTextureSize(64, 64); bipedBody.mirror = true; setRotation(bipedBody, 0F, 0F, 0F); + robe = new ModelRenderer(this, 40, 32); + robe.addBox(-4F, 0F, -2F, 8, 7, 4, delta); + robe.setRotationPoint(0F, 12, 0F); // 12.5 accounts for the expansion of each box + robe.setTextureSize(64, 64); + robe.mirror = true; + setRotation(robe, 0F, 0F, 0F); + // Makes the hat rotate with the head. - bipedHead.addChild(Shape1); - bipedHead.addChild(Shape2); - bipedHead.addChild(Shape3); - bipedHead.addChild(Shape4); - bipedHead.addChild(Shape5); - bipedHead.addChild(Shape6); - bipedHead.addChild(Shape7); - // Makes the robe move with the body - // bipedBody.addChild(robe); + bipedHead.addChild(hatBrim); + bipedHead.addChild(hatSegment1); + bipedHead.addChild(hatSegment2); + bipedHead.addChild(hatSegment3); + bipedHead.addChild(hatSegment4); + bipedHead.addChild(hatSegment5); + bipedHead.addChild(hatSegment6); } public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){ + if(entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY) + && !entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)) return; super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity); + this.robe.render(f5); } private void setRotation(ModelRenderer model, float x, float y, float z){ @@ -104,6 +115,20 @@ public class ModelWizardArmour extends ModelWtfMojang { public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity){ super.setRotationAngles(f, f1, f2, f3, f4, f5, entity); + this.robe.showModel = this.bipedBody.showModel; + if(this.isSneak){ + //this.robe.rotationPointY = 10.5f; + this.robe.rotationPointZ = 4; + }else{ + //this.robe.rotationPointY = 12.5f; + this.robe.rotationPointZ = 0; + } + + // The bottom part of the robe takes the y rotation from the rest of the robe but the x/z rotation + // from the average of the two legs + this.robe.rotateAngleX = (this.bipedLeftLeg.rotateAngleX + this.bipedRightLeg.rotateAngleX) / 2f; + this.robe.rotateAngleY = this.bipedBody.rotateAngleY; + this.robe.rotateAngleZ = (this.bipedLeftLeg.rotateAngleZ + this.bipedRightLeg.rotateAngleZ) / 2f; } } diff --git a/src/main/java/electroblob/wizardry/client/model/WizardryItemModels.java b/src/main/java/electroblob/wizardry/client/model/WizardryItemModels.java deleted file mode 100644 index 0f5cec39..00000000 --- a/src/main/java/electroblob/wizardry/client/model/WizardryItemModels.java +++ /dev/null @@ -1,198 +0,0 @@ -package electroblob.wizardry.client.model; - -import electroblob.wizardry.registry.WizardryBlocks; -import electroblob.wizardry.registry.WizardryItems; -import net.minecraft.client.renderer.block.model.ModelResourceLocation; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.util.NonNullList; -import net.minecraftforge.client.event.ModelRegistryEvent; -import net.minecraftforge.client.model.ModelLoader; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -import net.minecraftforge.oredict.OreDictionary; - -/** - * Class responsible for registering all of wizardry's item (and itemblock) models. - * - * @author Electroblob - * @since Wizardry 2.1 - */ -@SideOnly(Side.CLIENT) -@Mod.EventBusSubscriber(Side.CLIENT) -public final class WizardryItemModels { - - @SubscribeEvent - public static void register(ModelRegistryEvent event){ - - // ItemBlocks - - registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench)); - registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore)); - registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower)); - registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone)); - registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_block)); - - // Items - - registerItemModel(WizardryItems.magic_crystal); - - registerItemModel(WizardryItems.magic_wand); - registerItemModel(WizardryItems.apprentice_wand); - registerItemModel(WizardryItems.advanced_wand); - registerItemModel(WizardryItems.master_wand); - - registerItemModel(WizardryItems.spell_book); - // Wildcard registered for wizard trades. - registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal"); - registerItemModel(WizardryItems.arcane_tome); - registerItemModel(WizardryItems.wizard_handbook); - - registerItemModel(WizardryItems.basic_fire_wand); - registerItemModel(WizardryItems.basic_ice_wand); - registerItemModel(WizardryItems.basic_lightning_wand); - registerItemModel(WizardryItems.basic_necromancy_wand); - registerItemModel(WizardryItems.basic_earth_wand); - registerItemModel(WizardryItems.basic_sorcery_wand); - registerItemModel(WizardryItems.basic_healing_wand); - - registerItemModel(WizardryItems.apprentice_fire_wand); - registerItemModel(WizardryItems.apprentice_ice_wand); - registerItemModel(WizardryItems.apprentice_lightning_wand); - registerItemModel(WizardryItems.apprentice_necromancy_wand); - registerItemModel(WizardryItems.apprentice_earth_wand); - registerItemModel(WizardryItems.apprentice_sorcery_wand); - registerItemModel(WizardryItems.apprentice_healing_wand); - - registerItemModel(WizardryItems.advanced_fire_wand); - registerItemModel(WizardryItems.advanced_ice_wand); - registerItemModel(WizardryItems.advanced_lightning_wand); - registerItemModel(WizardryItems.advanced_necromancy_wand); - registerItemModel(WizardryItems.advanced_earth_wand); - registerItemModel(WizardryItems.advanced_sorcery_wand); - registerItemModel(WizardryItems.advanced_healing_wand); - - registerItemModel(WizardryItems.master_fire_wand); - registerItemModel(WizardryItems.master_ice_wand); - registerItemModel(WizardryItems.master_lightning_wand); - registerItemModel(WizardryItems.master_necromancy_wand); - registerItemModel(WizardryItems.master_earth_wand); - registerItemModel(WizardryItems.master_sorcery_wand); - registerItemModel(WizardryItems.master_healing_wand); - - registerItemModel(WizardryItems.spectral_sword); - registerItemModel(WizardryItems.spectral_pickaxe); - registerItemModel(WizardryItems.spectral_bow); - - registerItemModel(WizardryItems.mana_flask); - - registerItemModel(WizardryItems.storage_upgrade); - registerItemModel(WizardryItems.siphon_upgrade); - registerItemModel(WizardryItems.condenser_upgrade); - registerItemModel(WizardryItems.range_upgrade); - registerItemModel(WizardryItems.duration_upgrade); - registerItemModel(WizardryItems.cooldown_upgrade); - registerItemModel(WizardryItems.blast_upgrade); - registerItemModel(WizardryItems.attunement_upgrade); - - registerItemModel(WizardryItems.flaming_axe); - registerItemModel(WizardryItems.frost_axe); - - registerItemModel(WizardryItems.firebomb); - registerItemModel(WizardryItems.poison_bomb); - - registerItemModel(WizardryItems.blank_scroll); - registerItemModel(WizardryItems.scroll); - - registerItemModel(WizardryItems.armour_upgrade); - - registerItemModel(WizardryItems.magic_silk); - - registerItemModel(WizardryItems.wizard_hat); - registerItemModel(WizardryItems.wizard_robe); - registerItemModel(WizardryItems.wizard_leggings); - registerItemModel(WizardryItems.wizard_boots); - - registerItemModel(WizardryItems.wizard_hat_fire); - registerItemModel(WizardryItems.wizard_robe_fire); - registerItemModel(WizardryItems.wizard_leggings_fire); - registerItemModel(WizardryItems.wizard_boots_fire); - - registerItemModel(WizardryItems.wizard_hat_ice); - registerItemModel(WizardryItems.wizard_robe_ice); - registerItemModel(WizardryItems.wizard_leggings_ice); - registerItemModel(WizardryItems.wizard_boots_ice); - - registerItemModel(WizardryItems.wizard_hat_lightning); - registerItemModel(WizardryItems.wizard_robe_lightning); - registerItemModel(WizardryItems.wizard_leggings_lightning); - registerItemModel(WizardryItems.wizard_boots_lightning); - - registerItemModel(WizardryItems.wizard_hat_necromancy); - registerItemModel(WizardryItems.wizard_robe_necromancy); - registerItemModel(WizardryItems.wizard_leggings_necromancy); - registerItemModel(WizardryItems.wizard_boots_necromancy); - - registerItemModel(WizardryItems.wizard_hat_earth); - registerItemModel(WizardryItems.wizard_robe_earth); - registerItemModel(WizardryItems.wizard_leggings_earth); - registerItemModel(WizardryItems.wizard_boots_earth); - - registerItemModel(WizardryItems.wizard_hat_sorcery); - registerItemModel(WizardryItems.wizard_robe_sorcery); - registerItemModel(WizardryItems.wizard_leggings_sorcery); - registerItemModel(WizardryItems.wizard_boots_sorcery); - - registerItemModel(WizardryItems.wizard_hat_healing); - registerItemModel(WizardryItems.wizard_robe_healing); - registerItemModel(WizardryItems.wizard_leggings_healing); - registerItemModel(WizardryItems.wizard_boots_healing); - - registerItemModel(WizardryItems.spectral_helmet); - registerItemModel(WizardryItems.spectral_chestplate); - registerItemModel(WizardryItems.spectral_leggings); - registerItemModel(WizardryItems.spectral_boots); - - registerItemModel(WizardryItems.smoke_bomb); - - registerItemModel(WizardryItems.identification_scroll); - } - - // Moved from the proxies - - /** - * Registers an item model, using the item's registry name as the model name (this convention makes it easier to - * keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus - * all the other metadata values that the item can take, as defined in - * {@link Item#getSubItems(Item, net.minecraft.creativetab.CreativeTabs, java.util.List)}. The passed in item - * must allow null to be passed in for the creative tab parameter in the aforementioned method, or a - * {@link NullPointerException} will result. - */ - private static void registerItemModel(Item item){ - - if(item.getHasSubtypes()){ - NonNullList items = NonNullList.create(); - item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK. - for(ItemStack stack : items){ - ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(), - new ModelResourceLocation(item.getRegistryName(), "inventory")); - } - } - // Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why! - ModelLoader.setCustomModelResourceLocation(item, 0, - new ModelResourceLocation(item.getRegistryName(), "inventory")); - } - - /** - * Registers an item model for the given metadata, using the item's registry name as the model name (this convention - * makes it easier to keep track of everything). This is intended for registering additional metadata values which - * aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades. - */ - private static void registerItemModel(Item item, int metadata, String variant){ - ModelLoader.setCustomModelResourceLocation(item, metadata, - new ModelResourceLocation(item.getRegistryName(), variant)); - } - -} diff --git a/src/main/java/electroblob/wizardry/client/model/WizardryModels.java b/src/main/java/electroblob/wizardry/client/model/WizardryModels.java new file mode 100644 index 00000000..a6f2529e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/WizardryModels.java @@ -0,0 +1,343 @@ +package electroblob.wizardry.client.model; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockCrystal; +import electroblob.wizardry.block.BlockPedestal; +import electroblob.wizardry.block.BlockRunestone; +import electroblob.wizardry.item.IMultiTexturedItem; +import electroblob.wizardry.item.ItemBlockMultiTexturedElemental; +import electroblob.wizardry.item.ItemCrystal; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.block.statemap.StateMap; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraftforge.client.event.ModelBakeEvent; +import net.minecraftforge.client.event.ModelRegistryEvent; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.oredict.OreDictionary; + +/** + * Class responsible for registering all of wizardry's item and block models. + * + * @author Electroblob + * @since Wizardry 2.1 + */ +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public final class WizardryModels { + + private WizardryModels(){} // No instances! + + @SubscribeEvent + public static void register(ModelRegistryEvent event){ + + // ItemBlocks + + registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench)); + registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore)); + registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower)); + registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone)); + + ModelLoader.setCustomStateMapper(WizardryBlocks.crystal_block, new StateMap.Builder() + .withName(BlockCrystal.ELEMENT).withSuffix("_crystal_block").build()); + // Yay unchecked casting! But we know it's always ok here, and it makes everything much neater. + ItemBlockMultiTexturedElemental crystalBlockItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.crystal_block); + registerMultiTexturedModel(crystalBlockItem); + + ModelLoader.setCustomStateMapper(WizardryBlocks.runestone, new StateMap.Builder() + .withName(BlockRunestone.ELEMENT).withSuffix("_runestone").build()); + ItemBlockMultiTexturedElemental runestoneItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone); + registerMultiTexturedModel(runestoneItem); + + ModelLoader.setCustomStateMapper(WizardryBlocks.runestone_pedestal, new StateMap.Builder() + .withName(BlockPedestal.ELEMENT).ignore(BlockPedestal.NATURAL).withSuffix("_runestone_pedestal").build()); // Don't care about NATURAL property + ItemBlockMultiTexturedElemental pedestalItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone_pedestal); + registerMultiTexturedModel(pedestalItem); + + // Items + + registerMultiTexturedModel((ItemCrystal)WizardryItems.magic_crystal); + + registerItemModel(WizardryItems.magic_wand); + registerItemModel(WizardryItems.apprentice_wand); + registerItemModel(WizardryItems.advanced_wand); + registerItemModel(WizardryItems.master_wand); + + registerItemModel(WizardryItems.spell_book); + // Wildcard registered for wizard trades. + registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal"); + registerItemModel(WizardryItems.arcane_tome); + registerItemModel(WizardryItems.wizard_handbook); + + registerItemModel(WizardryItems.novice_fire_wand); + registerItemModel(WizardryItems.novice_ice_wand); + registerItemModel(WizardryItems.novice_lightning_wand); + registerItemModel(WizardryItems.novice_necromancy_wand); + registerItemModel(WizardryItems.novice_earth_wand); + registerItemModel(WizardryItems.novice_sorcery_wand); + registerItemModel(WizardryItems.novice_healing_wand); + + registerItemModel(WizardryItems.apprentice_fire_wand); + registerItemModel(WizardryItems.apprentice_ice_wand); + registerItemModel(WizardryItems.apprentice_lightning_wand); + registerItemModel(WizardryItems.apprentice_necromancy_wand); + registerItemModel(WizardryItems.apprentice_earth_wand); + registerItemModel(WizardryItems.apprentice_sorcery_wand); + registerItemModel(WizardryItems.apprentice_healing_wand); + + registerItemModel(WizardryItems.advanced_fire_wand); + registerItemModel(WizardryItems.advanced_ice_wand); + registerItemModel(WizardryItems.advanced_lightning_wand); + registerItemModel(WizardryItems.advanced_necromancy_wand); + registerItemModel(WizardryItems.advanced_earth_wand); + registerItemModel(WizardryItems.advanced_sorcery_wand); + registerItemModel(WizardryItems.advanced_healing_wand); + + registerItemModel(WizardryItems.master_fire_wand); + registerItemModel(WizardryItems.master_ice_wand); + registerItemModel(WizardryItems.master_lightning_wand); + registerItemModel(WizardryItems.master_necromancy_wand); + registerItemModel(WizardryItems.master_earth_wand); + registerItemModel(WizardryItems.master_sorcery_wand); + registerItemModel(WizardryItems.master_healing_wand); + + registerItemModel(WizardryItems.spectral_sword); + registerItemModel(WizardryItems.spectral_pickaxe); + registerItemModel(WizardryItems.spectral_bow); + + registerItemModel(WizardryItems.small_mana_flask); + registerItemModel(WizardryItems.medium_mana_flask); + registerItemModel(WizardryItems.large_mana_flask); + + registerItemModel(WizardryItems.crystal_shard); + registerItemModel(WizardryItems.grand_crystal); + + registerItemModel(WizardryItems.astral_diamond); + + registerItemModel(WizardryItems.purifying_elixir); + + registerItemModel(WizardryItems.storage_upgrade); + registerItemModel(WizardryItems.siphon_upgrade); + registerItemModel(WizardryItems.condenser_upgrade); + registerItemModel(WizardryItems.range_upgrade); + registerItemModel(WizardryItems.duration_upgrade); + registerItemModel(WizardryItems.cooldown_upgrade); + registerItemModel(WizardryItems.blast_upgrade); + registerItemModel(WizardryItems.attunement_upgrade); + registerItemModel(WizardryItems.melee_upgrade); + + registerItemModel(WizardryItems.flaming_axe); + registerItemModel(WizardryItems.frost_axe); + + registerItemModel(WizardryItems.firebomb); + registerItemModel(WizardryItems.poison_bomb); + registerItemModel(WizardryItems.smoke_bomb); + registerItemModel(WizardryItems.spark_bomb); + + registerItemModel(WizardryItems.blank_scroll); + registerItemModel(WizardryItems.scroll); + registerItemModel(WizardryItems.identification_scroll); + + registerItemModel(WizardryItems.armour_upgrade); + + registerItemModel(WizardryItems.magic_silk); + + registerItemModel(WizardryItems.wizard_hat); + registerItemModel(WizardryItems.wizard_robe); + registerItemModel(WizardryItems.wizard_leggings); + registerItemModel(WizardryItems.wizard_boots); + + registerItemModel(WizardryItems.wizard_hat_fire); + registerItemModel(WizardryItems.wizard_robe_fire); + registerItemModel(WizardryItems.wizard_leggings_fire); + registerItemModel(WizardryItems.wizard_boots_fire); + + registerItemModel(WizardryItems.wizard_hat_ice); + registerItemModel(WizardryItems.wizard_robe_ice); + registerItemModel(WizardryItems.wizard_leggings_ice); + registerItemModel(WizardryItems.wizard_boots_ice); + + registerItemModel(WizardryItems.wizard_hat_lightning); + registerItemModel(WizardryItems.wizard_robe_lightning); + registerItemModel(WizardryItems.wizard_leggings_lightning); + registerItemModel(WizardryItems.wizard_boots_lightning); + + registerItemModel(WizardryItems.wizard_hat_necromancy); + registerItemModel(WizardryItems.wizard_robe_necromancy); + registerItemModel(WizardryItems.wizard_leggings_necromancy); + registerItemModel(WizardryItems.wizard_boots_necromancy); + + registerItemModel(WizardryItems.wizard_hat_earth); + registerItemModel(WizardryItems.wizard_robe_earth); + registerItemModel(WizardryItems.wizard_leggings_earth); + registerItemModel(WizardryItems.wizard_boots_earth); + + registerItemModel(WizardryItems.wizard_hat_sorcery); + registerItemModel(WizardryItems.wizard_robe_sorcery); + registerItemModel(WizardryItems.wizard_leggings_sorcery); + registerItemModel(WizardryItems.wizard_boots_sorcery); + + registerItemModel(WizardryItems.wizard_hat_healing); + registerItemModel(WizardryItems.wizard_robe_healing); + registerItemModel(WizardryItems.wizard_leggings_healing); + registerItemModel(WizardryItems.wizard_boots_healing); + + registerItemModel(WizardryItems.spectral_helmet); + registerItemModel(WizardryItems.spectral_chestplate); + registerItemModel(WizardryItems.spectral_leggings); + registerItemModel(WizardryItems.spectral_boots); + + registerItemModel(WizardryItems.lightning_hammer); + + registerItemModel(WizardryItems.ring_condensing); + registerItemModel(WizardryItems.ring_siphoning); + registerItemModel(WizardryItems.ring_battlemage); + registerItemModel(WizardryItems.ring_combustion); + registerItemModel(WizardryItems.ring_fire_melee); + registerItemModel(WizardryItems.ring_fire_biome); + registerItemModel(WizardryItems.ring_disintegration); + registerItemModel(WizardryItems.ring_ice_melee); + registerItemModel(WizardryItems.ring_ice_biome); + registerItemModel(WizardryItems.ring_arcane_frost); + registerItemModel(WizardryItems.ring_shattering); + registerItemModel(WizardryItems.ring_lightning_melee); + registerItemModel(WizardryItems.ring_storm); + registerItemModel(WizardryItems.ring_seeking); + registerItemModel(WizardryItems.ring_hammer); + registerItemModel(WizardryItems.ring_soulbinding); + registerItemModel(WizardryItems.ring_leeching); + registerItemModel(WizardryItems.ring_necromancy_melee); + registerItemModel(WizardryItems.ring_mind_control); + registerItemModel(WizardryItems.ring_poison); + registerItemModel(WizardryItems.ring_earth_melee); + registerItemModel(WizardryItems.ring_earth_biome); + registerItemModel(WizardryItems.ring_full_moon); + registerItemModel(WizardryItems.ring_extraction); + registerItemModel(WizardryItems.ring_mana_return); + registerItemModel(WizardryItems.ring_blockwrangler); + registerItemModel(WizardryItems.ring_conjurer); + registerItemModel(WizardryItems.ring_defender); + registerItemModel(WizardryItems.ring_paladin); + registerItemModel(WizardryItems.ring_interdiction); + + registerItemModel(WizardryItems.amulet_arcane_defence); + registerItemModel(WizardryItems.amulet_warding); + registerItemModel(WizardryItems.amulet_wisdom); + registerItemModel(WizardryItems.amulet_fire_protection); + registerItemModel(WizardryItems.amulet_fire_cloaking); + registerItemModel(WizardryItems.amulet_ice_immunity); + registerItemModel(WizardryItems.amulet_ice_protection); + registerItemModel(WizardryItems.amulet_potential); + registerItemModel(WizardryItems.amulet_channeling); + registerItemModel(WizardryItems.amulet_lich); + registerItemModel(WizardryItems.amulet_wither_immunity); + registerItemModel(WizardryItems.amulet_glide); + registerItemModel(WizardryItems.amulet_banishing); + registerItemModel(WizardryItems.amulet_anchoring); + registerItemModel(WizardryItems.amulet_recovery); + registerItemModel(WizardryItems.amulet_transience); + registerItemModel(WizardryItems.amulet_resurrection); + registerItemModel(WizardryItems.amulet_auto_shield); + + registerItemModel(WizardryItems.charm_haggler); + registerItemModel(WizardryItems.charm_experience_tome); + registerItemModel(WizardryItems.charm_auto_smelt); + registerItemModel(WizardryItems.charm_lava_walking); + registerItemModel(WizardryItems.charm_storm); + registerItemModel(WizardryItems.charm_minion_health); + registerItemModel(WizardryItems.charm_minion_variants); + registerItemModel(WizardryItems.charm_flight); + registerItemModel(WizardryItems.charm_growth); + registerItemModel(WizardryItems.charm_abseiling); + registerItemModel(WizardryItems.charm_silk_touch); + registerItemModel(WizardryItems.charm_stop_time); + registerItemModel(WizardryItems.charm_light); + registerItemModel(WizardryItems.charm_transportation); + registerItemModel(WizardryItems.charm_feeding); + + } + + @SubscribeEvent + public static void bake(ModelBakeEvent event){ + // MMMmmmm I love the smell of freshly-baked models... + // This stuff is the boilerplate for making runestone overlay render with full brightness + // See https://www.minecraftforge.net/forum/topic/66005-how-do-i-make-a-tileentityspecialrenderer-solved-with-ibakedmodel/ + // As usual the Forge documentation is just a description of each class and not an explanation of how to use them + // I had to work out where this goes from the refined storage repo linked in the above thread, which is mixed in + // with a more extensive registration system (which is super neat, but it's overkill for our purposes) + // https://github.com/raoulvdberge/refinedstorage/blob/13d6e7f2b92f41b5009187aa2cbde50dbc72082f/src/main/java/com/raoulvdberge/refinedstorage/proxy/ProxyClient.java#L59 + + for(ModelResourceLocation location : event.getModelRegistry().getKeys()){ + + if(location.getNamespace().equals(Wizardry.MODID)){ + + if(location.getPath().contains("runestone") || location.getPath().contains("runestone_pedestal")){ + IBakedModel original = event.getModelRegistry().getObject(location); + event.getModelRegistry().putObject(location, new BakedModelGlowingOverlay(original, "overlay")); + } + } + } + } + + // Moved from the proxies + + /** + * Registers an item model, using the item's registry name as the model name (this convention makes it easier to + * keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus + * all the other metadata values that the item can take, as defined in + * {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied + * to the aforementioned method will be whichever one the item is in. + */ + private static void registerItemModel(Item item){ + + if(item.getHasSubtypes()){ + NonNullList items = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK. + for(ItemStack stack : items){ + ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(), + new ModelResourceLocation(item.getRegistryName(), "inventory")); + } + } + // Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why! + ModelLoader.setCustomModelResourceLocation(item, 0, + new ModelResourceLocation(item.getRegistryName(), "inventory")); + } + + /** + * Registers an item model, using the itemstack-sensitive {@link IMultiTexturedItem#getModelName(ItemStack)} as the + * model name. This allows items to change their texture based on metadata/NBT. Variant defaults to "normal". Registers the + * model for metadata 0 automatically, plus all the other metadata values that the item can take, as defined in + * {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied + * to the aforementioned method will be whichever one the item is in. + */ + private static void registerMultiTexturedModel(T item){ + + if(item.getHasSubtypes()){ + NonNullList items = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), items); + for(ItemStack stack : items){ + ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(), + new ModelResourceLocation(item.getModelName(stack), "inventory")); + } + } + } + + /** + * Registers an item model for the given metadata, using the item's registry name as the model name (this convention + * makes it easier to keep track of everything). This is intended for registering additional metadata values which + * aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades. + */ + private static void registerItemModel(Item item, int metadata, String variant){ + ModelLoader.setCustomModelResourceLocation(item, metadata, + new ModelResourceLocation(item.getRegistryName(), variant)); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleBeam.java b/src/main/java/electroblob/wizardry/client/particle/ParticleBeam.java new file mode 100644 index 00000000..3e6d3a3f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleBeam.java @@ -0,0 +1,99 @@ +package electroblob.wizardry.client.particle; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +public class ParticleBeam extends ParticleTargeted { + + /** Half the width of the outermost layer. */ + private static final float THICKNESS = 0.1f; + + public ParticleBeam(World world, double x, double y, double z){ + super(world, x, y, z); // Does not have a texture! + this.setRBGColorF(1, 1, 1); + this.setMaxAge(0); + this.particleScale = 1; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public int getFXLayer(){ + return 3; + } + + @Override + protected void draw(Tessellator tessellator, double length, float partialTicks){ + + float scale = this.particleScale; + + if(this.particleMaxAge > 0){ + float ageFraction = (particleAge + partialTicks - 1)/particleMaxAge; + // Squaring this makes it look smoother than a linear shrinking effect + scale = this.particleScale * (1 - ageFraction*ageFraction); + } + + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture2D(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + for(int layer=0; layer<3; layer++){ + drawSegment(tessellator, layer, 0, 0, 0, 0, 0, length, THICKNESS * scale); + } + + GlStateManager.enableTexture2D(); + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + } + + /** Draws the given layer of a segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with the given thickness. */ + private void drawSegment(Tessellator tessellator, int layer, double x1, double y1, double z1, double x2, double y2, double z2, float thickness){ + + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR); + + switch(layer){ + + case 0: + drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.25f*thickness, 1, 1, 1, 1); + break; + + case 1: + drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, 0.6f*thickness, (particleRed + 1)/2, (particleGreen + 1)/2, + (particleBlue + 1)/2, 0.65f); + break; + + case 2: + drawShearedBox(buffer, x1, y1, z1, x2, y2, z2, thickness, particleRed, particleGreen, particleBlue, 0.3f); + break; + } + + tessellator.draw(); + } + + /** Draws a single box for one segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with given width and colour. */ + private void drawShearedBox(BufferBuilder buffer, double x1, double y1, double z1, double x2, double y2, double z2, float width, float r, float g, float b, float a){ + + buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex(); + buffer.pos(x1-width, y1+width, z1).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2+width, z2).color(r, g, b, a).endVertex(); + buffer.pos(x1+width, y1+width, z1).color(r, g, b, a).endVertex(); + buffer.pos(x2+width, y2+width, z2).color(r, g, b, a).endVertex(); + buffer.pos(x1+width, y1-width, z1).color(r, g, b, a).endVertex(); + buffer.pos(x2+width, y2-width, z2).color(r, g, b, a).endVertex(); + buffer.pos(x1-width, y1-width, z1).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2-width, z2).color(r, g, b, a).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleBlizzard.java b/src/main/java/electroblob/wizardry/client/particle/ParticleBlizzard.java deleted file mode 100644 index 58e84e48..00000000 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleBlizzard.java +++ /dev/null @@ -1,74 +0,0 @@ -package electroblob.wizardry.client.particle; - -import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -public class ParticleBlizzard extends ParticleSnow { - - private double angle; - private double radius; - private double speed; - - public ParticleBlizzard(World world, int maxAge, double originX, double originZ, double radius, double yPos){ - super(world, 0, 0, 0, 0, 0, 0, maxAge); - this.angle = this.rand.nextDouble() * Math.PI * 2; - double x = originX - Math.cos(angle) * radius; - double z = originZ + radius * Math.sin(angle); - this.radius = radius; - this.setPosition(x, yPos, z); - this.prevPosX = x; - this.prevPosY = yPos; - this.prevPosZ = z; - if(rand.nextBoolean()){ - speed = rand.nextDouble() * 2 + 1; - }else{ - speed = rand.nextDouble() * -2 - 1; - } - this.multipleParticleScaleBy(1.5f); - } - - @Override - public void init(){ - super.init(); - this.fullBrightness = true; - } - - // @Override - // public void renderParticle(VertexBuffer buffer, Entity entity, float partialTicks, float rotationX, float - // rotationZ, float rotationYZ, float rotationXY, float rotationXZ){ - // if(this.particleAge < this.particleMaxAge / 3 || (this.particleAge + this.particleMaxAge) / 3 % 2 == 0){ - // super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ); - // } - // } - - @Override - public void onUpdate(){ - - this.prevPosX = this.posX; - this.prevPosY = this.posY; - this.prevPosZ = this.posZ; - - if(this.particleAge++ >= this.particleMaxAge){ - this.setExpired(); - } - - // This is in radians per tick... - double omega = Math.signum(speed) * ((Math.PI * 2) / 20 - speed / (20 * radius)); - - // v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi. - this.angle += omega; - - this.motionY -= 0.04D * (double)this.particleGravity; - this.motionZ = radius * omega * Math.cos(angle); - this.motionX = radius * omega * Math.sin(angle); - this.move(motionX, motionY, motionZ); - - if(this.particleAge > this.particleMaxAge / 2){ - this.setAlphaF( - 1.0F - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge); - } - - } -} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java b/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java new file mode 100644 index 00000000..a7a8a752 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java @@ -0,0 +1,136 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +//@SideOnly(Side.CLIENT) +public class ParticleBuff extends ParticleWizardry { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/buff.png"); + private final boolean mirror; + + public ParticleBuff(World world, double x, double y, double z){ + super(world, x, y, z); + this.setVelocity(0, 0.162, 0); // Approximately what it was before + this.mirror = random.nextBoolean(); + this.setMaxAge(15); + this.setGravity(false); + this.canCollide = false; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + + @Override + public void onUpdate(){ + super.onUpdate(); + if(this.particleAge > this.particleMaxAge/2) this.particleAlpha = 2f - 2f*(float)this.particleAge/(float)this.particleMaxAge; + } + + /* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png + * to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the + * render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine + * for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was + * presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning + * you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten + * anyway since they bind their own textures. It is of course important to bind the texture every time you render a + * custom particle, but I don't see how you could do it any other way, since you don't have access to + * EffectRenderer. */ + @Override + public int getFXLayer(){ + // This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer. + return 3; + } + + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, + float rotationYZ, float rotationXY, float rotationXZ){ + + // Copied from ParticleWizardry, needs to be here since we're not calling super + updateEntityLinking(partialTicks); + + GlStateManager.pushMatrix(); + GlStateManager.pushAttrib(); + + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.disableCull(); + GlStateManager.disableLighting(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); + // Makes the particle colour add to the colour of the texture pixels, rather than the default multiplying + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD); + + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + + // Does the texture translation wrapping thing (the cool stuff) + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + + GlStateManager.translate((this.particleAge + partialTicks)/(float)this.particleMaxAge * -2, 0, 0); + + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + RenderHelper.disableStandardItemLighting(); + + Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE); + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); + + float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); + float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); + float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ); + + // Increases from 0 to 1 in steps of 0.125 evenly throughout the particle's lifetime + float f = 0.875f - 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f); + float g = f + 0.125f; + float hrepeat = 1; + float scale = 0.6f; + float yScale = 0.7f * scale; + float dx = mirror ? -scale : scale; + float dz = scale; + + buffer.pos(x-dx, y-yScale, z-dz).tex(0, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-dx, y+yScale, z-dz).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+dx, y-yScale, z-dz).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+dx, y+yScale, z-dz).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+dx, y-yScale, z+dz).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+dx, y+yScale, z+dz).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-dx, y-yScale, z+dz).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-dx, y+yScale, z+dz).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-dx, y-yScale, z-dz).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-dx, y+yScale, z-dz).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + + Tessellator.getInstance().draw(); + + // Undoes the texture transformations + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + // Reverses the colour addition change from before + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + + GlStateManager.popAttrib(); + GlStateManager.popMatrix(); + + } +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleCustomTexture.java b/src/main/java/electroblob/wizardry/client/particle/ParticleCustomTexture.java deleted file mode 100644 index 38259b1a..00000000 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleCustomTexture.java +++ /dev/null @@ -1,217 +0,0 @@ -package electroblob.wizardry.client.particle; - -import org.lwjgl.opengl.GL11; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.entity.Entity; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -/** - * Abstract superclass for all particles that use custom textures. This is intended to centralise as much code as - * possible; all subclasses need to do is to define the texture to use, how the frames are arranged (and which to - * choose), and any properties like gravity and collisions. - * - * @author Electroblob - * @since Wizardry 1.2 - */ -@SideOnly(Side.CLIENT) -public abstract class ParticleCustomTexture extends Particle { - - /** True if the particle always renders at full brightness. Defaults to false. */ - protected boolean fullBrightness = false; - - public ParticleCustomTexture(World world, double x, double y, double z, double vx, double vy, double vz){ - super(world, x, y, z, vx, vy, vz); - this.motionX = vx; - this.motionY = vy; - this.motionZ = vz; - this.init(); - } - - public ParticleCustomTexture(World world, double x, double y, double z, double vx, double vy, double vz, - int maxAge){ - this(world, x, y, z, vx, vy, vz); - this.particleMaxAge = maxAge; - } - - /** - * Called from both constructors to set constants, avoiding duplicate code. Common fields to set here include: - * particleScale, particleGravity, canCollide, fullBrightness and setting the texture index. - */ - public abstract void init(); - - /** - * Returns a ResourceLocation for the particle's texture sheet. Do not create a new ResourceLocation in this method, - * only return a constant. - */ - public abstract ResourceLocation getTexture(); - - /** Returns how many 'frames' there are in the x direction on the texture. */ - protected abstract int getXFrames(); - - /** Returns how many 'frames' there are in the y direction on the texture. */ - protected abstract int getYFrames(); - - /* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png - * to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the - * render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine - * for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was - * presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning - * you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten - * anyway since they bind their own textures. It is of course important to bind the texture every time you render a - * custom particle, but I don't see how you could do it any other way, since you don't have access to - * EffectRenderer. */ - @Override - public int getFXLayer(){ - // This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer. - return 3; - } - - @Override - public void setParticleTextureIndex(int index){ - this.particleTextureIndexX = index % getXFrames(); - this.particleTextureIndexY = index / getYFrames(); - } - - // Overridden to fix the bug with vanilla that makes particles frictionless. (y != y... seriously, Mojang?) - // TESTME: Probably no longer necessary. - // @Override - // public void move(double x, double y, double z){ - // - // double d0 = y; - // - // if (this.canCollide) - // { - // List list = this.world.getCollisionBoxes((Entity)null, this.getBoundingBox().addCoord(x, y, z)); - // - // for (AxisAlignedBB axisalignedbb : list) - // { - // y = axisalignedbb.calculateYOffset(this.getBoundingBox(), y); - // } - // - // this.setBoundingBox(this.getBoundingBox().offset(0.0D, y, 0.0D)); - // - // for (AxisAlignedBB axisalignedbb1 : list) - // { - // x = axisalignedbb1.calculateXOffset(this.getBoundingBox(), x); - // } - // - // this.setBoundingBox(this.getBoundingBox().offset(x, 0.0D, 0.0D)); - // - // for (AxisAlignedBB axisalignedbb2 : list) - // { - // z = axisalignedbb2.calculateZOffset(this.getBoundingBox(), z); - // } - // - // this.setBoundingBox(this.getBoundingBox().offset(0.0D, 0.0D, z)); - // } - // else - // { - // this.setBoundingBox(this.getBoundingBox().offset(x, y, z)); - // } - // - // this.resetPositionToBB(); - // this.onGround = d0 != y && d0 < 0.0D; - // - // /* Can never be true! - But this doesn't seem to make any difference anyway. - // if (x != x) - // { - // this.motionX = 0.0D; - // } - // - // if (z != z) - // { - // this.motionZ = 0.0D; - // } - // */ - // } - - // Overridden to bind the new texture. I think this can be done with TextureAtlasSprite, but this works as it is - // so I'm not changing it for the time being. - @Override - public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, - float rotationYZ, float rotationXY, float rotationXZ){ - - GlStateManager.pushMatrix(); - - this.applyGLStateChanges(); - - // This stuff does the shading. It vanilla does this later on for each point, but this also seems to work. - int brightness = this.getBrightnessForRender(partialTicks); - int lightmapX = brightness % 65536; - int lightmapY = brightness / 65536; - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)lightmapX / 1.0F, - (float)lightmapY / 1.0F); - - RenderHelper.disableStandardItemLighting(); - - Minecraft.getMinecraft().getTextureManager().bindTexture(getTexture()); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - - float u1 = (float)this.particleTextureIndexX / (float)getXFrames(); - float u2 = u1 + 1.0f / getXFrames(); - float v1 = (float)this.particleTextureIndexY / (float)getYFrames(); - float v2 = v1 + 1.0f / getYFrames(); - float scale = 0.1F * this.particleScale; - - // I'm pretty sure these were always static. - Particle.interpPosX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * (double)partialTicks; - Particle.interpPosY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * (double)partialTicks; - Particle.interpPosZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * (double)partialTicks; - - float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); - float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); - float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ); - - buffer.pos((double)(x - rotationX * scale - rotationXY * scale), (double)(y - rotationZ * scale), - (double)(z - rotationYZ * scale - rotationXZ * scale)).tex(u2, v2) - .color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); - buffer.pos((double)(x - rotationX * scale + rotationXY * scale), (double)(y + rotationZ * scale), - (double)(z - rotationYZ * scale + rotationXZ * scale)).tex(u2, v1) - .color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); - buffer.pos((double)(x + rotationX * scale + rotationXY * scale), (double)(y + rotationZ * scale), - (double)(z + rotationYZ * scale + rotationXZ * scale)).tex(u1, v1) - .color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); - buffer.pos((double)(x + rotationX * scale - rotationXY * scale), (double)(y - rotationZ * scale), - (double)(z + rotationYZ * scale - rotationXZ * scale)).tex(u1, v2) - .color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); - ; - - Tessellator.getInstance().draw(); - - this.undoGLStateChanges(); - - GlStateManager.popMatrix(); - - } - - /** - * Override to add any GL state changes, like blending. Does nothing by default. State changes should be done - * using GLStateManager, not using GL11 directly (as is the case with all rendering code now). - */ - public void applyGLStateChanges(){ - } - - /** - * Override to undo any GL state changes, like blending. Does nothing by default. State changes should be done - * using GLStateManager, not using GL11 directly (as is the case with all rendering code now). - */ - public void undoGLStateChanges(){ - } - - @Override - public int getBrightnessForRender(float partialTick){ - return fullBrightness ? 15728880 : super.getBrightnessForRender(partialTick); - } -} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleDarkMagic.java b/src/main/java/electroblob/wizardry/client/particle/ParticleDarkMagic.java index 9c962a00..7558bf89 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleDarkMagic.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleDarkMagic.java @@ -1,27 +1,20 @@ package electroblob.wizardry.client.particle; -import net.minecraft.client.particle.Particle; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.entity.Entity; import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleDarkMagic extends Particle { +//@SideOnly(Side.CLIENT) +public class ParticleDarkMagic extends ParticleWizardry { /** Base spell texture index */ private int baseSpellTextureIndex = 128; - public ParticleDarkMagic(World par1World, double par2, double par4, double par6, double par8, double par10, - double par12, float r, float g, float b){ - super(par1World, par2, par4, par6, par8, par10, par12); + public ParticleDarkMagic(World world, double x, double y, double z){ + super(world, x, y, z); + this.motionY *= 0.20000000298023224D; - - this.particleRed = r; - this.particleGreen = g; - this.particleBlue = b; - + this.setRBGColorF(1, 1, 1); this.particleScale *= 0.75F; this.particleMaxAge = (int)(8.0D / (Math.random() * 0.8D + 0.2D)); this.canCollide = true; @@ -43,9 +36,7 @@ public class ParticleDarkMagic extends Particle { super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ); } - /** - * Called to update the entity's position/logic. - */ + @Override public void onUpdate(){ this.prevPosX = this.posX; this.prevPosY = this.posY; diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java b/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java index c37dbb51..cd925b81 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java @@ -1,48 +1,31 @@ package electroblob.wizardry.client.particle; -import net.minecraft.client.particle.Particle; import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleDust extends Particle { +//@SideOnly(Side.CLIENT) +public class ParticleDust extends ParticleWizardry { - private final boolean shaded; - - public ParticleDust(World par1World, double x, double y, double z, double par8, double par10, double par12, float r, - float g, float b, boolean shaded){ - super(par1World, x, y, z, par8, par10, par12); - this.particleRed = r; - this.particleGreen = g; - this.particleBlue = b; + public ParticleDust(World world, double x, double y, double z){ + super(world, x, y, z); + this.setParticleTextureIndex(0); this.setSize(0.01F, 0.01F); + + // Defaults this.particleScale *= this.rand.nextFloat() + 0.2F; - this.motionX = par8; - this.motionY = par10; - this.motionZ = par12; this.particleMaxAge = (int)(16.0D / (Math.random() * 0.8D + 0.2D)); - this.shaded = shaded; + this.setRBGColorF(1, 1, 1); } - /** - * Called to update the entity's position/logic. - */ + @Override public void onUpdate(){ this.prevPosX = this.posX; this.prevPosY = this.posY; this.prevPosZ = this.posZ; - // this.moveEntity(this.motionX, this.motionY, this.motionZ); + this.move(this.motionX, this.motionY, this.motionZ); if(this.particleMaxAge-- <= 0){ this.setExpired(); } } - - @Override - public int getBrightnessForRender(float par1){ - return shaded ? super.getBrightnessForRender(par1) : 15728880; - } - /* @Override public float getBrightness(float par1) { return shaded ? super.getBrightness(par1) : 1.0F; } */ } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java b/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java new file mode 100644 index 00000000..f85bc90c --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java @@ -0,0 +1,49 @@ +package electroblob.wizardry.client.particle; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +/** + * Copied from ParticleFirework.Overlay; for some reason that class has no public constructors, plus I want to change the + * scale and a few other things + * @author Electroblob + * @since Wizardry 4.2.0 + */ +public class ParticleFlash extends ParticleWizardry { + + public ParticleFlash(World world, double x, double y, double z){ + super(world, x, y, z); + this.setRBGColorF(1, 1, 1); + this.particleScale = 0.6f; // 7.1f is the value used in fireworks + this.particleMaxAge = 6; + } + + @Override + public boolean shouldDisableDepth(){ + return true; // Well this fixes everything... let's hope it doesn't cause any side-effects! + } + + @Override + public void drawParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){ + float f4 = particleScale * MathHelper.sin(((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * (float)Math.PI); + this.setAlphaF(0.6F - ((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * 0.5F); + float f5 = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); + float f6 = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); + float f7 = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ); + int i = this.getBrightnessForRender(partialTicks); + int j = i >> 16 & 65535; + int k = i & 65535; + buffer.pos((double)(f5 - rotationX * f4 - rotationXY * f4), (double)(f6 - rotationZ * f4), (double)(f7 - rotationYZ * f4 - rotationXZ * f4)).tex(0.5D, 0.375D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex(); + buffer.pos((double)(f5 - rotationX * f4 + rotationXY * f4), (double)(f6 + rotationZ * f4), (double)(f7 - rotationYZ * f4 + rotationXZ * f4)).tex(0.5D, 0.125D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex(); + buffer.pos((double)(f5 + rotationX * f4 + rotationXY * f4), (double)(f6 + rotationZ * f4), (double)(f7 + rotationYZ * f4 + rotationXZ * f4)).tex(0.25D, 0.125D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex(); + buffer.pos((double)(f5 + rotationX * f4 - rotationXY * f4), (double)(f6 - rotationZ * f4), (double)(f7 + rotationYZ * f4 - rotationXZ * f4)).tex(0.25D, 0.375D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex(); + } + + @Override + public int getBrightnessForRender(float partialTicks){ + return 15728880; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleGiantBubble.java b/src/main/java/electroblob/wizardry/client/particle/ParticleGiantBubble.java deleted file mode 100644 index b512aaba..00000000 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleGiantBubble.java +++ /dev/null @@ -1,49 +0,0 @@ -package electroblob.wizardry.client.particle; - -import electroblob.wizardry.Wizardry; -import net.minecraft.client.particle.Particle; -import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) -public class ParticleGiantBubble extends Particle { - /** - * The name used to identify this particle. Uses the mod id to avoid any possible conflicts (Not that there would be - * any, but I may as well.) - */ - public static final String NAME = Wizardry.MODID + "magicbubble"; - - public ParticleGiantBubble(World par1World, double par2, double par4, double par6, double par8, double par10, - double par12){ - super(par1World, par2, par4, par6, par8, par10, par12); - this.particleRed = 1.0F; - this.particleGreen = 1.0F; - this.particleBlue = 1.0F; - this.setParticleTextureIndex(32); - this.setSize(0.02F, 0.02F); - this.particleScale *= this.rand.nextFloat() * 0.6F + 0.2F; - this.motionX = par8 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F); - this.motionY = par10 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F); - this.motionZ = par12 * 0.20000000298023224D + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.02F); - this.particleMaxAge = (int)(8.0D / (Math.random() * 0.8D + 0.2D)); - } - - /** - * Called to update the entity's position/logic. - */ - public void onUpdate(){ - this.prevPosX = this.posX; - this.prevPosY = this.posY; - this.prevPosZ = this.posZ; - this.motionY += 0.002D; - this.move(this.motionX, this.motionY, this.motionZ); - this.motionX *= 0.8500000238418579D; - this.motionY *= 0.8500000238418579D; - this.motionZ *= 0.8500000238418579D; - - if(this.particleMaxAge-- <= 0){ - this.setExpired(); - } - } -} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleIce.java b/src/main/java/electroblob/wizardry/client/particle/ParticleIce.java index dd548900..22a86d18 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleIce.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleIce.java @@ -1,46 +1,35 @@ package electroblob.wizardry.client.particle; -import electroblob.wizardry.Wizardry; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleIce extends ParticleCustomTexture { +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleIce extends ParticleWizardry { - private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, - "textures/particle/ice_particles.png"); - - public ParticleIce(World world, double x, double y, double z, double vx, double vy, double vz){ - super(world, x, y, z, vx, vy, vz); - } - - public ParticleIce(World world, double x, double y, double z, double vx, double vy, double vz, int maxAge){ - super(world, x, y, z, vx, vy, vz, maxAge); - } - - @Override - public void init(){ - this.setParticleTextureIndex(rand.nextInt(8)); - this.particleScale *= 0.75f; - this.particleGravity = 1; + private static final ResourceLocation[] TEXTURES = generateTextures("ice", 8); + + public ParticleIce(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + this.canCollide = true; - this.fullBrightness = true; + + // Defaults + this.setRBGColorF(1, 1, 1); + this.particleScale *= 0.75f; + this.setGravity(true); + this.shaded = false; } - - @Override - public ResourceLocation getTexture(){ - return TEXTURE; - } - - @Override - protected int getXFrames(){ - return 4; - } - - @Override - protected int getYFrames(){ - return 4; + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation texture : TEXTURES){ + event.getMap().registerSprite(texture); + } } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleLeaf.java b/src/main/java/electroblob/wizardry/client/particle/ParticleLeaf.java index e2610a3d..07f7c9fb 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleLeaf.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleLeaf.java @@ -1,45 +1,46 @@ package electroblob.wizardry.client.particle; -import electroblob.wizardry.Wizardry; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleLeaf extends ParticleCustomTexture { +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleLeaf extends ParticleWizardry { - private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, - "textures/particle/leaf_particles.png"); + private static final ResourceLocation[] TEXTURES = generateTextures("leaf", 16); - public ParticleLeaf(World world, double x, double y, double z, double vx, double vy, double vz){ - super(world, x, y, z, vx, vy, vz); - } - - public ParticleLeaf(World world, double x, double y, double z, double vx, double vy, double vz, int maxAge){ - super(world, x, y, z, vx, vy, vz, maxAge); - } - - @Override - public void init(){ - this.setParticleTextureIndex(rand.nextInt(16)); + public ParticleLeaf(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + + this.setVelocity(0, -0.03, 0); + this.setMaxAge(10 + rand.nextInt(5)); this.particleScale *= 1.4f; this.particleGravity = 0; this.canCollide = true; + // Produces a variety of browns and greens + this.setRBGColorF(0.1f + 0.3f * random.nextFloat(), 0.5f + 0.3f * random.nextFloat(), 0.1f); } - + @Override - public ResourceLocation getTexture(){ - return TEXTURE; + public void onUpdate(){ + + super.onUpdate(); + + // Fading + if(this.particleAge > this.particleMaxAge / 2){ + this.setAlphaF(1 - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge); + } } - - @Override - protected int getXFrames(){ - return 4; - } - - @Override - protected int getYFrames(){ - return 4; + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation texture : TEXTURES){ + event.getMap().registerSprite(texture); + } } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleLightning.java b/src/main/java/electroblob/wizardry/client/particle/ParticleLightning.java new file mode 100644 index 00000000..fc73ff92 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleLightning.java @@ -0,0 +1,165 @@ +package electroblob.wizardry.client.particle; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +public class ParticleLightning extends ParticleTargeted { + + /** Half the width of the outermost layer. */ + private static final float THICKNESS = 0.04f; + /** Maximum length of a segment. */ + private static final double MAX_SEGMENT_LENGTH = 0.6; + /** Minimum length of a segment. */ + private static final double MIN_SEGMENT_LENGTH = 0.2; + /** Maximum deviation (in x or y, as drawn before transformations) from the centreline. */ + private static final double VERTEX_JITTER = 0.15; + /** Maximum number of segments a fork can have before ending. */ + private static final int MAX_FORK_SEGMENTS = 3; + /** Probability (as a fraction) that a vertex will have a fork. */ + private static final float FORK_CHANCE = 0.3f; + /** Number of ticks to wait before the arc changes shape again. */ + private static final int UPDATE_PERIOD = 1; + + public ParticleLightning(World world, double x, double y, double z){ + super(world, x, y, z); // Does not have a texture! + seed = this.rand.nextLong(); + this.setRBGColorF(0.2f, 0.6f, 1); // Default blue colour + this.setMaxAge(3); + this.particleScale = 1; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public int getFXLayer(){ + return 3; + } + + @Override + protected void draw(Tessellator tessellator, double length, float partialTicks){ + + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture2D(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + // The direction of the arc drawn by the tessellator is always along the z axis and is rotated to the + // correct orientation, that way there isn't a ton of trigonometry and the code is way neater. + + boolean freeEnd = this.target == null; + + int numberOfSegments = (int)Math.round(length/MAX_SEGMENT_LENGTH); // Number of segments + + for(int layer=0; layer<3; layer++){ + + double px=0, py=0, pz=0; + // Creates a random from the arc's seed field + the number of ticks it has existed/the update period. + // By using a seed, we can ensure the vertex positions and forks are identical a) for each layer, even + // though they are rendered sequentially, and b) across many frames (and ticks, if updateTime > 1). + random.setSeed(this.seed + this.particleAge/UPDATE_PERIOD); + + // numberOfSegments-1 because the last segment is handled separately. + for(int i=0; i= this.particleMaxAge){ - this.setExpired(); - } - - // This is in radians per tick... - double omega = Math.signum(speed) * ((Math.PI * 2) / 20 - speed / (20 * radius)); - - // v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi. - this.angle += omega; - - this.motionY -= 0.04D * (double)this.particleGravity; - this.motionZ = radius * omega * Math.cos(angle); - this.motionX = radius * omega * Math.sin(angle); - this.move(motionX, motionY, motionZ); - - if(this.particleAge > this.particleMaxAge / 2){ - this.setAlphaF( - 1.0F - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge); - } - - } -} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleScorch.java b/src/main/java/electroblob/wizardry/client/particle/ParticleScorch.java new file mode 100644 index 00000000..60d240fa --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleScorch.java @@ -0,0 +1,74 @@ +package electroblob.wizardry.client.particle; + +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleScorch extends ParticleWizardry { + + private static final ResourceLocation[] TEXTURES = generateTextures("scorch", 8); + + public ParticleScorch(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + + this.particleGravity = 0; + this.setMaxAge(100 + rand.nextInt(40)); + this.particleScale *= 2; + // Defaults to black (which looks like a 'normal' scorch mark) + this.setRBGColorF(0, 0, 0); + this.shaded = false; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public void setRBGColorF(float r, float g, float b){ + super.setRBGColorF(r, g, b); + this.setFadeColour(0, 0, 0); // Scorch particles fade to black by default + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + // Colour fading (scorch particles do this slightly differently) + float ageFraction = Math.min((float)this.particleAge / ((float)this.particleMaxAge * 0.5f), 1); + // No longer uses setRBGColorF because that method now also sets the initial values + this.particleRed = this.initialRed + (this.fadeRed - this.initialRed) * ageFraction; + this.particleGreen = this.initialGreen + (this.fadeGreen - this.initialGreen) * ageFraction; + this.particleBlue = this.initialBlue + (this.fadeBlue - this.initialBlue) * ageFraction; + + // Fading + if(this.particleAge > this.particleMaxAge/2){ + this.setAlphaF(1 - ((float)this.particleAge - this.particleMaxAge/2f) / (this.particleMaxAge/2f)); + } + + EnumFacing facing = EnumFacing.fromAngle(yaw); + if(pitch == 90) facing = EnumFacing.UP; + if(pitch == -90) facing = EnumFacing.DOWN; + + // Disappears if there is no block behind it (this is the same check used to spawn it) + if(!world.getBlockState(new BlockPos(posX, posY, posZ).offset(facing.getOpposite())).getMaterial().isSolid()){ + this.setExpired(); + } + } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation texture : TEXTURES){ + event.getMap().registerSprite(texture); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSnow.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSnow.java index e0d5250d..b34b3bab 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleSnow.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSnow.java @@ -1,45 +1,35 @@ package electroblob.wizardry.client.particle; -import electroblob.wizardry.Wizardry; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleSnow extends ParticleCustomTexture { +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleSnow extends ParticleWizardry { - private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, - "textures/particle/snow_particles.png"); + private static final ResourceLocation[] TEXTURES = generateTextures("snow", 4); - public ParticleSnow(World world, double x, double y, double z, double vx, double vy, double vz){ - super(world, x, y, z, vx, vy, vz); - } - - public ParticleSnow(World world, double x, double y, double z, double vx, double vy, double vz, int maxAge){ - super(world, x, y, z, vx, vy, vz, maxAge); - } - - @Override - public void init(){ - this.setParticleTextureIndex(rand.nextInt(8)); + public ParticleSnow(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + + this.setVelocity(0, -0.02, 0); this.particleScale *= 0.6f; this.particleGravity = 0; this.canCollide = true; + this.setMaxAge(40 + rand.nextInt(10)); + // Produces a variety of light blues and whites + this.setRBGColorF(0.9f + 0.1f * random.nextFloat(), 0.95f + 0.05f * random.nextFloat(), 1); } - - @Override - public ResourceLocation getTexture(){ - return TEXTURE; - } - - @Override - protected int getXFrames(){ - return 4; - } - - @Override - protected int getYFrames(){ - return 4; + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation texture : TEXTURES){ + event.getMap().registerSprite(texture); + } } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSpark.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSpark.java index 201d2373..7617d450 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleSpark.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSpark.java @@ -1,70 +1,58 @@ package electroblob.wizardry.client.particle; -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.Wizardry; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleSpark extends ParticleCustomTexture { +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleSpark extends ParticleWizardry { - private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, - "textures/particle/lightning_particles.png"); + // 8 different animation strips, 4 in each strip + private static final ResourceLocation[][] TEXTURES = generateTextures("lightning", 8, 4); - public ParticleSpark(World world, double x, double y, double z, double vx, double vy, double vz){ - // Max age is always 3. - super(world, x, y, z, vx, vy, vz, 3); - } - - @Override - public void init(){ - // Multiplied by 4 because the index works slightly differently for spark particles. - this.setParticleTextureIndex(rand.nextInt(8) * 4); + public ParticleSpark(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + this.particleScale *= 1.4f; - this.fullBrightness = true; + this.setRBGColorF(1, 1, 1); + this.shaded = false; this.canCollide = false; + this.setMaxAge(3); // Lifetime defaults to 3 (and is very unlikely to be changed) } @Override - public void onUpdate(){ - super.onUpdate(); - // Well this is handy! Looks like vanilla uses the texture index like this too. - this.nextTextureIndexX(); + public boolean shouldDisableDepth(){ + return true; } - @Override - public ResourceLocation getTexture(){ - return TEXTURE; - } + // May no longer be necessary, ParticleManager seems to enable blending now - @Override - protected int getXFrames(){ - return 4; - } - - @Override - protected int getYFrames(){ - return 8; - } - - @Override - public void applyGLStateChanges(){ - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - // TESTME: Are these two actually necessary? - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); - } - - @Override - public void undoGLStateChanges(){ - GlStateManager.disableBlend(); - GlStateManager.enableLighting(); +// @Override +// public void applyGLStateChanges(){ +// GlStateManager.enableBlend(); +// GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); +// GlStateManager.disableLighting(); +// OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); +// } +// +// @Override +// public void undoGLStateChanges(){ +// GlStateManager.disableBlend(); +// GlStateManager.enableLighting(); +// } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation[] array : TEXTURES){ + for(ResourceLocation texture : array){ + event.getMap().registerSprite(texture); + } + } } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSparkle.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSparkle.java index fdbd553c..812678a2 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleSparkle.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSparkle.java @@ -1,111 +1,45 @@ package electroblob.wizardry.client.particle; -import electroblob.wizardry.Wizardry; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) -public class ParticleSparkle extends ParticleCustomTexture { +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleSparkle extends ParticleWizardry { - /* I have now figured out what particle factories are for: they separate out the individual uses of the varargs - * parameter in spawnParticle so they are kept with the particle class. For my purposes, it would be easier to do - * that in the particle spawning method itself. */ + private static final ResourceLocation[] TEXTURES = generateTextures("sparkle", 11); - private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, - "textures/particle/sparkle_particles.png"); - - // NOTE: Uncomment once 2.1.0 is released - // private final float initialRed; - // private final float initialGreen; - // private final float initialBlue; - - // TODO: Assign these via the constructors, as part of the refactoring for particle parameters. - // NOTE: Uncomment once 2.1.0 is released - // private final float fadeRed = 1; - // private final float fadeGreen = 1; - // private final float fadeBlue = 0; - - public ParticleSparkle(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g, - float b){ - super(world, x, y, z, vx, vy, vz); - this.setRBGColorF(r, g, b); - // NOTE: Uncomment once 2.1.0 is released - // initialRed = r; - // initialGreen = g; - // initialBlue = b; + public ParticleSparkle(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES); // This time the textures are all one long animation + + this.setRBGColorF(1, 1, 1); this.particleMaxAge = 48 + this.rand.nextInt(12); - } - - public ParticleSparkle(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g, - float b, int maxAge){ - super(world, x, y, z, vx, vy, vz, maxAge); - this.setRBGColorF(r, g, b); - // NOTE: Uncomment once 2.1.0 is released - // initialRed = r; - // initialGreen = g; - // initialBlue = b; - } - - public ParticleSparkle(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g, - float b, boolean doGravity){ - this(world, x, y, z, vx, vy, vz, r, g, b); - this.particleGravity = doGravity ? 1 : 0; - } - - public ParticleSparkle(World world, double x, double y, double z, double vx, double vy, double vz, float r, float g, - float b, int maxAge, boolean doGravity){ - this(world, x, y, z, vx, vy, vz, r, g, b, maxAge); - this.particleGravity = doGravity ? 1 : 0; - } - - @Override - public void init(){ - this.setParticleTextureIndex(rand.nextInt(16)); this.particleScale *= 0.75f; this.particleGravity = 0; this.canCollide = false; - this.fullBrightness = true; - } - - @Override - public ResourceLocation getTexture(){ - return TEXTURE; - } - - @Override - protected int getXFrames(){ - return 4; - } - - @Override - protected int getYFrames(){ - return 4; + this.shaded = false; } @Override public void onUpdate(){ super.onUpdate(); + // Fading if(this.particleAge > this.particleMaxAge / 2){ - this.setAlphaF( - 1.0F - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge); + this.setAlphaF(1 - ((float)this.particleAge - (float)(this.particleMaxAge / 2)) / (float)this.particleMaxAge); + } + } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + for(ResourceLocation texture : TEXTURES){ + event.getMap().registerSprite(texture); } - // Colour fading TODO Uncomment once 2.1.0 is released - // float ageFraction = (float)this.particleAge / (float)this.particleMaxAge; - // this.setRBGColorF(this.initialRed + (this.fadeRed - this.initialRed)*ageFraction, - // this.initialGreen + (this.fadeGreen - this.initialGreen)*ageFraction, - // this.initialBlue + (this.fadeBlue - this.initialBlue)*ageFraction); - - this.setParticleTextureIndex((this.particleAge * 11)/this.particleMaxAge); } - - /* As a side note, I see a lot of magic mods with fancy-looking particle effects that really seem to 'glow'. It's - * actually not that hard - you simply create a reasonably high-res texture with translucency and then set the - * OpenGL blend function to something like SRC_ALPHA, SRC_ALPHA or ONE, ONE. The thing is... they're not very - * Minecraft-y. I still maintain that part of wizardry's appeal is that it stays true to the game's pixelated charm, - * rather than trying to make it something it's not. Still, the newer textures are much better than the defaults I - * used to use. */ } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java new file mode 100644 index 00000000..89eaf5a7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java @@ -0,0 +1,131 @@ +package electroblob.wizardry.client.particle; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +public class ParticleSphere extends ParticleWizardry { + + public ParticleSphere(World world, double x, double y, double z){ + super(world, x, y, z); + this.setRBGColorF(1, 1, 1); + this.particleMaxAge = 5; + this.particleAlpha = 0.8f; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public int getFXLayer(){ + return 3; + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + } + + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ, + float rotationXY, float rotationXZ){ + + // Copied from ParticleWizardry, needs to be here since we're not calling super + updateEntityLinking(partialTicks); + + float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks); + float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks); + float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks); + + GlStateManager.pushMatrix(); + GlStateManager.translate(x - interpPosX, y - interpPosY, z - interpPosZ); + + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.enableCull(); + GlStateManager.disableTexture2D(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + float latStep = (float)Math.PI/20; + float longStep = (float)Math.PI/20; + + float sphereRadius = this.particleScale * (this.particleAge + partialTicks - 1) / this.particleMaxAge; + float alpha = this.particleAlpha * (1 - (this.particleAge + partialTicks - 1) / this.particleMaxAge); + + drawSphere(Tessellator.getInstance(), buffer, sphereRadius, latStep, longStep, true, particleRed, particleGreen, particleBlue, alpha); + drawSphere(Tessellator.getInstance(), buffer, sphereRadius, latStep, longStep, false, particleRed, particleGreen, particleBlue, alpha); + + GlStateManager.enableTexture2D(); + GlStateManager.enableLighting(); + GlStateManager.disableCull(); + GlStateManager.disableBlend(); + + GlStateManager.popMatrix(); + + } + + @Override + public int getBrightnessForRender(float partialTicks){ + return 15728880; + } + + /** + * Draws a sphere (using lat/long triangles) with the given parameters. + * @param radius The radius of the sphere. + * @param latStep The latitude step; smaller is smoother but increases performance cost. + * @param longStep The longitude step; smaller is smoother but increases performance cost. + * @param inside Whether to draw the outside or the inside of the sphere. + * @param r The red component of the sphere colour. + * @param g The green component of the sphere colour. + * @param b The blue component of the sphere colour. + * @param a The alpha component of the sphere colour. + */ + private static void drawSphere(Tessellator tessellator, BufferBuilder buffer, float radius, float latStep, float longStep, boolean inside, float r, float g, float b, float a){ + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR); + + boolean goingUp = inside; + + buffer.pos(0, goingUp ? -radius : radius, 0).color(r, g, b, a).endVertex(); // Start at the north pole + + for(float longitude = -(float)Math.PI; longitude <= (float)Math.PI; longitude += longStep){ + + // Leave the poles out since they only have a single point per stack instead of two + for(float theta = (float)Math.PI/2 - latStep; theta >= -(float)Math.PI/2 + latStep; theta -= latStep){ + + float latitude = goingUp ? -theta : theta; + + float hRadius = radius * MathHelper.cos(latitude); + float vy = radius * MathHelper.sin(latitude); + float vx = hRadius * MathHelper.sin(longitude); + float vz = hRadius * MathHelper.cos(longitude); + + buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex(); + + vx = hRadius * MathHelper.sin(longitude + longStep); + vz = hRadius * MathHelper.cos(longitude + longStep); + + buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex(); + } + + // The next pole + buffer.pos(0, goingUp ? radius : -radius, 0).color(r, g, b, a).endVertex(); + + goingUp = !goingUp; + } + + tessellator.draw(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java new file mode 100644 index 00000000..55daba3e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java @@ -0,0 +1,132 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +//@SideOnly(Side.CLIENT) +public class ParticleSummon extends ParticleWizardry { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/summon.png"); + private final boolean mirror; + + public ParticleSummon(World world, double x, double y, double z){ + super(world, x, y, z); + this.mirror = random.nextBoolean(); + this.setMaxAge(10); + this.setGravity(false); + this.canCollide = false; + } + +// @Override +// public void onUpdate(){ +// super.onUpdate(); +// if(this.particleAge > this.particleMaxAge/2) this.particleAlpha = 2f - 2f*(float)this.particleAge/(float)this.particleMaxAge; +// } + + /* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png + * to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the + * render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine + * for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was + * presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning + * you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten + * anyway since they bind their own textures. It is of course important to bind the texture every time you render a + * custom particle, but I don't see how you could do it any other way, since you don't have access to + * EffectRenderer. */ + @Override + public int getFXLayer(){ + // This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer. + return 3; + } + + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, + float rotationYZ, float rotationXY, float rotationXZ){ + + // Copied from ParticleWizardry, needs to be here since we're not calling super + updateEntityLinking(partialTicks); + + GlStateManager.pushMatrix(); + GlStateManager.pushAttrib(); + + float scale = 0.6f; + GlStateManager.scale(scale, scale, scale); + if(mirror) GlStateManager.scale(-1, 1, 1); + + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.disableCull(); + GlStateManager.disableLighting(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); + // Makes the particle colour add to the colour of the texture pixels, rather than the default multiplying + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD); + + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + + // Does the texture translation wrapping thing (the cool stuff) +// GlStateManager.matrixMode(GL11.GL_TEXTURE); +// GlStateManager.loadIdentity(); +// +// GlStateManager.translate((this.particleAge + partialTicks)/(float)this.particleMaxAge * -2, 0, 0); +// +// GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + RenderHelper.disableStandardItemLighting(); + + Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE); + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); + + float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); + float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); + float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ); + + // Increases from 0 to 1 in steps of 0.125 evenly throughout the particle's lifetime + float f = 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f); + float g = f + 0.125f; + float hrepeat = 1; + float yScale = 3f; + + this.setRBGColorF(1, 1, 1); + + buffer.pos(x-1, y, z-1).tex(0, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-1, y+yScale, z-1).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+1, y, z-1).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+1, y+yScale, z-1).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+1, y, z+1).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x+1, y+yScale, z+1).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-1, y, z+1).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-1, y+yScale, z+1).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-1, y, z-1).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + buffer.pos(x-1, y+yScale, z-1).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); + + Tessellator.getInstance().draw(); + + // Undoes the texture transformations +// GlStateManager.matrixMode(GL11.GL_TEXTURE); +// GlStateManager.loadIdentity(); +// GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + // Reverses the colour addition change from before + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + + GlStateManager.popAttrib(); + GlStateManager.popMatrix(); + + } +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleTargeted.java b/src/main/java/electroblob/wizardry/client/particle/ParticleTargeted.java new file mode 100644 index 00000000..cd89d0af --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleTargeted.java @@ -0,0 +1,150 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nullable; + +/** Superclass for particles with a second target entity or target position. */ +public abstract class ParticleTargeted extends ParticleWizardry { + + protected double targetX; + protected double targetY; + protected double targetZ; + protected double targetVelX; + protected double targetVelY; + protected double targetVelZ; + + protected double length; + + /** The target this particle is linked to. The particle will stretch to touch this entity. */ + @Nullable + protected Entity target = null; + + public ParticleTargeted(World world, double x, double y, double z, ResourceLocation... textures){ + super(world, x, y, z, textures); + } + + @Override + public void setTargetPosition(double x, double y, double z){ + this.targetX = x; + this.targetY = y; + this.targetZ = z; + } + + @Override + public void setTargetVelocity(double vx, double vy, double vz){ + this.targetVelX = vx; + this.targetVelY = vy; + this.targetVelZ = vz; + } + + @Override + public void setTargetEntity(Entity target){ + this.target = target; + } + + @Override + public void setLength(double length){ + this.length = length; + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){ + this.targetX += this.targetVelX; + this.targetY += this.targetVelY; + this.targetZ += this.targetVelZ; + } + } + + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ, + float rotationXY, float rotationXZ){ + + // Copied from ParticleWizardry, needs to be here since we're not calling super + updateEntityLinking(partialTicks); + + float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks); + float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks); + float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks); + + if(this.target != null){ + + this.targetX = this.target.prevPosX + (this.target.posX - this.target.prevPosX) * partialTicks; + double correction = this.target.getEntityBoundingBox().minY - this.target.posY; + this.targetY = this.target.prevPosY + (this.target.posY - this.target.prevPosY) * partialTicks + + target.height/2 + correction; + this.targetZ = this.target.prevPosZ + (this.target.posZ - this.target.prevPosZ) * partialTicks; + + }else if(this.entity != null && this.length > 0){ + + Vec3d look = entity.getLook(partialTicks).scale(length); + this.targetX = x + look.x; + this.targetY = y + look.y; + this.targetZ = z + look.z; + } + + if(Double.isNaN(targetX) || Double.isNaN(targetY) || Double.isNaN(targetZ)){ + Wizardry.logger.warn("Attempted to render a targeted particle, but neither its target entity nor target" + + "position was set, and it either had no length assigned or was not linked to an entity!"); + return; + } + + GlStateManager.pushMatrix(); + GlStateManager.translate(x - interpPosX, y - interpPosY, z - interpPosZ); + + double dx = this.targetX - x; + double dy = this.targetY - y; + double dz = this.targetZ - z; + + // No need for previous tick target positions and all that stuff since this is the only place they're used + // and interpolating like this works just as well + if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){ + dx += partialTicks * this.targetVelX; + dy += partialTicks * this.targetVelY; + dz += partialTicks * this.targetVelZ; + } + + // The distance from origin to endpoint + double length = Math.sqrt(dx*dx+dy*dy+dz*dz); + + // Math.atan2 computes within -180 to +180, rather than -90 to +90. + float yaw = (float)(180d/Math.PI * Math.atan2(dx, dz)); + float pitch = (float)(180f/(float)Math.PI * Math.atan(-dy/Math.sqrt(dz*dz+dx*dx))); + + GL11.glRotatef(yaw, 0, 1, 0); + GL11.glRotatef(pitch, 1, 0, 0); + + Tessellator tessellator = Tessellator.getInstance(); + + this.draw(tessellator, length, partialTicks); + + GlStateManager.popMatrix(); + } + + /** Called from {@link ParticleTargeted#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)}, + * once the appropriate calculations and transformations have been applied, to actually render the particle. Subclasses + * override this instead of overriding {@code renderParticle} directly, and inside render the particle along + * the z-axis, starting at (0, 0, 0) - it will be translated and rotated automatically. + *

    + * N.B. Other than transformations, no GL state changes are applied; these should be done within this method. + * + * @param tessellator A reference to the tessellator, for convenience. + * @param length The distance from the origin to the endpoint for the particle being rendered; the particle should + * therefore be rendered between (0, 0, 0) and (0, 0, length) within this method. + * @param partialTicks The partial tick time. + */ + protected abstract void draw(Tessellator tessellator, double length, float partialTicks); + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleTornado.java b/src/main/java/electroblob/wizardry/client/particle/ParticleTornado.java index 8887e503..8dab58fa 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleTornado.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleTornado.java @@ -3,14 +3,13 @@ package electroblob.wizardry.client.particle; import net.minecraft.block.state.IBlockState; import net.minecraft.client.particle.ParticleDigging; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class ParticleTornado extends ParticleDigging { - private double angle; + private float angle; private double radius; private double speed; /** Velocity of the tornado itself; in other words the velocity of the point the particle circles around. */ @@ -20,9 +19,9 @@ public class ParticleTornado extends ParticleDigging { public ParticleTornado(World world, int maxAge, double originX, double originZ, double radius, double yPos, double velX, double velZ, IBlockState block){ super(world, 0, 0, 0, 0, 0, 0, block); - this.angle = this.rand.nextDouble() * Math.PI * 2; - double x = originX - Math.cos(angle) * radius; - double z = originZ + radius * Math.sin(angle); + float angle = this.rand.nextFloat() * (float)Math.PI * 2; + double x = originX - MathHelper.cos(angle) * radius; + double z = originZ + radius * MathHelper.sin(angle); this.radius = radius; this.setPosition(x, yPos, z); this.prevPosX = x; @@ -66,8 +65,8 @@ public class ParticleTornado extends ParticleDigging { // v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi. this.angle += omega; - this.motionZ = radius * omega * Math.cos(angle); - this.motionX = radius * omega * Math.sin(angle); + this.motionZ = radius * omega * MathHelper.cos(angle); + this.motionX = radius * omega * MathHelper.sin(angle); this.move(motionX + velX, 0, motionZ + velZ); if(this.particleAge > this.particleMaxAge / 2){ diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java b/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java new file mode 100644 index 00000000..79c9d872 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java @@ -0,0 +1,154 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +//@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleVine extends ParticleTargeted { + + /** Half the width of the vine. */ + private static final float THICKNESS = 0.02f; + private static final float LEAF_SPACING = 0.5f; + private static final float SEGMENT_LENGTH = 1; + + private static final ResourceLocation STEM_TEXTURE = new ResourceLocation(Wizardry.MODID, "particle/vine"); + private static final ResourceLocation[] LEAF_TEXTURES = generateTextures("vine_leaf", 5); + + public ParticleVine(World world, double x, double y, double z){ + super(world, x, y, z, STEM_TEXTURE); + //this.setRBGColorF(1, 1, 1); + this.setMaxAge(0); + this.particleScale = 1; + this.setRBGColorF(0.2f, 0.65f, 0f); + } + + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){ + // When using FX layer 1 the BufferBuilder is already drawing... but in the wrong mode :/ + Tessellator.getInstance().draw(); + super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.PARTICLE_POSITION_TEX_COLOR_LMAP); + } + + @Override + protected void draw(Tessellator tessellator, double length, float partialTicks){ + + random.setSeed(seed); // Reset the random so we get the same sequence of numbers each frame + + float scale = this.particleScale; + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + // Hmmmm we can't get the texture to tile using OpenGL texture space because it's on a sprite sheet... + // Solution: Draw loads of boxes. Simple! + // (Since there aren't going to be that many of these particles around we could have not used sprite sheets + // and done the OpenGL texture space thing, but this is kinda easier) + // Everything is drawn back-to-front so it looks like the vine is growing from the origin, not the endpoint + int i = 0; + while(i + SEGMENT_LENGTH < length){ + drawShearedBox(tessellator, 0, 0, length-i, 0, 0, length-i-SEGMENT_LENGTH, THICKNESS * scale, + particleRed, particleGreen, particleBlue, particleAlpha); + i += SEGMENT_LENGTH; + } + + drawShearedBox(tessellator, 0, 0, length-i, 0, 0, 0, THICKNESS * scale, + particleRed, particleGreen, particleBlue, particleAlpha); + + for(double l=length; l>0; l-=LEAF_SPACING){ + + GlStateManager.pushMatrix(); + + GlStateManager.rotate(random.nextInt(4) * 90, 0, 0, 1); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + + TextureAtlasSprite leaf = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite( + LEAF_TEXTURES[random.nextInt(LEAF_TEXTURES.length)].toString()); + + float w = 16 * THICKNESS * scale; + float u1 = leaf.getMinU(); + float u2 = leaf.getMaxU(); + float v1 = leaf.getMinV(); + float v2 = leaf.getMaxV(); + + float colourVariation = 0.3f; + + float r = MathHelper.clamp(particleRed + (random.nextFloat() - 0.5f) * colourVariation, 0, 1); + float g = MathHelper.clamp(particleGreen + (random.nextFloat() - 0.5f) * colourVariation, 0, 1); + float b = MathHelper.clamp(particleBlue + (random.nextFloat() - 0.5f) * colourVariation, 0, 1); + + buffer.pos(0, 0, l).tex(u1, v1).color(r, g, b, particleAlpha).endVertex(); + buffer.pos(w, 0, l).tex(u2, v1).color(r, g, b, particleAlpha).endVertex(); + buffer.pos(w, w, l).tex(u2, v2).color(r, g, b, particleAlpha).endVertex(); + buffer.pos(0, w, l).tex(u1, v2).color(r, g, b, particleAlpha).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + // Makes the rain go weird + //GlStateManager.enableLighting(); + } + + /** Draws a single box for one segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with given width and colour. */ + private void drawShearedBox(Tessellator tessellator, double x1, double y1, double z1, double x2, double y2, double z2, float width, float r, float g, float b, float a){ + + float u1 = particleTexture.getMinU(); + float u2 = u1 + (particleTexture.getMaxU() - u1) * (float)(z1-z2)/SEGMENT_LENGTH; + float v1 = particleTexture.getMinV(); + float dv = particleTexture.getMaxV() - v1; + // width * 8 gives the total 'circumference' of the box + float v2 = v1 + dv * 0.0625f; + float v3 = v1 + dv * 0.125f; + float v4 = v1 + dv * 0.1875f; + float v5 = v1 + dv * 0.25f; + + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); + + buffer.pos(x1-width, y1-width, z1).tex(u1, v1).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2-width, z2).tex(u2, v1).color(r, g, b, a).endVertex(); + buffer.pos(x1-width, y1+width, z1).tex(u1, v2).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2+width, z2).tex(u2, v2).color(r, g, b, a).endVertex(); + buffer.pos(x1+width, y1+width, z1).tex(u1, v3).color(r, g, b, a).endVertex(); + buffer.pos(x2+width, y2+width, z2).tex(u2, v3).color(r, g, b, a).endVertex(); + buffer.pos(x1+width, y1-width, z1).tex(u1, v4).color(r, g, b, a).endVertex(); + buffer.pos(x2+width, y2-width, z2).tex(u2, v4).color(r, g, b, a).endVertex(); + buffer.pos(x1-width, y1-width, z1).tex(u1, v5).color(r, g, b, a).endVertex(); + buffer.pos(x2-width, y2-width, z2).tex(u2, v5).color(r, g, b, a).endVertex(); + + tessellator.draw(); + } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + + event.getMap().registerSprite(STEM_TEXTURE); + + for(ResourceLocation texture : LEAF_TEXTURES){ + event.getMap().registerSprite(texture); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java b/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java new file mode 100644 index 00000000..1172bb34 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java @@ -0,0 +1,580 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.ClientProxy; +import electroblob.wizardry.entity.ICustomHitbox; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * Abstract superclass for all of wizardry's particles. This replaces {@code ParticleCustomTexture} (the functionality of + * which is no longer necessary since wizardry now uses {@code TextureAtlasSprite}s to do the rendering), and fits into + * {@code ParticleBuilder} by exposing all the necessary variables through getters, allowing them to be set on the fly + * rather than needing to be passed into the constructor. + *

    + * The new system is as follows: + *

    + * - All particle classes have a single constructor which takes a world and a position only.
    + * - Each particle class defines any relevant default values in its constructor, including velocity.
    + * - The particle builder then overwrites any other values that were set during building. + *

    + * This beauty of this system is that there are never any redundant parameters when spawning particles, since you can set + * as many or as few parameters as necessary - and in addition, common defaults don't need setting at all. For example, + * snow particles nearly always fall at the same speed, which can now be defined in the particle class and no longer + * needs to be defined when spawning the particle - but importantly, it can still be overridden if desired. + * + * @author Electroblob + * @since Wizardry 4.2.0 + * @see electroblob.wizardry.util.ParticleBuilder ParticleBuilder + */ +//@SideOnly(Side.CLIENT) +public abstract class ParticleWizardry extends Particle { + + /** Implementation of animated particles using the TextureAtlasSprite system. Why vanilla doesn't support this I + * don't know, considering it too has animated particles. */ + protected final TextureAtlasSprite[] sprites; + + /** A long value used by the renderer as a random number seed, ensuring anything that is randomised remains the + * same across multiple frames. For example, lightning particles use this to keep their shape across ticks. + * This value can also be set during particle creation, allowing users to keep randomised properties the same + * even across multiple particles. If unspecified, the seed is chosen at random. */ + protected long seed; + /** This particle's random number generator. All particles should use this in preference to any other random + * instance (like random), even if it isn't actually necessary to keep properties across frames. Note that + * if you do need to generate the same sequence of random numbers each frame, you must call + * {@code random.setSeed(seed)} from the {@link ParticleWizardry#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)} + * method - this is not done automatically. */ + protected Random random = new Random(); // If we're not using a seed, this defaults to any old seed + + /** True if the particle is shaded, false if the particle always renders at full brightness. Defaults to false. */ + protected boolean shaded = false; + + protected float initialRed; + protected float initialGreen; + protected float initialBlue; + + protected float fadeRed = 0; + protected float fadeGreen = 0; + protected float fadeBlue = 0; + + protected float angle; + protected double radius = 0; + protected double speed = 0; + + /** The entity this particle is linked to. The particle will move with this entity. */ + @Nullable + protected Entity entity = null; + /** Coordinates of this particle relative to the linked entity. If the linked entity is null, these are used as + * the absolute coordinates of the centre of rotation for particles with spin. If the particle has neither a + * linked entity nor spin, these are not used. */ + protected double relativeX, relativeY, relativeZ; + /** Velocity of this particle relative to the linked entity. If the linked entity is null, these are not used. */ + protected double relativeMotionX, relativeMotionY, relativeMotionZ; + // Note that roll (equivalent to rotating the texture) is effectively handled by particleAngle - although that is + // actually the rotation speed and not the angle itself. + /** The yaw angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */ + protected float yaw = Float.NaN; + /** The pitch angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */ + protected float pitch = Float.NaN; + + /** The fraction of the impact velocity that should be the maximum spread speed added on impact. */ + private static final double SPREAD_FACTOR = 0.2; + /** Lateral velocity is reduced by this factor on impact, before adding random spread velocity. */ + private static final double IMPACT_FRICTION = 0.2; + + /** Previous-tick velocity, used in collision detection. */ + private double prevVelX, prevVelY, prevVelZ; + + /** + * Creates a new particle in the given world at the given position. All other parameters are set via the various + * setter methods ({@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} deals with all of that anyway). + * @param world The world in which to create the particle. + * @param x The x-coordinate at which to create the particle. + * @param y The y-coordinate at which to create the particle. + * @param z The z-coordinate at which to create the particle. + * @param textures One or more {@code ResourceLocation}s representing the texture(s) used by this particle. These + * must be registered as {@link TextureAtlasSprite}s using {@link TextureStitchEvent} or the textures will be + * missing. If more than one {@code ResourceLocation} is specified, the particle will be animated with each texture + * shown in order for an equal proportion of the particle's lifetime. If this argument is omitted (or a zero-length + * array is given), the particle will use the vanilla system instead (based on the X/Y texture indices). + */ + public ParticleWizardry(World world, double x, double y, double z, ResourceLocation... textures){ + + super(world, x, y, z); + + // Sets the relative coordinates in case they are needed + this.relativeX = x; + this.relativeY = y; + this.relativeZ = z; + + // Deals with the textures + if(textures.length > 0){ + + sprites = Arrays.stream(textures).map(t -> Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite( + t.toString())).collect(Collectors.toList()).toArray(new TextureAtlasSprite[0]); + + this.setParticleTexture(sprites[0]); + + }else{ + sprites = new TextureAtlasSprite[0]; + } + } + + // ============================================== Parameter Setters ============================================== + + // Setters for parameters that affect all particles - these are implemented in this class (although they may be + // reimplemented in subclasses) + + /** Sets the seed for this particle's randomly generated values and resets {@link ParticleWizardry#random} to use + * that seed. Implementations will differ between particle types; for example, ParticleLightning has an update + * period which changes the seed every few ticks, whereas ParticleVine simply retains the same seed for its entire + * lifetime. */ + public void setSeed(long seed){ + this.seed = seed; + this.random = new Random(seed); + } + + /** Sets whether the particle should render at full brightness or not. True if the particle is shaded, false if + * the particle always renders at full brightness. Defaults to false.*/ + public void setShaded(boolean shaded){ + this.shaded = shaded; + } + + /** Sets this particle's gravity. True to enable gravity, false to disable. Defaults to false.*/ + public void setGravity(boolean gravity){ + this.particleGravity = gravity ? 1 : 0; + } + + /** Sets this particle's collisions. True to enable block collisions, false to disable. Defaults to false.*/ + public void setCollisions(boolean canCollide){ + this.canCollide = canCollide; + } + + /** + * Sets the velocity of the particle. + * @param vx The x velocity + * @param vy The y velocity + * @param vz The z velocity + */ + public void setVelocity(double vx, double vy, double vz){ + this.motionX = vx; + this.motionY = vy; + this.motionZ = vz; + } + + /** + * Sets the spin parameters of the particle. + * @param radius The spin radius + * @param speed The spin speed in rotations per tick + */ + public void setSpin(double radius, double speed){ + this.radius = radius; + this.speed = speed * 2 * Math.PI; // Converts rotations per tick into radians per tick for the trig functions + this.angle = this.rand.nextFloat() * (float)Math.PI * 2; // Random start angle + // Need to set the start position or the circle won't be centred on the correct position + this.posX = relativeX - radius * MathHelper.cos(angle); + this.posZ = relativeZ + radius * MathHelper.sin(angle); + // Set these to the correct values + this.relativeMotionX = motionX; + this.relativeMotionY = motionY; + this.relativeMotionZ = motionZ; + } + + /** + * Links this particle to the given entity. This will cause its position and velocity to be relative to the entity. + * @param entity The entity to link to. + */ + public void setEntity(Entity entity){ + this.entity = entity; + // Set these to the correct values + if(entity != null){ + this.setPosition(this.entity.posX + relativeX, this.entity.getEntityBoundingBox().minY + + relativeY, this.entity.posZ + relativeZ); + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + // Set these to the correct values + this.relativeMotionX = motionX; + this.relativeMotionY = motionY; + this.relativeMotionZ = motionZ; + } + } + + // Overridden to set the initial colour values + /** + * Sets the base colour of the particle. Note that this also sets the fade colour so that particles without a + * fade colour do not change colour at all; as such fade colour must be set after calling this method. + * @param r The red colour component + * @param g The green colour component + * @param b The blue colour component + */ + @Override + public void setRBGColorF(float r, float g, float b){ + super.setRBGColorF(r, g, b); + initialRed = r; + initialGreen = g; + initialBlue = b; + // If fade colour is not specified, it defaults to the main colour - this method is always called first + setFadeColour(r, g, b); + } + + /** + * Sets the fade colour of the particle. + * @param r The red colour component + * @param g The green colour component + * @param b The blue colour component + */ + public void setFadeColour(float r, float g, float b){ + this.fadeRed = r; + this.fadeGreen = g; + this.fadeBlue = b; + } + + /** + * Sets the direction this particle faces. This will cause the particle to render facing the given direction. + * @param yaw The yaw angle of this particle in degrees, where 0 is south. + * @param pitch The pitch angle of this particle in degrees, where 0 is horizontal. + */ + public void setFacing(float yaw, float pitch){ + this.yaw = yaw; + this.pitch = pitch; + } + + // Setters for parameters that only affect some particles - these are unimplemented in this class because they + // doesn't make sense for most particles + + /** + * Sets the target position for this particle. This will cause it to stretch to touch the given position, + * if supported. + * @param x The x-coordinate of the target position. + * @param y The y-coordinate of the target position. + * @param z The z-coordinate of the target position. + */ + public void setTargetPosition(double x, double y, double z){ + // Does nothing for normal particles since normal particles always render at a single point + } + + /** + * Sets the target point velocity for this particle. This will cause the position it stretches to touch to move + * at the given velocity. Has no effect unless {@link ParticleWizardry#setTargetVelocity(double, double, double)} + * is also used. + * @param vx The x velocity of the target point. + * @param vy The y velocity of the target point. + * @param vz The z velocity of the target point. + */ + public void setTargetVelocity(double vx, double vy, double vz){ + // Does nothing for normal particles since normal particles always render at a single point + } + + /** + * Links this particle to the given target. This will cause it to stretch to touch the target, if supported. + * @param target The target to link to. + */ + public void setTargetEntity(Entity target){ + // Does nothing for normal particles since normal particles always render at a single point + } + + /** + * Sets the length of this particle. This will cause it to stretch to touch a point this distance along its + * linked entity's line of sight. + * @param length The length to set. + */ + public void setLength(double length){ + // Does nothing for normal particles since normal particles always render at a single point + } + + // ============================================== Method Overrides ============================================== + + @Override + public int getFXLayer(){ + return sprites.length == 0 ? super.getFXLayer() : 1; // This has to be 1 for the TextureAtlasSprites to work + } + + @Override + public int getBrightnessForRender(float partialTick){ + return shaded ? super.getBrightnessForRender(partialTick) : 15728880; + } + + /** + * Renders the particle. The mapping names given to the parameters in this method are very misleading; see below for + * details of what they actually do. (They're also in a strange order...) + * @param buffer The {@code BufferBuilder} object. + * @param viewer The entity whose viewpoint the particle is being rendered from; this should always be the + * client-side player. + * @param partialTicks The partial tick time. + * @param lookZ Equal to the cosine of {@code viewer.rotationYaw}. Will be -1 when facing north (negative Z), 0 when + * east/west, and +1 when facing south (positive Z). Independent of pitch. + * @param lookY Equal to the cosine of {@code viewer.rotationPitch}. Will be 1 when facing directly up or down, and 0 + * when facing directly horizontally. + * @param lookX Equal to the sine of {@code viewer.rotationYaw}. Will be -1 when facing east (positive X), 0 when + * facing north/south, and +1 when facing west (negative X). Independent of pitch. + * @param lookXY Equal to {@code lookX} times the sine of {@code viewer.rotationPitch}. Will be 0 when facing directly horizontal. + * When facing directly up, will be equal to {@code -lookX}. When facing directly down, will be equal to {@code lookX}. + * @param lookYZ Equal to {@code -lookZ} times the sine of {@code viewer.rotationPitch}. Will be 0 when facing directly horizontal. + * When facing directly up, will be equal to {@code -lookZ}. When facing directly down, will be equal to {@code lookZ}. + */ + // Fun fact: unlike entities, particles don't seem to bother checking the camera frustum... + @Override + public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float lookZ, float lookY, + float lookX, float lookXY, float lookYZ){ + + updateEntityLinking(partialTicks); + + if(Float.isNaN(this.yaw) || Float.isNaN(this.pitch)){ + // Normal behaviour (rotates to face the viewer) + drawParticle(buffer, viewer, partialTicks, lookZ, lookY, lookX, lookXY, lookYZ); + }else{ + + // Specific rotation + + // Copied from ActiveRenderInfo; converts yaw and pitch into the weird parameters used by renderParticle. + // The 1st/3rd person distinction has been removed since this has nothing to do with the view angle. + + float degToRadFactor = 0.017453292f; // Conversion from degrees to radians + + float rotationX = MathHelper.cos(yaw * degToRadFactor); + float rotationZ = MathHelper.sin(yaw * degToRadFactor); + float rotationY = MathHelper.cos(pitch * degToRadFactor); + float rotationYZ = -rotationZ * MathHelper.sin(pitch * degToRadFactor); + float rotationXY = rotationX * MathHelper.sin(pitch * degToRadFactor); + + drawParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY); + } + } + + /** + * Delegate function for {@link ParticleWizardry#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)}; + * does the actual rendering. Subclasses should override this method instead of renderParticle. By default, this + * method simply calls super.renderParticle. + */ + protected void drawParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationY, float rotationZ, float rotationYZ, float rotationXY){ + super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY); + } + + protected void updateEntityLinking(float partialTicks){ + if(this.entity != null){ + // This is kind of cheating but we know it's always a constant velocity so it works fine + prevPosX = posX + entity.prevPosX - entity.posX - relativeMotionX * (1-partialTicks); + prevPosY = posY + entity.prevPosY - entity.posY - relativeMotionY * (1-partialTicks); + prevPosZ = posZ + entity.prevPosZ - entity.posZ - relativeMotionZ * (1-partialTicks); + } + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(this.canCollide && this.onGround){ + // I reject your friction and substitute my own! + this.motionX /= 0.699999988079071D; + this.motionZ /= 0.699999988079071D; + } + + if(entity != null || radius > 0){ + + double x = relativeX; + double y = relativeY; + double z = relativeZ; + + // Entity linking + if(this.entity != null){ + if(this.entity.isDead){ + this.setExpired(); + }else{ + x += this.entity.posX; + y += this.entity.posY; + z += this.entity.posZ; + } + } + + // Spin + if(radius > 0){ + angle += speed; + // If the particle has spin, x/z relative position is used as centre and coords are changed each tick + x += radius * -MathHelper.cos(angle); + z += radius * MathHelper.sin(angle); + } + + this.setPosition(x, y, z); + + this.relativeX += relativeMotionX; + this.relativeY += relativeMotionY; + this.relativeZ += relativeMotionZ; + } + + // Colour fading + float ageFraction = (float)this.particleAge / (float)this.particleMaxAge; + // No longer uses setRBGColorF because that method now also sets the initial values + this.particleRed = this.initialRed + (this.fadeRed - this.initialRed) * ageFraction; + this.particleGreen = this.initialGreen + (this.fadeGreen - this.initialGreen) * ageFraction; + this.particleBlue = this.initialBlue + (this.fadeBlue - this.initialBlue) * ageFraction; + + // Animation + if(sprites.length > 1){ + // Math.min included for safety so the index cannot possibly exceed the length - 1 an cause an AIOOBE + // (which would probably otherwise happen if particleAge == particleMaxAge) + this.setParticleTexture(sprites[Math.min((int)(ageFraction * sprites.length), sprites.length - 1)]); + } + + // Collision spreading + if(canCollide){ + + if(this.motionX == 0 && this.prevVelX != 0){ // If the particle just collided in x + // Reduce lateral velocity so the added spread speed actually has an effect + this.motionY *= IMPACT_FRICTION; + this.motionZ *= IMPACT_FRICTION; + // Add random velocity in y and z proportional to the impact velocity + this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR; + this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR; + } + + if(this.motionY == 0 && this.prevVelY != 0){ // If the particle just collided in y + // Reduce lateral velocity so the added spread speed actually has an effect + this.motionX *= IMPACT_FRICTION; + this.motionZ *= IMPACT_FRICTION; + // Add random velocity in x and z proportional to the impact velocity + this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR; + this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR; + } + + if(this.motionZ == 0 && this.prevVelZ != 0){ // If the particle just collided in z + // Reduce lateral velocity so the added spread speed actually has an effect + this.motionX *= IMPACT_FRICTION; + this.motionY *= IMPACT_FRICTION; + // Add random velocity in x and y proportional to the impact velocity + this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR; + this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR; + } + + double searchRadius = 20; + + List nearbyEntities = WizardryUtilities.getEntitiesWithinRadius(searchRadius, this.posX, + this.posY, this.posZ, world, Entity.class); + + nearbyEntities.removeIf(e -> !(e instanceof ICustomHitbox && ((ICustomHitbox)e).contains(new Vec3d(this.posX, this.posY, this.posZ)))); + + if(nearbyEntities.size() > 0) this.setExpired(); + + } + + this.prevVelX = motionX; + this.prevVelY = motionY; + this.prevVelZ = motionZ; + } + + // Overridden and copied to fix the collision behaviour + @Override + public void move(double x, double y, double z){ + + double origY = y; + double origX = x; + double origZ = z; + + if(this.canCollide){ + + List list = this.world.getCollisionBoxes(null, this.getBoundingBox().expand(x, y, z)); + + for(AxisAlignedBB axisalignedbb : list){ + y = axisalignedbb.calculateYOffset(this.getBoundingBox(), y); + } + + this.setBoundingBox(this.getBoundingBox().offset(0.0D, y, 0.0D)); + + for(AxisAlignedBB axisalignedbb1 : list){ + x = axisalignedbb1.calculateXOffset(this.getBoundingBox(), x); + } + + this.setBoundingBox(this.getBoundingBox().offset(x, 0.0D, 0.0D)); + + for(AxisAlignedBB axisalignedbb2 : list){ + z = axisalignedbb2.calculateZOffset(this.getBoundingBox(), z); + } + + this.setBoundingBox(this.getBoundingBox().offset(0.0D, 0.0D, z)); + + }else{ + this.setBoundingBox(this.getBoundingBox().offset(x, y, z)); + } + + this.resetPositionToBB(); + this.onGround = origY != y && origY < 0.0D; + + if(origX != x) this.motionX = 0.0D; + if(origY != y) this.motionY = 0.0D; // Why doesn't Particle do this for y? + if(origZ != z) this.motionZ = 0.0D; + } + + + // =============================================== Helper Methods =============================================== + + /** Static helper method that generates an array of n ResourceLocations using the particle file naming convention, + * which is the given stem plus an underscore plus the integer index. */ + public static ResourceLocation[] generateTextures(String stem, int n){ + + ResourceLocation[] textures = new ResourceLocation[n]; + + for(int i=0; ithis method may only be called from the client side
    , probably a client proxy. + * @param name The {@link ResourceLocation} to use for the particle. This effectively replaces the particle type + * enum from previous versions. Keep a reference to this somewhere in common code for use later. + * @param factory A {@link IWizardryParticleFactory} that produces your particle. A constructor reference is usually + * sufficient. + */ + public static void registerParticle(ResourceLocation name, IWizardryParticleFactory factory){ + ClientProxy.addParticleFactory(name, factory); + } + + /** Simple particle factory interface which takes a world and a position and returns a particle. Used (via method + * references) in the client proxy to link particle enum types to actual particle classes. */ + @SideOnly(Side.CLIENT) + @FunctionalInterface + public interface IWizardryParticleFactory { + ParticleWizardry createParticle(World world, double x, double y, double z); + } +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java b/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java new file mode 100644 index 00000000..ae8536dd --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java @@ -0,0 +1,153 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.registry.WizardryPotions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelPlayer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderLivingBase; +import net.minecraft.client.renderer.entity.RenderPlayer; +import net.minecraft.client.renderer.entity.layers.LayerRenderer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +/** + * Layer used to render the frost texture on a creature with the frostbite effect. Handles dynamic tiling of the texture. + * + * @author Electroblob + * @since Wizardry 1.2 + */ +public class LayerFrost implements LayerRenderer { + + protected ModelBase model; + private final RenderLivingBase renderer; + + private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/entity/frost_overlay.png"); + + public static void initialiseLayers(){ + + for(Render renderer : Minecraft.getMinecraft().getRenderManager().entityRenderMap.values()){ + // Because the zombie classes are now split properly, their renderers play nicely like everything else. + if(renderer instanceof RenderLivingBase){ + // Adds a frost layer to all the living entity renderers in the game. Whether it is actually rendered + // is decided in doRenderLayer below on a per-entity basis. + ((RenderLivingBase)renderer).addLayer(new LayerFrost((RenderLivingBase)renderer)); + } + } + + for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){ + renderer.addLayer(new LayerFrost(renderer)); + } + } + + public LayerFrost(RenderLivingBase renderer){ + this.renderer = renderer; + this.model = renderer.getMainModel(); + } + + @Override + public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + if(entity.isPotionActive(WizardryPotions.frost) || entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)){ + + GlStateManager.enableLighting(); + int i = this.getBlockBrightnessForEntity(entity, partialTicks); + + int j = i % 65536; + int k = i / 65536; + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)j / 1.0F, (float)k / 1.0F); + + // Frost texture + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + this.renderer.bindTexture(texture); + this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, + headPitch, scale); + GlStateManager.disableBlend(); + + } + } + + private int getBlockBrightnessForEntity(Entity entity, float partialTicks){ + + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(MathHelper.floor(entity.posX), 0, + MathHelper.floor(entity.posZ)); + + if(entity.world.isBlockLoaded(pos)){ + pos.setY(MathHelper.floor(entity.posY + (double)entity.getEyeHeight())); + return entity.world.getCombinedLight(pos, 0); + }else{ + return 0; + } + } + + private void renderEntityModel(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.pushMatrix(); + // Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam) + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + + GlStateManager.depthMask(true); // Some entities set depth mask to false (i.e. no sorting of faces by depth) + // In particular, LayerSpiderEyes sets it to false when the spider is invisible, for some reason. + + // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, + // but with translation instead of scaling. + // NOTE: You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + double scaleX = 1, scaleY = 1; + // It's more logical to use the model's texture size, but some classes don't bother setting it properly + // (e.g. ModelVillager), so to get the correct dimensions I'm getting them from the first box instead. + if(model.boxList != null && model.boxList.get(0) != null){ + scaleX = (double)model.boxList.get(0).textureWidth / 16d; + scaleY = (double)model.boxList.get(0).textureHeight / 16d; + }else{ // Fallback to model fields; should never be needed + scaleX = (double)model.textureWidth / 16d; + scaleY = (double)model.textureHeight / 16d; + } + GlStateManager.scale(scaleX, scaleY, 1); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + // Hides the hat layer for bipeds + if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = true; + + if(this.model instanceof ModelPlayer){ + ((ModelPlayer)this.model).bipedBodyWear.isHidden = true; + ((ModelPlayer)this.model).bipedLeftArmwear.isHidden = true; + ((ModelPlayer)this.model).bipedRightArmwear.isHidden = true; + ((ModelPlayer)this.model).bipedLeftLegwear.isHidden = true; + ((ModelPlayer)this.model).bipedRightLegwear.isHidden = true; + } + + this.model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); + this.model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + + if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = false; + + // Undoes the texture scaling + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.popMatrix(); + } + + @Override + public boolean shouldCombineTextures(){ + return false; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java b/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java index 82f5950f..ed1d5669 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java @@ -1,11 +1,7 @@ package electroblob.wizardry.client.renderer; -import java.util.Map.Entry; - -import org.lwjgl.opengl.GL11; - +import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.client.ClientProxy; -import electroblob.wizardry.spell.Petrify; import net.minecraft.client.Minecraft; import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelBiped; @@ -21,6 +17,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; /** * Layer used to render the stone texture on a petrified creature. Handles dynamic tiling of the stone texture. @@ -36,16 +33,14 @@ public class LayerStone implements LayerRenderer { private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.png"); public static void initialiseLayers(){ - for(Entry, Render> entry : Minecraft.getMinecraft() - .getRenderManager().entityRenderMap.entrySet()){ + + for(Render renderer : Minecraft.getMinecraft().getRenderManager().entityRenderMap.values()){ // Because the zombie classes are now split properly, their renderers play nicely like everything else. - if(entry.getValue() instanceof RenderLivingBase){ + if(renderer instanceof RenderLivingBase){ // Adds a stone layer to all the living entity renderers in the game. Whether it is actually rendered // is decided in doRenderLayer below on a per-entity basis. - ((RenderLivingBase)entry.getValue()).addLayer(new LayerStone((RenderLivingBase)entry.getValue())); + ((RenderLivingBase)renderer).addLayer(new LayerStone((RenderLivingBase)renderer)); } - // NOTE: May have to do some special stuff for players if they are to be added; see - // Minecraft.getMinecraft().getRenderManager().getSkinMap() } } @@ -53,12 +48,15 @@ public class LayerStone implements LayerRenderer { this.renderer = renderer; this.model = renderer.getMainModel(); } + + // FIXME: Does not work with zombie pigmen, I have no idea why. + // I believe the issue is with the TESR actually, since LayerFrost works fine @Override public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale){ - if(entity.getEntityData().getBoolean(Petrify.NBT_KEY)){ + if(entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)){ GlStateManager.enableLighting(); int i = this.getBlockBrightnessForEntity(entity, partialTicks); @@ -106,7 +104,6 @@ public class LayerStone implements LayerRenderer { GlStateManager.pushMatrix(); // Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam) - // TODO: Backport this improvement GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java b/src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java new file mode 100644 index 00000000..00496ecb --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java @@ -0,0 +1,33 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.entity.living.EntityStrayMinion; +import net.minecraft.client.model.ModelSkeleton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.RenderLivingBase; +import net.minecraft.client.renderer.entity.layers.LayerRenderer; +import net.minecraft.util.ResourceLocation; + +/** Had to copy this entire class just because of one unnecessarily specific type parameter. The type parameter has been + * changed to {@code EntityStrayMinion} and parameter types updated accordingly. Everything else is identical. */ +public class LayerStrayMinionClothing implements LayerRenderer { + + private static final ResourceLocation STRAY_CLOTHES_TEXTURES = new ResourceLocation("textures/entity/skeleton/stray_overlay.png"); + private final RenderLivingBase renderer; + private final ModelSkeleton layerModel = new ModelSkeleton(0.25F, true); + + public LayerStrayMinionClothing(RenderLivingBase renderer){ + this.renderer = renderer; + } + + public void doRenderLayer(EntityStrayMinion entity, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale){ + this.layerModel.setModelAttributes(this.renderer.getMainModel()); + this.layerModel.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + this.renderer.bindTexture(STRAY_CLOTHES_TEXTURES); + this.layerModel.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + } + + public boolean shouldCombineTextures(){ + return true; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderArc.java b/src/main/java/electroblob/wizardry/client/renderer/RenderArc.java deleted file mode 100644 index 1e59d927..00000000 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderArc.java +++ /dev/null @@ -1,133 +0,0 @@ -package electroblob.wizardry.client.renderer; - -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.EntityArc; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.entity.Render; -import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.util.ResourceLocation; - -public class RenderArc extends Render { - - private static final ResourceLocation[] textures = new ResourceLocation[16]; - - public RenderArc(RenderManager renderManager){ - super(renderManager); - for(int i = 0; i < 16; i++){ - textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/arc_" + i + ".png"); - } - } - - @Override - public void doRender(EntityArc arc, double d0, double d1, double d2, float fa, float fb){ - GlStateManager.pushMatrix(); - GlStateManager.translate((float)d0, (float)d1, (float)d2); - GlStateManager.disableLighting(); - GlStateManager.enableBlend(); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // This line fixes the weird - // brightness bug. - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - - // System.out.println("Entity coords: " + entity.posX + ", " + entity.posY + ", " + entity.posZ); - // System.out.println("doRender parameters: " + d0 + ", " + d1 + ", " + d2 + ", " + fa + ", " + fb); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - bindTexture(textures[arc.textureIndex]); // This MUST be after the tessellator declaration and the gl stuff - - /** - * Note: A lot of the maths here works on similar triangles and the ratios between them, avoiding too much - * pythagoras and eliminating the need for any trig. Ratios are used for the positioning of the arc endpoints. - * Ratios are usually used swapping x and z because the triangles are rotated through 90 degrees. - */ - - double dx = -d0; - double dy = -d1; - double dz = -d2; - - if(arc.x1 != 0){ - dx = arc.x1 - arc.posX;// - d2/lengthOffsetRatio; - dy = arc.y1 - arc.posY + 0.3; - dz = arc.z1 - arc.posZ;// + d0/lengthOffsetRatio; - - // The distance from caster to target - double arcLength = Math.sqrt(dz * dz + dx * dx); - - // The ratio between the length of the arc and the offset of the start point from the player's centre (which - // is always 0.3). - // double lengthOffsetRatio = arcLength/0.3; - - // EntityClientPlayerMP player = Minecraft.getMinecraft().player; - - // double xViewDist = player.posX - d0; - // double yViewDist = player.posY + player.eyeHeight - d1; - // double zViewDist = player.posZ - d2; - - // double xzViewDist = Math.sqrt(xViewDist * xViewDist + zViewDist * zViewDist); - - // The angle above the horizontal that this particular player is viewing the arc from - // double viewAngle = Math.atan(yViewDist/xzViewDist); - - // Half the width of the arc - double arcWidth = 0.3d; - - // Right hand side of vertical plane - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - // Target end - buffer.pos(0, -0.5, 0).tex(1, 1).endVertex(); - buffer.pos(0, 0.5, 0).tex(1, 0).endVertex(); - // Caster end - buffer.pos(dx, dy, dz).tex(0, 0).endVertex(); - buffer.pos(dx, dy - 1, dz).tex(0, 1).endVertex(); - tessellator.draw(); - - // Left - buffer.begin(GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX); - // Target end - buffer.pos(0, -0.5, 0).tex(1, 1).endVertex(); - // Caster end - buffer.pos(dx, dy - 1, dz).tex(0, 1).endVertex(); - buffer.pos(dx, dy, dz).tex(0, 0).endVertex(); - // Target end - buffer.pos(0, 0.5, 0).tex(1, 0).endVertex(); - tessellator.draw(); - - // Bottom of horizontal plane - buffer.begin(GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX); - buffer.pos((arcWidth / arcLength) * dz, 0, (-arcWidth / arcLength) * dx).tex(1, 1).endVertex(); - buffer.pos(dx + (arcWidth / arcLength) * dz, dy - 0.5, dz - (arcWidth / arcLength) * dx).tex(0, 1) - .endVertex(); - buffer.pos(dx - (arcWidth / arcLength) * dz, dy - 0.5, dz + (arcWidth / arcLength) * dx).tex(0, 0) - .endVertex(); - buffer.pos((-arcWidth / arcLength) * dz, 0, (arcWidth / arcLength) * dx).tex(1, 0).endVertex(); - tessellator.draw(); - - // Top - buffer.begin(GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX); - buffer.pos((arcWidth / arcLength) * dz, 0, (-arcWidth / arcLength) * dx).tex(1, 1).endVertex(); - buffer.pos((-arcWidth / arcLength) * dz, 0, (arcWidth / arcLength) * dx).tex(1, 0).endVertex(); - buffer.pos(dx - (arcWidth / arcLength) * dz, dy - 0.5, dz + (arcWidth / arcLength) * dx).tex(0, 0) - .endVertex(); - buffer.pos(dx + (arcWidth / arcLength) * dz, dy - 0.5, dz - (arcWidth / arcLength) * dx).tex(0, 1) - .endVertex(); - tessellator.draw(); - } - - GlStateManager.enableLighting(); - GlStateManager.disableBlend(); - GlStateManager.popMatrix(); - } - - @Override - protected ResourceLocation getEntityTexture(EntityArc entity){ - return textures[entity.textureIndex]; - } - -} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java b/src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java new file mode 100644 index 00000000..cb2058f8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java @@ -0,0 +1,89 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.spell.ArcaneLock; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderArcaneLock { + + private static final ResourceLocation[] textures = new ResourceLocation[8]; + + static { + for(int i=0; i { @@ -30,21 +30,21 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer { @@ -81,21 +77,21 @@ public class RenderBlackHole extends Render { int sliceAngle = 20 + a; - double x1 = scale * Math.sin((blackhole.ticksExisted + 40 * j) * (Math.PI / 180)); - // double y1 = 0.7*Math.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10; - double z1 = scale * Math.cos((blackhole.ticksExisted + 40 * j) * (Math.PI / 180)); + double x1 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180f)); + // double y1 = 0.7*MathHelper.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10; + double z1 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180)); - double x2 = scale * Math.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180)); - // double y2 = 0.7*Math.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10; - double z2 = scale * Math.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180)); + double x2 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180)); + // double y2 = 0.7*MathHelper.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10; + double z2 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180)); - double absoluteX = x1 * Math.cos(31 * b); - double absoluteY = z1 * Math.sin(31 * a) + x1 * Math.cos(31 * a) * Math.sin(31 * b); - double absoluteZ = z1 * Math.cos(31 * a); + double absoluteX = x1 * MathHelper.cos(31 * b); + double absoluteY = z1 * MathHelper.sin(31 * a) + x1 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b); + double absoluteZ = z1 * MathHelper.cos(31 * a); - double absoluteX2 = x2 * Math.cos(31 * b); - double absoluteY2 = z2 * Math.sin(31 * a) + x2 * Math.cos(31 * a) * Math.sin(31 * b); - double absoluteZ2 = z2 * Math.cos(31 * a); + double absoluteX2 = x2 * MathHelper.cos(31 * b); + double absoluteY2 = z2 * MathHelper.sin(31 * a) + x2 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b); + double absoluteZ2 = z2 * MathHelper.cos(31 * a); /* buffer.begin(0, DefaultVertexFormats.POSITION_TEX); * * tessellator.setColorOpaque(255, 255, 255); GL11.glPointSize(5); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java b/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java index fa0cb695..2694f1e8 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityBubble; import electroblob.wizardry.util.WizardryUtilities; @@ -14,6 +12,7 @@ import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; public class RenderBubble extends Render { diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java b/src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java new file mode 100644 index 00000000..b52b83b0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java @@ -0,0 +1,268 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.potion.PotionContainment; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderContainmentField { + + private static final ResourceLocation[] textures = new ResourceLocation[8]; + + private static final float ANIMATION_SPEED = 0.004f; + private static final float FADE_DISTANCE_SQUARED = 15; + + static { + for(int i=0; i { @@ -44,7 +43,7 @@ public class RenderDecay extends Render { GlStateManager.rotate(-90, 1, 0, 0); - float scale = 2 * Math.min(1, (float)(EntityDecay.LIFETIME - entity.ticksExisted) / 50f); + float scale = 2 * Math.min(1, (float)(entity.lifetime - entity.ticksExisted) / 50f); GlStateManager.scale(scale, scale, scale); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java b/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java index 4c421adc..10acfa56 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java @@ -1,8 +1,5 @@ package electroblob.wizardry.client.renderer; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntityDecoy; import net.minecraft.client.model.ModelBiped; @@ -12,10 +9,11 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.relauncher.ReflectionHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +//@SideOnly(Side.CLIENT) public class RenderDecoy extends RenderBiped { private static final ResourceLocation steveTextures = new ResourceLocation("textures/entity/steve.png"); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java b/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java index 2828261f..34299e1c 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java @@ -7,10 +7,8 @@ import net.minecraft.client.renderer.entity.RenderBiped; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.layers.LayerBipedArmor; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderEvilWizard extends RenderBiped { static final ResourceLocation[] textures = new ResourceLocation[6]; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java b/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java index 8597bacf..9e01b89b 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.entity.construct.EntityFireRing; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; @@ -15,6 +13,7 @@ import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.init.Blocks; import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; public class RenderFireRing extends Render { diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java b/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java index 5878aeeb..7f908e71 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.projectile.EntityForceArrow; import net.minecraft.client.renderer.BufferBuilder; @@ -13,10 +11,9 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderForceArrow extends Render { private static final ResourceLocation arrowTextures = new ResourceLocation(Wizardry.MODID, @@ -73,13 +70,23 @@ public class RenderForceArrow extends Render { GlStateManager.scale(scale, scale, scale); GlStateManager.translate(-4.0F, 0.0F, 0.0F); + // Front buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); buffer.pos(-5, 3.5, -3.5).tex((double)u5, (double)v5).endVertex(); buffer.pos(-5, 3.5, 3.5).tex((double)u6, (double)v5).endVertex(); buffer.pos(-5, -3.5, 3.5).tex((double)u6, (double)v6).endVertex(); - buffer.pos(-5, -3.5, -3.5).tex((double)u5, (double)v6); + buffer.pos(-5, -3.5, -3.5).tex((double)u5, (double)v6).endVertex(); + tessellator.draw(); + + // Back + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.pos(-5, -3.5, -3.5).tex((double)u5, (double)v6).endVertex(); + buffer.pos(-5, -3.5, 3.5).tex((double)u6, (double)v6).endVertex(); + buffer.pos(-5, 3.5, 3.5).tex((double)u6, (double)v5).endVertex(); + buffer.pos(-5, 3.5, -3.5).tex((double)u5, (double)v5).endVertex(); tessellator.draw(); + // Rings for(int i = 0; i < 5; i++){ GlStateManager.color(1, 1, 1, 1 - i * 0.2f); double j = i + ((double)arrow.ticksExisted % 3) / 3; @@ -102,6 +109,7 @@ public class RenderForceArrow extends Render { GlStateManager.color(1, 1, 1, 1); + // Sides for(int i = 0; i < 4; ++i){ GlStateManager.rotate(90.0F, 1.0F, 0.0F, 0.0F); GL11.glNormal3f(0.0F, 0.0F, scale); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java b/src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java new file mode 100644 index 00000000..660c1e1a --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java @@ -0,0 +1,124 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.entity.construct.EntityForcefield; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +public class RenderForcefield extends Render { + + private static final float EXPANSION_TIME = 3; + + public RenderForcefield(RenderManager renderManager){ + super(renderManager); + } + + @Override + public void doRender(EntityForcefield entity, double x, double y, double z, float yaw, float partialTicks){ + + // For now we're just using a UV sphere + + GlStateManager.pushMatrix(); + + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture2D(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.translate(x, y, z); + + float latStep = (float)Math.PI/20; + float longStep = (float)Math.PI/20; + + float pulse = MathHelper.sin((entity.ticksExisted + partialTicks)/10f); + + float r = 0.35f, g = 0.55f + 0.05f * pulse, b = 1; + + float radius = entity.getRadius(); + float a = 0.5f; + + if(entity.ticksExisted > entity.lifetime - EXPANSION_TIME){ + radius *= 1 + 0.2f * (entity.ticksExisted + partialTicks - (entity.lifetime - EXPANSION_TIME))/EXPANSION_TIME; + a *= Math.max(0, 1 - (entity.ticksExisted + partialTicks - (entity.lifetime - EXPANSION_TIME))/EXPANSION_TIME); + }else if(entity.ticksExisted < EXPANSION_TIME){ + radius *= 1 - (EXPANSION_TIME - entity.ticksExisted - partialTicks)/EXPANSION_TIME; + a *= 1 - (EXPANSION_TIME - entity.ticksExisted - partialTicks)/EXPANSION_TIME; + } + + // Draw the inside first + drawSphere(radius - 0.1f - 0.025f * pulse, latStep, longStep, true, r, g, b, a); + drawSphere(radius - 0.1f - 0.025f * pulse, latStep, longStep, false, 1, 1, 1, a); + drawSphere(radius, latStep, longStep, false, r, g, b, 0.7f * a); + + GlStateManager.enableTexture2D(); + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + + GlStateManager.popMatrix(); + } + + @Override + protected ResourceLocation getEntityTexture(EntityForcefield entity){ + return null; + } + + /** + * Draws a sphere (using lat/long triangles) with the given parameters. + * @param radius The radius of the sphere. + * @param latStep The latitude step; smaller is smoother but increases performance cost. + * @param longStep The longitude step; smaller is smoother but increases performance cost. + * @param inside Whether to draw the outside or the inside of the sphere. + * @param r The red component of the sphere colour. + * @param g The green component of the sphere colour. + * @param b The blue component of the sphere colour. + * @param a The alpha component of the sphere colour. + */ + private static void drawSphere(float radius, float latStep, float longStep, boolean inside, float r, float g, float b, float a){ + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR); + + boolean goingUp = inside; + + buffer.pos(0, goingUp ? -radius : radius, 0).color(r, g, b, a).endVertex(); // Start at the north pole + + for(float longitude = -(float)Math.PI; longitude <= (float)Math.PI; longitude += longStep){ + + // Leave the poles out since they only have a single point per stack instead of two + for(float theta = (float)Math.PI/2 - latStep; theta >= -(float)Math.PI/2 + latStep; theta -= latStep){ + + float latitude = goingUp ? -theta : theta; + + float hRadius = radius * MathHelper.cos(latitude); + float vy = radius * MathHelper.sin(latitude); + float vx = hRadius * MathHelper.sin(longitude); + float vz = hRadius * MathHelper.cos(longitude); + + buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex(); + + vx = hRadius * MathHelper.sin(longitude + longStep); + vz = hRadius * MathHelper.cos(longitude + longStep); + + buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex(); + } + + // The next pole + buffer.pos(0, goingUp ? radius : -radius, 0).color(r, g, b, a).endVertex(); + + goingUp = !goingUp; + } + + tessellator.draw(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java b/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java index 8f448603..3962e04b 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java @@ -19,11 +19,13 @@ public class RenderHammer extends Render { } @Override - public void doRender(EntityHammer entity, double x, double y, double z, float f, float f1){ + public void doRender(EntityHammer entity, double x, double y, double z, float yaw, float partialTicks){ GlStateManager.pushMatrix(); GlStateManager.translate(x, y + 1.5, z); GlStateManager.rotate(180, 0F, 0F, 1F); + GlStateManager.rotate(yaw, 0, 1, 0); + GlStateManager.rotate(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * partialTicks, 0, 0, 1); this.bindTexture(texture); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java b/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java index b78c6888..e20f22bf 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java @@ -7,10 +7,8 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.entity.RenderLiving; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderIceGiant extends RenderLiving { private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java b/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java index 961b87f5..3a88d93b 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityIceSpike; import net.minecraft.client.renderer.BufferBuilder; @@ -12,6 +10,7 @@ import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; public class RenderIceSpike extends Render { @@ -23,13 +22,14 @@ public class RenderIceSpike extends Render { } @Override - public void doRender(EntityIceSpike entity, double x, double y, double z, float fa, float partialTickTime){ + public void doRender(EntityIceSpike entity, double x, double y, double z, float yaw, float partialTickTime){ GlStateManager.pushMatrix(); GlStateManager.translate((float)x, (float)y, (float)z); - // Apparently, disabling lighting... doesn't disable lighting. Or at least, you can still set the brightness - // with setLightmapTextureCoords. + GlStateManager.rotate(entity.rotationYaw - 90.0F, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(entity.rotationPitch - 90, 0.0F, 0.0F, 1.0F); + GlStateManager.disableLighting(); int j = entity.getBrightnessForRender(); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java b/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java index 77af69d3..96fe6110 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.entity.projectile.EntityLightningDisc; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -11,6 +9,7 @@ import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; public class RenderLightningDisc extends Render { diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningPulse.java b/src/main/java/electroblob/wizardry/client/renderer/RenderLightningPulse.java deleted file mode 100644 index 65f2bc1d..00000000 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningPulse.java +++ /dev/null @@ -1,72 +0,0 @@ -package electroblob.wizardry.client.renderer; - -import org.lwjgl.opengl.GL11; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.construct.EntityLightningPulse; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.entity.Render; -import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.util.ResourceLocation; - -public class RenderLightningPulse extends Render { - - private final ResourceLocation[] textures = new ResourceLocation[8]; - private float scale = 1.0f; - - public RenderLightningPulse(RenderManager renderManager, float scale){ - super(renderManager); - for(int i = 0; i < textures.length; i++){ - textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_pulse_" + i + ".png"); - } - this.scale = scale; - } - - @Override - public void doRender(EntityLightningPulse entity, double par2, double par4, double par6, float par8, float par9){ - - GlStateManager.pushMatrix(); - GlStateManager.enableBlend(); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - - float yOffset = 0; - - GlStateManager.translate((float)par2, (float)par4 + yOffset, (float)par6); - - this.bindTexture(textures[entity.ticksExisted]); - float f6 = 1.0F; - float f7 = 0.5F; - float f8 = 0.5F; - - GlStateManager.rotate(-90, 1, 0, 0); - - GlStateManager.scale(scale, scale, scale); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos((double)(0.0F - f7), (double)(0.0F - f8), 0.01).tex(0, 1).endVertex(); - buffer.pos((double)(f6 - f7), (double)(0.0F - f8), 0.01).tex(1, 1).endVertex(); - buffer.pos((double)(f6 - f7), (double)(1.0F - f8), 0.01).tex(1, 0).endVertex(); - buffer.pos((double)(0.0F - f7), (double)(1.0F - f8), 0.01).tex(0, 0).endVertex(); - - tessellator.draw(); - - GlStateManager.disableBlend(); - GlStateManager.enableLighting(); - GlStateManager.disableRescaleNormal(); - GlStateManager.popMatrix(); - } - - @Override - protected ResourceLocation getEntityTexture(EntityLightningPulse entity){ - return null; - } - -} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java b/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java index 9dfb0527..b0db9b36 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.entity.projectile.EntityMagicArrow; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -12,10 +10,9 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderMagicArrow extends Render { private final ResourceLocation texture; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java b/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java index 198e02f7..a0e2a2ce 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java @@ -1,18 +1,14 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.tileentity.TileEntityMagicLight; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; public class RenderMagicLight extends TileEntitySpecialRenderer { @@ -40,10 +36,9 @@ public class RenderMagicLight extends TileEntitySpecialRenderer tileentity.maxTimer - 10 && tileentity.timer <= tileentity.maxTimer){ - GlStateManager.scale((float)(tileentity.maxTimer - tileentity.timer) / 10, - (float)(tileentity.maxTimer - tileentity.timer) / 10, - (float)(tileentity.maxTimer - tileentity.timer) / 10); + if(tileentity.maxTimer > 0 && tileentity.timer > tileentity.maxTimer - 10){ + float scale = Math.max(0, (float)(tileentity.maxTimer - tileentity.timer) / 10); + GlStateManager.scale(scale, scale, scale); } // Renders the aura effect @@ -105,13 +100,13 @@ public class RenderMagicLight extends TileEntitySpecialRenderer { private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/entity/phoenix.png"); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java b/src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java new file mode 100644 index 00000000..0820a178 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java @@ -0,0 +1,41 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.spell.Possession; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderPossessingPlayer { + + @SubscribeEvent + @SuppressWarnings("unchecked") // Can't check it due to type erasure + public static void onRenderPlayerPreEvent(RenderPlayerEvent.Pre event){ + + EntityPlayer player = event.getEntityPlayer(); + EntityLiving possessee = Possession.getPossessee(player); + + if(possessee != null){ + // I reject your renderer and substitute my own! + Render renderer = (Render)event.getRenderer().getRenderManager().entityRenderMap.get(possessee.getClass()); + float yaw = player.prevRotationYaw + (player.rotationYaw - player.prevRotationYaw) * event.getPartialRenderTick(); + possessee.swingProgress = player.swingProgress; + possessee.prevSwingProgress = player.prevSwingProgress; + possessee.renderYawOffset = player.renderYawOffset; + possessee.prevRenderYawOffset = player.prevRenderYawOffset; + possessee.rotationYawHead = player.rotationYawHead; + possessee.prevRotationYawHead = player.prevRotationYawHead; + possessee.rotationPitch = player.rotationPitch; + possessee.prevRotationPitch = player.prevRotationPitch; + possessee.limbSwing = player.limbSwing; + possessee.limbSwingAmount = player.limbSwingAmount; + possessee.prevLimbSwingAmount = player.prevLimbSwingAmount; + renderer.doRender(possessee, event.getX(), event.getY(), event.getZ(), yaw, event.getPartialRenderTick()); + event.setCanceled(true); + } + } +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java b/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java index 387257b4..f5ece03f 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java @@ -1,7 +1,5 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.entity.projectile.EntityMagicProjectile; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; @@ -12,10 +10,9 @@ import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderProjectile extends Render { private float scale; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java b/src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java new file mode 100644 index 00000000..a0cf181f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java @@ -0,0 +1,148 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderShadowWard { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/shadow_ward.png"); + + // First person + @SubscribeEvent + public static void onRenderWorldLastEvent(RenderWorldLastEvent event){ + // Only render in first person + if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){ + + EntityPlayer player = Minecraft.getMinecraft().player; + + if(WizardryUtilities.isCasting(player, Spells.shadow_ward)){ + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + //GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.disableLighting(); + //GlStateManager.disableAlpha(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.translate(0, 1.2, 0); + GlStateManager.rotate(-player.rotationYaw, 0, 1, 0); + GlStateManager.rotate(player.rotationPitch, 1, 0, 0); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + GlStateManager.pushMatrix(); + + GlStateManager.translate(0, 0, 1.2); + GlStateManager.rotate(player.world.getTotalWorldTime() * -2, 0, 0, 1); + GlStateManager.scale(1.1, 1.1, 1.1); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); + buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); + buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); + buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); + + tessellator.draw(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); + buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); + buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); + buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + + //GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + + GlStateManager.popMatrix(); + + } + } + } + + // Third person + @SubscribeEvent + public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){ + + EntityPlayer player = event.getEntityPlayer(); + + if(WizardryUtilities.isCasting(player, Spells.shadow_ward)){ + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + Vec3d delta = player.getPositionEyes(event.getPartialRenderTick()) + .subtract(Minecraft.getMinecraft().player.getPositionEyes(event.getPartialRenderTick())); + GlStateManager.translate(delta.x, delta.y, delta.z); + + GlStateManager.rotate(180, 0, 1, 0); + GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.translate(0, 1.2, 0); + GlStateManager.rotate(player.world.getTotalWorldTime() * -2, 0, 0, 1); + GlStateManager.scale(1.1, 1.1, 1.1); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); + buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); + buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); + buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); + + tessellator.draw(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex(); + buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex(); + buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex(); + buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + + GlStateManager.popMatrix(); + + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderShield.java b/src/main/java/electroblob/wizardry/client/renderer/RenderShield.java new file mode 100644 index 00000000..6211d4f4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderShield.java @@ -0,0 +1,168 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Shield; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderShield { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/shield.png"); + + // First person + @SubscribeEvent + public static void onRenderWorldLastEvent(RenderWorldLastEvent event){ + // Only render in first person + if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){ + + EntityPlayer player = Minecraft.getMinecraft().player; + + if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && WizardryUtilities.isCasting(player, Spells.shield)){ + + GlStateManager.pushMatrix(); + + GlStateManager.disableCull(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA); + GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.translate(0, 1.4, 0); + + GlStateManager.rotate(-player.rotationYaw, 0, 1, 0); + GlStateManager.rotate(player.rotationPitch, 1, 0, 0); + + GlStateManager.translate(0, 0, 0.8); + + Tessellator tessellator = Tessellator.getInstance(); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + render(tessellator); + + GlStateManager.enableLighting(); + + GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableCull(); + GlStateManager.disableBlend(); + // RenderHelper.enableStandardItemLighting(); + + GlStateManager.popMatrix(); + } + } + } + + // Third person + @SubscribeEvent + public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){ + + EntityPlayer player = event.getEntityPlayer(); + + if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && WizardryUtilities.isCasting(player, Spells.shield)){ + + GlStateManager.pushMatrix(); + + GlStateManager.disableCull(); + GlStateManager.enableBlend(); + // For some reason, the old blend function (GL11.GL_SRC_ALPHA, GL11.GL_SRC_ALPHA) caused the inner + // edges to appear black, so I have changed it to this, which looks very slightly different. + GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA); + GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + Vec3d delta = player.getPositionEyes(event.getPartialRenderTick()) + .subtract(Minecraft.getMinecraft().player.getPositionEyes(event.getPartialRenderTick())); + GlStateManager.translate(delta.x, delta.y, delta.z); + + GlStateManager.translate(0, 1.3, 0); + + // GlStateManager.rotate(180, 0, 1, 0); + GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); + // GlStateManager.rotate(-player.rotationPitch, 1, 0, 0); + + GlStateManager.translate(0, 0, 0.8); + + Tessellator tessellator = Tessellator.getInstance(); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + render(tessellator); + + GlStateManager.enableLighting(); + + GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableCull(); + GlStateManager.disableBlend(); + // RenderHelper.enableStandardItemLighting(); + + GlStateManager.popMatrix(); + } + } + + private static void render(Tessellator tessellator){ + + BufferBuilder buffer = tessellator.getBuffer(); + + double widthOuter = 0.6d; + double heightOuter = 0.7d; + double widthInner = 0.3d; + double heightInner = 0.4d; + double depth = 0.2d; + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); + + buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex(); + buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); + buffer.pos(-widthInner, heightOuter, -depth).tex(0.2, 0).color(0, 0, 0, 255).endVertex(); + buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); + + buffer.pos(widthInner, heightOuter, -depth).tex(0.8, 0).color(0, 0, 0, 255).endVertex(); + buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); + buffer.pos(widthOuter, heightInner, -depth).tex(1, 0.2).color(0, 0, 0, 255).endVertex(); + buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); + + buffer.pos(widthOuter, -heightInner, -depth).tex(1, 0.8).color(0, 0, 0, 255).endVertex(); + buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); + buffer.pos(widthInner, -heightOuter, -depth).tex(0.8, 1).color(0, 0, 0, 255).endVertex(); + buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); + + buffer.pos(-widthInner, -heightOuter, -depth).tex(0.2, 1).color(0, 0, 0, 255).endVertex(); + buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); + buffer.pos(-widthOuter, -heightInner, -depth).tex(0, 0.8).color(0, 0, 0, 255).endVertex(); + buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); + + buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex(); + buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); + + tessellator.draw(); + + buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); + + buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex(); + buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex(); + buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex(); + buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex(); + + tessellator.draw(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java b/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java index 739b44c1..5d43d31c 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java @@ -1,10 +1,8 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.entity.construct.EntityHealAura; import electroblob.wizardry.entity.construct.EntityMagicConstruct; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.AllyDesignationSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -15,6 +13,7 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; public class RenderSigil extends Render { @@ -34,8 +33,8 @@ public class RenderSigil extends Render { // Makes the sigil invisible to enemies of the player that created it if(this.invisibleToEnemies){ - - if(entity.getCaster() instanceof EntityPlayer && !WizardryUtilities + // Unfortunately we can't access the caster's allies if they're not online, it only works the other way round + if(entity.getCaster() instanceof EntityPlayer && !AllyDesignationSystem .isPlayerAlly((EntityPlayer)entity.getCaster(), Minecraft.getMinecraft().player)){ return; } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java b/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java index c3452e38..79cd0ab7 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java @@ -1,22 +1,23 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.living.EntitySpiritHorse; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.entity.RenderHorse; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.passive.EntityHorse; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderSpiritHorse extends RenderHorse { private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/entity/spirit_horse.png"); +// private static final int GHOST_COPIES = 3; +// private static final float DECONVERGENCE = 0.35f; + public RenderSpiritHorse(RenderManager renderManager){ super(renderManager); } @@ -27,10 +28,36 @@ public class RenderSpiritHorse extends RenderHorse { } @Override - protected void preRenderCallback(EntityHorse entitylivingbaseIn, float partialTickTime){ - super.preRenderCallback(entitylivingbaseIn, partialTickTime); + protected void preRenderCallback(EntityHorse horse, float partialTickTime){ + super.preRenderCallback(horse, partialTickTime); GlStateManager.enableBlend(); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + if(horse instanceof EntitySpiritHorse){ // Always true + GlStateManager.color(1, 1, 1, ((EntitySpiritHorse)horse).getOpacity()); + } + } + + @Override + public void doRender(EntityHorse entity, double x, double y, double z, float entityYaw, float partialTicks){ + + super.doRender(entity, x, y, z, entityYaw, partialTicks); + +// double dx = (entity.posX - entity.prevPosX) * DECONVERGENCE; +// double dy = (entity.posY - entity.prevPosY) * DECONVERGENCE; +// double dz = (entity.posZ - entity.prevPosZ) * DECONVERGENCE; +// float dyaw = (entity.rotationYaw - entity.prevRotationYaw) * DECONVERGENCE; +// +// float opacity = 1; +// if(entity instanceof EntitySpiritHorse){ // Always true +// opacity = ((EntitySpiritHorse)entity).getOpacity(); +// } +// +// for(int i = 0; i < GHOST_COPIES; i++){ +// +// GlStateManager.color(1, 1, 1, opacity * (0.6f - (float)i/(GHOST_COPIES*2))); +// +// super.doRender(entity, x - dx * i, y - dy * i, z - dz * i, entityYaw - dyaw * i, partialTicks); +// } } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java b/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java index a3f4fadc..0564f69b 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java @@ -1,22 +1,23 @@ package electroblob.wizardry.client.renderer; -import org.lwjgl.opengl.GL11; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.living.EntitySpiritWolf; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderWolf; import net.minecraft.entity.passive.EntityWolf; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderSpiritWolf extends RenderWolf { private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/entity/spirit_wolf.png"); +// private static final int GHOST_COPIES = 3; +// private static final float DECONVERGENCE = 0.8f; + public RenderSpiritWolf(RenderManager renderManager){ super(renderManager); } @@ -31,5 +32,31 @@ public class RenderSpiritWolf extends RenderWolf { super.preRenderCallback(entity, partialTickTime); GlStateManager.enableBlend(); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + if(entity instanceof EntitySpiritWolf){ // Always true + GlStateManager.color(1, 1, 1, ((EntitySpiritWolf)entity).getOpacity()); + } + } + + @Override + public void doRender(EntityWolf entity, double x, double y, double z, float entityYaw, float partialTicks){ + + super.doRender(entity, x, y, z, entityYaw, partialTicks); + +// double dx = (entity.posX - entity.prevPosX) * DECONVERGENCE; +// double dy = (entity.posY - entity.prevPosY) * DECONVERGENCE; +// double dz = (entity.posZ - entity.prevPosZ) * DECONVERGENCE; +// float dyaw = (entity.rotationYaw - entity.prevRotationYaw) * DECONVERGENCE; +// +// float opacity = 1; +// if(entity instanceof EntitySpiritWolf){ // Always true +// opacity = ((EntitySpiritWolf)entity).getOpacity(); +// } +// +// for(int i = 0; i < GHOST_COPIES; i++){ +// +// GlStateManager.color(1, 1, 1, opacity * (0.6f - (float)i/(GHOST_COPIES*2))); +// +// super.doRender(entity, x - dx * i, y - dy * i, z - dz * i, entityYaw - dyaw * i, partialTicks); +// } } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java b/src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java new file mode 100644 index 00000000..815d4979 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java @@ -0,0 +1,24 @@ +package electroblob.wizardry.client.renderer; + +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.entity.RenderSkeleton; +import net.minecraft.entity.monster.AbstractSkeleton; +import net.minecraft.util.ResourceLocation; + +/** This class also had to be copied for the same reason as {@link LayerStrayMinionClothing}. */ +public class RenderStrayMinion extends RenderSkeleton { + + private static final ResourceLocation STRAY_SKELETON_TEXTURES = new ResourceLocation("textures/entity/skeleton/stray.png"); + + public RenderStrayMinion(RenderManager manager){ + super(manager); + this.addLayer(new LayerStrayMinionClothing(this)); // This is the only change + } + + /** + * Returns the location of an entity's texture. Doesn't seem to be called unless you call Render.bindEntityTexture. + */ + protected ResourceLocation getEntityTexture(AbstractSkeleton entity){ + return STRAY_SKELETON_TEXTURES; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java b/src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java new file mode 100644 index 00000000..599d168e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java @@ -0,0 +1,177 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.spell.Transportation; +import electroblob.wizardry.util.Location; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +import java.util.List; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderTransportationUI { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/transportation_marker.png"); + + // I can't get rid of the view bobbing with RenderWorldLastEvent, is there an alternative? + @SubscribeEvent + public static void onRenderWorldLastEvent(RenderWorldLastEvent event){ + + // Only render in first person + if(Minecraft.getMinecraft().gameSettings.thirdPersonView != 0) return; + + EntityPlayer player = Minecraft.getMinecraft().player; + + ItemStack stack = player.getHeldItemMainhand(); + if(!(stack.getItem() instanceof ISpellCastingItem)){ + stack = player.getHeldItemOffhand(); + if(!(stack.getItem() instanceof ISpellCastingItem)) return; + } + + if(((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) == Spells.transportation + && ItemArtefact.isArtefactActive(player, WizardryItems.charm_transportation)){ + + WizardData data = WizardData.get(player); + if(data == null) return; + + List locations = data.getVariable(Transportation.LOCATIONS_KEY); + + if(locations == null) return; + + GlStateManager.pushMatrix(); + + Vec3d origin = player.getPositionEyes(event.getPartialTicks()); + GlStateManager.translate(0, origin.y - Minecraft.getMinecraft().getRenderManager().viewerPosY, 0); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + Location target = Transportation.getLocationAimedAt(player, locations, event.getPartialTicks()); + + for(Location location : locations){ + + if(location.dimension != player.dimension) continue; + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GlStateManager.color(1, 1, 1, 1); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + + Vec3d position = WizardryUtilities.getCentre(location.pos).subtract(origin); + double distance = position.length(); + // The icon lines up perfectly if you render it at actual distance, otherwise view bobbing messes things up + // However, if that's outside the render distance it won't render at all! To fudge our way around this + // problem, we're capping the distance to just below the render distance and adjusting the scale accordingly + double distanceCap = Minecraft.getMinecraft().gameSettings.renderDistanceChunks * 16 - 8; + double displayDist = distance > distanceCap ? distanceCap : distance; + double factor = displayDist/distance; + + GlStateManager.translate(position.x * factor, position.y * factor, position.z * factor); + + GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F); + + // Get the angle between the player's look vector and the direction of the stone circle + double angle = Transportation.getLookDeviationAngle(player, location.pos, event.getPartialTicks()); + double iconSize = Transportation.getIconSize(distance); + + // Now apply a fancy formula to make it enlarge with a nice smooth animation: + double proximityFactor = Math.max(0, Math.pow(1 - angle/iconSize * angle/iconSize, 3)); + iconSize *= 1 + 0.3 * proximityFactor; + iconSize *= displayDist; // Adjust the icon size for perspective + + float f = location == target ? 1 : 0.5f; // Makes it obvious which one is being aimed at + + buffer.pos(-iconSize, iconSize, 0).tex(0, 0).color(f, 1, f, f).endVertex(); + buffer.pos(iconSize, iconSize, 0).tex(1, 0).color(f, 1, f, f).endVertex(); + buffer.pos(iconSize, -iconSize, 0).tex(1, 1).color(f, 1, f, f).endVertex(); + buffer.pos(-iconSize, -iconSize, 0).tex(0, 1).color(f, 1, f, f).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + + if(location == target){ + String label = location.pos.getX() + ", " + location.pos.getY() + ", " + location.pos.getZ(); + drawLabel(Minecraft.getMinecraft().fontRenderer, label, (float)(position.x * factor), + (float)(position.y * factor + iconSize*1.5f), (float)(position.z * factor), (float)displayDist * 0.2f, 0, + Minecraft.getMinecraft().getRenderManager().playerViewY, Minecraft.getMinecraft().getRenderManager().playerViewX); + } + } + + GlStateManager.disableBlend(); + GlStateManager.enableTexture2D(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + GlStateManager.disableRescaleNormal(); + GlStateManager.popMatrix(); + } + } + + // Copied from EntityRenderer#drawNameplate and tweaked a bit + private static void drawLabel(FontRenderer fontRendererIn, String str, float x, float y, float z, float scale, int verticalShift, float viewerYaw, float viewerPitch){ + + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y, z); + GlStateManager.glNormal3f(0.0F, 1.0F, 0.0F); + GlStateManager.rotate(-viewerYaw, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(viewerPitch, 1.0F, 0.0F, 0.0F); + GlStateManager.scale(-0.025F, -0.025F, 0.025F); + GlStateManager.scale(scale, scale, scale); + GlStateManager.disableLighting(); + GlStateManager.depthMask(false); + + GlStateManager.disableDepth(); + + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + int i = fontRendererIn.getStringWidth(str) / 2; + GlStateManager.disableTexture2D(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + bufferbuilder.begin(7, DefaultVertexFormats.POSITION_COLOR); + bufferbuilder.pos((double)(-i - 1), (double)(-1 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos((double)(-i - 1), (double)(8 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos((double)(i + 1), (double)(8 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos((double)(i + 1), (double)(-1 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + tessellator.draw(); + GlStateManager.enableTexture2D(); + + fontRendererIn.drawString(str, -fontRendererIn.getStringWidth(str) / 2, verticalShift, 0x86ff65); + GlStateManager.enableDepth(); + + GlStateManager.depthMask(true); + fontRendererIn.drawString(str, -fontRendererIn.getStringWidth(str) / 2, verticalShift, 0x86ff65); + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWings.java b/src/main/java/electroblob/wizardry/client/renderer/RenderWings.java new file mode 100644 index 00000000..5701d736 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderWings.java @@ -0,0 +1,113 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderWings { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/wing.png"); + + // No first person in here because you can never see the wings on your back! + + // Third person + @SubscribeEvent + public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){ + + EntityPlayer player = event.getEntityPlayer(); + + if(WizardryUtilities.isCasting(player, Spells.flight)){ + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + Vec3d delta = player.getPositionEyes(event.getPartialRenderTick()) + .subtract(Minecraft.getMinecraft().player.getPositionEyes(event.getPartialRenderTick())); + GlStateManager.translate(delta.x, delta.y, delta.z); + + // GlStateManager.rotate(-entityplayer.rotationYawHead, 0, 1, 0); + GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); + // GlStateManager.rotate(180, 1, 0, 0); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + GlStateManager.translate(0.1, 0.4, -0.15); + GlStateManager.rotate(20 + 20 * MathHelper.sin((player.ticksExisted + event.getPartialRenderTick()) * 0.3f), 0, 1, 0); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(0, 2, 0).tex(0, 0).endVertex(); + buffer.pos(2, 2, 0).tex(1, 0).endVertex(); + buffer.pos(2, 0, 0).tex(1, 1).endVertex(); + buffer.pos(0, 0, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(0, 2, 0).tex(0, 0).endVertex(); + buffer.pos(0, 0, 0).tex(0, 1).endVertex(); + buffer.pos(2, 0, 0).tex(1, 1).endVertex(); + buffer.pos(2, 2, 0).tex(1, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + + GlStateManager.pushMatrix(); + + GlStateManager.translate(-0.1, 0.4, -0.15); + GlStateManager.rotate(-200 - 20 * MathHelper.sin((player.ticksExisted + event.getPartialRenderTick()) * 0.3f), 0, 1, 0); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(0, 2, 0).tex(0, 0).endVertex(); + buffer.pos(2, 2, 0).tex(1, 0).endVertex(); + buffer.pos(2, 0, 0).tex(1, 1).endVertex(); + buffer.pos(0, 0, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + buffer.pos(0, 2, 0).tex(0, 0).endVertex(); + buffer.pos(0, 0, 0).tex(0, 1).endVertex(); + buffer.pos(2, 0, 0).tex(1, 1).endVertex(); + buffer.pos(2, 2, 0).tex(1, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + + GlStateManager.popMatrix(); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java b/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java index baa322ee..566bc5a7 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java @@ -7,10 +7,8 @@ import net.minecraft.client.renderer.entity.RenderBiped; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.layers.LayerBipedArmor; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderWizard extends RenderBiped { static final ResourceLocation[] textures = new ResourceLocation[6]; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java b/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java index da691189..d8cfe382 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java @@ -5,10 +5,8 @@ import net.minecraft.client.model.ModelBlaze; import net.minecraft.client.renderer.entity.RenderLiving; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; -@SideOnly(Side.CLIENT) +//@SideOnly(Side.CLIENT) public class RenderWraithMinion extends RenderLiving { private ResourceLocation texture = new ResourceLocation("textures/entity/blaze.png"); diff --git a/src/main/java/electroblob/wizardry/command/CommandCastSpell.java b/src/main/java/electroblob/wizardry/command/CommandCastSpell.java index 7dbbde14..c71e9e29 100644 --- a/src/main/java/electroblob/wizardry/command/CommandCastSpell.java +++ b/src/main/java/electroblob/wizardry/command/CommandCastSpell.java @@ -1,36 +1,41 @@ package electroblob.wizardry.command; -import java.util.List; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent.Source; import electroblob.wizardry.packet.PacketCastSpell; +import electroblob.wizardry.packet.PacketCastSpellAtPos; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; -import net.minecraft.command.CommandBase; -import net.minecraft.command.CommandException; -import net.minecraft.command.ICommandSender; -import net.minecraft.command.NumberInvalidException; -import net.minecraft.command.PlayerNotFoundException; -import net.minecraft.command.WrongUsageException; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.command.*; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.JsonToNBT; import net.minecraft.nbt.NBTException; import net.minecraft.server.MinecraftServer; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import java.util.List; + public class CommandCastSpell extends CommandBase { + /** The default number of ticks for which /cast will cast a continuous spell, if duration is not specified. */ + public static final int DEFAULT_CASTING_DURATION = 100; + /** The minimum number of seconds for which /cast may cast a continuous spell. */ + public static final int MIN_CASTING_DURATION = 0; + /** The maximum number of seconds for which /cast may cast a continuous spell. */ + public static final int MAX_CASTING_DURATION = 1000000; + @Override public String getName(){ return Wizardry.settings.castCommandName; @@ -74,6 +79,8 @@ public class CommandCastSpell extends CommandBase { int i = 0; EntityPlayerMP caster = null; + Vec3d origin = null; + EnumFacing direction = null; try{ caster = getCommandSenderAsPlayer(sender); @@ -85,12 +92,24 @@ public class CommandCastSpell extends CommandBase { Spell spell = Spell.get(arguments[i++]); if(spell == null){ - throw new NumberInvalidException("commands." + Wizardry.MODID + ":cast.not_found", new Object[]{arguments[i - 1]}); + throw new NumberInvalidException("commands." + Wizardry.MODID + ":cast.not_found", arguments[i - 1]); } boolean castAsOtherPlayer = false; - if(i < arguments.length){ + if(i + 3 < arguments.length){ + + Vec3d vec3d = sender.getPositionVector(); + CoordinateArg x = parseCoordinate(vec3d.x, arguments[i++], true); + CoordinateArg y = parseCoordinate(vec3d.y, arguments[i++], 0, 256, false); + CoordinateArg z = parseCoordinate(vec3d.z, arguments[i++], true); + + origin = new Vec3d(x.getResult(), y.getResult(), z.getResult()); + + direction = EnumFacing.byName(arguments[i++]); + if(direction == null) throw new NumberInvalidException("commands." + Wizardry.MODID + ":cast.invalid_direction", arguments[i - 1]); + + }else if(i < arguments.length){ try{ // If the second argument is a player and is not the player that gave the command, the spell is cast // as the given player rather than the command sender, and there is a different chat readout. @@ -107,8 +126,31 @@ public class CommandCastSpell extends CommandBase { // If, after this point, the player is still null, the sender must be a command block or the console and the // player must not have been specified, meaning an exception should be thrown. - if(caster == null) - throw new PlayerNotFoundException("You must specify which player you wish to perform this action on."); + if(caster == null && origin == null) + throw new PlayerNotFoundException("commands." + Wizardry.MODID + ":cast.origin_not_specified"); + + int duration = DEFAULT_CASTING_DURATION; + int seconds = duration/20; + + if(spell.isContinuous){ + + if(i >= arguments.length) throw new CommandException("commands." + Wizardry.MODID + ":cast.duration_not_specified"); + + try{ + seconds = parseInt(arguments[i++]); + }catch(NumberInvalidException e){ + // If no duration was found, assume it was unspecified + i--; + } + + if(seconds < MIN_CASTING_DURATION){ + throw new NumberInvalidException("commands.generic.num.tooSmall", seconds, MIN_CASTING_DURATION); + }else if(seconds > MAX_CASTING_DURATION){ + throw new NumberInvalidException("commands.generic.num.tooBig", seconds, MAX_CASTING_DURATION); + } + + duration = seconds * 20; + } SpellModifiers modifiers = new SpellModifiers(); @@ -136,64 +178,124 @@ public class CommandCastSpell extends CommandBase { // ===== Spell casting ===== - // If anything stops the spell working at this point, nothing else happens. - if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(caster, spell, modifiers, Source.COMMAND))){ - displayFailMessage(sender, spell); - return; - } + if(origin != null){ // Positional - WizardData data = WizardData.get((EntityPlayer)caster); + World world = sender.getEntityWorld(); - if(spell.isContinuous){ + // If anything stops the spell working at this point, nothing else happens. + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.COMMAND, spell, world, + origin.x, origin.y, origin.z, direction, modifiers))){ + if(server.sendCommandFeedback()) displayFailMessage(sender, spell); + return; + } - // Events for continuous spell casting via commands are dealt with in WizardData. + if(spell.isContinuous){ - if(data != null){ - if(data.isCasting()){ - data.stopCastingContinuousSpell(); - }else{ + if(spell.cast(world, origin.x, origin.y, origin.z, direction, 0, duration, modifiers)){ - data.startCastingContinuousSpell(spell, modifiers); + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, origin.x, origin.y, origin.z, direction, modifiers)); - if(castAsOtherPlayer){ - sender.sendMessage( - new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_remote_continuous", - spell.getNameForTranslationFormatted(), caster.getName())); - }else{ - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_continuous", - spell.getNameForTranslationFormatted())); + SpellEmitter.add(spell, world, origin.x, origin.y, origin.z, direction, duration, modifiers); + IMessage msg = new PacketCastSpellAtPos.Message(origin, direction, spell, modifiers, duration); + WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); + + if(server.sendCommandFeedback()){ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_position_continuous", + spell.getNameForTranslationFormatted(), origin.x, origin.y, origin.z, seconds)); } + + return; } + }else{ + + if(spell.cast(world, origin.x, origin.y, origin.z, direction, 0, -1, modifiers)){ + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, origin.x, origin.y, origin.z, direction, modifiers)); + + if(spell.requiresPacket()){ + // Sends a packet to all players in dimension to tell them to spawn particles. + // Only sent if the spell succeeded, because if the spell failed, you wouldn't + // need to spawn any particles! + IMessage msg = new PacketCastSpellAtPos.Message(origin, direction, spell, modifiers); + WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); + } + + if(server.sendCommandFeedback()){ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_position", + spell.getNameForTranslationFormatted(), origin.x, origin.y, origin.z)); + } + + return; + } + } + + }else{ // Player-based + + // If anything stops the spell working at this point, nothing else happens. + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.COMMAND, spell, caster, modifiers))){ + if(server.sendCommandFeedback()) displayFailMessage(sender, spell); return; } - }else{ + if(spell.isContinuous){ - if(spell.cast(caster.world, caster, EnumHand.MAIN_HAND, 0, modifiers)){ + WizardData data = WizardData.get(caster); - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(caster, spell, modifiers, Source.COMMAND)); + // Events/packets for continuous spell casting via commands are dealt with in WizardData. - if(spell.doesSpellRequirePacket()){ - // Sends a packet to all players in dimension to tell them to spawn particles. - // Only sent if the spell succeeded, because if the spell failed, you wouldn't - // need to spawn any particles! - IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), null, spell.id(), modifiers); - WizardryPacketHandler.net.sendToDimension(msg, caster.world.provider.getDimension()); + if(data != null){ + if(data.isCasting()){ + data.stopCastingContinuousSpell(); // TODO: Where should this go now? + }else{ + + data.startCastingContinuousSpell(spell, modifiers, duration); + + if(server.sendCommandFeedback()){ + if(castAsOtherPlayer){ + sender.sendMessage( + new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_remote_continuous", + spell.getNameForTranslationFormatted(), caster.getName(), seconds)); + }else{ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_continuous", + spell.getNameForTranslationFormatted(), seconds)); + } + } + } + + return; } - if(castAsOtherPlayer){ - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_remote", - spell.getNameForTranslationFormatted(), caster.getName())); - }else{ - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success", - spell.getNameForTranslationFormatted())); + }else{ + + if(spell.cast(caster.world, caster, EnumHand.MAIN_HAND, 0, modifiers)){ + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, caster, modifiers)); + + if(spell.requiresPacket()){ + // Sends a packet to all players in dimension to tell them to spawn particles. + // Only sent if the spell succeeded, because if the spell failed, you wouldn't + // need to spawn any particles! + IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), null, spell, modifiers); + WizardryPacketHandler.net.sendToDimension(msg, caster.world.provider.getDimension()); + } + + if(server.sendCommandFeedback()){ + if(castAsOtherPlayer){ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success_remote", + spell.getNameForTranslationFormatted(), caster.getName())); + }else{ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":cast.success", + spell.getNameForTranslationFormatted())); + } + } + + return; } - return; } } - displayFailMessage(sender, spell); + if(server.sendCommandFeedback()) displayFailMessage(sender, spell); } } diff --git a/src/main/java/electroblob/wizardry/command/CommandDiscoverSpell.java b/src/main/java/electroblob/wizardry/command/CommandDiscoverSpell.java index d97e6470..84733924 100644 --- a/src/main/java/electroblob/wizardry/command/CommandDiscoverSpell.java +++ b/src/main/java/electroblob/wizardry/command/CommandDiscoverSpell.java @@ -1,24 +1,19 @@ package electroblob.wizardry.command; -import java.util.List; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.event.DiscoverSpellEvent; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; -import net.minecraft.command.CommandBase; -import net.minecraft.command.CommandException; -import net.minecraft.command.ICommandSender; -import net.minecraft.command.NumberInvalidException; -import net.minecraft.command.PlayerNotFoundException; -import net.minecraft.command.WrongUsageException; +import net.minecraft.command.*; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraftforge.common.MinecraftForge; +import java.util.List; + public class CommandDiscoverSpell extends CommandBase { @Override @@ -92,7 +87,7 @@ public class CommandDiscoverSpell extends CommandBase { if(spell == null){ throw new NumberInvalidException("commands." + Wizardry.MODID + ":discoverspell.not_found", - new Object[]{arguments[i - 1]}); + arguments[i - 1]); } } @@ -110,32 +105,32 @@ public class CommandDiscoverSpell extends CommandBase { if(player == null) throw new PlayerNotFoundException("You must specify which player you wish to perform this action on."); - WizardData properties = WizardData.get(player); + WizardData data = WizardData.get(player); - if(properties != null){ + if(data != null){ if(clear){ - properties.spellsDiscovered.clear(); - sender.sendMessage( + data.spellsDiscovered.clear(); + if(server.sendCommandFeedback()) sender.sendMessage( new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.clear", player.getName())); }else if(all){ - properties.spellsDiscovered.addAll(Spell.getSpells(Spell.allSpells)); - sender.sendMessage( + data.spellsDiscovered.addAll(Spell.getSpells(Spell.allSpells)); + if(server.sendCommandFeedback()) sender.sendMessage( new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.all", player.getName())); }else{ - if(properties.hasSpellBeenDiscovered(spell)){ - properties.spellsDiscovered.remove(spell); - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.removespell", + if(data.hasSpellBeenDiscovered(spell)){ + data.spellsDiscovered.remove(spell); + if(server.sendCommandFeedback()) sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.removespell", spell.getNameForTranslationFormatted(), player.getName())); }else{ if(!MinecraftForge.EVENT_BUS .post(new DiscoverSpellEvent(player, spell, DiscoverSpellEvent.Source.COMMAND))){ - properties.discoverSpell(spell); - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.addspell", + data.discoverSpell(spell); + if(server.sendCommandFeedback()) sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.addspell", spell.getNameForTranslationFormatted(), player.getName())); } } } - properties.sync(); + data.sync(); } } } diff --git a/src/main/java/electroblob/wizardry/command/CommandSetAlly.java b/src/main/java/electroblob/wizardry/command/CommandSetAlly.java index 2ca42dfa..2ffcc375 100644 --- a/src/main/java/electroblob/wizardry/command/CommandSetAlly.java +++ b/src/main/java/electroblob/wizardry/command/CommandSetAlly.java @@ -1,16 +1,9 @@ package electroblob.wizardry.command; -import java.util.List; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.command.CommandBase; -import net.minecraft.command.CommandException; -import net.minecraft.command.ICommandSender; -import net.minecraft.command.NumberInvalidException; -import net.minecraft.command.PlayerNotFoundException; -import net.minecraft.command.WrongUsageException; +import net.minecraft.command.*; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; @@ -18,6 +11,8 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; +import java.util.List; + public class CommandSetAlly extends CommandBase { @Override @@ -85,10 +80,12 @@ public class CommandSetAlly extends CommandBase { if(allyOf != sender && sender instanceof EntityPlayer && !WizardryUtilities.isPlayerOp((EntityPlayer)sender, server)){ // Displays a chat message if a non-op tries to modify another player's allies. - TextComponentTranslation TextComponentTranslation2 = new TextComponentTranslation( - "commands." + Wizardry.MODID + ":ally.permission"); - TextComponentTranslation2.getStyle().setColor(TextFormatting.RED); - allyOf.sendMessage(TextComponentTranslation2); + if(server.sendCommandFeedback()){ + TextComponentTranslation TextComponentTranslation2 = new TextComponentTranslation( + "commands." + Wizardry.MODID + ":ally.permission"); + TextComponentTranslation2.getStyle().setColor(TextFormatting.RED); + allyOf.sendMessage(TextComponentTranslation2); + } return; } @@ -102,15 +99,17 @@ public class CommandSetAlly extends CommandBase { if(allyOf == ally) throw new NumberInvalidException("commands." + Wizardry.MODID + ":ally.self"); - if(WizardData.get(allyOf) != null){ - String string = WizardData.get(allyOf).toggleAlly(ally) ? "add" : "remove"; - if(executeAsOtherPlayer){ - sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":ally." + string + "ally", - ally.getName(), allyOf.getName())); - // In this case, the player whose allies have been modified is also notified. - allyOf.sendMessage(new TextComponentTranslation("item.wand." + string + "ally", ally.getName())); - }else{ - sender.sendMessage(new TextComponentTranslation("item.wand." + string + "ally", ally.getName())); + if(server.sendCommandFeedback()){ + if(WizardData.get(allyOf) != null){ + String string = WizardData.get(allyOf).toggleAlly(ally) ? "add" : "remove"; + if(executeAsOtherPlayer){ + sender.sendMessage(new TextComponentTranslation("commands." + Wizardry.MODID + ":ally." + string + "ally", + ally.getName(), allyOf.getName())); + // In this case, the player whose allies have been modified is also notified. + allyOf.sendMessage(new TextComponentTranslation("item.wand." + string + "ally", ally.getName())); + }else{ + sender.sendMessage(new TextComponentTranslation("item.wand." + string + "ally", ally.getName())); + } } } diff --git a/src/main/java/electroblob/wizardry/command/CommandViewAllies.java b/src/main/java/electroblob/wizardry/command/CommandViewAllies.java index 3bb82a2b..dde5e46d 100644 --- a/src/main/java/electroblob/wizardry/command/CommandViewAllies.java +++ b/src/main/java/electroblob/wizardry/command/CommandViewAllies.java @@ -1,12 +1,8 @@ package electroblob.wizardry.command; -import java.util.List; -import java.util.Set; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.resources.I18n; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; @@ -18,6 +14,9 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; +import java.util.List; +import java.util.Set; + public class CommandViewAllies extends CommandBase { @Override @@ -75,10 +74,12 @@ public class CommandViewAllies extends CommandBase { if(player != sender && sender instanceof EntityPlayer && !WizardryUtilities.isPlayerOp((EntityPlayer)sender, server)){ // Displays a chat message if a non-op tries to view another player's allies. - TextComponentTranslation TextComponentTranslation2 = new TextComponentTranslation( - "commands." + Wizardry.MODID + ":allies.permission"); - TextComponentTranslation2.getStyle().setColor(TextFormatting.RED); - player.sendMessage(TextComponentTranslation2); + if(server.sendCommandFeedback()){ + TextComponentTranslation TextComponentTranslation2 = new TextComponentTranslation( + "commands." + Wizardry.MODID + ":allies.permission"); + TextComponentTranslation2.getStyle().setColor(TextFormatting.RED); + player.sendMessage(TextComponentTranslation2); + } return; } @@ -101,6 +102,7 @@ public class CommandViewAllies extends CommandBase { playerList = new TextComponentTranslation("commands." + Wizardry.MODID + ":allies.none"); } + // Ignore sendCommandFeedback here since that's the entire point of this command if(executeAsOtherPlayer){ sender.sendMessage( new TextComponentTranslation("commands." + Wizardry.MODID + ":allies.list_other", player.getName(), playerList)); diff --git a/src/main/java/electroblob/wizardry/command/SpellEmitter.java b/src/main/java/electroblob/wizardry/command/SpellEmitter.java new file mode 100644 index 00000000..7a09e6b0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/command/SpellEmitter.java @@ -0,0 +1,177 @@ +package electroblob.wizardry.command; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.SpellEmitterData; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import io.netty.buffer.ByteBuf; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ITickable; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; + +/** + * A {@code SpellEmitter} represents a continuous spell being cast from a position via commands. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +public class SpellEmitter implements ITickable { + + protected final Spell spell; + protected World world; + protected final double x, y, z; + protected final EnumFacing direction; + protected final int duration; + protected final SpellModifiers modifiers; + + protected int castingTick = 0; + protected boolean needsRemoving = false; + + protected SpellEmitter(Spell spell, World world, double x, double y, double z, EnumFacing direction, int duration, SpellModifiers modifiers){ + this.spell = spell; + this.world = world; + this.duration = duration; + this.x = x; + this.y = y; + this.z = z; + this.direction = direction; + this.modifiers = modifiers; + } + + /** Marks this spell emitter to be removed next tick. */ + protected void markForRemoval(){ + this.needsRemoving = true; + } + + /** Returns whether this spell emitter is marked for removal. */ + public boolean needsRemoving(){ + return needsRemoving; + } + + /** Returns the {@link SpellCastEvent.Source} that should be used for events fired by this spell emitter. */ + protected SpellCastEvent.Source getSource(){ + return SpellCastEvent.Source.COMMAND; + } + + /** Sets this spell emitter's world. This should only be used on the client side when the world has not yet been + * set, otherwise the world will not be changed and a warning will be printed to the console. */ + public void setWorld(World world){ + if(world.isRemote && this.world == null){ + this.world = world; + }else{ + Wizardry.logger.warn("Tried to change the world for a spell emitter, this shouldn't happen!"); + } + } + + @Override + public void update(){ + + if(castingTick < duration){ + + if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(getSource(), spell, world, x, y, z, direction, modifiers, castingTick))){ + + if(spell.cast(world, x, y, z, direction, castingTick, duration, modifiers)){ + if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(getSource(), spell, world, x, y, z, direction, modifiers)); + castingTick++; + return; + } + } + } + // If the time ran out or the spell failed, interrupt spell casting + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Finish(getSource(), spell, world, x, y, z, direction, modifiers, castingTick)); + spell.finishCasting(world, null, x, y, z, direction, duration, modifiers); + markForRemoval(); + } + + /** Writes this {@code SpellEmitter} to the given ByteBuf. */ + public void write(ByteBuf buf){ + buf.writeInt(spell.networkID()); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeInt(direction.getIndex()); + // This is sent through as the duration, meaning castingTick always starts at zero client-side, which is + // important for sounds to work correctly. As a consequence, the client's castingTick will be different to the + // server value if the player changes dimension or re-logs. However, since this is pretty uncommon anyway I + // think it's an ok compromise. + buf.writeInt(duration - castingTick); + modifiers.write(buf); + } + + /** Reads a {@code SpellEmitter} from the given ByteBuf and returns it. */ + public static SpellEmitter read(ByteBuf buf){ + + Spell spell = Spell.byNetworkID(buf.readInt()); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + EnumFacing direction = EnumFacing.byIndex(buf.readInt()); + int duration = buf.readInt(); + SpellModifiers modifiers = new SpellModifiers(); + modifiers.read(buf); + + return new SpellEmitter(spell, null, x, y, z, direction, duration, modifiers); + } + + // INBTSerializable is annoying, it doesn't allow you to have final fields + + /** Returns a new {@link NBTTagCompound} representing this {@code SpellEmitter}. */ + public NBTTagCompound toNBT(){ + + NBTTagCompound nbt = new NBTTagCompound(); + + nbt.setInteger("spell", spell.metadata()); + nbt.setDouble("x", x); + nbt.setDouble("y", y); + nbt.setDouble("z", z); + nbt.setInteger("direction", direction.getIndex()); + nbt.setInteger("duration", duration); + nbt.setTag("modifiers", modifiers.toNBT()); + nbt.setInteger("castingTick", castingTick); + + return nbt; + } + + /** Creates a new {@code SpellEmitter} from the given {@link NBTTagCompound} and returns it. */ + public static SpellEmitter fromNBT(World world, NBTTagCompound nbt){ + + Spell spell = Spell.byMetadata(nbt.getInteger("spell")); + double x = nbt.getDouble("x"); + double y = nbt.getDouble("y"); + double z = nbt.getDouble("z"); + EnumFacing direction = EnumFacing.byIndex(nbt.getInteger("direction")); + int duration = nbt.getInteger("duration"); + SpellModifiers modifiers = SpellModifiers.fromNBT(nbt.getCompoundTag("modifiers")); + int castingTick = nbt.getInteger("castingTick"); + + SpellEmitter emitter = new SpellEmitter(spell, world, x, y, z, direction, duration, modifiers); + emitter.castingTick = castingTick; + return emitter; + } + + /** + * Creates a new {@code SpellEmitter} and adds it to the list of active emitters in {@link SpellEmitterData}. + * This method does not perform any syncing. + * + * @param spell The spell to be cast + * @param world The world in which to cast the spell + * @param x The x-coordinate of the spell origin + * @param y The y-coordinate of the spell origin + * @param z The z-coordinate of the spell origin + * @param direction The direction to cast the spell in + * @param duration The number of ticks to cast the spell for + * @param modifiers The {@link SpellModifiers} for the spell + */ + public static void add(Spell spell, World world, double x, double y, double z, EnumFacing direction, int duration, SpellModifiers modifiers){ + if(spell.isContinuous){ + if(duration <= 0) Wizardry.logger.warn("Adding a spell emitter with negative or zero duration!"); + SpellEmitterData.get(world).add(new SpellEmitter(spell, world, x, y, z, direction, duration, modifiers)); + }else{ + Wizardry.logger.warn("Tried to add a non-continuous spell emitter for spell {}", spell.getRegistryName()); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/constants/Constants.java b/src/main/java/electroblob/wizardry/constants/Constants.java index 28bba9a9..0316b4e3 100644 --- a/src/main/java/electroblob/wizardry/constants/Constants.java +++ b/src/main/java/electroblob/wizardry/constants/Constants.java @@ -5,23 +5,25 @@ import electroblob.wizardry.WizardryEventHandler; /** Stores various global constants used in Wizardry. */ public final class Constants { + /** The amount of mana a crystal shard is worth */ + // 100 doesn't divide nicely by 9 so we're calling this 10. I guess you lose a little bit by smashing a crystal. + public static final int MANA_PER_SHARD = 10; /** The amount of mana each magic crystal is worth */ public static final int MANA_PER_CRYSTAL = 100; - /** The amount of mana each mana flask can hold */ - public static final int MANA_PER_FLASK = 700; + /** The amount of mana a grand magic crystal is worth */ + public static final int GRAND_CRYSTAL_MANA = 400; /** The maximum number of one type of wand upgrade which can be applied to a wand. */ public static final int UPGRADE_STACK_LIMIT = 3; /** The fraction by which cooldowns are reduced for each level of cooldown upgrade. */ public static final float COOLDOWN_REDUCTION_PER_LEVEL = 0.15f; /** The fraction by which maximum charge is increased for each level of storage upgrade. */ public static final float STORAGE_INCREASE_PER_LEVEL = 0.15f; - /** The fraction by which damage is increased for each tier of matching wand. */ - public static final float DAMAGE_INCREASE_PER_TIER = 0.15f; - /** - * The fraction by which costs are reduced for each piece of matching armour. Note that changing this value will not - * affect continuous spells, since they are handled differently. - */ - public static final float COST_REDUCTION_PER_ARMOUR = 0.2f; + /** The fraction by which potency is increased for each tier of matching wand. */ + public static final float POTENCY_INCREASE_PER_TIER = 0.15f; + /** The fraction by which costs are reduced for each piece of matching armour. */ + public static final float COST_REDUCTION_PER_ARMOUR = 0.15f; + /** The extra fraction by which costs are reduced for a full set of elemental armour. */ + public static final float FULL_ARMOUR_SET_BONUS = 0.2f; /** The fraction by which spell duration is increased for each level of duration upgrade. */ public static final float DURATION_INCREASE_PER_LEVEL = 0.25f; /** The fraction by which spell range is increased for each level of range upgrade. */ @@ -32,7 +34,7 @@ public final class Constants { public static final double FROST_SLOWNESS_PER_LEVEL = 0.5; /** The fraction by which movement speed is reduced per level of decay effect. */ public static final double DECAY_SLOWNESS_PER_LEVEL = 0.2; - /** The fraction by which dig speed is reduced per level of frost effect. */ + /** The fraction by which dig speed is reduced per level of frostbite effect. */ public static final float FROST_FATIGUE_PER_LEVEL = 0.45f; /** The number of ticks between each mana increase for wands with the condenser upgrade. */ public static final int CONDENSER_TICK_INTERVAL = 50; @@ -40,11 +42,13 @@ public final class Constants { * The amount of mana given for a kill for each level of siphon upgrade. A random amount from 0 to this number - 1 * is also added. See {@link WizardryEventHandler#onLivingDeathEvent} for more details. */ - public static final int SIPHON_MANA_PER_LEVEL = 3; + public static final int SIPHON_MANA_PER_LEVEL = 5; /** * The number of ticks between the spawning of patches of decay when an entity has the decay effect. Note that decay * won't spawn again if something is already standing in it. */ public static final int DECAY_SPREAD_INTERVAL = 8; + /** The fraction by which potency is increased per level of the empowerment effect. */ + public static final float EMPOWERMENT_POTENCY_PER_LEVEL = 0.25f; } diff --git a/src/main/java/electroblob/wizardry/constants/Element.java b/src/main/java/electroblob/wizardry/constants/Element.java index dd357774..7b433693 100644 --- a/src/main/java/electroblob/wizardry/constants/Element.java +++ b/src/main/java/electroblob/wizardry/constants/Element.java @@ -1,7 +1,7 @@ package electroblob.wizardry.constants; import electroblob.wizardry.Wizardry; -import net.minecraft.client.resources.I18n; +import net.minecraft.util.IStringSerializable; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.Style; @@ -10,19 +10,18 @@ import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -public enum Element { +public enum Element implements IStringSerializable { - /** - * The 'default' element, with {@link electroblob.wizardry.spell.MagicMissile MagicMissile} being its only spell. - */ - MAGIC(new Style().setColor(TextFormatting.GRAY), "simple", Wizardry.MODID), - FIRE(new Style().setColor(TextFormatting.DARK_RED), "fire", Wizardry.MODID), - ICE(new Style().setColor(TextFormatting.AQUA), "ice", Wizardry.MODID), - LIGHTNING(new Style().setColor(TextFormatting.DARK_AQUA), "lightning", Wizardry.MODID), - NECROMANCY(new Style().setColor(TextFormatting.DARK_PURPLE), "necromancy", Wizardry.MODID), - EARTH(new Style().setColor(TextFormatting.DARK_GREEN), "earth", Wizardry.MODID), - SORCERY(new Style().setColor(TextFormatting.GREEN), "sorcery", Wizardry.MODID), - HEALING(new Style().setColor(TextFormatting.YELLOW), "healing", Wizardry.MODID); + /** The 'default' element, with {@link electroblob.wizardry.registry.Spells#magic_missile magic missile} being its + * only spell. */ + MAGIC(new Style().setColor(TextFormatting.GRAY), "magic"), + FIRE(new Style().setColor(TextFormatting.DARK_RED), "fire"), + ICE(new Style().setColor(TextFormatting.AQUA), "ice"), + LIGHTNING(new Style().setColor(TextFormatting.DARK_AQUA), "lightning"), + NECROMANCY(new Style().setColor(TextFormatting.DARK_PURPLE), "necromancy"), + EARTH(new Style().setColor(TextFormatting.DARK_GREEN), "earth"), + SORCERY(new Style().setColor(TextFormatting.GREEN), "sorcery"), + HEALING(new Style().setColor(TextFormatting.YELLOW), "healing"); /** Display colour for this element */ private final Style colour; @@ -31,16 +30,31 @@ public enum Element { /** The {@link ResourceLocation} for this element's 8x8 icon (displayed in the arcane workbench GUI) */ private final ResourceLocation icon; - private Element(Style colour, String name, String modid){ + Element(Style colour, String name){ + this(colour, name, Wizardry.MODID); + } + + Element(Style colour, String name, String modid){ this.colour = colour; this.unlocalisedName = name; this.icon = new ResourceLocation(modid, "textures/gui/element_icon_" + unlocalisedName + ".png"); } + /** Returns the element with the given name, or throws an {@link java.lang.IllegalArgumentException} if no such + * element exists. */ + public static Element fromName(String name){ + + for(Element element : values()){ + if(element.unlocalisedName.equals(name)) return element; + } + + throw new IllegalArgumentException("No such element with unlocalised name: " + name); + } + /** Returns the translated display name of this element, without formatting. */ @SideOnly(Side.CLIENT) public String getDisplayName(){ - return I18n.format("element." + getUnlocalisedName()); + return net.minecraft.client.resources.I18n.format("element." + getName()); } /** Returns the {@link Style} object representing the colour of this element. */ @@ -55,11 +69,12 @@ public enum Element { /** Returns the translated display name for wizards of this element, shown in the trading GUI. */ public ITextComponent getWizardName(){ - return new TextComponentTranslation("element." + getUnlocalisedName() + ".wizard"); + return new TextComponentTranslation("element." + getName() + ".wizard"); } - /** Returns this element's unlocalised name. */ - public String getUnlocalisedName(){ + /** Returns this element's unlocalised name. Also used as the serialised string in block properties. */ + @Override + public String getName(){ return unlocalisedName; } diff --git a/src/main/java/electroblob/wizardry/constants/SpellType.java b/src/main/java/electroblob/wizardry/constants/SpellType.java index 5f58dede..afefec7f 100644 --- a/src/main/java/electroblob/wizardry/constants/SpellType.java +++ b/src/main/java/electroblob/wizardry/constants/SpellType.java @@ -1,12 +1,18 @@ package electroblob.wizardry.constants; -import net.minecraft.client.resources.I18n; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; public enum SpellType { - ATTACK("attack"), DEFENCE("defence"), UTILITY("utility"), MINION("minion"); + ATTACK("attack"), + DEFENCE("defence"), + UTILITY("utility"), + MINION("minion"), + BUFF("buff"), + CONSTRUCT("construct"), + PROJECTILE("projectile"), + ALTERATION("alteration"); private final String unlocalisedName; @@ -14,8 +20,23 @@ public enum SpellType { this.unlocalisedName = name; } + /** Returns the spell type with the given name, or throws an {@link java.lang.IllegalArgumentException} if no such + * spell type exists. */ + public static SpellType fromName(String name){ + + for(SpellType type : values()){ + if(type.unlocalisedName.equals(name)) return type; + } + + throw new IllegalArgumentException("No such spell type with unlocalised name: " + name); + } + + public String getUnlocalisedName(){ + return unlocalisedName; + } + @SideOnly(Side.CLIENT) public String getDisplayName(){ - return I18n.format("spelltype." + unlocalisedName); + return net.minecraft.client.resources.I18n.format("spelltype." + unlocalisedName); } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/constants/Tier.java b/src/main/java/electroblob/wizardry/constants/Tier.java index 6c7d8958..5cf6bdd0 100644 --- a/src/main/java/electroblob/wizardry/constants/Tier.java +++ b/src/main/java/electroblob/wizardry/constants/Tier.java @@ -1,19 +1,20 @@ package electroblob.wizardry.constants; -import java.util.Random; - -import net.minecraft.client.resources.I18n; +import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.Random; + public enum Tier { - BASIC(700, 3, 12, new Style().setColor(TextFormatting.WHITE), "basic"), APPRENTICE(1000, 4, 5, - new Style().setColor(TextFormatting.AQUA), "apprentice"), ADVANCED(1500, 5, 2, - new Style().setColor(TextFormatting.DARK_BLUE), - "advanced"), MASTER(2500, 6, 1, new Style().setColor(TextFormatting.DARK_PURPLE), "master"); + NOVICE(700, 3, 12, 0, new Style().setColor(TextFormatting.WHITE), "novice"), + APPRENTICE(1000, 5, 5, 6000, new Style().setColor(TextFormatting.AQUA), "apprentice"), + ADVANCED(1500, 7, 2, 9000, new Style().setColor(TextFormatting.DARK_BLUE), "advanced"), + MASTER(2500, 9, 1, 15000, new Style().setColor(TextFormatting.DARK_PURPLE), "master"); /** Maximum mana a wand of this tier can store. */ public final int maxCharge; @@ -23,29 +24,59 @@ public enum Tier { public final int upgradeLimit; /** The weight given to this tier in the standard weighting. */ public final int weight; + /** The progression required for a wand to be upgraded to this tier. */ + public final int progression; /** The colour of text associated with this tier. */ // Changed to a Style object for consistency. private final Style colour; private final String unlocalisedName; - private Tier(int maxCharge, int upgradeLimit, int weight, Style colour, String name){ + Tier(int maxCharge, int upgradeLimit, int weight, int progression, Style colour, String name){ this.maxCharge = maxCharge; this.level = ordinal(); this.upgradeLimit = upgradeLimit; this.weight = weight; + this.progression = progression; this.colour = colour; this.unlocalisedName = name; } + /** Returns the tier with the given name, or throws an {@link java.lang.IllegalArgumentException} if no such + * tier exists. */ + public static Tier fromName(String name){ + + for(Tier tier : values()){ + if(tier.unlocalisedName.equals(name)) return tier; + } + + throw new IllegalArgumentException("No such tier with unlocalised name: " + name); + } + @SideOnly(Side.CLIENT) public String getDisplayName(){ - return I18n.format("tier." + unlocalisedName); + return net.minecraft.client.resources.I18n.format("tier." + unlocalisedName); + } + + /** + * Returns a {@code TextComponentTranslation} which will be translated to the display name of the tier, without + * formatting (i.e. not coloured). + */ + public TextComponentTranslation getNameForTranslation(){ + return new TextComponentTranslation("tier." + unlocalisedName); } @SideOnly(Side.CLIENT) public String getDisplayNameWithFormatting(){ - return this.getFormattingCode() + I18n.format("tier." + unlocalisedName); + return this.getFormattingCode() + net.minecraft.client.resources.I18n.format("tier." + unlocalisedName); + } + + /** + * Returns a {@code TextComponentTranslation} which will be translated to the display name of the tier, with + * formatting (i.e. coloured). + */ + public ITextComponent getNameForTranslationFormatted(){ + return new TextComponentTranslation("tier." + unlocalisedName).setStyle(this.colour); } public String getUnlocalisedName(){ @@ -68,8 +99,7 @@ public enum Tier { int totalWeight = 0; - for(Tier tier : tiers) - totalWeight += tier.weight; + for(Tier tier : tiers) totalWeight += tier.weight; int randomiser = random.nextInt(totalWeight); int cumulativeWeight = 0; diff --git a/src/main/java/electroblob/wizardry/data/BlockCastingData.java b/src/main/java/electroblob/wizardry/data/BlockCastingData.java new file mode 100644 index 00000000..a02d2de7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/BlockCastingData.java @@ -0,0 +1,183 @@ +package electroblob.wizardry.data; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.packet.PacketDispenserCastSpell; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.None; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +/** + * Base class for {@link DispenserCastingData}. Originally this was written because command blocks had a similar system, + * but that was later removed in favour of spell emitters - however, this class has been kept so that others can use it + * for different spellcasting blocks if they wish. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +public abstract class BlockCastingData implements INBTSerializable { + + /** The tile entity this BlockCastingData instance belongs to. */ + protected final T tileEntity; + + /** The continuous spell this tile entity is currently casting, or the {@link None} spell if it is not casting. */ + protected Spell spell; + /** The coordinates of the current continuous spell's origin. */ + protected double x, y, z; + /** The time for which this tile entity has been casting a continuous spell. Increments by 1 each tick. */ + protected int castingTick; + /** SpellModifiers object for the current continuous spell. */ + protected SpellModifiers modifiers; + + public BlockCastingData(T tileEntity){ + this.tileEntity = tileEntity; + this.spell = Spells.none; + this.modifiers = new SpellModifiers(); + this.castingTick = 0; + } + + /** Returns whether this tile entity is currently casting a continuous spell. */ + public boolean isCasting(){ + return this.spell != null && this.spell != Spells.none; + } + + /** Returns the continuous spell this tile entity is currently casting, or the {@link None} spell if it isn't + * casting anything. */ + public Spell currentlyCasting(){ + return spell; + } + + /** Starts casting the given continuous spell from this tile entity. */ + protected void startCasting(Spell spell, double x, double y, double z, SpellModifiers modifiers){ + + if(!spell.isContinuous){ + Wizardry.logger.warn("Tried to start casting a continuous spell from a tile entity, but the given spell was not continuous!"); + return; + } + + this.spell = spell; + this.x = x; + this.y = y; + this.z = z; + this.castingTick = 0; + this.modifiers = modifiers; + } + + /** Stops casting the current spell. */ + protected void stopCasting(){ + this.spell = Spells.none; + this.castingTick = 0; + this.modifiers.reset(); + } + + /** Stops casting the current spell and sends a packet to clients to update them. If called client-side, this just + * delegates to {@link BlockCastingData#stopCasting()}. */ + protected void stopCastingAndNotify(){ + + stopCasting(); + + if(!tileEntity.getWorld().isRemote){ + IMessage msg = new PacketDispenserCastSpell.Message(x, y, z, getDirection(), tileEntity.getPos(), spell, 0, modifiers); + WizardryPacketHandler.net.sendToDimension(msg, tileEntity.getWorld().provider.getDimension()); + } + } + + /** Called once per tick to update the block casting data. This is not called automatically, subclasses must + * do so using their own tick event handlers. */ + protected void update(){ + + if(this.tileEntity.isInvalid()){ + return; + } + + if(this.isCasting() && this.spell.isContinuous){ + + // If the dispenser has stopped receiving power, the spell stops immediately. + if(!shouldContinueCasting()){ + this.stopCasting(); // This seems to work fine on both sides, so no point sending a packet + return; + } + + EnumFacing direction = getDirection(); + + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(getSource(), spell, tileEntity.getWorld(), + x, y, z, direction, modifiers, castingTick))){ + // When the event is canceled client-side, this will stop the spell on the client only, as specified in + // the javadoc for SpellCastEvent.Tick. + this.stopCastingAndNotify(); + return; + } + + this.spell.cast(tileEntity.getWorld(), x, y, z, direction, castingTick, -1, modifiers); + + castingTick++; + + }else{ + this.castingTick = 0; + } + } + + /** Returns the direction to cast the current spell in. */ + protected abstract EnumFacing getDirection(); + + /** Returns the source of spells cast from this block. */ + protected abstract SpellCastEvent.Source getSource(); + + /** Called each tick during continuous spell casting to determine if the spell should continue or stop. */ + protected abstract boolean shouldContinueCasting(); + + @Override + public NBTTagCompound serializeNBT(){ + + NBTTagCompound nbt = new NBTTagCompound(); + + nbt.setInteger("spell", spell.metadata()); + nbt.setInteger("castingTick", castingTick); + nbt.setTag("modifiers", modifiers.toNBT()); + + return nbt; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt){ + + if(nbt != null){ + + this.spell = Spell.byMetadata(nbt.getInteger("spell")); + this.castingTick = nbt.getInteger("castingTick"); + this.modifiers = SpellModifiers.fromNBT(nbt.getCompoundTag("modifiers")); + } + } + + // The two methods below broke EVERYTHING, somehow they made the server think it was the client... + +// // Only fired server-side +// @SubscribeEvent +// public static void onWorldTickEvent(TickEvent.WorldTickEvent event){ +// +// if(!event.world.isRemote && event.phase == TickEvent.Phase.END){ +// // This will fire once for each dimension, but since we want dispenser-casting to work in all dimensions, +// // this is correct (the loaded tile entity list will of course be different in each case. +// this.update(); +// } +// } +// +// // Only called client-side +// @SubscribeEvent +// public static void onClientTickEvent(TickEvent.ClientTickEvent event){ +// World world = net.minecraft.client.Minecraft.getMinecraft().world; +// if(event.phase == TickEvent.Phase.END && !net.minecraft.client.Minecraft.getMinecraft().isGamePaused() +// && world != null){ +// this.update(); +// } +// } + +} diff --git a/src/main/java/electroblob/wizardry/data/DispenserCastingData.java b/src/main/java/electroblob/wizardry/data/DispenserCastingData.java new file mode 100644 index 00000000..28d0fe56 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/DispenserCastingData.java @@ -0,0 +1,219 @@ +package electroblob.wizardry.data; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.event.SpellCastEvent.Source; +import electroblob.wizardry.item.ItemScroll; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.block.BlockDispenser; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.Capability.IStorage; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Internal capability for attaching data to dispensers. The sole purpose of this class is to keep track of continuous + * spell casting for dispensers. + *

    + * Forge seems to have separate classes to hold the Capability<...> instance ('key') and methods for getting the + * capability, but in my opinion there are already too many classes to deal with, so I'm not adding any more than are + * necessary, meaning those constants and values are kept here instead. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +@Mod.EventBusSubscriber +public class DispenserCastingData extends BlockCastingData { + + /** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */ + // This annotation does some crazy Forge magic behind the scenes and assigns this field a value. + @CapabilityInject(DispenserCastingData.class) + private static final Capability DISPENSER_CASTING_CAPABILITY = null; + + /** The time for which this dispenser will continue casting a continuous spell. When castingTick exceeds this value, + * the dispenser will either stop casting or, if it contains more of the same type of scroll, continue casting and + * increase this value by the duration that the spell should be cast for. */ + private int duration; + + public DispenserCastingData(){ + this(null); // Nullary constructor for the registration method factory parameter + } + + public DispenserCastingData(TileEntityDispenser dispenser){ + super(dispenser); + } + + /** Starts casting the given continuous spell from this dispenser. */ + public void startCasting(Spell spell, double x, double y, double z, int duration, SpellModifiers modifiers){ + startCasting(spell, x, y, z, modifiers); + this.castingTick = 1; // 1 because we already cast it once in BehaviourSpellDispense + this.duration = duration; + } + + @Override + public void stopCasting(){ + super.stopCasting(); + } + + @Override + protected Source getSource(){ + return Source.DISPENSER; + } + + @Override + protected EnumFacing getDirection(){ + return tileEntity.getWorld().getBlockState(tileEntity.getPos()).getValue(BlockDispenser.FACING); + } + + @Override + protected boolean shouldContinueCasting(){ + return tileEntity.getWorld().isBlockPowered(tileEntity.getPos()); + } + + @Override + public void update(){ + + super.update(); + + // Check whether enough scrolls are left + if(this.isCasting() && this.spell.isContinuous){ + + if(castingTick > duration && !tileEntity.getWorld().isRemote){ + + if(findNewScroll()){ + duration += ItemScroll.CASTING_TIME; // Best way to do it for now. + }else{ + this.stopCastingAndNotify(); + } + } + } + } + + /** Searches through the dispenser's inventory for a new stack of scrolls of the same spell that is currently being + * cast and returns true if at least one such stack is found. Also consumes one scroll if a stack is found; if more + * than one applicable stack is found then one will be chosen at random. */ + private boolean findNewScroll(){ + + if(spell == Spells.none) return false; + + List slots = new ArrayList(); + + for(int i = 0; i < tileEntity.getSizeInventory(); i++){ + ItemStack stack = tileEntity.getStackInSlot(i); + if(stack.getItem() instanceof ItemScroll && stack.getMetadata() == spell.metadata()) slots.add(i); + } + + if(slots.isEmpty()) return false; // If no stack was found that matched the current spell + + tileEntity.decrStackSize(slots.get(tileEntity.getWorld().rand.nextInt(slots.size())), 1); // Consumes 1 scroll + return true; + } + + /** Returns the DispenserCastingData instance for the specified dispenser. */ + public static DispenserCastingData get(TileEntityDispenser dispenser){ + return dispenser.getCapability(DISPENSER_CASTING_CAPABILITY, null); + } + + /** Called from preInit in the main mod class to register the DispenserCastingData capability. */ + public static void register(){ + + CapabilityManager.INSTANCE.register(DispenserCastingData.class, new IStorage(){ + + @Override + public NBTBase writeNBT(Capability capability, DispenserCastingData instance, EnumFacing side){ + return null; + } + + @Override + public void readNBT(Capability capability, DispenserCastingData instance, EnumFacing side, NBTBase nbt){} + + }, DispenserCastingData::new); + } + + // Event handlers + + @SubscribeEvent + // The type parameter here has to be SoundLoopSpellDispenser, not TileEntityDispenser, or the event won't get fired. + public static void onCapabilityLoad(AttachCapabilitiesEvent event){ + + if(event.getObject() instanceof TileEntityDispenser) + event.addCapability(new ResourceLocation(Wizardry.MODID, "casting_data"), + new DispenserCastingData.Provider((TileEntityDispenser)event.getObject())); + } + + // Only fired server-side + @SubscribeEvent + public static void onWorldTickEvent(TickEvent.WorldTickEvent event){ + + if(event.phase == TickEvent.Phase.END){ + + // This will fire once for each dimension, but since we want dispenser-casting to work in all dimensions, + // this is correct (the loaded tile entity list will of course be different in each case. + + for(TileEntity tileentity : event.world.loadedTileEntityList){ + if(tileentity instanceof TileEntityDispenser){ + if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){ + DispenserCastingData.get((TileEntityDispenser)tileentity).update(); + } + } + } + } + } + + /** + * This is a nested class for a few reasons: firstly, it makes sense because instances of this and + * DispenserCastingData go hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most + * importantly) it allows me to access DISPENSER_CASTING_CAPABILITY while keeping it private. + */ + public static class Provider implements ICapabilitySerializable { + + private final DispenserCastingData data; + + public Provider(TileEntityDispenser dispenser){ + data = new DispenserCastingData(dispenser); + } + + @Override + public boolean hasCapability(Capability capability, EnumFacing facing){ + return capability == DISPENSER_CASTING_CAPABILITY; + } + + @Override + public T getCapability(Capability capability, EnumFacing facing){ + + if(capability == DISPENSER_CASTING_CAPABILITY){ + return DISPENSER_CASTING_CAPABILITY.cast(data); + } + + return null; + } + + @Override + public NBTTagCompound serializeNBT(){ + return data.serializeNBT(); + } + + @Override + public void deserializeNBT(NBTTagCompound nbt){ + data.deserializeNBT(nbt); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/data/IStoredVariable.java b/src/main/java/electroblob/wizardry/data/IStoredVariable.java new file mode 100644 index 00000000..f6594f57 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/IStoredVariable.java @@ -0,0 +1,256 @@ +package electroblob.wizardry.data; + +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.common.network.ByteBufUtils; + +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Extension of {@link IVariable} which adds NBT read/write methods. Instances of this interface must be + * registered on load using {@link WizardData#registerStoredVariables(IStoredVariable...)} in order for NBT storage + * to work. A good place to do this is in spell constructors, if that's where the variable is being used. + *

    + * This interface is provided for complex cases that require custom NBT handling of some kind. In most cases, + * {@link StoredVariable} should be sufficient. + *

    + * @param The type of variable stored. + */ +public interface IStoredVariable extends IVariable { + + /** Writes the value to the given NBT tag. */ + void write(NBTTagCompound nbt, T value); + + /** Reads the value from the given NBT tag. */ + T read(NBTTagCompound nbt); + + /** + * General-purpose implementation of {@link IStoredVariable}. In most cases, this should be sufficient. This class + * also contains a number of static methods for common implementations (primitives, {@code String}, {@code UUID}, + * {@code BlockPos} and {@code ItemStack}). + *

    + * @param The type of variable stored. + * @param The type of NBT tag the variable will be stored as. + */ + class StoredVariable implements IStoredVariable { + + private final String key; + private final Persistence persistence; + + private final Function serialiser; + private final Function deserialiser; + + private boolean synced; + + private BiFunction ticker; + + /** + * Creates a new {@code StoredVariable} with the given key and serialisation behaviour. + * @param key The string key used to write the value to NBT (should be unique). This serves no other purpose. + * @param serialiser A function used to write the value to NBT. + * @param deserialiser A function used to read the value from NBT. + */ + public StoredVariable(String key, Function serialiser, Function deserialiser, Persistence persistence){ + this.key = key; + this.serialiser = serialiser; + this.deserialiser = deserialiser; + this.persistence = persistence; + this.ticker = (p, t) -> t; // Initialise this with a do-nothing function, can be overwritten later + } + + /** + * Replaces this variable's update method with the given update function. Beware of auto-unboxing of + * primitive types! For lambda expressions, check the second parameter isn't null before operating on it. + * For method references, do not reference a method that takes a primitive type. Otherwise, this will cause + * a (difficult to debug) {@link NullPointerException} if the key was not stored. + * @param ticker A {@link BiFunction} specifying the actions to be performed on this variable each tick. The + * {@code BiFunction} returns the new value for this variable. + * @return This {@code StoredVariable} object, allowing this method to be chained onto object creation. + */ + public StoredVariable withTicker(BiFunction ticker){ + this.ticker = ticker; + return this; + } + + /** + * Adds synchronisation to this variable, meaning it will be sent to clients whenever {@link WizardData#sync()} + * is called (this always happens on player login, but other than that you'll need to do it yourself). + * @return This {@code StoredVariable} object, allowing this method to be chained onto object creation. + */ + public StoredVariable setSynced(){ + this.synced = true; + return this; + } + + @Override + public void write(NBTTagCompound nbt, T value){ + if(value != null) nbt.setTag(key, serialiser.apply(value)); + } + + @Override + @SuppressWarnings("unchecked") // Can't check it due to type erasure + public T read(NBTTagCompound nbt){ + // A system allowing any kind of variable to be stored on the fly cannot be made without casting somewhere. + // However, doing it like this means we only cast once, below, and proper regulation of access means we + // can effectively guarantee the cast is safe. + return nbt.hasKey(key) ? deserialiser.apply((E)nbt.getTag(key)) : null; // Still gotta check it ain't null + } + + @Override + public T update(EntityPlayer player, T value){ + return ticker.apply(player, value); + } + + @Override + public boolean isPersistent(boolean respawn){ + return respawn ? persistence.persistsOnRespawn() : persistence.persistsOnDimensionChange(); + } + + @Override + public boolean isSynced(){ + return synced; + } + + @Override + public void write(ByteBuf buf, T value){ + if(!synced) return; + NBTTagCompound nbt = new NBTTagCompound(); + write(nbt, value); + ByteBufUtils.writeTag(buf, nbt); // Sure, it's not super-efficient, but it's by far the simplest way! + } + + @Override + public T read(ByteBuf buf){ + if(!synced) return null; // Better to check in here because this method should only read if it needs to + NBTTagCompound nbt = ByteBufUtils.readTag(buf); + if(nbt == null) return null; + return read(nbt); + } + + // Standard implementations to shorten common usages a bit + + /** Creates a new {@code StoredVariable} for a byte value with the given key. */ + public static StoredVariable ofByte(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagByte::new, NBTTagByte::getByte, persistence); + } + + /** Creates a new {@code StoredVariable} for a boolean value with the given key. As per Minecraft's usual + * NBT conventions, the boolean value is stored as an {@link NBTTagByte} (1 = true, 0 = false). */ + public static StoredVariable ofBoolean(String key, Persistence persistence){ + return new StoredVariable<>(key, b -> new NBTTagByte((byte)(b?1:0)), t -> t.getByte() == 1, persistence); + } + + /** Creates a new {@code StoredVariable} for an integer value with the given key. */ + public static StoredVariable ofInt(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagInt::new, NBTTagInt::getInt, persistence); + } + + // I'm not going to do byte and long arrays here, if you really need them it's pretty obvious how to do it + + /** Creates a new {@code StoredVariable} for an integer array value with the given key. */ + public static StoredVariable ofIntArray(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagIntArray::new, NBTTagIntArray::getIntArray, persistence); + } + + /** Creates a new {@code StoredVariable} for a float value with the given key. */ + public static StoredVariable ofFloat(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagFloat::new, NBTTagFloat::getFloat, persistence); + } + + /** Creates a new {@code StoredVariable} for a double value with the given key. */ + public static StoredVariable ofDouble(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagDouble::new, NBTTagDouble::getDouble, persistence); + } + + /** Creates a new {@code StoredVariable} for a short value with the given key. */ + public static StoredVariable ofShort(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagShort::new, NBTTagShort::getShort, persistence); + } + + /** Creates a new {@code StoredVariable} for a long value with the given key. */ + public static StoredVariable ofLong(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagLong::new, NBTTagLong::getLong, persistence); + } + + /** Creates a new {@code StoredVariable} for a {@link String} value with the given key. */ + public static StoredVariable ofString(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTTagString::new, NBTTagString::getString, persistence); + } + + /** Creates a new {@code StoredVariable} for a {@link BlockPos} value with the given key. */ + public static StoredVariable ofBlockPos(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTUtil::createPosTag, NBTUtil::getPosFromTag, persistence); + } + + /** Creates a new {@code StoredVariable} for a {@link UUID} value with the given key. */ + public static StoredVariable ofUUID(String key, Persistence persistence){ + return new StoredVariable<>(key, NBTUtil::createUUIDTag, NBTUtil::getUUIDFromTag, persistence); + } + + /** Creates a new {@code StoredVariable} for an {@link ItemStack} value with the given key. */ + public static StoredVariable ofItemStack(String key, Persistence persistence){ + return new StoredVariable<>(key, ItemStack::serializeNBT, ItemStack::new, persistence); + } + + /** Creates a new {@code StoredVariable} for an {@link NBTTagCompound} value with the given key. */ + public static StoredVariable ofNBT(String key, Persistence persistence){ + return new StoredVariable<>(key, t -> t, t -> t, persistence); // No conversion required! + } + + // Neither of these work just ignore them + +// /** Creates a new {@code StoredVariable} for an {@link NBTTagCompound} value with the given key which stores the +// * given {@code IVariable} for an entity. Entities cannot be stored directly as an {@code IStoredVariable} +// * because they require a world instance on construction. */ +// @SuppressWarnings("unchecked") // Can't check it due to type erasure +// public static StoredVariable ofNBTForEntity(String key, Persistence persistence, IVariable toStore){ +// return ofNBT(key, persistence).withTicker((p, t) -> { +// if(WizardData.get(p) != null){ +// try{ +// T e = (T)EntityList.createEntityByIDFromName(new ResourceLocation(t.getString("entityType")), p.world); +// e.readFromNBT(t); +// WizardData.get(p).setVariable(toStore, e); +// }catch(ClassCastException e){ +// Wizardry.logger.error("Error reading entity from NBT: entity not of expected type", e); +// } +// } +// return t; +// }); +// } + +// /** Creates a new {@code StoredVariable} for an {@link Entity} value with the given key. The returned +// * {@code StoredVariable} has a ticker which extracts the entity from the given; this functionality will need to be +// * replicated in any replacement ticker function. */ +// @SuppressWarnings("unchecked") // Can't check it due to type erasure +// public static StoredVariable ofEntity(String key, Persistence persistence, IVariable storage){ +// // Well this is horrible +// return new IStoredVariable.StoredVariable<>(key, +// (T e) -> { +// NBTTagCompound nbt = new NBTTagCompound(); +// nbt.setString("entityType", EntityList.getKey(e).toString()); +// e.writeToNBT(nbt); +// return nbt; +// }, +// t -> null, persistence) +// .withTicker((p, e) -> { +// if(e == null){ +// try{ +// NBTTagCompound nbt = WizardData.get(p).getVariable(storage); +// if(nbt == null) return null; +// e = (T)EntityList.createEntityByIDFromName(new ResourceLocation(nbt.getString("entityType")), p.world); +// e.readFromNBT(nbt); +// return e; +// }catch(ClassCastException x){ +// Wizardry.logger.error("Error reading stored variable from NBT: entity not of expected type", x); +// } +// } +// return null; +// }); +// } + } +} diff --git a/src/main/java/electroblob/wizardry/data/IVariable.java b/src/main/java/electroblob/wizardry/data/IVariable.java new file mode 100644 index 00000000..3388be93 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/IVariable.java @@ -0,0 +1,118 @@ +package electroblob.wizardry.data; + +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayer; + +import java.util.function.BiFunction; + +/** + * Instances of this interface act as keys which allow spellData of any type to be stored in {@link WizardData} at + * runtime. This means spells (or anything else, for that matter) may define their own storedVariables to be stored + * with the player and handle those storedVariables themselves. This prevents {@code WizardData} from being cluttered + * with spell-specific fields and allows addon mods to leverage {@code WizardData} for their own spells or other data, + * rather than defining their own capability. This system is somewhat similar to {@code DataManager}. + *

    + * Instances should be created once and stored statically (or pseudo-statically) in some sensible location, such + * as a spell class. They can then be used as keys to access the values themselves via {@link WizardData}. + * Encapsulation can also be achieved by simply restricting access to the keys. + *

    + * @param The type of variable stored. + */ +public interface IVariable { + + // To reiterate, instances of this interface are KEYS. They are both accessors for the data and define how + // it is stored and handled, but they DO NOT CONTAIN THE ACTUAL DATA. + // Only one instance exists for each thing to be stored, and is shared across instances of WizardData. + + /** Convenience method that allows this variable to define tick behaviour. This is particularly useful for + * trivial operations such as decrementing a value, for which a dedicated event handling method would be + * unnecessarily verbose. */ + T update(EntityPlayer player, T value); + + /** + * Returns whether this variable persists when data is copied. + * @param respawn True if the player died and is respawning, false if they are just travelling between dimensions. + * @return True if the variable should be copied over, false if not. + */ + boolean isPersistent(boolean respawn); + + /** + * Returns whether this variable requires syncing with clients. + * @return True if the variable should be synced with clients, false if not. + */ + boolean isSynced(); + + /** + * Writes this variable's value to the given {@link ByteBuf}. + */ + void write(ByteBuf buf, T value); + + /** + * Reads this variable's value from the given {@link ByteBuf}. + */ + T read(ByteBuf buf); + + /** If you're storing a lot of data, you can optionally implement this method to define a condition which, if + * satisfied, will result in the data being removed from storage, reducing unnecessary syncing and saving. This + * is particularly relevant if the value is synced as it reduces packet size. */ + default boolean canPurge(EntityPlayer player, T value){ + return false; + } + + /** + * General-purpose implementation of {@link IVariable} for non-stored variables. These may still, however, persist + * across player respawn/dimension change. + *

    + * @param The type of variable stored. + */ + class Variable implements IVariable { + + private final Persistence persistence; + + private BiFunction ticker; + + public Variable(Persistence persistence){ + this.persistence = persistence; + this.ticker = (p, t) -> t; + } + + /** + * Replaces this variable's update method with the given update function. Beware of auto-unboxing of + * primitive types! For lambda expressions, check the second parameter isn't null before operating on it. + * For method references, do not reference a method that takes a primitive type. Otherwise, this will cause + * a (difficult to debug) {@link NullPointerException} if the key was not stored. + * @param ticker A {@link BiFunction} specifying the actions to be performed on this variable each tick. The + * {@code BiFunction} returns the new value for this variable. + * @return This {@code Variable} object, allowing this method to be chained onto object creation. + */ + public Variable withTicker(BiFunction ticker){ + this.ticker = ticker; + return this; + } + + @Override + public T update(EntityPlayer player, T value){ + return ticker.apply(player, value); + } + + @Override + public boolean isPersistent(boolean respawn){ + return respawn ? persistence.persistsOnRespawn() : persistence.persistsOnDimensionChange(); + } + + @Override + public boolean isSynced(){ + return false;// Not implemented for now, maybe we will one day + } + + @Override + public void write(ByteBuf buf, T value){ + // NYI + } + + @Override + public T read(ByteBuf buf){ + return null; // NYI + } + } +} diff --git a/src/main/java/electroblob/wizardry/data/Persistence.java b/src/main/java/electroblob/wizardry/data/Persistence.java new file mode 100644 index 00000000..8babc457 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/Persistence.java @@ -0,0 +1,25 @@ +package electroblob.wizardry.data; + +/** Enum which defines the circumstances in which an {@link IVariable} persists, i.e. whether its value is carried over. */ +public enum Persistence { + + NEVER(false, false), + DIMENSION_CHANGE(false, true), + RESPAWN(true, false), + ALWAYS(true, true); + + private boolean persistsOnRespawn, persistsOnDimensionChange; + + Persistence(boolean persistsOnRespawn, boolean persistsOnDimensionChange){ + this.persistsOnRespawn = persistsOnRespawn; + this.persistsOnDimensionChange = persistsOnDimensionChange; + } + + public boolean persistsOnRespawn(){ + return persistsOnRespawn; + } + + public boolean persistsOnDimensionChange(){ + return persistsOnDimensionChange; + } +} diff --git a/src/main/java/electroblob/wizardry/data/SpellEmitterData.java b/src/main/java/electroblob/wizardry/data/SpellEmitterData.java new file mode 100644 index 00000000..a3178303 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/SpellEmitterData.java @@ -0,0 +1,128 @@ +package electroblob.wizardry.data; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.command.SpellEmitter; +import electroblob.wizardry.packet.PacketEmitterData; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.NBTExtras; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.World; +import net.minecraft.world.storage.WorldSavedData; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class responsible for storing and keeping track of {@link SpellEmitter}s. Each world has its own instance of + * {@code SpellEmitterData} which can be retrieved using {@link SpellEmitterData#get(World)}.
    + *
    + * To add a new {@code SpellEmitter}, use {@link SpellEmitter#add(Spell, World, double, double, double, EnumFacing, int, SpellModifiers)}. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +@Mod.EventBusSubscriber +public class SpellEmitterData extends WorldSavedData { + + public static final String NAME = Wizardry.MODID + "_spell_emitters"; + + private final List emitters = new ArrayList<>(); + + private NBTTagList emitterTags = null; + + // Required constructors + public SpellEmitterData(){ + this(NAME); + } + + public SpellEmitterData(String name){ + super(name); + } + + /** Returns the spell emitter data for this world, or creates a new instance if it doesn't exist yet. */ + public static SpellEmitterData get(World world){ + + SpellEmitterData instance = (SpellEmitterData)world.getPerWorldStorage().getOrLoadData(SpellEmitterData.class, NAME); + + if(instance == null){ + instance = new SpellEmitterData(); + world.getPerWorldStorage().setData(NAME, instance); + }else if(instance.emitters.isEmpty() && instance.emitterTags != null){ + instance.loadEmitters(world); + } + + return instance; + } + + /** Sends the active spell emitters for this world to the specified player's client. */ + public void sync(EntityPlayerMP player){ + PacketEmitterData.Message msg = new PacketEmitterData.Message(emitters); + WizardryPacketHandler.net.sendTo(msg, player); + Wizardry.logger.info("Synchronising spell emitters for " + player.getName()); + } + + /** Adds the given {@link SpellEmitter} to the list of emitters for this {@code SpellEmitterData}. */ + public void add(SpellEmitter emitter){ + emitters.add(emitter); + markDirty(); + } + + @Override + public void readFromNBT(NBTTagCompound nbt){ + emitterTags = nbt.getTagList("emitters", Constants.NBT.TAG_COMPOUND); + } + + private void loadEmitters(World world){ + emitters.clear(); + emitters.addAll(NBTExtras.NBTToList(emitterTags, (NBTTagCompound t) -> SpellEmitter.fromNBT(world, t))); + emitterTags = null; // Now we know it's loaded + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound compound){ + compound.setTag("emitters", NBTExtras.listToNBT(emitters, SpellEmitter::toNBT)); + return compound; + } + + public static void update(World world){ + SpellEmitterData data = SpellEmitterData.get(world); + if(!data.emitters.isEmpty()){ + data.emitters.forEach(SpellEmitter::update); + data.emitters.removeIf(SpellEmitter::needsRemoving); + data.markDirty(); // Mark dirty if there are changes to be saved + } + } + + @SubscribeEvent + public static void tick(TickEvent.WorldTickEvent event){ + if(!event.world.isRemote && event.phase == TickEvent.Phase.END){ + update(event.world); + } + } + + @SubscribeEvent + public static void onWorldLoadEvent(WorldEvent.Load event){ + // Called to initialise the spell emitter data when a world loads, if it isn't already. + SpellEmitterData.get(event.getWorld()); + } + + @SubscribeEvent + public static void onPlayerChangedDimensionEvent(PlayerEvent.PlayerChangedDimensionEvent event){ + // Needs to be done here as well as PlayerLoggedInEvent because SpellEmitterData is dimension-specific + if(event.player instanceof EntityPlayerMP){ + SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/SpellGlyphData.java b/src/main/java/electroblob/wizardry/data/SpellGlyphData.java similarity index 85% rename from src/main/java/electroblob/wizardry/SpellGlyphData.java rename to src/main/java/electroblob/wizardry/data/SpellGlyphData.java index 58f0635a..03fc3f89 100644 --- a/src/main/java/electroblob/wizardry/SpellGlyphData.java +++ b/src/main/java/electroblob/wizardry/data/SpellGlyphData.java @@ -1,13 +1,6 @@ -package electroblob.wizardry; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import org.apache.commons.lang3.RandomStringUtils; +package electroblob.wizardry.data; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.packet.PacketGlyphData; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.spell.Spell; @@ -20,6 +13,9 @@ import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.*; /** * Class responsible for generating and storing the randomised spell names and descriptions for each world, which are @@ -32,8 +28,8 @@ public class SpellGlyphData extends WorldSavedData { public static final String NAME = Wizardry.MODID + "_glyphData"; - public Map randomNames = new HashMap(Spell.getTotalSpellCount()); - public Map randomDescriptions = new HashMap(Spell.getTotalSpellCount()); + public Map randomNames = new HashMap<>(Spell.getTotalSpellCount()); + public Map randomDescriptions = new HashMap<>(Spell.getTotalSpellCount()); // Required constructors public SpellGlyphData(){ @@ -110,12 +106,16 @@ public class SpellGlyphData extends WorldSavedData { /** Sends the random spell names for this world to the specified player's client. */ public void sync(EntityPlayerMP player){ - List names = new ArrayList(); - List descriptions = new ArrayList(); + List names = new ArrayList<>(); + List descriptions = new ArrayList<>(); - for(Spell spell : Spell.getSpells(Spell.allSpells)){ + int id = 0; + + while(id < Spell.getTotalSpellCount()){ + Spell spell = Spell.byNetworkID(id + 1); // +1 because the None spell is not included names.add(this.randomNames.get(spell)); descriptions.add(this.randomDescriptions.get(spell)); + id++; } PacketGlyphData.Message msg = new PacketGlyphData.Message(names, descriptions); @@ -144,15 +144,15 @@ public class SpellGlyphData extends WorldSavedData { @Override public void readFromNBT(NBTTagCompound nbt){ - this.randomNames = new HashMap(); - this.randomDescriptions = new HashMap(); + this.randomNames = new HashMap<>(); + this.randomDescriptions = new HashMap<>(); NBTTagList tagList = nbt.getTagList("spellGlyphData", NBT.TAG_COMPOUND); for(int i = 0; i < tagList.tagCount(); i++){ NBTTagCompound tag = tagList.getCompoundTagAt(i); - randomNames.put(Spell.get(tag.getInteger("spell")), tag.getString("name")); - randomDescriptions.put(Spell.get(tag.getInteger("spell")), tag.getString("description")); + randomNames.put(Spell.byMetadata(tag.getInteger("spell")), tag.getString("name")); + randomDescriptions.put(Spell.byMetadata(tag.getInteger("spell")), tag.getString("description")); } } @@ -165,7 +165,7 @@ public class SpellGlyphData extends WorldSavedData { // Much like the enchantments tag for items, this stores a list of spell-id-to-name tag pairs // The description is now also included; there's no point in making a second compound tag! NBTTagCompound tag = new NBTTagCompound(); - tag.setInteger("spell", spell.id()); + tag.setInteger("spell", spell.metadata()); tag.setString("name", this.randomNames.get(spell)); tag.setString("description", this.randomDescriptions.get(spell)); tagList.appendTag(tag); @@ -180,7 +180,6 @@ public class SpellGlyphData extends WorldSavedData { public static void onWorldLoadEvent(WorldEvent.Load event){ if(!event.getWorld().isRemote && event.getWorld().provider.getDimension() == 0){ // Called to initialise the spell glyph data when a world loads, if it isn't already. - // NOTE: Do we actually need this, or can we just let it initialise the first time it is needed? (see below) SpellGlyphData.get(event.getWorld()); } } diff --git a/src/main/java/electroblob/wizardry/data/WizardData.java b/src/main/java/electroblob/wizardry/data/WizardData.java new file mode 100644 index 00000000..433e3855 --- /dev/null +++ b/src/main/java/electroblob/wizardry/data/WizardData.java @@ -0,0 +1,656 @@ +package electroblob.wizardry.data; + +import com.google.common.collect.EvictingQueue; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.enchantment.Imbuement; +import electroblob.wizardry.entity.living.ISummonedCreature; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.event.SpellCastEvent.Source; +import electroblob.wizardry.packet.PacketCastContinuousSpell; +import electroblob.wizardry.packet.PacketPlayerSync; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.None; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.NBTExtras; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Items; +import net.minecraft.item.ItemEnchantedBook; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.Capability.IStorage; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.Constants.NBT; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave minimum + * external changes (for my own sanity, mainly!). Turns out the only major difference between an internal capability and + * an IEEP is a couple of redundant classes and a different way of registering it. + *

    + * Forge seems to have separate classes to hold the Capability<...> instance ('key') and methods for getting the + * capability, but in my opinion there are already too many classes to deal with, so I'm not adding any more than are + * necessary, meaning those constants and values are kept here instead. + * + * @since Wizardry 2.1 + * @author Electroblob + */ +// On the plus side, having to rethink this class allowed me to clean it up a lot. +@Mod.EventBusSubscriber +public class WizardData implements INBTSerializable { + + /** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */ + // This annotation does some crazy Forge magic behind the scenes and assigns this field a value. + @CapabilityInject(WizardData.class) + private static final Capability WIZARD_DATA_CAPABILITY = null; + + /** Internal storage of registered variable keys. This only contains the stored keys. */ + private static final Set storedVariables = new HashSet<>(); + + /** The maximum number of recent spells to track. */ + public static final int MAX_RECENT_SPELLS = 10; + + /** The player this WizardData instance belongs to. */ + private final EntityPlayer player; + + /** An instance of {@link Random} which is guaranteed to produce the same number sequence client and server + * side provided that it is always called from common code. This can be useful in reducing the number of + * packets sent in certain situations.
    + *
    + * This is achieved by setting the seed to a new random value each time {@link WizardData#sync()} is called and + * sending this to the client so it can also set its seed to that value. */ + public final Random synchronisedRandom; + + /** Whether this player is currently casting a continuous spell via commands. Not saved over world reload and reset + * on player death. */ + private Spell castCommandSpell; + /** The time for which this player has been casting a continuous spell via commands. Increments by 1 each tick. Not + * saved over world reload and reset on player death. */ + private int castCommandTick; + /** SpellModifiers object for the current continuous spell cast via commands. Not saved over world reload and reset + * on player death. */ + private SpellModifiers castCommandModifiers; + /** The number of ticks this player's current continuous spell lasts for, or null if there is none. Not saved over + * world reload and reset on player death. */ + private int castCommandDuration; + + /** SpellModifiers object for the current continuous spell cast via items. Not saved over world reload and reset + * on player death. N.B. Since a player can only use one item at a time, this can be reused for any item that + * casts spells, it's not just for wands.*/ + public SpellModifiers itemCastingModifiers; + + public WeakReference selectedMinion; + + /** Set of this player's discovered spells. Do not write to this list directly, use + * {@link WizardData#discoverSpell(Spell)} instead. */ + public Set spellsDiscovered; + + private Set allies; + /** List of usernames of this player's allies. May not be accurate 100% of the time. This is here so that a player + * can view the usernames of their allies even when those allies are not online. Do not use this for any other + * purpose than displaying the names! */ + public Set allyNames; + + /** Internal storage of custom (spell-specific) data. Note that a {@code Map} cannot specify that its values are of + * the same type as the type parameter of its keys, so to ensure this condition always holds, the map must only + * be modified via {@link WizardData#setVariable(IVariable, Object)}, which (as a method) is able to enforce it. */ + private final Map spellData; + + private Queue recentSpells; + + // This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry. + // Leaving this for now because merging it into the spell data system will be more tricky + private Map imbuementDurations; + + /** Stores this player's y velocity from the previous tick; used for the velocity-based fall damage replacement. */ + public double prevMotionY; + + public WizardData(){ + this(null); // Nullary constructor for the registration method factory parameter + } + + public WizardData(EntityPlayer player){ + this.player = player; + this.synchronisedRandom = new Random(); + this.imbuementDurations = new HashMap<>(); + this.spellsDiscovered = new HashSet<>(); + // All players can recognise magic missile. This is not done using discoverSpell because that seems to cause + // a crash on load occasionally (probably something to do with achievements being initialised) + this.spellsDiscovered.add(Spells.magic_missile); + this.recentSpells = EvictingQueue.create(MAX_RECENT_SPELLS); // Only keeps a reference to the last 10 spells cast + this.castCommandSpell = Spells.none; + this.castCommandModifiers = new SpellModifiers(); + this.castCommandTick = 0; + this.itemCastingModifiers = new SpellModifiers(); + this.allies = new HashSet<>(); + this.allyNames = new HashSet<>(); + this.spellData = new HashMap<>(); + } + + /** Called from preInit in the main mod class to register the WizardData capability. */ + public static void register(){ + + // 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); + } + + /** Returns the WizardData instance for the specified player. */ + public static WizardData get(EntityPlayer player){ + return player.getCapability(WIZARD_DATA_CAPABILITY, null); + } + + // ============================================= Variable Storage ============================================= + + // This is my answer to having spells define their own player variables. It's not the prettiest system ever, but + // I think the ability to add arbitrary data to this class and have it save itself to NBT automatically is pretty + // powerful. If it doesn't need saving, this can even be done on the fly - no registration necessary. + + // The reason we have interfaces here is to allow custom implementations of the NBT read/write methods, for + // example, reading/writing multiple keys without having to wrap them in an NBTTagCompound. + + /** Registers the given {@link IStoredVariable} objects as keys that will be stored to NBT for each {@code WizardData} + * instance. */ + public static void registerStoredVariables(IStoredVariable... variables){ + storedVariables.addAll(Arrays.asList(variables)); + } + + /** Returns a set containing the registered {@link IStoredVariable} objects for which {@link IVariable#isSynced()} + * returns true. Used internally for packet reading. */ + public static Set getSyncedVariables(){ + return storedVariables.stream().filter(IVariable::isSynced).collect(Collectors.toSet()); + } + + /** + * Stores the given value under the given key in this {@code WizardData} object. + * @param variable The key under which the value is to be stored. See {@link IVariable} for more details. + * @param value The value to be stored. + * @param The type of the value to be stored. Note that the given variable (key) may be of a supertype of the + * stored value itself; however, when the value is retrieved its type will match that of the key. In + * other words, if an {@code Integer} is stored under a {@code IVariable}, a {@code Number} will + * be returned when the value is retrieved. + */ + // This use of type parameters guarantees that spellData may only be stored (and therefore may only be accessed) + // using a compatible key. For instance, the following code will not compile: + // Number i = 1; + // setVariable(StoredVariable.ofInt("key", Persistence.ALWAYS), i); + public void setVariable(IVariable variable, T value){ + this.spellData.put(variable, value); + } + + /** + * Returns the value stored under the given key in this {@code WizardData} object, or null if the key was not + * stored. + * @param variable The key whose associated value is to be returned. + * @param The type of the returned value. + * @return The value associated with the given key, or null no such key was stored. Beware of auto-unboxing + * of primitive types! Directly assigning the result to a primitive type, as in {@code int i = getVariable(...)}, + * will cause a {@link NullPointerException} if the key was not stored. + */ + @SuppressWarnings("unchecked") // The spellData map is fully encapsulated so we can be sure that the cast is safe + @Nullable + public T getVariable(IVariable variable){ + return (T)spellData.get(variable); + } + + // ============================================== Miscellaneous ============================================== + + // Spell discovery + + public boolean hasSpellBeenDiscovered(Spell spell){ + return spellsDiscovered.contains(spell) || spell instanceof None; + } + + /** + * Adds the given spell to the list of discovered spells for this player. Automatically takes into account whether + * the spell has been discovered. Use this method rather than adding directly to the list because it handles + * achievements. + * + * @param spell The spell to be discovered + * @return True if the spell had not already been discovered; false otherwise. + */ + public boolean discoverSpell(Spell spell){ + + if(spellsDiscovered == null){ + spellsDiscovered = new HashSet<>(); + } + // The 'none' spell cannot be discovered + if(spell instanceof None) return false; + // Tries to add the spell to the list of discovered spells, and returns false if it was already present + return spellsDiscovered.add(spell); + } + + // Recent spell tracking + + /** + * Adds the given spell to this player's recently-cast spells. Spells can (and will) be added multiple times, and + * will be automatically removed when enough spells are added after them. + * @param spell The spell to be tracked. + */ + public void trackRecentSpell(Spell spell){ + this.recentSpells.add(spell); + } + + /** + * Returns the number of times the given spell is tracked in this player's recently-cast spells. + * @param spell The spell to count casts for. + */ + public int countRecentCasts(Spell spell){ + return (int)this.recentSpells.stream().filter(s -> s == spell).count(); // We know this can't be more than 10 + } + + // Imbuements + + /** + * Overwrites the imbuement duration associated with the given imubement for this player, or creates it if there was + * none previously. + * + * @throws IllegalArgumentException if the given {@link Enchantment} is not an {@link Imbuement}. + */ + public void setImbuementDuration(Enchantment enchantment, int duration){ + // It is best to throw an exception here, because otherwise the error would either go unnoticed (if + // non-imbuements + // were ignored) or cause a ClassCastException later (if non-imbuements were allowed to be added). + if(enchantment instanceof Imbuement){ + this.imbuementDurations.put((Imbuement)enchantment, duration); + }else{ + throw new IllegalArgumentException( + "Attempted to set an imbuement duration for something that isn't an Imbuement! (This exception has been thrown now to prevent a ClassCastException from occurring later.)"); + } + } + + /** + * Returns the imbuement duration associated with the given imbuement for this player, or 0 if it does not exist. + */ + @SuppressWarnings("unlikely-arg-type") + public int getImbuementDuration(Enchantment enchantment){ + // Need to check that i is not null, otherwise it throws an NPE when Java auto-unboxes it. + // What's nice here is that the map simply accepts objects as keys, so there's no need to cast or throw + // exceptions. + Integer i = this.imbuementDurations.get(enchantment); + // If i is null, returns 0; otherwise returns i, auto-unboxed to an int. + return i == null ? 0 : i; + } + + /** + * Decrements the duration for each conjured item by 1, and removes from the map any that are 0 or less or that the + * player no longer has. Also deletes the item from the player's inventory if it runs out of time. + */ + private void updateImbuedItems(){ + + Set activeImbuements = new HashSet(); + + // For each item in the player's inventory + for(ItemStack stack : player.inventory.mainInventory){ + if(stack.isItemEnchanted()){ + + NBTTagList enchantmentList = stack.getItem() == Items.ENCHANTED_BOOK ? + ItemEnchantedBook.getEnchantments(stack) : stack.getEnchantmentTagList(); + + Iterator iterator = enchantmentList.iterator(); + // For each of the item's enchantments + while(iterator.hasNext()){ + NBTTagCompound enchantmentTag = (NBTTagCompound) iterator.next(); + Enchantment enchantment = Enchantment.getEnchantmentByID(enchantmentTag.getShort("id")); + // Ignores the enchantment unless it is an imbuement + if(enchantment instanceof Imbuement){ + int duration = this.getImbuementDuration(enchantment); + // If the imbuement is still active: + if(duration > 0){ + // Decrements the timer + this.imbuementDurations.put((Imbuement)enchantment, duration - 1); + // Adds this imbuement to the set of imbuements that need to be kept + activeImbuements.add((Imbuement)enchantment); + }else{ + // Otherwise, removes the enchantment from the item + iterator.remove(); + } + } + } + } + } + // Removes all imbuements from the map that are no longer active + this.imbuementDurations.keySet().retainAll(activeImbuements); + } + + // Ally designation system + + /** + * Adds the given player to the list of allies belonging to the associated player, or removes the player if they are + * already in the list of allies. Returns true if the player was added, false if they were removed. + */ + public boolean toggleAlly(EntityPlayer player){ + if(this.isPlayerAlly(player)){ + this.allies.remove(player.getUniqueID()); + // The remove method uses .equals() rather than == so this will work fine. + this.allyNames.remove(player.getName()); + return false; + }else{ + this.allies.add(player.getUniqueID()); + this.allyNames.add(player.getName()); + return true; + } + } + + /** Returns whether the given player is in this player's list of allies, or is on the same team as this player. */ + public boolean isPlayerAlly(EntityPlayer player){ + return this.allies.contains(player.getUniqueID()) || this.player.isOnSameTeam(player); + } + + /** Returns whether the player with the given UUID is in this player's list of allies. The player to whom the given + * UUID belongs need not be logged in. This method is intended for use by owned entities so that their owner's + * allies don't accidentally damage them, even when the owner is offline. */ + public boolean isPlayerAlly(UUID playerUUID){ + // Scoreboard teams use usernames, but since we keep a cache of those... + return this.allies.contains(playerUUID) || (this.player.getTeam() != null && this.player.getTeam().getMembershipCollection() != null + && this.player.getTeam().getMembershipCollection().stream().anyMatch(allyNames::contains)); + } + + // Command continuous spell casting + + /** Starts casting the given spell with the given modifiers. */ + public void startCastingContinuousSpell(Spell spell, SpellModifiers modifiers, int duration){ + + this.castCommandSpell = spell; + this.castCommandModifiers = modifiers; + this.castCommandDuration = duration; + + if(!this.player.world.isRemote){ + PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player, spell, modifiers, duration); + WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension()); + } + } + + /** Stops casting the current spell. */ + public void stopCastingContinuousSpell(){ + + this.castCommandSpell = Spells.none; + this.castCommandTick = 0; + this.castCommandModifiers.reset(); + + if(!this.player.world.isRemote){ + PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player, Spells.none, this.castCommandModifiers, this.castCommandDuration); + WizardryPacketHandler.net.sendToDimension(message, this.player.world.provider.getDimension()); + } + } + + /** Casts the current continuous spell, fires relevant events and updates the castCommandTick field. */ + public void updateContinuousSpellCasting(){ + + if(this.castCommandSpell != null && this.castCommandSpell.isContinuous){ + + if(castCommandTick >= castCommandDuration){ + this.stopCastingContinuousSpell(); + return; + } + + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.COMMAND, castCommandSpell, player, castCommandModifiers, castCommandTick))){ + this.stopCastingContinuousSpell(); + return; + } + + if(this.castCommandSpell.cast(player.world, player, EnumHand.MAIN_HAND, castCommandTick, this.castCommandModifiers) + && this.castCommandTick == 0){ + // On the first tick casting a continuous spell via commands, SpellCastEvent.Post is fired. + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, castCommandSpell, player, castCommandModifiers)); + } + + castCommandTick++; + + }else{ + // Why is this here? Surely castCommandTick will always be 0 if castCommandSpell is null? + this.castCommandTick = 0; + } + } + + /** Returns whether this player is currently casting a continuous spell via commands. */ + public boolean isCasting(){ + return this.castCommandSpell != null && this.castCommandSpell != Spells.none; + } + + /** + * Returns the continuous spell this player is currently casting via commands, or the 'none' spell if they aren't + * casting anything. + */ + public Spell currentlyCasting(){ + return castCommandSpell; + } + + // ============================================== Data Handling ============================================== + + /** Called each time the associated player is updated. */ + @SuppressWarnings("unchecked") // Again, we know it must be ok + private void update(){ + + if(this.selectedMinion != null && this.selectedMinion.get() == null) this.selectedMinion = null; + + prevMotionY = player.motionY; + + // This new system removes a lot of repetitive event handler code and inflexible spellData which had duplicate + // functions, just for different enchantments. + updateImbuedItems(); + updateContinuousSpellCasting(); + + this.spellData.forEach((k, v) -> this.spellData.put(k, k.update(player, v))); + this.spellData.keySet().removeIf(k -> k.canPurge(player, this.spellData.get(k))); + } + + /** + * Called from the event handler each time the associated player entity is cloned, i.e. on respawn or when + * travelling to a different dimension. Used to copy over any spellData that should persist over player death. This + * is the inverse of the old onPlayerDeath method, which reset the spellData that shouldn't persist. + * + * @param data The old WizardData whose spellData are to be copied over. + * @param respawn True if the player died and is respawning, false if they are just travelling between dimensions. + */ + public void copyFrom(WizardData data, boolean respawn){ + + this.allies = data.allies; + this.allyNames = data.allyNames; + this.selectedMinion = data.selectedMinion; + this.spellsDiscovered = data.spellsDiscovered; + this.recentSpells = data.recentSpells; + + for(IVariable variable : data.spellData.keySet()){ + if(variable.isPersistent(respawn)) this.spellData.put(variable, data.spellData.get(variable)); + } + + // Imbuements are lost on death so their durations do not persist. + // Command spell casting is reset on death so the associated variables do not persist. + } + + /** Sends a packet to this player's client to synchronise necessary information. Only called server side. */ + public void sync(){ + if(this.player instanceof EntityPlayerMP){ + int id = -1; + if(this.selectedMinion != null && this.selectedMinion.get() instanceof Entity) + id = ((Entity)this.selectedMinion.get()).getEntityId(); + long seed = player.world.rand.nextLong(); + this.synchronisedRandom.setSeed(seed); + IMessage msg = new PacketPlayerSync.Message(seed, this.spellsDiscovered, id, this.spellData); + WizardryPacketHandler.net.sendTo(msg, (EntityPlayerMP)this.player); + } + } + + @Override + @SuppressWarnings("unchecked") + public NBTTagCompound serializeNBT(){ + + NBTTagCompound properties = new NBTTagCompound(); + + properties.setTag("imbuements", NBTExtras.mapToNBT(this.imbuementDurations, + imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new)); + + // Mmmmmm Java 8.... + properties.setTag("allies", NBTExtras.listToNBT(this.allies, NBTUtil::createUUIDTag)); + properties.setTag("allyNames", NBTExtras.listToNBT(this.allyNames, NBTTagString::new)); + + // Might be worth converting this over to WizardryUtilities.listToNBT. + int[] spells = new int[this.spellsDiscovered.size()]; + int i = 0; + for(Spell spell : this.spellsDiscovered){ + spells[i] = spell.metadata(); + i++; + } + properties.setIntArray("discoveredSpells", spells); + + properties.setTag("recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata()))); + + storedVariables.forEach(k -> k.write(properties, this.spellData.get(k))); + + return properties; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt){ + + if(nbt != null){ + + this.imbuementDurations = NBTExtras.NBTToMap(nbt.getTagList("imbuements", NBT.TAG_COMPOUND), + (NBTTagInt tag) -> (Imbuement)Enchantment.getEnchantmentByID(tag.getInt()), NBTTagInt::getInt); + + this.allies = new HashSet<>(NBTExtras.NBTToList(nbt.getTagList("allies", NBT.TAG_COMPOUND), NBTUtil::getUUIDFromTag)); + this.allyNames = new HashSet<>(NBTExtras.NBTToList(nbt.getTagList("allyNames", NBT.TAG_STRING), NBTTagString::getString)); + + this.spellsDiscovered = new HashSet<>(); + for(int id : nbt.getIntArray("discoveredSpells")){ + spellsDiscovered.add(Spell.byMetadata(id)); + } + + // Probably won't be null but we may as well just reinitialise it instead of clearing it + this.recentSpells = EvictingQueue.create(MAX_RECENT_SPELLS); + this.recentSpells.addAll(NBTExtras.NBTToList(nbt.getTagList("recentSpells", NBT.TAG_INT), + (NBTTagInt tag) -> Spell.byMetadata(tag.getInt()))); + + try{ + storedVariables.forEach(k -> this.spellData.put(k, k.read(nbt))); + }catch(ClassCastException e){ + // Should only happen if someone manually edits the save file + Wizardry.logger.error("Wizard data NBT tag was not of expected type!", e); + } + } + } + + // ============================================== Event Handlers ============================================== + + @SubscribeEvent + // The type parameter here has to be Entity, not EntityPlayer, or the event won't get fired. + public static void onCapabilityLoad(AttachCapabilitiesEvent event){ + + if(event.getObject() instanceof EntityPlayer) + event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), + new WizardData.Provider((EntityPlayer)event.getObject())); + } + + @SubscribeEvent + public static void onPlayerCloneEvent(PlayerEvent.Clone event){ + + WizardData newData = WizardData.get(event.getEntityPlayer()); + WizardData oldData = WizardData.get(event.getOriginal()); + + newData.copyFrom(oldData, event.isWasDeath()); + + newData.sync(); // In theory this should fix client/server discrepancies (see #69) + } + + @SubscribeEvent + public static void onEntityJoinWorld(EntityJoinWorldEvent event){ + if(!event.getEntity().world.isRemote && event.getEntity() instanceof EntityPlayerMP){ + // Synchronises wizard data after loading. + WizardData data = WizardData.get((EntityPlayer)event.getEntity()); + if(data != null) data.sync(); + } + } + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + + if(event.getEntityLiving() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getEntityLiving(); + + if(WizardData.get(player) != null){ + WizardData.get(player).update(); + } + } + } + + // ========================================== Capability Boilerplate ========================================== + + /** + * This is a nested class for a few reasons: firstly, it makes sense because instances of this and WizardData go + * hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most importantly) it allows + * me to access WIZARD_DATA_CAPABILITY while keeping it private. + */ + public static class Provider implements ICapabilitySerializable { + + private final WizardData data; + + public Provider(EntityPlayer player){ + data = new WizardData(player); + } + + @Override + public boolean hasCapability(Capability capability, EnumFacing facing){ + return capability == WIZARD_DATA_CAPABILITY; + } + + @Override + public T getCapability(Capability capability, EnumFacing facing){ + + if(capability == WIZARD_DATA_CAPABILITY){ + return WIZARD_DATA_CAPABILITY.cast(data); + } + + return null; + } + + @Override + public NBTTagCompound serializeNBT(){ + return data.serializeNBT(); + } + + @Override + public void deserializeNBT(NBTTagCompound nbt){ + data.deserializeNBT(nbt); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/enchantment/EnchantmentMagicProtection.java b/src/main/java/electroblob/wizardry/enchantment/EnchantmentMagicProtection.java new file mode 100644 index 00000000..c397d37b --- /dev/null +++ b/src/main/java/electroblob/wizardry/enchantment/EnchantmentMagicProtection.java @@ -0,0 +1,111 @@ +package electroblob.wizardry.enchantment; + +import electroblob.wizardry.util.IElementalDamage; +import electroblob.wizardry.util.MagicDamage; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentProtection; +import net.minecraft.enchantment.EnumEnchantmentType; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.util.DamageSource; + +import java.util.function.Predicate; + +/** + * This class is for the various magic protection enchantments added by wizardry. + */ +// This was mostly copied from EnchantmentProtection, with the code cleaned up a lot (the vanilla one is awful java...) +public class EnchantmentMagicProtection extends Enchantment { + + /** The type of protection this enchantment gives. */ + public final EnchantmentMagicProtection.Type protectionType; + + public EnchantmentMagicProtection(Enchantment.Rarity rarity, EnchantmentMagicProtection.Type protectionType, EntityEquipmentSlot... slots){ + super(rarity, EnumEnchantmentType.ARMOR, slots); + this.protectionType = protectionType; + } + + // Who knew this was so complicated? + // https://minecraft.gamepedia.com/Tutorials/Enchantment_mechanics#How_enchantments_are_chosen + // https://minecraft.gamepedia.com/Enchanting/Levels <- This is what the results of the 2 methods below are compared to + + @Override + public int getMinEnchantability(int enchantmentLevel){ + return this.protectionType.getMinimalEnchantability() + (enchantmentLevel - 1) * this.protectionType.getEnchantIncreasePerLevel(); + } + + @Override + public int getMaxEnchantability(int enchantmentLevel){ + return this.getMinEnchantability(enchantmentLevel) + this.protectionType.getEnchantIncreasePerLevel(); + } + + @Override + public int getMaxLevel(){ + return 4; + } + + @Override + public int calcModifierDamage(int level, DamageSource source){ + if(source.canHarmInCreative()) return 0; + if(this.protectionType.protectsAgainst(source)) return this.protectionType.getProtectionMultiplier() * level; + return 0; + } + + @Override + public String getName(){ + return "enchantment.ebwizardry:" + this.protectionType.getTypeName() + "_protection"; + } + + @Override + public boolean canApplyTogether(Enchantment ench){ + if(ench instanceof EnchantmentMagicProtection){ + return false; // As per EnchantmentProtection, only feather falling can be applied with other protection types + }else if(ench instanceof EnchantmentProtection){ + return ((EnchantmentProtection)ench).protectionType == EnchantmentProtection.Type.FALL; + }else{ + return super.canApplyTogether(ench); + } + } + + public enum Type { + + MAGIC("magic", 1, 5, 8, s -> s instanceof IElementalDamage), + FROST("frost", 2, 10, 8, s -> s instanceof IElementalDamage && ((IElementalDamage)s).getType() == MagicDamage.DamageType.FROST), + SHOCK("shock", 2, 10, 8, s -> s instanceof IElementalDamage && ((IElementalDamage)s).getType() == MagicDamage.DamageType.SHOCK); + // Fire already exists, and the other types aren't used enough to be worth having + + private final String typeName; + private final int minEnchantability; + private final int levelCost; + // What the heck was levelCostSpan for? Removed since it's never used. + private final Predicate criteria; + private final int protectionMultiplier; + + Type(String name, int protectionMultiplier, int minEnchantability, int perLevelEnchantability, Predicate criteria){ + this.typeName = name; + this.minEnchantability = minEnchantability; + this.levelCost = perLevelEnchantability; + this.criteria = criteria; + this.protectionMultiplier = protectionMultiplier; + } + + public String getTypeName(){ + return this.typeName; + } + + public boolean protectsAgainst(DamageSource source){ + return criteria.test(source); + } + + public int getMinimalEnchantability(){ + return this.minEnchantability; + } + + public int getEnchantIncreasePerLevel(){ + return this.levelCost; + } + + public int getProtectionMultiplier(){ + return protectionMultiplier; + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/enchantment/Imbuement.java b/src/main/java/electroblob/wizardry/enchantment/Imbuement.java index 18eee9bf..53906427 100644 --- a/src/main/java/electroblob/wizardry/enchantment/Imbuement.java +++ b/src/main/java/electroblob/wizardry/enchantment/Imbuement.java @@ -1,12 +1,10 @@ package electroblob.wizardry.enchantment; -import java.util.Iterator; -import java.util.Map; - import com.google.common.collect.Iterables; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.spell.FreezingWeapon; +import electroblob.wizardry.spell.ImbueWeapon; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.EntityLivingBase; @@ -15,7 +13,6 @@ import net.minecraft.entity.projectile.EntityArrow; import net.minecraft.init.Items; import net.minecraft.inventory.ContainerChest; import net.minecraft.inventory.Slot; -import net.minecraft.item.ItemBow; import net.minecraft.item.ItemEnchantedBook; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; @@ -28,6 +25,8 @@ import net.minecraftforge.event.entity.player.PlayerContainerEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.Iterator; + /** * Interface for temporary enchantments that last for a certain duration ('imbuements'). This interface allows * {@link EnchantmentMagicSword} and {@link EnchantmentTimed} to both be treated as instances of a single type, rather @@ -59,8 +58,7 @@ public interface Imbuement { /** Removes all imbuements from the given itemstack. */ static void removeImbuements(ItemStack stack){ if(stack.isItemEnchanted()){ - // No need to check what enchantments the item has, since remove() does nothing if the element does not - // exist. + // No need to check what enchantments the item has, since remove() does nothing if the element does not exist NBTTagList enchantmentList = stack.getItem() == Items.ENCHANTED_BOOK ? ItemEnchantedBook.getEnchantments(stack) : stack.getEnchantmentTagList(); // Check all enchantments of the item @@ -96,7 +94,7 @@ public interface Imbuement { // If any imbuements were removed, inform about the removal of the enchantment(s), or // delete the book entirely if there are none left. if(enchantmentList.isEmpty()){ - slot.putStack(ItemStack.EMPTY); // NOTE: Will need changing in 1.11 + slot.putStack(ItemStack.EMPTY); Wizardry.logger.info("Deleted enchanted book with illegal enchantments"); }else{ // Inform about enchantment removal @@ -122,9 +120,9 @@ public interface Imbuement { ItemStack bow = archer.getHeldItemMainhand(); - if(!(bow.getItem() instanceof ItemBow)){ + if(!ImbueWeapon.isBow(bow.getItem())){ bow = archer.getHeldItemOffhand(); - if(!(bow.getItem() instanceof ItemBow)) return; + if(!ImbueWeapon.isBow(bow.getItem())) return; } // Taken directly from ItemBow, so it works exactly the same as the power enchantment. diff --git a/src/main/java/electroblob/wizardry/entity/EntityArc.java b/src/main/java/electroblob/wizardry/entity/EntityArc.java deleted file mode 100644 index 695de2cd..00000000 --- a/src/main/java/electroblob/wizardry/entity/EntityArc.java +++ /dev/null @@ -1,73 +0,0 @@ -package electroblob.wizardry.entity; - -import io.netty.buffer.ByteBuf; -import net.minecraft.entity.Entity; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.world.World; -import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; - -public class EntityArc extends Entity implements IEntityAdditionalSpawnData { - - public int textureIndex = 0; - public double x1, y1, z1, x2, y2, z2; - // The number of ticks the arc lasts for before disappearing - public int lifetime = 3; - public double offsetX, offsetZ; - - public EntityArc(World par1World){ - super(par1World); - textureIndex = this.rand.nextInt(16); - this.ignoreFrustumCheck = true; - } - - public void setEndpointCoords(double x1, double y1, double z1, double x2, double y2, double z2){ - this.x1 = x1; - this.y1 = y1; - this.z1 = z1; - this.x2 = x2; - this.y2 = y2; - this.z2 = z2; - this.setPosition(x2, y2, z2); - } - - @Override - public void onUpdate(){ - if(this.ticksExisted >= lifetime){ - this.setDead(); - } - } - - protected void entityInit(){ - } - - @Override - protected void readEntityFromNBT(NBTTagCompound nbttagcompound){ - - } - - @Override - protected void writeEntityToNBT(NBTTagCompound nbttagcompound){ - // Nothing needed here; arc is merely a graphic effect that only exists for a few ticks; as such there is no - // need to save it. - } - - @Override - public boolean isInRangeToRenderDist(double distance){ - return true; - } - - @Override - public void writeSpawnData(ByteBuf data){ - data.writeDouble(this.x1); - data.writeDouble(this.y1); - data.writeDouble(this.z1); - } - - @Override - public void readSpawnData(ByteBuf data){ - this.x1 = data.readDouble(); - this.y1 = data.readDouble(); - this.z1 = data.readDouble(); - } - -} diff --git a/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java b/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java new file mode 100644 index 00000000..84305760 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java @@ -0,0 +1,296 @@ +package electroblob.wizardry.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.WizardryUtilities; +import io.netty.buffer.ByteBuf; +import net.minecraft.block.Block; +import net.minecraft.block.BlockFalling; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.MoverType; +import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.List; +import java.util.UUID; + +/** Custom extended version of {@link EntityFallingBlock} for use with the greater telekinesis spell. */ +public class EntityLevitatingBlock extends EntityFallingBlock implements IEntityAdditionalSpawnData { + + private static final Field fallTile; + + static { + fallTile = ObfuscationReflectionHelper.findField(EntityFallingBlock.class, "field_175132_d"); + fallTile.setAccessible(true); + } + + /** The entity that created this levitating block */ + private WeakReference caster; + + /** + * The UUID of the caster. Note that this is only for loading purposes; during normal updates the actual entity + * instance is stored (so that getEntityByUUID is not called constantly), so this will not always be synced (this is + * why it is private). + */ + private UUID casterUUID; + + /** The damage multiplier for this levitating block, determined by the wand with which it was cast. */ + public float damageMultiplier = 1.0f; + + private int suspendTimer = 5; + + public EntityLevitatingBlock(World world){ + super(world); + // EntityFallingBlock never uses this constructor so doesn't bother setting this, but we need to + this.setSize(0.98F, 0.98F); + } + + public EntityLevitatingBlock(World world, double x, double y, double z, IBlockState state){ + super(world, x, y, z, state); + } + + /** Resets the suspension timer to 5 ticks, during which this block will not re-attach itself to the ground. */ + public void suspend(){ + suspendTimer = 5; + } + + @Override + public void onUpdate(){ + + if(suspendTimer > 0){ + suspendTimer--; + } + + if(this.getCaster() == null && this.casterUUID != null){ + Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); + if(entity instanceof EntityLivingBase){ + this.caster = new WeakReference<>((EntityLivingBase)entity); + } + } + + if(getBlock() != null){ + + // === Copied from super === + + Block block = getBlock().getBlock(); + + if(getBlock().getMaterial() == Material.AIR){ + this.setDead(); + + }else{ + + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + + if(this.fallTime++ == 0){ + + BlockPos blockpos = new BlockPos(this); + + if(this.world.getBlockState(blockpos).getBlock() == block){ + this.world.setBlockToAir(blockpos); + }else if(!this.world.isRemote){ + this.setDead(); + return; + } + } + + if(!this.hasNoGravity()){ + this.motionY -= 0.03999999910593033D; + } + + this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ); + + if(!this.world.isRemote){ + + BlockPos blockpos1 = new BlockPos(this); + boolean isConcrete = getBlock().getBlock() == Blocks.CONCRETE_POWDER; + boolean isConcreteInWater = isConcrete && this.world.getBlockState(blockpos1).getMaterial() == Material.WATER; + double d0 = this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ; + + if(isConcrete && d0 > 1.0D){ + + RayTraceResult raytraceresult = this.world.rayTraceBlocks(new Vec3d(this.prevPosX, this.prevPosY, this.prevPosZ), new Vec3d(this.posX, this.posY, this.posZ), true); + + if(raytraceresult != null && this.world.getBlockState(raytraceresult.getBlockPos()).getMaterial() == Material.WATER){ + blockpos1 = raytraceresult.getBlockPos(); + isConcreteInWater = true; + } + } + + if(!this.onGround && !isConcreteInWater){ + + if(this.fallTime > 100 && !this.world.isRemote && (blockpos1.getY() < 1 || blockpos1.getY() > 256) || this.fallTime > 600){ + this.setDead(); + } + + }else{ + + IBlockState iblockstate = this.world.getBlockState(blockpos1); + + if(this.world.isAirBlock(new BlockPos(this.posX, this.posY - 0.009999999776482582D, this.posZ))){ + if(!isConcreteInWater && BlockFalling.canFallThrough(this.world.getBlockState(new BlockPos(this.posX, this.posY - 0.009999999776482582D, this.posZ)))){ + this.onGround = false; + return; + } + } + + this.motionX *= 0.699999988079071D; + this.motionZ *= 0.699999988079071D; + this.motionY *= -0.5D; + + if(iblockstate.getBlock() != Blocks.PISTON_EXTENSION){ + + if(suspendTimer == 0){ + + this.setDead(); // Moved inside the above if statement + + if(this.world.mayPlace(block, blockpos1, true, EnumFacing.UP, null) + && (isConcreteInWater || !BlockFalling.canFallThrough(this.world.getBlockState(blockpos1.down()))) + && this.world.setBlockState(blockpos1, getBlock(), 3)){ + + if(block instanceof BlockFalling){ + ((BlockFalling)block).onEndFalling(this.world, blockpos1, getBlock(), iblockstate); + } + + if(this.tileEntityData != null && block.hasTileEntity(getBlock())){ + + TileEntity tileentity = this.world.getTileEntity(blockpos1); + + if(tileentity != null){ + + NBTTagCompound nbttagcompound = tileentity.writeToNBT(new NBTTagCompound()); + + for(String s : this.tileEntityData.getKeySet()){ + NBTBase nbtbase = this.tileEntityData.getTag(s); + + if(!"x".equals(s) && !"y".equals(s) && !"z".equals(s)){ + nbttagcompound.setTag(s, nbtbase.copy()); + } + } + + tileentity.readFromNBT(nbttagcompound); + tileentity.markDirty(); + } + } + + }else{ + // Never drops the block, instead if it can't reattach to the world it breaks + world.playEvent(2001, this.getPosition(), Block.getStateId(getBlock())); + } + } + } + } + } + + this.motionX *= 0.9800000190734863D; + this.motionY *= 0.9800000190734863D; + this.motionZ *= 0.9800000190734863D; + } + + // === End super copy === + } + + double velocitySquared = motionX * motionX + motionY * motionY + motionZ * motionZ; + + if(velocitySquared >= 0.2){ + + List list = this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox()); + + for(Entity entity : list){ + + if(entity instanceof EntityLivingBase && isValidTarget(entity)){ + + float damage = Spells.greater_telekinesis.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + damage *= Math.min(1, velocitySquared/0.4); // Reduce damage at low speeds + + entity.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), + MagicDamage.DamageType.FORCE), damage); + + double dx = -this.motionX; + double dz; + for(dz = -this.motionZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) * 0.01D){ + dx = (Math.random() - Math.random()) * 0.01D; + } + ((EntityLivingBase)entity).knockBack(this, 0.6f, dx, dz); + } + } + } + + } + + /** + * Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the entity + * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to + * another dimension, or this construct simply had no caster in the first place. + */ + public EntityLivingBase getCaster(){ + return caster == null ? null : caster.get(); + } + + public void setCaster(EntityLivingBase caster){ + if(getCaster() != caster) this.caster = new WeakReference<>(caster); + } + + /** + * Shorthand for {@link AllyDesignationSystem#isValidTarget(Entity, Entity)}, with the owner of this construct as the + * attacker. Also allows subclasses to override it if they wish to do so. + */ + public boolean isValidTarget(Entity target){ + return AllyDesignationSystem.isValidTarget(this.getCaster(), target); + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbttagcompound){ + super.readEntityFromNBT(nbttagcompound); + casterUUID = nbttagcompound.getUniqueId("casterUUID"); + damageMultiplier = nbttagcompound.getFloat("damageMultiplier"); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbttagcompound){ + super.writeEntityToNBT(nbttagcompound); + if(this.getCaster() != null){ + nbttagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID()); + } + nbttagcompound.setFloat("damageMultiplier", damageMultiplier); + } + + @Override + public void readSpawnData(ByteBuf buf){ + if(buf.isReadable()){ + Block block = Block.REGISTRY.getObjectById(buf.readInt()); + try{ + fallTile.set(this, block.getStateFromMeta(buf.readInt())); + }catch(IllegalAccessException e){ + Wizardry.logger.error("Error reading levitating block data from packet: ", e); + } + } + } + + @Override + public void writeSpawnData(ByteBuf buf){ + if(getBlock() != null){ + buf.writeInt(Block.REGISTRY.getIDForObject(getBlock().getBlock())); + buf.writeInt(getBlock().getBlock().getMetaFromState(getBlock())); + } + } +} diff --git a/src/main/java/electroblob/wizardry/entity/EntityMeteor.java b/src/main/java/electroblob/wizardry/entity/EntityMeteor.java index 53e3a87d..f41d63ad 100644 --- a/src/main/java/electroblob/wizardry/entity/EntityMeteor.java +++ b/src/main/java/electroblob/wizardry/entity/EntityMeteor.java @@ -1,15 +1,15 @@ package electroblob.wizardry.entity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.spell.Meteor; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.MoverType; import net.minecraft.entity.item.EntityFallingBlock; -import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.math.BlockPos; +import net.minecraft.util.SoundCategory; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -17,10 +17,10 @@ import net.minecraftforge.fml.relauncher.SideOnly; public class EntityMeteor extends EntityFallingBlock { /** - * The entity blast multiplier. Only some projectiles cause a blast, which is why this isn't in - * EntityMagicProjectile. + * The entity blast multiplier. */ public float blastMultiplier; + private boolean damageBlocks; public EntityMeteor(World world){ super(world); @@ -28,11 +28,12 @@ public class EntityMeteor extends EntityFallingBlock { this.setSize(0.98F, 0.98F); } - public EntityMeteor(World world, double x, double y, double z, float blastMultiplier){ + public EntityMeteor(World world, double x, double y, double z, float blastMultiplier, boolean damageBlocks){ super(world, x, y, z, WizardryBlocks.meteor.getDefaultState()); this.motionY = -1.0D; this.setFire(200); this.blastMultiplier = blastMultiplier; + this.damageBlocks = damageBlocks; } @Override @@ -44,7 +45,7 @@ public class EntityMeteor extends EntityFallingBlock { public void onUpdate(){ if(this.ticksExisted % 16 == 1 && world.isRemote){ - Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_FIRE, 3.0f, 1.0f, false); + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_METEOR_FALLING, WizardrySounds.SPELLS, 3.0f, 1.0f, false); } // You'd think the best way to do this would be to call super and do all the exploding stuff in fall() instead. @@ -67,21 +68,9 @@ public class EntityMeteor extends EntityFallingBlock { this.motionX *= 0.699999988079071D; this.motionZ *= 0.699999988079071D; this.motionY *= -0.5D; - this.world.createExplosion(this, this.posX, this.posY, this.posZ, 2.0f * blastMultiplier, true); - for(int i1 = -3; i1 < 4; i1++){ - for(int j1 = -3; j1 < 4; j1++){ - int y = WizardryUtilities.getNearestFloorLevelB(this.world, - new BlockPos(this.posX + i1, this.posY, this.posZ + j1), 7); - // System.out.println(y); - double dist = this.getDistance((int)this.posX + i1, y, (int)this.posZ + j1); - // Randomised with weighting so that the nearer the block the more likely it is to be set on - // fire. - if(y != -1 && rand.nextInt((int)dist * 2 + 1) < 3 && dist < 4){ - this.world.setBlockState(new BlockPos(this.posX + i1, y, this.posZ + j1), - Blocks.FIRE.getDefaultState()); - } - } - } + this.world.newExplosion(this, this.posX, this.posY, this.posZ, + Spells.meteor.getProperty(Meteor.BLAST_STRENGTH).floatValue() * blastMultiplier, + damageBlocks, damageBlocks); this.setDead(); } } @@ -124,12 +113,19 @@ public class EntityMeteor extends EntityFallingBlock { public void readEntityFromNBT(NBTTagCompound nbttagcompound){ super.readEntityFromNBT(nbttagcompound); blastMultiplier = nbttagcompound.getFloat("blastMultiplier"); + damageBlocks = nbttagcompound.getBoolean("damageBlocks"); } @Override public void writeEntityToNBT(NBTTagCompound nbttagcompound){ super.writeEntityToNBT(nbttagcompound); nbttagcompound.setFloat("blastMultiplier", blastMultiplier); + nbttagcompound.setBoolean("damageBlocks", damageBlocks); + } + + @Override + public SoundCategory getSoundCategory(){ + return WizardrySounds.SPELLS; } } diff --git a/src/main/java/electroblob/wizardry/entity/EntityShield.java b/src/main/java/electroblob/wizardry/entity/EntityShield.java index 0f483493..7138dc22 100644 --- a/src/main/java/electroblob/wizardry/entity/EntityShield.java +++ b/src/main/java/electroblob/wizardry/entity/EntityShield.java @@ -1,18 +1,20 @@ package electroblob.wizardry.entity; -import java.lang.ref.WeakReference; - -import electroblob.wizardry.WizardData; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Shield; import net.minecraft.entity.Entity; import net.minecraft.entity.IProjectile; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; +import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.world.World; +import java.lang.ref.WeakReference; + public class EntityShield extends Entity { public WeakReference player; @@ -46,8 +48,8 @@ public class EntityShield extends Entity { entityplayer.posY + 1 + entityplayer.getLookVec().y * 0.3, entityplayer.posZ + entityplayer.getLookVec().z * 0.3, entityplayer.rotationYawHead, entityplayer.rotationPitch); - if(!entityplayer.isHandActive() || !(entityplayer.getHeldItem(entityplayer.getActiveHand()).getItem() instanceof ItemWand)){ - WizardData.get(entityplayer).shield = null; + if(!entityplayer.isHandActive() || !(entityplayer.getHeldItem(entityplayer.getActiveHand()).getItem() instanceof ISpellCastingItem)){ + WizardData.get(entityplayer).setVariable(Shield.SHIELD_KEY, null); this.setDead(); } }else if(!world.isRemote){ @@ -62,13 +64,19 @@ public class EntityShield extends Entity { this.setRotation(par7, par8); } - public boolean attackEntityFrom(DamageSource par1DamageSource, float par2){ - if(par1DamageSource != null && par1DamageSource.getImmediateSource() instanceof IProjectile){ - par1DamageSource.getImmediateSource().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f); + public boolean attackEntityFrom(DamageSource source, float damage){ + if(source != null && source.getImmediateSource() instanceof IProjectile){ + world.playSound(null, source.getImmediateSource().posX, source.getImmediateSource().posY, + source.getImmediateSource().posZ, WizardrySounds.ENTITY_SHIELD_DEFLECT, WizardrySounds.SPELLS, 0.3f, 1.3f); } - super.attackEntityFrom(par1DamageSource, par2); + super.attackEntityFrom(source, damage); return false; } + + @Override + public SoundCategory getSoundCategory(){ + return WizardrySounds.SPELLS; + } public boolean canBeCollidedWith(){ return !this.isDead; diff --git a/src/main/java/electroblob/wizardry/entity/ICustomHitbox.java b/src/main/java/electroblob/wizardry/entity/ICustomHitbox.java new file mode 100644 index 00000000..95c0baa9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/ICustomHitbox.java @@ -0,0 +1,31 @@ +package electroblob.wizardry.entity; + +import net.minecraft.util.math.Vec3d; + +/** This interface allows implementing entity classes to define their own hitbox for wizardry's raytracing and + * particle collision methods. Typically, entities implementing this interface will return null from the collision + * bounding box methods in {@code Entity} (there are two for some reason) but return true from + * {@link net.minecraft.entity.Entity#canBeCollidedWith()} */ +public interface ICustomHitbox { + + /** + * Calculates the point at which the line starting at the given origin and ending at the given endpoint hits this + * entity, if any. Used in raytracing to allow entities to define fully custom behaviour. See + * {@link electroblob.wizardry.entity.construct.EntityForcefield} for an example implementation of a spherical hitbox. + * @param origin The origin of the line. + * @param endpoint The endpoint of the line. + * @param fuzziness Maximum distance around the line that should still count as a hit. + * @return A {@link Vec3d} representing the point hit, or null if there is no intercept. This should be the first + * point that the line hits, i.e. if there is more than one intercept this method should return the one nearest to + * the given origin. + */ + Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness); + + /** + * Returns whether the given point is inside this entity. + * @param point The coordinates to test. + * @return True if the point is inside this entity, false if not. + */ + boolean contains(Vec3d point); + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java b/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java index 1ad7632d..9f26e85b 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java @@ -1,22 +1,15 @@ package electroblob.wizardry.entity.construct; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.projectile.EntityTippedArrow; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; public class EntityArrowRain extends EntityMagicConstruct { - public EntityArrowRain(World par1World){ - super(par1World); - this.height = 3.0f; - this.width = 5.0f; - } - - public EntityArrowRain(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); + public EntityArrowRain(World world){ + super(world); this.height = 3.0f; this.width = 5.0f; } @@ -28,9 +21,9 @@ public class EntityArrowRain extends EntityMagicConstruct { if(!this.world.isRemote){ EntityTippedArrow arrow = new EntityTippedArrow(world, this.posX + rand.nextDouble() * 6 - 3, this.posY + rand.nextDouble() * 4 - 2, this.posZ + rand.nextDouble() * 6 - 3); - arrow.motionX = Math.cos(Math.toRadians(this.rotationYaw + 90)); + arrow.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90)); arrow.motionY = -0.6; - arrow.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90)); + arrow.motionZ = MathHelper.sin((float)Math.toRadians(this.rotationYaw + 90)); arrow.shootingEntity = this.getCaster(); arrow.setDamage(7.0d * damageMultiplier); arrow.setPotionEffect(new ItemStack(Items.ARROW)); diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java index b7d77fb8..378c9066 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java @@ -1,21 +1,28 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.init.SoundEvents; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.List; public class EntityBlackHole extends EntityMagicConstruct { + + private static final double SUCTION_STRENGTH = 0.075; public int[] randomiser; public int[] randomiser2; @@ -34,21 +41,6 @@ public class EntityBlackHole extends EntityMagicConstruct { } } - public EntityBlackHole(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.width = 6.0f; - this.height = 3.0f; - randomiser = new int[30]; - for(int i = 0; i < randomiser.length; i++){ - randomiser[i] = this.rand.nextInt(10); - } - randomiser2 = new int[30]; - for(int i = 0; i < randomiser2.length; i++){ - randomiser2[i] = this.rand.nextInt(10); - } - } - @Override protected void readEntityFromNBT(NBTTagCompound nbttagcompound){ super.readEntityFromNBT(nbttagcompound); @@ -83,42 +75,49 @@ public class EntityBlackHole extends EntityMagicConstruct { } if(this.lifetime - this.ticksExisted == 75){ - this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f); + this.playSound(WizardrySounds.ENTITY_BLACK_HOLE_VANISH, 1.5f, 1.0f); }else if(this.ticksExisted % 80 == 1 && this.ticksExisted + 80 < this.lifetime){ - this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.5f, 1.0f); + this.playSound(WizardrySounds.ENTITY_BLACK_HOLE_AMBIENT, 1.5f, 1.0f); } - List targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY, - this.posZ, this.world); - if(!this.world.isRemote){ + List targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY, + this.posZ, this.world); + for(EntityLivingBase target : targets){ if(this.isValidTarget(target)){ - // Sucks the target in - if(this.posX > target.posX && target.motionX < 1){ - target.motionX += 0.1; - }else if(this.posX < target.posX && target.motionX > -1){ - target.motionX -= 0.1; - } + // If the target can't be moved, it isn't sucked in but is still damaged if it gets too close + if(!(target instanceof EntityPlayer && ((getCaster() instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther) + || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring)))){ - if(this.posY > target.posY && target.motionY < 1){ - target.motionY += 0.1; - }else if(this.posY < target.posY && target.motionY > -1){ - target.motionY -= 0.1; - } + WizardryUtilities.undoGravity(target); - if(this.posZ > target.posZ && target.motionZ < 1){ - target.motionZ += 0.1; - }else if(this.posZ < target.posZ && target.motionZ > -1){ - target.motionZ -= 0.1; - } + // Sucks the target in + if(this.posX > target.posX && target.motionX < 1){ + target.motionX += SUCTION_STRENGTH; + }else if(this.posX < target.posX && target.motionX > -1){ + target.motionX -= SUCTION_STRENGTH; + } - // Player motion is handled on that player's client so needs packets - if(target instanceof EntityPlayerMP){ - ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + if(this.posY > target.posY && target.motionY < 1){ + target.motionY += SUCTION_STRENGTH; + }else if(this.posY < target.posY && target.motionY > -1){ + target.motionY -= SUCTION_STRENGTH; + } + + if(this.posZ > target.posZ && target.motionZ < 1){ + target.motionZ += SUCTION_STRENGTH; + }else if(this.posZ < target.posZ && target.motionZ > -1){ + target.motionZ -= SUCTION_STRENGTH; + } + + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + } } if(this.getDistance(target) <= 2){ @@ -135,12 +134,16 @@ public class EntityBlackHole extends EntityMagicConstruct { } } } - - /** - * Checks using a Vec3dd to determine if this entity is within range of that vector to be rendered. Args: Vec3dD - */ - public boolean isInRangeToRenderVec3dD(Vec3d par1Vec3d){ + + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance){ return true; } + @Override + public boolean shouldRenderInPass(int pass){ + return pass == 1; + } + } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java index 50ecabd4..9cc90a43 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java @@ -1,30 +1,25 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import java.util.List; + public class EntityBlizzard extends EntityMagicConstruct { - public EntityBlizzard(World par1World){ - super(par1World); - this.height = 1.0f; - this.width = 1.0f; - } - - public EntityBlizzard(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); + public EntityBlizzard(World world){ + super(world); this.height = 1.0f; this.width = 1.0f; } @@ -32,14 +27,18 @@ public class EntityBlizzard extends EntityMagicConstruct { public void onUpdate(){ if(this.ticksExisted % 120 == 1){ - this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_BLIZZARD_AMBIENT, 1.0f, 1.0f); } super.onUpdate(); + // This is a good example of why you might define a spell base property without necessarily using it in the + // spell - in fact, blizzard doesn't even have a spell class (yet) + double radius = Spells.blizzard.getProperty(Spell.EFFECT_RADIUS).doubleValue(); + if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(3.0d, this.posX, this.posY, + List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ @@ -60,17 +59,13 @@ public class EntityBlizzard extends EntityMagicConstruct { if(!MagicDamage.isEntityImmune(DamageType.FROST, target)) target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 20, 0)); } + }else{ - // For some reason this number of particles now causes the game to lag significantly, despite it being fine - // in 1.7.10. I thought particles were supposed to be LESS laggy now... - for(int i = 1; i < 6; i++){ - float brightness = 0.5f + (rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.BLIZZARD, world, this.posX, - this.posY + rand.nextDouble() * 3, this.posZ, 0, 0, 0, 100, brightness, brightness + 0.1f, 1.0f, - false, rand.nextDouble() * 2.5d + 0.5d); - Wizardry.proxy.spawnParticle(WizardryParticleType.BLIZZARD, world, this.posX, - this.posY + rand.nextDouble() * 3, this.posZ, 0, 0, 0, 100, 1.0f, 1.0f, 1.0f, false, - rand.nextDouble() * 2.5d + 0.5d); + + for(int i=1; i<12; i++){ + double speed = (rand.nextBoolean() ? 1 : -1) * 0.1 + 0.05 * rand.nextDouble(); + ParticleBuilder.create(Type.SNOW).pos(this.posX, this.posY + rand.nextDouble() * 3, this.posZ).vel(0, 0, 0) + .time(100).scale(2).spin(rand.nextDouble() * (radius - 0.5) + 0.5, speed).spawn(world); } } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java index a864526b..e494c610 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java @@ -1,14 +1,14 @@ package electroblob.wizardry.entity.construct; -import java.lang.ref.WeakReference; - +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Entrapment; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.MoverType; -import net.minecraft.init.SoundEvents; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; @@ -17,6 +17,8 @@ import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.lang.ref.WeakReference; + @Mod.EventBusSubscriber public class EntityBubble extends EntityMagicConstruct { @@ -28,13 +30,6 @@ public class EntityBubble extends EntityMagicConstruct { super(world); } - public EntityBubble(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - boolean isDarkOrb, float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - // this.setSize(0.1f, 0.1f); - this.isDarkOrb = isDarkOrb; - } - @Override public double getMountedYOffset(){ return 0.1; @@ -53,7 +48,7 @@ public class EntityBubble extends EntityMagicConstruct { if((this.rider == null || this.rider.get() == null) && WizardryUtilities.getRider(this) instanceof EntityLivingBase && !WizardryUtilities.getRider(this).isDead){ - this.rider = new WeakReference((EntityLivingBase)WizardryUtilities.getRider(this)); + this.rider = new WeakReference<>((EntityLivingBase)WizardryUtilities.getRider(this)); } // Prevents dismounting @@ -69,7 +64,8 @@ public class EntityBubble extends EntityMagicConstruct { if(isDarkOrb){ - if(WizardryUtilities.getRider(this) != null && this.ticksExisted % 30 == 0){ + if(WizardryUtilities.getRider(this) != null + && this.ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){ if(this.getCaster() != null){ WizardryUtilities.getRider(this).attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), @@ -88,16 +84,16 @@ public class EntityBubble extends EntityMagicConstruct { (this.rand.nextDouble() - 0.5D) * 2.0D); } if(lifetime - this.ticksExisted == 75){ - this.playSound(SoundEvents.BLOCK_PORTAL_TRIGGER, 1.5f, 1.0f); + this.playSound(WizardrySounds.ENTITY_ENTRAPMENT_VANISH, 1.5f, 1.0f); }else if(this.ticksExisted % 100 == 1 && this.ticksExisted < 150){ - this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.5f, 1.0f); + this.playSound(WizardrySounds.ENTITY_ENTRAPMENT_AMBIENT, 1.5f, 1.0f); } } // Bubble bursts if the entity is hurt (see event handler) or killed, or if the bubble has existed for more than // 10 seconds. if(WizardryUtilities.getRider(this) == null && this.ticksExisted > 1){ - if(!this.isDarkOrb) this.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f); + if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f); this.setDead(); } } @@ -107,7 +103,7 @@ public class EntityBubble extends EntityMagicConstruct { if(WizardryUtilities.getRider(this) != null){ ((EntityLivingBase)WizardryUtilities.getRider(this)).dismountEntity(this); } - if(!this.isDarkOrb) this.playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f); + if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f); super.despawn(); } @@ -140,7 +136,7 @@ public class EntityBubble extends EntityMagicConstruct { // Bursts bubble when the creature inside takes damage if(event.getEntityLiving().getRidingEntity() instanceof EntityBubble && !((EntityBubble)event.getEntityLiving().getRidingEntity()).isDarkOrb){ - event.getEntityLiving().getRidingEntity().playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f); + event.getEntityLiving().getRidingEntity().playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f); event.getEntityLiving().getRidingEntity().setDead(); } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java b/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java new file mode 100644 index 00000000..f9188d6c --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import java.util.List; + +public class EntityCombustionRune extends EntityMagicConstruct { + + public EntityCombustionRune(World world){ + super(world); + this.height = 0.2f; + this.width = 2.0f; + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(!this.world.isRemote){ + + List targets = WizardryUtilities.getEntitiesWithinRadius(width/2, posX, posY, posZ, world); + + for(EntityLivingBase target : targets){ + + if(this.isValidTarget(target)){ + + float strength = Spells.combustion_rune.getProperty(Spell.BLAST_RADIUS).floatValue(); + + world.newExplosion(this.getCaster(), this.posX, this.posY, this.posZ, strength, true, true); + + // The trap is destroyed once triggered. + this.setDead(); + } + } + }else if(this.rand.nextInt(15) == 0){ + double radius = 0.5 + rand.nextDouble() * 0.3; + float angle = rand.nextFloat() * (float)Math.PI * 2; + world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, + this.posZ + radius * MathHelper.sin(angle), 0, 0, 0); + } + } + + @Override + protected void entityInit(){} + + @Override + public boolean canRenderOnFire(){ + return false; + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java b/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java index a2835764..e781a2d9 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java @@ -1,32 +1,26 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityDecay extends EntityMagicConstruct { public int textureIndex = 0; - public static final int LIFETIME = 400; - public EntityDecay(World par1World){ - super(par1World); - textureIndex = this.rand.nextInt(10); - this.height = 0.2f; - this.width = 2.0f; - } - - public EntityDecay(World par1World, double x, double y, double z, EntityLivingBase caster){ - super(par1World, x, y, z, caster, LIFETIME, 1); + public EntityDecay(World world){ + super(world); textureIndex = this.rand.nextInt(10); this.height = 0.2f; this.width = 2.0f; @@ -37,8 +31,8 @@ public class EntityDecay extends EntityMagicConstruct { super.onUpdate(); - if(this.rand.nextInt(700) == 0 && this.ticksExisted + 100 < LIFETIME) - this.playSound(SoundEvents.BLOCK_LAVA_AMBIENT, 0.2F + rand.nextFloat() * 0.2F, + if(this.rand.nextInt(700) == 0 && this.ticksExisted + 100 < lifetime) + this.playSound(WizardrySounds.ENTITY_DECAY_AMBIENT, 0.2F + rand.nextFloat() * 0.2F, 0.6F + rand.nextFloat() * 0.15F); if(!this.world.isRemote){ @@ -50,35 +44,33 @@ public class EntityDecay extends EntityMagicConstruct { // damaged each tick. // In this case, we do want particles to be shown. if(!target.isPotionActive(WizardryPotions.decay)) - target.addPotionEffect(new PotionEffect(WizardryPotions.decay, LIFETIME, 0)); + target.addPotionEffect(new PotionEffect(WizardryPotions.decay, + Spells.decay.getProperty(Spell.EFFECT_DURATION).intValue(), 0)); } } + }else if(this.rand.nextInt(15) == 0){ + double radius = rand.nextDouble() * 0.8; - double angle = rand.nextDouble() * Math.PI * 2; + float angle = rand.nextFloat() * (float)Math.PI * 2; float brightness = rand.nextFloat() * 0.4f; - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, this.posX + radius * Math.cos(angle), - this.posY, this.posZ + radius * Math.sin(angle), 0, 0, 0, 0, brightness, 0, brightness + 0.1f); + + ParticleBuilder.create(Type.DARK_MAGIC) + .pos(this.posX + radius * MathHelper.cos(angle), this.posY, this.posZ + radius * MathHelper.sin(angle)) + .clr(brightness, 0, brightness + 0.1f) + .spawn(world); } } - protected void entityInit(){ - } + @Override protected void entityInit(){} + + @Override protected void readEntityFromNBT(NBTTagCompound nbttagcompound){} + + @Override protected void writeEntityToNBT(NBTTagCompound nbttagcompound){} @Override - protected void readEntityFromNBT(NBTTagCompound nbttagcompound){ - - } - - @Override - protected void writeEntityToNBT(NBTTagCompound nbttagcompound){ - - } - - /** - * Checks using a Vec3dd to determine if this entity is within range of that vector to be rendered. Args: Vec3dD - */ - public boolean isInRangeToRenderVec3dD(Vec3d par1Vec3d){ + public boolean isInRangeToRenderDist(double distance){ return true; } + } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java b/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java index abc8d982..2a6f455a 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java @@ -1,19 +1,23 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Earthquake; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.MobEffects; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.potion.PotionEffect; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityEarthquake extends EntityMagicConstruct { public EntityEarthquake(World world){ @@ -22,32 +26,24 @@ public class EntityEarthquake extends EntityMagicConstruct { this.width = 1.0f; } - public EntityEarthquake(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.height = 1.0f; - this.width = 1.0f; - } - public void onUpdate(){ super.onUpdate(); + double speed = Spells.earthquake.getProperty(Earthquake.SPREAD_SPEED).doubleValue(); + if(!world.isRemote){ - double speed = 0.4; - // The further the earthquake is going to spread, the finer the angle increments. - for(double angle = 0; angle < 2 * Math.PI; angle += Math.PI / (lifetime * 1.5)){ + for(float angle = 0; angle < 2 * Math.PI; angle += Math.PI / (lifetime * 1.5)){ // Calculates coordinates for the block to be moved. The radius increases with time. The +1.5 is to - // leave - // blocks in the centre untouched. - int x = this.posX < 0 ? (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * Math.sin(angle) - 1) - : (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * Math.sin(angle)); + // leave blocks in the centre untouched. + int x = this.posX < 0 ? (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * MathHelper.sin(angle) - 1) + : (int)(this.posX + ((this.ticksExisted * speed) + 1.5) * MathHelper.sin(angle)); int y = (int)(this.posY - 0.5); - int z = this.posZ < 0 ? (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * Math.cos(angle) - 1) - : (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * Math.cos(angle)); + int z = this.posZ < 0 ? (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * MathHelper.cos(angle) - 1) + : (int)(this.posZ + ((this.ticksExisted * speed) + 1.5) * MathHelper.cos(angle)); BlockPos pos = new BlockPos(x, y, z); @@ -64,54 +60,54 @@ public class EntityEarthquake extends EntityMagicConstruct { } } - List targets = WizardryUtilities - .getEntitiesWithinRadius((this.ticksExisted * speed) + 1.5, this.posX, this.posY, this.posZ, world); + } - // In this particular instance, the caster is completely unaffected because they will always be in the - // centre. - targets.remove(this.getCaster()); + List targets = WizardryUtilities + .getEntitiesWithinRadius((this.ticksExisted * speed) + 1.5, this.posX, this.posY, this.posZ, world); - for(EntityLivingBase target : targets){ + // In this particular instance, the caster is completely unaffected because they will always be in the + // centre. + targets.remove(this.getCaster()); - // Searches in a 1 wide ring. - if(this.getDistance(target) > (this.ticksExisted * speed) + 0.5 && target.posY < this.posY + 1 - && target.posY > this.posY - 1){ + for(EntityLivingBase target : targets){ - // Knockback must be removed in this instance, or the target will fall into the floor. - double motionX = target.motionX; - double motionZ = target.motionZ; + // Searches in a 1 wide ring. + if(this.getDistance(target) > (this.ticksExisted * speed) + 0.5 && target.posY < this.posY + 1 + && target.posY > this.posY - 1){ - if(this.isValidTarget(target)){ - target.attackEntityFrom( - MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST), - 10 * this.damageMultiplier); - target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1)); - } + // Knockback must be removed in this instance, or the target will fall into the floor. + double motionX = target.motionX; + double motionZ = target.motionZ; - // All targets are thrown, even those immune to the damage, so they don't fall into the ground. - target.motionX = motionX; - target.motionY = 0.8; // Throws target into the air. - target.motionZ = motionZ; + if(this.isValidTarget(target)){ + target.attackEntityFrom( + MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.BLAST), + 10 * this.damageMultiplier); + target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 400, 1)); + } - // Player motion is handled on that player's client so needs packets - if(target instanceof EntityPlayerMP){ - ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); - } + // All targets are thrown, even those immune to the damage, so they don't fall into the ground. + target.motionX = motionX; + target.motionY = 0.8; // Throws target into the air. + target.motionZ = motionZ; + + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); } } - // TODO: Uncomment once 2.1.0 is released - // }else{ - // - // // Constant 15 blocks for now - // List targets = WizardryUtilities.getEntitiesWithinRadius(15, this.posX, this.posY, - // this.posZ, world, EntityPlayer.class); - // - // float magnitude = 6f * ((float)(this.lifetime - this.ticksExisted))/(float)this.lifetime; - // - // // Makes the screen shake - // for(EntityLivingBase target : targets){ - // target.setAngles(0, this.ticksExisted % 4 < 2 ? magnitude : -magnitude); - // } + } + + if(!world.isRemote){ + // Constant 15 blocks for now + List targets2 = WizardryUtilities.getEntitiesWithinRadius(15, posX, posY, posZ, world, EntityPlayer.class); + + float magnitude = 10f * ((float)(this.lifetime - this.ticksExisted))/(float)this.lifetime; + + // Makes the screen shake + for(EntityPlayer target : targets2){ + target.rotationPitch += this.ticksExisted % 2 == 0 ? magnitude : -magnitude; + } } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java index 97ed7876..e6e20e96 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java @@ -1,26 +1,23 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.util.DamageSource; import net.minecraft.world.World; +import java.util.List; + public class EntityFireRing extends EntityMagicConstruct { - public EntityFireRing(World par1World){ - super(par1World); - this.height = 1.0f; - this.width = 5.0f; - } + // TODO: Implement blast modifiers - public EntityFireRing(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); + public EntityFireRing(World world){ + super(world); this.height = 1.0f; this.width = 5.0f; } @@ -28,7 +25,7 @@ public class EntityFireRing extends EntityMagicConstruct { public void onUpdate(){ if(this.ticksExisted % 40 == 1){ - this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 4.0f, 0.7f); + this.playSound(WizardrySounds.ENTITY_FIRE_RING_AMBIENT, 4.0f, 0.7f); } super.onUpdate(); @@ -48,14 +45,15 @@ public class EntityFireRing extends EntityMagicConstruct { if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)){ - target.setFire(10); + target.setFire(Spells.ring_of_fire.getProperty(Spell.BURN_DURATION).intValue()); + + float damage = Spells.ring_of_fire.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; if(this.getCaster() != null){ - target.attackEntityFrom( - MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.FIRE), - 1 * damageMultiplier); + target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), + DamageType.FIRE), damage); }else{ - target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier); + target.attackEntityFrom(DamageSource.MAGIC, damage); } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java index ecf45f74..509511ff 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java @@ -1,46 +1,35 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityFireSigil extends EntityMagicConstruct { - public EntityFireSigil(World par1World){ - super(par1World); + public EntityFireSigil(World world){ + super(world); this.height = 0.2f; this.width = 2.0f; } - public EntityFireSigil(World par1World, double x, double y, double z, EntityLivingBase caster, - float damageMultiplier){ - super(par1World, x, y, z, caster, -1, damageMultiplier); - this.height = 0.2f; - this.width = 2.0f; - } - - // Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow - // it to stick in blocks. - public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9){ - this.setPosition(par1, par3, par5); - this.setRotation(par7, par8); - } - + @Override public void onUpdate(){ super.onUpdate(); if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, - this.posZ, this.world); + List targets = WizardryUtilities.getEntitiesWithinRadius(width/2, posX, posY, posZ, world); for(EntityLivingBase target : targets){ @@ -52,16 +41,18 @@ public class EntityFireSigil extends EntityMagicConstruct { target.attackEntityFrom(this.getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FIRE) - : DamageSource.MAGIC, 6); + : DamageSource.MAGIC, Spells.fire_sigil.getProperty(Spell.DAMAGE).floatValue() + * damageMultiplier); // Removes knockback target.motionX = velX; target.motionY = velY; target.motionZ = velZ; - if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) target.setFire(10); + if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) + target.setFire(Spells.fire_sigil.getProperty(Spell.BURN_DURATION).intValue()); - this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); + this.playSound(WizardrySounds.ENTITY_FIRE_SIGIL_TRIGGER, 1, 1); // The trap is destroyed once triggered. this.setDead(); @@ -69,20 +60,16 @@ public class EntityFireSigil extends EntityMagicConstruct { } }else if(this.rand.nextInt(15) == 0){ double radius = 0.5 + rand.nextDouble() * 0.3; - double angle = rand.nextDouble() * Math.PI * 2; - world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * Math.cos(angle), this.posY + 0.1, - this.posZ + radius * Math.sin(angle), 0, 0, 0); + float angle = rand.nextFloat() * (float)Math.PI * 2;; + world.spawnParticle(EnumParticleTypes.FLAME, this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, + this.posZ + radius * MathHelper.sin(angle), 0, 0, 0); } } @Override - protected void entityInit(){ + protected void entityInit(){} - } - - /** - * Return whether this entity should be rendered as on fire. - */ + @Override public boolean canRenderOnFire(){ return false; } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java b/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java index f26c8a95..759b7cb9 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java @@ -1,95 +1,364 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.ICustomHitbox; +import electroblob.wizardry.entity.projectile.EntityMagicArrow; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; +import io.netty.buffer.ByteBuf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityXPOrb; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityThrowable; import net.minecraft.network.play.server.SPacketEntityVelocity; -import net.minecraft.util.DamageSource; import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.event.world.ExplosionEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -public class EntityForcefield extends EntityMagicConstruct { +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +@Mod.EventBusSubscriber +public class EntityForcefield extends EntityMagicConstruct implements ICustomHitbox { + + /** Extra radius to search around the forcefield for incoming entities. Any entities with a velocity greater than + * this could potentially penetrate the forcefield. */ + private static final double SEARCH_BORDER_SIZE = 4; + + private static final float BOUNCINESS = 0.2f; + + private float radius; public EntityForcefield(World world){ super(world); - this.height = 6; - this.width = 6; - this.setEntityBoundingBox(new AxisAlignedBB(this.posX - 3, this.posY - 3, this.posZ - 3, this.posX + 3, - this.posY + 3, this.posZ + 3)); + setRadius(3); // Shouldn't be needed but it's a good failsafe + this.ignoreFrustumCheck = true; + this.noClip = true; } - public EntityForcefield(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ + public void setRadius(float radius){ + this.radius = radius; + this.height = 2 * radius; + this.width = 2 * radius; // y-3 because it needs to be centred on the given position - // Damage multiplier is 1 because forcefields do no damage! - super(world, x, y - 3, z, caster, lifetime, 1.0f); - this.height = 6; - this.width = 6; - this.setEntityBoundingBox(new AxisAlignedBB(this.posX - 3, this.posY - 3, this.posZ - 3, this.posX + 3, - this.posY + 3, this.posZ + 3)); + this.setEntityBoundingBox(new AxisAlignedBB(posX - radius, posY - radius, posZ - radius, + posX + radius, posY + radius, posZ + radius)); } + public float getRadius(){ + return radius; + } + + @Override public boolean canBeCollidedWith(){ - return !this.isDead; + return false;//!this.isDead; } - public AxisAlignedBB getCollisionBox(Entity par1Entity){ - return par1Entity.getEntityBoundingBox(); + @Override + public AxisAlignedBB getCollisionBox(Entity entity){ + return null;//entity.getEntityBoundingBox(); } + @Nullable + @Override + public AxisAlignedBB getCollisionBoundingBox(){ + return super.getCollisionBoundingBox(); + } + + @Override + public boolean shouldRenderInPass(int pass){ + return pass == 1; + } + + @Override public void onUpdate(){ super.onUpdate(); - if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(3.5, this.posX, this.posY + 3, - this.posZ, this.world); - for(EntityLivingBase target : targets){ - if(this.isValidTarget(target)){ - double multiplier = (3.5 - target.getDistance(this.posX, this.posY + 3, this.posZ)) * 0.1; - target.addVelocity((target.posX - this.posX) * multiplier, - (target.posY - (this.posY + 3)) * multiplier, (target.posZ - this.posZ) * multiplier); - // Player motion is handled on that player's client so needs packets - if(target instanceof EntityPlayerMP){ - ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + // New forcefield repulsion system: + // Searches for all entities near the forcefield and determines where they will be next tick. + // If they will be inside the forcefield next tick, sets their position and velocity such that they appear to + // bounce off the forcefield and creates impact particle effects and sounds where they hit it + + List targets = WizardryUtilities.getEntitiesWithinRadius(radius + SEARCH_BORDER_SIZE, posX, posY, posZ, world, Entity.class); + + targets.remove(this); + targets.removeIf(t -> t instanceof EntityXPOrb); // Gets annoying since they're attracted to the player + + // Ring of the defender allows players to shoot through their own forcefields + if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), + WizardryItems.ring_defender)){ + targets.removeIf(t -> t instanceof EntityMagicArrow && !this.isValidTarget(((EntityMagicArrow)t).getCaster()) + || t instanceof EntityThrowable && !this.isValidTarget(((EntityThrowable)t).getThrower()) + || t instanceof EntityArrow && !this.isValidTarget(((EntityArrow)t).shootingEntity)); + } + + for(Entity target : targets){ + + if(this.isValidTarget(target)){ + + Vec3d currentPos = Arrays.stream(WizardryUtilities.getVertices(target.getEntityBoundingBox())) + .min(Comparator.comparingDouble(v -> v.distanceTo(this.getPositionVector()))) + .orElse(target.getPositionVector()); // This will never happen, it's just here to make the compiler happy + + double currentDistance = target.getDistance(this); + + // Estimate the target's position next tick + // We have to assume the same vertex is closest or the velocity will be wrong + Vec3d nextTickPos = currentPos.add(target.motionX, target.motionY, target.motionZ); + double nextTickDistance = nextTickPos.distanceTo(this.getPositionVector()); + + boolean flag; + + if(WizardryUtilities.isLiving(target)){ + // Non-allied living entities shouldn't be inside at all + flag = nextTickDistance <= radius; + }else{ + // Non-living entities will bounce off if they hit the forcefield within the next tick... + flag = (currentDistance > radius && nextTickDistance <= radius) // ...from the outside... + || (currentDistance < radius && nextTickDistance >= radius); // ...or from the inside + } + + if(flag){ + + // Ring of interdiction + if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), + WizardryItems.ring_interdiction) && WizardryUtilities.isLiving(target)){ + target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), + MagicDamage.DamageType.MAGIC), 1); + } + + Vec3d targetRelativePos = currentPos.subtract(this.getPositionVector()); + + double nudgeVelocity = this.contains(target) ? -0.1 : 0.1; + if(WizardryUtilities.isLiving(target)) nudgeVelocity = 0.25; + Vec3d extraVelocity = targetRelativePos.normalize().scale(nudgeVelocity); + + // ...make it bounce off! + target.motionX = target.motionX * -BOUNCINESS + extraVelocity.x; + target.motionY = target.motionY * -BOUNCINESS + extraVelocity.y; + target.motionZ = target.motionZ * -BOUNCINESS + extraVelocity.z; + + // Prevents the forcefield bouncing things into the floor + if(target.onGround && target.motionY < 0) target.motionY = 0.1; + + // How far the target needs to move towards the centre (negative means away from the centre) + double distanceTowardsCentre = -(targetRelativePos.length() - radius) - (radius - nextTickDistance); + Vec3d targetNewPos = target.getPositionVector().add(targetRelativePos.normalize().scale(distanceTowardsCentre)); + target.setPosition(targetNewPos.x, targetNewPos.y, targetNewPos.z); + + world.playSound(target.posX, target.posY, target.posZ, WizardrySounds.ENTITY_FORCEFIELD_DEFLECT, + WizardrySounds.SPELLS, 0.3f, 1.3f, false); + + if(!world.isRemote){ + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + } + + }else{ + + Vec3d relativeImpactPos = targetRelativePos.normalize().scale(radius); + + float yaw = (float)Math.atan2(relativeImpactPos.x, -relativeImpactPos.z); + float pitch = (float)Math.asin(relativeImpactPos.y/ radius); + + ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector().add(relativeImpactPos)) + .time(6).face((float)(yaw * 180/Math.PI), (float)(pitch * 180/Math.PI)) + .clr(0.9f, 0.95f, 1).spawn(world); + + for(int i = 0; i < 12; i++){ + + float yaw1 = yaw + 0.3f * (rand.nextFloat() - 0.5f) - (float)Math.PI/2; + float pitch1 = pitch + 0.3f * (rand.nextFloat() - 0.5f); + + float brightness = rand.nextFloat(); + + double r = radius + 0.05; + double x = this.posX + r * MathHelper.cos(yaw1) * MathHelper.cos(pitch1); + double y = this.posY + r * MathHelper.sin(pitch1); + double z = this.posZ + r * MathHelper.sin(yaw1) * MathHelper.cos(pitch1); + + ParticleBuilder.create(Type.DUST).pos(x, y, z).time(6 + rand.nextInt(6)) + .face((float)(yaw1 * 180/Math.PI) + 90, (float)(pitch1 * 180/Math.PI)).scale(1.5f) + .clr(0.7f + 0.3f * brightness, 0.85f + 0.15f * brightness, 1).spawn(world); + } } } } + } + } + + @Override + public boolean contains(Vec3d vec){ + return vec.distanceTo(this.getPositionVector()) < radius; // The surface counts as outside + } + + /** Returns true if the given bounding box is completely inside this forcefield (the surface counts as outside). */ + public boolean contains(AxisAlignedBB box){ + return Arrays.stream(WizardryUtilities.getVertices(box)).allMatch(this::contains); + } + + /** Returns true if the given entity is completely inside this forcefield (the surface counts as outside). */ + public boolean contains(Entity entity){ + return contains(entity.getEntityBoundingBox()); + } + + @Override + public Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness){ + + // We want the intercept between the line and a sphere + // First we need to find the point where the line is closest to the centre + // Then we can use a bit of geometry to find the intercept + + // Find the closest point to the centre + // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html + Vec3d line = endpoint.subtract(origin); + double t = -origin.subtract(this.getPositionVector()).dotProduct(line) / line.lengthSquared(); + Vec3d closestPoint = origin.add(line.scale(t)); + // Now calculate the distance from that point to the centre (squared because that's all we need) + double dsquared = closestPoint.squareDistanceTo(this.getPositionVector()); + double rsquared = Math.pow(radius + fuzziness, 2); + // If the minimum distance is outside the radius (plus fuzziness) then there is no intercept + if(dsquared > rsquared) return null; + // Now do pythagoras to find the other side of the triangle, which is the distance along the line from + // the closest point to the edge of the sphere, and go that far back towards the origin - and that's it! + return closestPoint.subtract(line.normalize().scale(MathHelper.sqrt(rsquared - dsquared))); + } + + // Need to sync the caster because we're now dealing with client-side motion + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeFloat(getRadius()); + if(getCaster() != null) data.writeInt(getCaster().getEntityId()); + } + + @Override + public void readSpawnData(ByteBuf data){ + + super.readSpawnData(data); + + setRadius(data.readFloat()); + + if(!data.isReadable()) return; + + Entity entity = world.getEntityByID(data.readInt()); + + if(entity instanceof EntityLivingBase){ + setCaster((EntityLivingBase)entity); }else{ - for(int i = 1; i < 40; i++){ - float brightness = 0.5f + (rand.nextFloat() * 0.5f); - double radius = 3; - double yaw = rand.nextDouble() * Math.PI * 2; - double pitch = (rand.nextDouble() - 0.5) * Math.PI; - // Generates a spherical pattern of particles - Wizardry.proxy.spawnParticle(WizardryParticleType.BRIGHT_DUST, world, - this.posX + radius * Math.cos(yaw) * Math.cos(pitch), this.posY + 3 + radius * Math.sin(pitch), - this.posZ + radius * Math.sin(yaw) * Math.cos(pitch), 0, 0, 0, 48 + this.rand.nextInt(12), - brightness, brightness, 1.0f); - } + Wizardry.logger.warn("Forcefield caster with ID in spawn data not found"); } } - public boolean attackEntityFrom(DamageSource source, float par2){ - - if(source != null && source.getImmediateSource() != null){ - // Now works for any source of damage. - source.getImmediateSource().playSound(WizardrySounds.SPELL_DEFLECTION, 0.3f, 1.3f); - } - super.attackEntityFrom(source, par2); - return false; - } - - /** - * Return whether this entity should be rendered as on fire. - */ + @Override public boolean canRenderOnFire(){ return false; } + // Prevents any kind of interactions or attacks through the forcefield + // We may as well include projectile damage for this, then it will act as a failsafe + + @SubscribeEvent + public static void onLivingAttackEvent(LivingAttackEvent event){ + + if(event.getSource().getTrueSource() instanceof EntityPlayer && event.getSource().isProjectile() + && ItemArtefact.isArtefactActive((EntityPlayer)event.getSource().getTrueSource(), WizardryItems.ring_defender)){ + return; // Players wearing a ring of the defender can shoot stuff as normal, so don't cancel the event + } + + if(!event.getSource().isUnblockable() && event.getSource().getTrueSource() != null && event.getEntityLiving() != null + && !(event.getSource().getImmediateSource() instanceof EntityForcefield)){ // If the damage was from a forcefield that's ok + // This condition will be false if both entities are outside a forcefield or both are in the same one + if(getSurroundingForcefield(event.getEntityLiving()) != getSurroundingForcefield(event.getSource().getTrueSource())){ + event.setCanceled(true); + } + } + } + + @Nullable + private static EntityForcefield getSurroundingForcefield(World world, Vec3d vec){ + + double searchRadius = 20; + + List forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x, + vec.y, vec.z, world, EntityForcefield.class); + + forcefields.removeIf(f -> !f.contains(vec)); + // There should only be one left at this point since we now have anti-overlap, but commands might bypass that + return forcefields.stream().min(Comparator.comparingDouble(f -> vec.squareDistanceTo(f.getPositionVector()))) + .orElse(null); + } + + @Nullable + private static EntityForcefield getSurroundingForcefield(World world, AxisAlignedBB box, Vec3d vec){ + + double searchRadius = 20; + + List forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x, + vec.y, vec.z, world, EntityForcefield.class); + + forcefields.removeIf(f -> !f.contains(box)); + // There should only be one left at this point since we now have anti-overlap, but commands might bypass that + return forcefields.stream().min(Comparator.comparingDouble(f -> vec.squareDistanceTo(f.getPositionVector()))) + .orElse(null); + } + + @Nullable + private static EntityForcefield getSurroundingForcefield(Entity entity){ + return getSurroundingForcefield(entity.world, entity.getEntityBoundingBox(), entity.getPositionVector()); + } + + @SubscribeEvent + public static void onPlayerInteractEvent(PlayerInteractEvent event){ + + if(!event.isCancelable()) return; // We don't care about clicking empty space + + // For some reason block bounding boxes are relative whereas entity bounding boxes are absolute + AxisAlignedBB box = event.getWorld().getBlockState(event.getPos()).getBoundingBox(event.getWorld(), event.getPos()) + .offset(event.getPos().getX(), event.getPos().getY(), event.getPos().getZ()); + + if(event instanceof PlayerInteractEvent.EntityInteract){ + box = ((PlayerInteractEvent.EntityInteract)event).getTarget().getEntityBoundingBox(); + }else if(event instanceof PlayerInteractEvent.EntityInteractSpecific){ + box = ((PlayerInteractEvent.EntityInteractSpecific)event).getTarget().getEntityBoundingBox(); + } + + // If the player is trying to interact across a forcefield boundary, cancel the event + // The most pragmatic solution here is to use the centres - it's not perfect, but it's simple! + if(getSurroundingForcefield(event.getWorld(), WizardryUtilities.getCentre(box)) + != getSurroundingForcefield(event.getWorld(), event.getEntityPlayer().getPositionVector())){ + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onExplosionEvent(ExplosionEvent event){ + + EntityForcefield forcefield = getSurroundingForcefield(event.getWorld(), event.getExplosion().getPosition()); + // Not a particularly efficient way of doing it but explosions are laggy anyway, and the code is neat :P + event.getExplosion().getAffectedBlockPositions().removeIf(p -> getSurroundingForcefield(event.getWorld(), + new Vec3d(p).add(0.5, 0.5, 0.5)) != forcefield); + + event.getExplosion().getPlayerKnockbackMap().keySet().removeIf(p -> getSurroundingForcefield(p) != forcefield); + } + } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java index 7812ca96..690e7ec8 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java @@ -1,41 +1,31 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityFrostSigil extends EntityMagicConstruct { - public EntityFrostSigil(World par1World){ - super(par1World); + public EntityFrostSigil(World world){ + super(world); this.height = 0.2f; this.width = 2.0f; } - public EntityFrostSigil(World par1World, double x, double y, double z, EntityLivingBase caster, - float damageMultiplier){ - super(par1World, x, y, z, caster, -1, damageMultiplier); - this.height = 0.2f; - this.width = 2.0f; - } - - // Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow - // it to stick in blocks. - public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9){ - this.setPosition(par1, par3, par5); - this.setRotation(par7, par8); - } - + @Override public void onUpdate(){ super.onUpdate(); @@ -48,24 +38,18 @@ public class EntityFrostSigil extends EntityMagicConstruct { for(EntityLivingBase target : targets){ if(this.isValidTarget(target)){ - - double velX = target.motionX; - double velY = target.motionY; - double velZ = target.motionZ; - - target.attackEntityFrom(this.getCaster() != null + + WizardryUtilities.attackEntityWithoutKnockback(target, this.getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FROST) - : DamageSource.MAGIC, 8); - - // Removes knockback - target.motionX = velX; - target.motionY = velY; - target.motionZ = velZ; + : DamageSource.MAGIC, Spells.frost_sigil.getProperty(Spell.DAMAGE).floatValue() + * damageMultiplier); if(!MagicDamage.isEntityImmune(DamageType.FROST, target)) - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 1)); + target.addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.frost_sigil.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.frost_sigil.getProperty(Spell.EFFECT_STRENGTH).intValue())); - this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_FROST_SIGIL_TRIGGER, 1.0f, 1.0f); // The trap is destroyed once triggered. this.setDead(); @@ -73,20 +57,18 @@ public class EntityFrostSigil extends EntityMagicConstruct { } }else if(this.rand.nextInt(15) == 0){ double radius = 0.5 + rand.nextDouble() * 0.3; - double angle = rand.nextDouble() * Math.PI * 2; - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, this.posX + radius * Math.cos(angle), - this.posY + 0.1, this.posZ + radius * Math.sin(angle), 0, 0, 0, 40 + rand.nextInt(10)); + float angle = rand.nextFloat() * (float)Math.PI * 2;; + ParticleBuilder.create(Type.SNOW) + .pos(this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, this.posZ + radius * MathHelper.sin(angle)) + .vel(0, 0, 0) // Required since default for snow is not stationary + .spawn(world); } } @Override - protected void entityInit(){ + protected void entityInit(){} - } - - /** - * Return whether this entity should be rendered as on fire. - */ + @Override public boolean canRenderOnFire(){ return false; } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java b/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java index 5bb73e18..f792d4ef 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java @@ -1,20 +1,13 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.entity.projectile.EntityIceShard; -import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; public class EntityHailstorm extends EntityMagicConstruct { - public EntityHailstorm(World par1World){ - super(par1World); - this.height = 3.0f; - this.width = 5.0f; - } - - public EntityHailstorm(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); + public EntityHailstorm(World world){ + super(world); this.height = 3.0f; this.width = 5.0f; } @@ -25,12 +18,13 @@ public class EntityHailstorm extends EntityMagicConstruct { if(!this.world.isRemote){ // System.out.println(this.rotationYaw); - EntityIceShard iceshard = new EntityIceShard(world, this.posX + rand.nextDouble() * 6 - 3, - this.posY + rand.nextDouble() * 4 - 2, this.posZ + rand.nextDouble() * 6 - 3); - iceshard.motionX = Math.cos(Math.toRadians(this.rotationYaw + 90)); + EntityIceShard iceshard = new EntityIceShard(world); + iceshard.setPosition(this.posX + rand.nextDouble() * 6 - 3, this.posY + rand.nextDouble() * 4 - 2, + this.posZ + rand.nextDouble() * 6 - 3); + iceshard.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90)); iceshard.motionY = -0.6; - iceshard.motionZ = Math.sin(Math.toRadians(this.rotationYaw + 90)); - iceshard.setShootingEntity(this.getCaster()); + iceshard.motionZ = MathHelper.sin((float)Math.toRadians(this.rotationYaw + 90)); + iceshard.setCaster(this.getCaster()); iceshard.damageMultiplier = this.damageMultiplier; this.world.spawnEntity(iceshard); } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java b/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java index 13225ca7..8a56a293 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java @@ -1,41 +1,46 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.EntityArc; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.ItemLightningHammer; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.LightningHammer; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; +import io.netty.buffer.ByteBuf; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.MoverType; import net.minecraft.entity.effect.EntityLightningBolt; -import net.minecraft.init.SoundEvents; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import java.util.List; + public class EntityHammer extends EntityMagicConstruct { /** How long the hammer has been falling for. */ public int fallTime; - public EntityHammer(World par1World){ - super(par1World); - this.setSize(1.0f, 1.9F); - this.noClip = false; - } + public boolean spin = false; - public EntityHammer(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); + public EntityHammer(World world){ + super(world); this.setSize(1.0f, 1.9F); this.motionX = 0.0D; this.motionY = 0.0D; @@ -58,90 +63,102 @@ public class EntityHammer extends EntityMagicConstruct { return this.getEntityBoundingBox(); } + @Override + public void applyEntityCollision(Entity entity){ + super.applyEntityCollision(entity); + } + @Override public void onUpdate(){ super.onUpdate(); - if(this.ticksExisted % 20 == 1 && !this.onGround && world.isRemote){ - // Though this sound does repeat, it stops when it hits the ground. - Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_LIGHTNING, 3.0f, 1.0f, false); - } +// if(this.ticksExisted % 20 == 1 && !this.onGround && world.isRemote){ +// // Though this sound does repeat, it stops when it hits the ground. +// Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_HAMMER_FALLING, WizardrySounds.SPELLS, 3.0f, 1.0f, false); +// } if(this.world.isRemote && this.ticksExisted % 3 == 0){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX - 0.5d + rand.nextDouble(), - this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, 0, 0, 3); + ParticleBuilder.create(Type.SPARK) + .pos(this.posX - 0.5d + rand.nextDouble(), this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble()) + .spawn(world); } - if(!this.world.isRemote){ + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + ++this.fallTime; + this.motionY -= 0.03999999910593033D; + this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ); + this.motionX *= 0.9800000190734863D; + this.motionY *= 0.9800000190734863D; + this.motionZ *= 0.9800000190734863D; - this.prevPosX = this.posX; - this.prevPosY = this.posY; - this.prevPosZ = this.posZ; - ++this.fallTime; - this.motionY -= 0.03999999910593033D; - this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ); - this.motionX *= 0.9800000190734863D; - this.motionY *= 0.9800000190734863D; - this.motionZ *= 0.9800000190734863D; + if(this.onGround){ - if(this.onGround){ + this.motionX *= 0.699999988079071D; + this.motionZ *= 0.699999988079071D; + this.motionY *= -0.5D; - this.motionX *= 0.699999988079071D; - this.motionZ *= 0.699999988079071D; - this.motionY *= -0.5D; + this.rotationPitch = 0; + this.spin = false; - if(this.ticksExisted % 40 == 0){ + if(this.ticksExisted % Spells.lightning_hammer.getProperty(LightningHammer.ATTACK_INTERVAL).floatValue() == 0){ - double seekerRange = 10.0d; + double seekerRange = Spells.lightning_hammer.getProperty(Spell.EFFECT_RADIUS).doubleValue(); - List targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, - this.posY + 1, this.posZ, world); + List targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, + this.posY + 1, this.posZ, world); - // For this spell there is no limit to the amount of secondary targets! - for(EntityLivingBase target : targets){ + int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue(); + while(targets.size() > maxTargets) targets.remove(targets.size() - 1); - if(this.isValidTarget(target)){ + for(EntityLivingBase target : targets){ - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(this.posX, this.posY + this.height - 0.1, this.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - world.spawnEntity(arc); - }else{ - for(int j = 0; j < 8; j++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 - + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + rand.nextFloat(), - target.getEntityBoundingBox().minY + target.height / 2 + rand.nextFloat(), - target.posZ + rand.nextFloat(), 0, 0, 0); - } - } + if(WizardryUtilities.isLiving(target) && this.isValidTarget(target)){ - target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, rand.nextFloat() * 0.4F + 1.5F); + if(world.isRemote){ - if(this.getCaster() != null){ - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), - 6 * damageMultiplier); - WizardryUtilities.applyStandardKnockback(this, target); - }else{ - target.attackEntityFrom(DamageSource.MAGIC, 6 * damageMultiplier); - } + ParticleBuilder.create(Type.LIGHTNING).pos(posX, posY + height - 0.1, posZ) .target(target).spawn(world); + + ParticleBuilder.spawnShockParticles(world, target.posX, + target.getEntityBoundingBox().minY + target.height, target.posZ); + } + + target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F); + + float damage = Spells.lightning_hammer.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier; + + if(this.getCaster() != null){ + WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage( + this, getCaster(), DamageType.SHOCK), damage); + WizardryUtilities.applyStandardKnockback(this, target); + }else{ + target.attackEntityFrom(DamageSource.MAGIC, damage); } } } } + + }else{ + + if(spin) this.setRotation(this.rotationYaw, this.rotationPitch + 15); + + List collided = world.getEntitiesInAABBexcluding(this, this.getCollisionBoundingBox(), e -> e instanceof EntityLivingBase); + + float damage = Spells.lightning_hammer.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier; + + for(Entity entity : collided){ + entity.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), damage); + //if(entity instanceof EntityLivingBase) ((EntityLivingBase)entity).knockBack(this, 2, -this.motionX, -this.motionZ); + } } } @Override public void despawn(){ - this.playSound(SoundEvents.ENTITY_GENERIC_EXPLODE, 1.0F, 1.0f); + this.playSound(WizardrySounds.ENTITY_HAMMER_EXPLODE, 1.0F, 1.0f); if(this.world.isRemote){ this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); @@ -172,6 +189,30 @@ public class EntityHammer extends EntityMagicConstruct { false); world.addWeatherEffect(entitylightning); } + + this.playSound(WizardrySounds.ENTITY_HAMMER_LAND, 1.0F, 0.6f); + } + } + + @Override + public boolean processInitialInteract(EntityPlayer player, EnumHand hand){ + + if(player == this.getCaster() && ItemArtefact.isArtefactActive(player, WizardryItems.ring_hammer) + && player.getHeldItemMainhand().isEmpty() && ticksExisted > 10){ + + this.setDead(); + + ItemStack hammer = new ItemStack(WizardryItems.lightning_hammer); + if(!hammer.hasTagCompound()) hammer.setTagCompound(new NBTTagCompound()); + hammer.getTagCompound().setInteger(ItemLightningHammer.DURATION_NBT_KEY, lifetime); + hammer.setItemDamage(ticksExisted); + hammer.getTagCompound().setFloat(ItemLightningHammer.DAMAGE_MULTIPLIER_NBT_KEY, damageMultiplier); + + player.setHeldItem(EnumHand.MAIN_HAND, hammer); + return true; + + }else{ + return super.processInitialInteract(player, hand); } } @@ -179,12 +220,39 @@ public class EntityHammer extends EntityMagicConstruct { public void writeEntityToNBT(NBTTagCompound nbttagcompound){ super.writeEntityToNBT(nbttagcompound); nbttagcompound.setByte("Time", (byte)this.fallTime); + nbttagcompound.setBoolean("Spin", spin); } @Override public void readEntityFromNBT(NBTTagCompound nbttagcompound){ super.readEntityFromNBT(nbttagcompound); this.fallTime = nbttagcompound.getByte("Time") & 255; + this.spin = nbttagcompound.getBoolean("Spin"); + } + + // Need to sync the caster so they don't have particles spawned at them + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeBoolean(spin); + if(getCaster() != null) data.writeInt(getCaster().getEntityId()); + } + + @Override + public void readSpawnData(ByteBuf data){ + super.readSpawnData(data); + spin = data.readBoolean(); + + if(!data.isReadable()) return; + + Entity entity = world.getEntityByID(data.readInt()); + + if(entity instanceof EntityLivingBase){ + setCaster((EntityLivingBase)entity); + }else{ + Wizardry.logger.warn("Lightning hammer caster with ID in spawn data not found"); + } } @Override diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java b/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java index 25f93849..df3dd462 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java @@ -1,44 +1,42 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityHealAura extends EntityMagicConstruct { + // TODO: Implement blast modifiers + public EntityHealAura(World world){ super(world); this.height = 1.0f; this.width = 5.0f; } - public EntityHealAura(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.height = 1.0f; - this.width = 5.0f; - } - + @Override public void onUpdate(){ if(this.ticksExisted % 25 == 1){ - this.playSound(WizardrySounds.SPELL_LOOP_SPARKLE, 0.1f, 1.0f); + this.playSound(WizardrySounds.ENTITY_HEAL_AURA_AMBIENT, 0.1f, 1.0f); } super.onUpdate(); if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(2.5d, this.posX, this.posY, - this.posZ, this.world); + List targets = WizardryUtilities.getEntitiesWithinRadius(2.5, posX, posY, posZ, world); for(EntityLivingBase target : targets){ @@ -53,9 +51,9 @@ public class EntityHealAura extends EntityMagicConstruct { if(this.getCaster() != null){ target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.RADIANT), - 1 * damageMultiplier); + Spells.healing_aura.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier); }else{ - target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier); + target.attackEntityFrom(DamageSource.MAGIC, Spells.healing_aura.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier); } // Removes knockback @@ -65,24 +63,25 @@ public class EntityHealAura extends EntityMagicConstruct { } }else if(target.getHealth() < target.getMaxHealth() && this.ticksExisted % 5 == 0){ - target.heal(1 * damageMultiplier); + target.heal(Spells.healing_aura.getProperty(Spell.HEALTH).floatValue() * damageMultiplier); } } }else{ - for(int i = 1; i < 3; i++){ + for(int i=1; i<3; i++){ float brightness = 0.5f + (rand.nextFloat() * 0.5f); double radius = rand.nextDouble() * 2.0; - double angle = rand.nextDouble() * Math.PI * 2; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX + radius * Math.cos(angle), - this.posY, this.posZ + radius * Math.sin(angle), 0, 0.05f, 0, 48 + this.rand.nextInt(12), 1.0f, - 1.0f, brightness); + float angle = rand.nextFloat() * (float)Math.PI * 2;; + ParticleBuilder.create(Type.SPARKLE) + .pos(this.posX + radius * MathHelper.cos(angle), this.posY, this.posZ + radius * MathHelper.sin(angle)) + .vel(0, 0.05, 0) + .time(48 + this.rand.nextInt(12)) + .clr(1.0f, 1.0f, brightness) + .spawn(world); } } } - /** - * Return whether this entity should be rendered as on fire. - */ + @Override public boolean canRenderOnFire(){ return false; } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java b/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java index d415480e..75a83bc4 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java @@ -1,40 +1,62 @@ package electroblob.wizardry.entity.construct; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.MoverType; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class EntityIceSpike extends EntityMagicConstruct { + private EnumFacing facing; + public EntityIceSpike(World world){ super(world); this.setSize(0.5f, 1.0f); } - public EntityIceSpike(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.setSize(0.5f, 1.0f); + public void setFacing(EnumFacing facing){ + this.facing = facing; + this.setRotation(-facing.getHorizontalAngle(), WizardryUtilities.getPitch(facing)); + float yaw = (-facing.getHorizontalAngle()) * (float)Math.PI/180; + float pitch = (WizardryUtilities.getPitch(facing) - 90) * (float)Math.PI/180; + Vec3d min = new Vec3d(-width/2, 0, -width/2).rotatePitch(pitch).rotateYaw(yaw); + Vec3d max = new Vec3d(width/2, height, width/2).rotatePitch(pitch).rotateYaw(yaw); + this.setEntityBoundingBox(new AxisAlignedBB(this.getPositionVector().add(min), this.getPositionVector().add(max))); } + public EnumFacing getFacing(){ + return facing; + } + + @Override public void onUpdate(){ + double extensionSpeed = 0; + if(lifetime - this.ticksExisted < 15){ - this.motionY = -0.01 * (this.ticksExisted - (lifetime - 15)); + extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 15)); }else if(lifetime - this.ticksExisted < 25){ - this.motionY = 0; + extensionSpeed = 0; }else if(lifetime - this.ticksExisted < 28){ - this.motionY = 0.25; + extensionSpeed = 0.25; } - this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ); + if(facing != null){ // Will probably be null on the client side, but should never be on the server side + this.move(MoverType.SELF, this.facing.getXOffset() * extensionSpeed, this.facing.getYOffset() * extensionSpeed, + this.facing.getZOffset() * extensionSpeed); + } - if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.SPELL_ICE, 1, 2); + if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.ENTITY_ICE_SPIKE_EXTEND, 1, 2); if(!this.world.isRemote){ for(Object entity : this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox())){ @@ -42,8 +64,10 @@ public class EntityIceSpike extends EntityMagicConstruct { // Potion effect only gets added if the damage succeeded. if(((EntityLivingBase)entity).attackEntityFrom( MagicDamage.causeDirectMagicDamage(this.getCaster(), DamageType.FROST), - 5 * this.damageMultiplier)) - ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0)); + Spells.ice_spikes.getProperty(Spell.DAMAGE).floatValue() * this.damageMultiplier)) + ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.ice_spikes.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.ice_spikes.getProperty(Spell.EFFECT_STRENGTH).intValue())); } } } @@ -51,4 +75,8 @@ public class EntityIceSpike extends EntityMagicConstruct { super.onUpdate(); } + @Override + public int getBrightnessForRender(){ + return 15728880; + } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningPulse.java b/src/main/java/electroblob/wizardry/entity/construct/EntityLightningPulse.java deleted file mode 100644 index edab4346..00000000 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningPulse.java +++ /dev/null @@ -1,26 +0,0 @@ -package electroblob.wizardry.entity.construct; - -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.world.World; - -public class EntityLightningPulse extends EntityMagicConstruct { - - public EntityLightningPulse(World world){ - super(world); - this.setSize(6, 0.2f); - } - - public EntityLightningPulse(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.setSize(6, 0.2f); - } - - /** - * Return whether this entity should be rendered as on fire. - */ - public boolean canRenderOnFire(){ - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java index 7e427637..412df121 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java @@ -1,41 +1,31 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.EntityArc; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; -import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class EntityLightningSigil extends EntityMagicConstruct { - public EntityLightningSigil(World par1World){ - super(par1World); + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; + + public EntityLightningSigil(World world){ + super(world); this.height = 0.2f; this.width = 2.0f; } - public EntityLightningSigil(World par1World, double x, double y, double z, EntityLivingBase caster, - float damageMultiplier){ - super(par1World, x, y, z, caster, -1, damageMultiplier); - this.height = 0.2f; - this.width = 2.0f; - } - - // Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow - // it to stick in blocks. - public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9){ - this.setPosition(par1, par3, par5); - this.setRotation(par7, par8); - } - + @Override public void onUpdate(){ super.onUpdate(); @@ -44,8 +34,6 @@ public class EntityLightningSigil extends EntityMagicConstruct { this.setDead(); } - // if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.world); @@ -58,56 +46,46 @@ public class EntityLightningSigil extends EntityMagicConstruct { double velZ = target.motionZ; // Only works if target is actually damaged to account for hurtResistantTime - if(target.attackEntityFrom( - getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK) - : DamageSource.MAGIC, - 6)){ + if(target.attackEntityFrom(getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, getCaster(), + DamageType.SHOCK) : DamageSource.MAGIC, Spells.lightning_sigil.getProperty(Spell.DIRECT_DAMAGE) + .floatValue() * damageMultiplier)){ // Removes knockback target.motionX = velX; target.motionY = velY; target.motionZ = velZ; - this.playSound(WizardrySounds.SPELL_SPARK, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_LIGHTNING_SIGIL_TRIGGER, 1.0f, 1.0f); // Secondary chaining effect - double seekerRange = 5.0d; + double seekerRange = Spells.lightning_sigil.getProperty(Spell.EFFECT_RADIUS).doubleValue(); List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, target.posX, target.posY + target.height / 2, target.posZ, world); - for(int j = 0; j < Math.min(secondaryTargets.size(), 3); j++){ + for(int j = 0; j < Math.min(secondaryTargets.size(), + Spells.lightning_sigil.getProperty(SECONDARY_MAX_TARGETS).floatValue()); j++){ EntityLivingBase secondaryTarget = secondaryTargets.get(j); if(secondaryTarget != target && this.isValidTarget(secondaryTarget)){ - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(target.posX, target.posY + target.height / 2, target.posZ, - secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, + if(world.isRemote){ + + ParticleBuilder.create(Type.LIGHTNING).entity(target) + .pos(0, target.height/2, 0).target(secondaryTarget).spawn(world); + + ParticleBuilder.spawnShockParticles(world, secondaryTarget.posX, + secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2, secondaryTarget.posZ); - world.spawnEntity(arc); - }else{ - for(int k = 0; k < 8; k++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } } - secondaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, + secondaryTarget.playSound(WizardrySounds.ENTITY_LIGHTNING_SIGIL_TRIGGER, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); secondaryTarget.attackEntityFrom( - MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), 4); + MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.SHOCK), + Spells.lightning_sigil.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier); } } @@ -116,24 +94,20 @@ public class EntityLightningSigil extends EntityMagicConstruct { } } } - // } if(this.world.isRemote && this.rand.nextInt(15) == 0){ double radius = 0.5 + rand.nextDouble() * 0.3; - double angle = rand.nextDouble() * Math.PI * 2; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + radius * Math.cos(angle), - this.posY + 0.1, this.posZ + radius * Math.sin(angle), 0, 0, 0, 3); + float angle = rand.nextFloat() * (float)Math.PI * 2;; + ParticleBuilder.create(Type.SPARK) + .pos(this.posX + radius * MathHelper.cos(angle), this.posY + 0.1, this.posZ + radius * MathHelper.sin(angle)) + .spawn(world); } } @Override - protected void entityInit(){ + protected void entityInit(){} - } - - /** - * Return whether this entity should be rendered as on fire. - */ + @Override public boolean canRenderOnFire(){ return false; } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java b/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java index 36e93b46..14b514a9 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java @@ -1,36 +1,38 @@ package electroblob.wizardry.entity.construct; -import java.lang.ref.WeakReference; -import java.util.UUID; - +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.IEntityOwnable; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.SoundCategory; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.UUID; /** * This class is for all inanimate magical constructs which are not projectiles. It was made from scratch to provide a * unifying superclass for black hole, blizzard, tornado and a few others which all share some characteristics. The - * EntityPlayer instance of the caster, the lifetime and the damage multiplier are stored and synced here. - *

    + * caster UUID, lifetime and damage multiplier are stored here, and lifetime is also synced here. + *

    * When extending this class, override both constructors. Generally speaking, subclasses of this class are areas of * effect which deal damage or apply effects over time. * * @since Wizardry 1.0 */ -public abstract class EntityMagicConstruct extends Entity implements IEntityAdditionalSpawnData { +public abstract class EntityMagicConstruct extends Entity implements IEntityOwnable, IEntityAdditionalSpawnData { - /** The entity that created this construct */ - private WeakReference caster; - - /** - * The UUID of the caster. Note that this is only for loading purposes; during normal updates the actual entity - * instance is stored (so that getEntityByUUID is not called constantly), so this will not always be synced (this is - * why it is private). - */ + /** The UUID of the caster. As of Wizardry 4.2, this is synced, and rather than storing the caster + * instance via a weak reference, it is fetched from the UUID each time it is needed in + * {@link EntityMagicConstruct#getCaster()}. */ private UUID casterUUID; /** @@ -42,43 +44,24 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityAddi /** The damage multiplier for this construct, determined by the wand with which it was cast. */ public float damageMultiplier = 1.0f; - public EntityMagicConstruct(World par1World){ - super(par1World); - this.height = 1.0f; - this.width = 1.0f; - this.noClip = true; - } - - public EntityMagicConstruct(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, - float damageMultiplier){ + public EntityMagicConstruct(World world){ super(world); this.height = 1.0f; this.width = 1.0f; - this.setPosition(x, y, z); - this.caster = new WeakReference(caster); this.noClip = true; - this.lifetime = lifetime; - this.damageMultiplier = damageMultiplier; } // Overrides the original to stop the entity moving when it intersects stuff. The default arrow does this to allow // it to stick in blocks. @Override - public void setPositionAndRotationDirect(double x, double y, double z, float yaw, float pitch, - int posRotationIncrements, boolean teleport){ + @SideOnly(Side.CLIENT) + public void setPositionAndRotationDirect(double x, double y, double z, float yaw, float pitch, int posRotationIncrements, boolean teleport){ this.setPosition(x, y, z); this.setRotation(yaw, pitch); } public void onUpdate(){ - if(this.getCaster() == null && this.casterUUID != null){ - Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); - if(entity instanceof EntityLivingBase){ - this.caster = new WeakReference((EntityLivingBase)entity); - } - } - if(this.ticksExisted > lifetime && lifetime != -1){ this.despawn(); } @@ -128,21 +111,51 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityAddi lifetime = data.readInt(); } + @Nullable + @Override + public UUID getOwnerId(){ + return casterUUID; + } + + @Nullable + @Override + public Entity getOwner(){ + return getCaster(); // Delegate to getCaster + } + /** * Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the entity * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to * another dimension, or this construct simply had no caster in the first place. */ - public EntityLivingBase getCaster(){ - return caster == null ? null : caster.get(); + @Nullable + public EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase + + Entity entity = WizardryUtilities.getEntityByUUID(world, getOwnerId()); + + if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen + Wizardry.logger.warn("{} has a non-living owner!", this); + entity = null; + } + + return (EntityLivingBase)entity; + } + + public void setCaster(@Nullable EntityLivingBase caster){ + this.casterUUID = caster == null ? null : caster.getUniqueID(); } /** - * Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this construct as the + * Shorthand for {@link AllyDesignationSystem#isValidTarget(Entity, Entity)}, with the owner of this construct as the * attacker. Also allows subclasses to override it if they wish to do so. */ public boolean isValidTarget(Entity target){ - return WizardryUtilities.isValidTarget(this.getCaster(), target); + return AllyDesignationSystem.isValidTarget(this.getCaster(), target); + } + + @Override + public SoundCategory getSoundCategory(){ + return WizardrySounds.SPELLS; } @Override diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java b/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java index 5f8abcb0..18c8d51d 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java @@ -1,28 +1,34 @@ package electroblob.wizardry.entity.construct; -import java.util.List; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.spell.Tornado; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.MoverType; -import net.minecraft.entity.passive.EntityPig; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import java.util.List; + public class EntityTornado extends EntityMagicConstruct { private double velX, velZ; @@ -33,78 +39,80 @@ public class EntityTornado extends EntityMagicConstruct { this.width = 5.0f; this.isImmuneToFire = false; } - - public EntityTornado(World world, double x, double y, double z, EntityLivingBase caster, int lifetime, double velX, - double velZ, float damageMultiplier){ - super(world, x, y, z, caster, lifetime, damageMultiplier); - this.height = 8.0f; - this.width = 5.0f; + + public void setHorizontalVelocity(double velX, double velZ){ this.velX = velX; this.velZ = velZ; - this.isImmuneToFire = false; } + @Override public void onUpdate(){ super.onUpdate(); + double radius = Spells.tornado.getProperty(Spell.EFFECT_RADIUS).doubleValue(); + if(this.ticksExisted % 120 == 1 && world.isRemote){ // Repeat is false so that the sound fades out when the tornado does rather than stopping suddenly - Wizardry.proxy.playMovingSound(this, WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f, false); + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_TORNADO_AMBIENT, WizardrySounds.SPELLS, 1.0f, 1.0f, false); } this.move(MoverType.SELF, velX, motionY, velZ); BlockPos pos = new BlockPos(this); - int y = WizardryUtilities.getNearestFloorLevelC(world, pos.up(3), 5); - pos = new BlockPos(pos.getX(), y, pos.getZ()); + Integer y = WizardryUtilities.getNearestSurface(world, pos.up(3), EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR); - if(this.world.getBlockState(pos).getMaterial() == Material.LAVA){ - // Fire tornado! - this.setFire(5); + if(y != null){ + + pos = new BlockPos(pos.getX(), y, pos.getZ()); + + if(this.world.getBlockState(pos).getMaterial() == Material.LAVA){ + // Fire tornado! + this.setFire(5); + } } if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(4.0d, this.posX, this.posY, + List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ + if(target instanceof EntityPlayer && ((getCaster() instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther) + || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){ + continue; + } + if(this.isValidTarget(target)){ double velY = target.motionY; - double dx = this.posX - target.posX > 0 ? 0.5 - (this.posX - target.posX) / 8 - : -0.5 - (this.posX - target.posX) / 8; - double dz = this.posZ - target.posZ > 0 ? 0.5 - (this.posZ - target.posZ) / 8 - : -0.5 - (this.posZ - target.posZ) / 8; + // TODO: This doesn't seem right... + double dx = (this.posX - target.posX > 0 ? 0.5 : -0.5) - (this.posX - target.posX) * 0.125; + double dz = (this.posZ - target.posZ > 0 ? 0.5 : -0.5) - (this.posZ - target.posZ) * 0.125; if(this.isBurning()){ - target.setFire(4); + target.setFire(4); // Just a fun Easter egg so no properties here! } + float damage = Spells.tornado.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + if(this.getCaster() != null){ - target.attackEntityFrom( - MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), - 1 * damageMultiplier); + target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, getCaster(), + DamageType.MAGIC), damage); }else{ - target.attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier); + target.attackEntityFrom(DamageSource.MAGIC, damage); } target.motionX = dx; - target.motionY = velY + 0.2; + target.motionY = velY + Spells.tornado.getProperty(Tornado.UPWARD_ACCELERATION).floatValue(); target.motionZ = dz; // Player motion is handled on that player's client so needs packets if(target instanceof EntityPlayerMP){ ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); } - - // The 'Not Again...' achievement - if(target instanceof EntityPig && WizardryUtilities.getRider(target) instanceof EntityPlayer){ - WizardryAdvancementTriggers.pig_tornado.triggerFor((EntityPlayer)WizardryUtilities.getRider(target)); - } } } }else{ @@ -117,40 +125,44 @@ public class EntityTornado extends EntityMagicConstruct { BlockPos pos1 = new BlockPos(blockX, this.posY + 3, blockZ); - int blockY = WizardryUtilities.getNearestFloorLevelC(world, pos1, 5) - 1; + Integer blockY = WizardryUtilities.getNearestSurface(world, pos1, EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR); - pos1 = new BlockPos(pos1.getX(), blockY, pos1.getZ()); + if(blockY != null){ - IBlockState block = this.world.getBlockState(pos1); + blockY--; - // If the block it found was air or something it can't pick up, it makes a best guess based on the - // biome. - if(!canTornadoPickUpBitsOf(block)){ - block = world.getBiome(pos1).topBlock; - } + pos1 = new BlockPos(pos1.getX(), blockY, pos1.getZ()); - Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, - yPos / 3 + 0.5d, 100, block, pos1); - Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, - yPos / 3 + 0.5d, 100, block, pos1); + IBlockState block = this.world.getBlockState(pos1); - // Sometimes spawns leaf particles if the block is leaves - if(block.getMaterial() == Material.LEAVES && this.rand.nextInt(3) == 0){ - double yPos1 = rand.nextDouble() * 8; - Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, world, - this.posX + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), this.posY + yPos1, - this.posZ + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), 0, -0.05, 0, - 40 + rand.nextInt(10)); - } + // If the block it found was air or something it can't pick up, it makes a best guess based on the biome + if(!canTornadoPickUpBitsOf(block)){ + block = world.getBiome(pos1).topBlock; + } - // Sometimes spawns snow particles if the block is snow - if(block.getMaterial() == Material.SNOW - || block.getMaterial() == Material.CRAFTED_SNOW && this.rand.nextInt(3) == 0){ - double yPos1 = rand.nextDouble() * 8; - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, - this.posX + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), this.posY + yPos1, - this.posZ + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), 0, -0.02, 0, - 40 + rand.nextInt(10)); + Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, + yPos / 3 + 0.5d, 100, block, pos1); + Wizardry.proxy.spawnTornadoParticle(world, this.posX, this.posY + yPos, this.posZ, this.velX, this.velZ, + yPos / 3 + 0.5d, 100, block, pos1); + + // Sometimes spawns leaf particles if the block is leaves, or snow particles if the block is snow + if(this.rand.nextInt(3) == 0){ + + ResourceLocation type = null; + + if(block.getMaterial() == Material.LEAVES) type = Type.LEAF; + if(block.getMaterial() == Material.SNOW || block.getMaterial() == Material.CRAFTED_SNOW) + type = Type.SNOW; + + if(type != null){ + double yPos1 = rand.nextDouble() * 8; + ParticleBuilder.create(type) + .pos(this.posX + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d), this.posY + yPos1, + this.posZ + (rand.nextDouble() * 2 - 1) * (yPos1 / 3 + 0.5d)) + .time(40 + rand.nextInt(10)) + .spawn(world); + } + } } } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java b/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java index e4384e9b..d0cd4c93 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java @@ -1,8 +1,5 @@ package electroblob.wizardry.entity.living; -import java.util.ArrayList; -import java.util.List; - import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent.Source; import electroblob.wizardry.packet.PacketNPCCastSpell; @@ -18,18 +15,21 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import java.util.ArrayList; +import java.util.List; + /** * Entity AI class for use by instances of {@link ISpellCaster}. This deals with pathing, the spell casting itself and * the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods * specified in {@code ISpellCaster}; all the entity class needs to do is implement those methods. + * @param The type of entity that this AI belongs to; must both extend EntityLiving and implement ISpellCaster */ -public class EntityAIAttackSpell extends EntityAIBase { +// Mmmm generics... +public class EntityAIAttackSpell extends EntityAIBase { - /** The entity the AI instance has been applied to. */ - private final EntityLiving attacker; - /** The entity the AI instance has been applied to, but as an ISpellCaster. */ - private final ISpellCaster caster; - /** The tagret to be attacked. */ + /** The entity the AI instance has been applied to. Thanks to type parameters, methods from both EntityLiving and + * ISummonedCreature may be invoked on this field. */ + private final T attacker; private EntityLivingBase target; /** * Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus the @@ -65,23 +65,14 @@ public class EntityAIAttackSpell extends EntityAIBase { * attacking, and also the amount that is added to the cooldown of the spell that has just been cast. * @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down. */ - public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, - int continuousSpellDuration){ - + public EntityAIAttackSpell(T attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){ this.cooldown = -1; - - if(!(attacker instanceof EntityLiving)){ - throw new IllegalArgumentException( - "Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving"); - }else{ - this.caster = attacker; - this.attacker = (EntityLiving)attacker; - this.baseCooldown = baseCooldown; - this.continuousSpellDuration = continuousSpellDuration; - this.speed = speed; - this.maxAttackDistance = maxDistance * maxDistance; - this.setMutexBits(3); - } + this.attacker = attacker; + this.baseCooldown = baseCooldown; + this.continuousSpellDuration = continuousSpellDuration; + this.speed = speed; + this.maxAttackDistance = maxDistance * maxDistance; + this.setMutexBits(3); } @Override @@ -112,11 +103,12 @@ public class EntityAIAttackSpell extends EntityAIBase { } private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){ - caster.setContinuousSpell(spell); + attacker.setContinuousSpell(spell); WizardryPacketHandler.net.sendToAllAround( new PacketNPCCastSpell.Message(attacker.getEntityId(), target == null ? -1 : target.getEntityId(), - EnumHand.MAIN_HAND, spell.id(), modifiers), + EnumHand.MAIN_HAND, spell, modifiers), // Particles are usually only visible from 16 blocks away, so 128 is more than far enough. + // TODO: Why is this one a 128 block radius, whilst the other one is all in dimension? new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128)); } @@ -151,11 +143,11 @@ public class EntityAIAttackSpell extends EntityAIBase { if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible // ...or the spell is cancelled via events... || MinecraftForge.EVENT_BUS - .post(new SpellCastEvent.Tick(attacker, caster.getContinuousSpell(), caster.getModifiers(), - Source.NPC, this.continuousSpellDuration - this.continuousSpellTimer)) + .post(new SpellCastEvent.Tick(Source.NPC, attacker.getContinuousSpell(), attacker, + attacker.getModifiers(), this.continuousSpellDuration - this.continuousSpellTimer)) // ...or the spell no longer succeeds... - || !caster.getContinuousSpell().cast(attacker.world, attacker, EnumHand.MAIN_HAND, - this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers()) + || !attacker.getContinuousSpell().cast(attacker.world, attacker, EnumHand.MAIN_HAND, + this.continuousSpellDuration - this.continuousSpellTimer, target, attacker.getModifiers()) // ...or the time has elapsed... || this.continuousSpellTimer == 0){ @@ -167,8 +159,8 @@ public class EntityAIAttackSpell extends EntityAIBase { }else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){ // On the first tick, if the spell did succeed, fire SpellCastEvent.Post. - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, caster.getContinuousSpell(), - caster.getModifiers(), Source.NPC)); + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, attacker.getContinuousSpell(), + attacker, attacker.getModifiers())); } }else if(--this.cooldown == 0){ @@ -180,7 +172,7 @@ public class EntityAIAttackSpell extends EntityAIBase { double dx = target.posX - attacker.posX; double dz = target.posZ - attacker.posZ; - List spells = new ArrayList(caster.getSpells()); + List spells = new ArrayList(attacker.getSpells()); if(spells.size() > 0){ @@ -194,7 +186,7 @@ public class EntityAIAttackSpell extends EntityAIBase { spell = spells.get(attacker.world.rand.nextInt(spells.size())); - SpellModifiers modifiers = caster.getModifiers(); + SpellModifiers modifiers = attacker.getModifiers(); if(spell != null && attemptCastSpell(spell, modifiers)){ // The spell worked, so we're done! @@ -217,7 +209,7 @@ public class EntityAIAttackSpell extends EntityAIBase { private boolean attemptCastSpell(Spell spell, SpellModifiers modifiers){ // If anything stops the spell working at this point, nothing else happens. - if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(attacker, spell, modifiers, Source.NPC))){ + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.NPC, spell, attacker, modifiers))){ return false; } @@ -230,16 +222,16 @@ public class EntityAIAttackSpell extends EntityAIBase { }else{ - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, spell, modifiers, Source.NPC)); + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, attacker, modifiers)); // For now, the cooldown is just added to the constant base cooldown. I think this // is a reasonable way of doing things; it's certainly better than before. - this.cooldown = this.baseCooldown + spell.cooldown; + this.cooldown = this.baseCooldown + spell.getCooldown(); - if(spell.doesSpellRequirePacket()){ + if(spell.requiresPacket()){ // Sends a packet to all players in dimension to tell them to spawn particles. IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(), target.getEntityId(), - EnumHand.MAIN_HAND, spell.id(), modifiers); + EnumHand.MAIN_HAND, spell, modifiers); WizardryPacketHandler.net.sendToDimension(msg, attacker.world.provider.getDimension()); } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java index c609375c..fc7873b2 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java @@ -1,8 +1,5 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIHurtByTarget; @@ -16,13 +13,15 @@ import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations @@ -37,58 +36,31 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature } @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ + public UUID getOwnerId(){ return casterUUID; } @Override - public void setCasterUUID(UUID uuid){ + public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new blaze minion in the given world. */ public EntityBlazeMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntityBlazeMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // EntityBlaze overrides // This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its - // targeting system with one that targets hostile mobs and takes the ADS into account. + // targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account. @Override protected void initEntityAI(){ super.initEntityAI(); this.targetTasks.taskEntries.clear(); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); - this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityLivingBase.class, + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 0, false, true, this.getTargetSelector())); } @@ -154,30 +126,20 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } - - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override @@ -198,6 +160,6 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java b/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java index dfe9c44f..8dc135d2 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java @@ -1,10 +1,7 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryParticleType; -import io.netty.buffer.ByteBuf; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAILookIdle; @@ -18,12 +15,14 @@ import net.minecraft.world.World; public class EntityDecoy extends EntitySummonedCreature { + /** Creates a new decoy in the given world. */ public EntityDecoy(World world){ super(world); } - - public EntityDecoy(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); + + @Override + public void setCaster(EntityLivingBase caster){ + super.setCaster(caster); this.setAlwaysRenderNameTag(caster instanceof EntityPlayer); } @@ -39,11 +38,17 @@ public class EntityDecoy extends EntitySummonedCreature { @Override public void onDespawn(){ super.onDespawn(); - for(int i = 0; i < 20; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, - this.posX + (this.rand.nextDouble() - 0.5) * this.width, - this.getEntityBoundingBox().minY + this.rand.nextDouble() * this.height, - this.posZ + (this.rand.nextDouble() - 0.5) * this.width, 0, 0, 0, 40, 0.2f, 1.0f, 0.8f); + + if(world.isRemote){ + for(int i = 0; i < 20; i++){ + ParticleBuilder.create(Type.DUST) + .pos(this.posX + (this.rand.nextDouble() - 0.5) * this.width, this.getEntityBoundingBox().minY + + this.rand.nextDouble() * this.height, this.posZ + (this.rand.nextDouble() - 0.5) * this.width) + .time(40) + .clr(0.2f, 1.0f, 0.8f) + .shaded(true) + .spawn(world); + } } } @@ -91,18 +96,19 @@ public class EntityDecoy extends EntitySummonedCreature { return false; } - @Override - public void writeSpawnData(ByteBuf data){ - super.writeSpawnData(data); - if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId()); - } - - @Override - public void readSpawnData(ByteBuf data){ - super.readSpawnData(data); - if(!data.isReadable()) return; - this.setCasterReference( - new WeakReference((EntityLivingBase)this.world.getEntityByID(data.readInt()))); - } + // TESTME: Why was this here? It gets done in ISummonedCreature anyway +// @Override +// public void writeSpawnData(ByteBuf data){ +// super.writeSpawnData(data); +// if(this.getCaster() != null) data.writeInt(this.getCaster().getEntityId()); +// } +// +// @Override +// public void readSpawnData(ByteBuf data){ +// super.readSpawnData(data); +// if(!data.isReadable()) return; +// this.setCasterReference( +// new WeakReference((EntityLivingBase)this.world.getEntityByID(data.readInt()))); +// } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java b/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java index d4789bb6..4a9c8755 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java @@ -1,11 +1,6 @@ package electroblob.wizardry.entity.living; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import com.google.common.base.Predicate; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; @@ -15,30 +10,18 @@ import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.ParticleBuilder.Type; import io.netty.buffer.ByteBuf; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityList; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.IEntityLivingData; -import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction; -import net.minecraft.entity.ai.EntityAINearestAttackableTarget; -import net.minecraft.entity.ai.EntityAIOpenDoor; -import net.minecraft.entity.ai.EntityAIRestrictOpenDoor; -import net.minecraft.entity.ai.EntityAISwimming; -import net.minecraft.entity.ai.EntityAIWander; -import net.minecraft.entity.ai.EntityAIWatchClosest2; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.EntityMob; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTUtil; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; @@ -47,18 +30,28 @@ import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; +import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; +import javax.annotation.Nullable; +import java.util.*; + public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntityAdditionalSpawnData { - private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50); + private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell<>(this, 0.5D, 14.0F, 30, 50); public int textureIndex = 0; - public boolean hasTower = false; + /** True if this evil wizard was spawned as part of a structure (tower or shrine), false if it spawned naturally. */ + public boolean hasStructure = false; + + /** Stores the UUIDs of the other evil wizards spawned in the same group, if any. The wizard will not revenge-target + * entities whose UUIDs are in this set. This is currently used only for shrines. */ + public final Set groupUUIDs = new HashSet<>(); /** The entity selector passed into the new AI methods. */ protected Predicate targetSelector; @@ -92,11 +85,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity protected void entityInit(){ super.entityInit(); this.dataManager.register(HEAL_COOLDOWN, -1); - this.dataManager.register(ELEMENT, 0); + this.dataManager.register(ELEMENT, -1); } @Override protected void initEntityAI(){ + this.tasks.addTask(0, new EntityAISwimming(this)); this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this)); this.tasks.addTask(5, new EntityAIOpenDoor(this, true)); @@ -104,34 +98,31 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity this.tasks.addTask(7, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F)); this.tasks.addTask(7, new EntityAIWander(this, 0.6D)); - this.targetSelector = new Predicate(){ + this.targetSelector = entity -> { - public boolean apply(Entity entity){ + // If the target is valid and not invisible... + if(entity != null && !entity.isInvisible() + && AllyDesignationSystem.isValidTarget(EntityEvilWizard.this, entity)){ - // If the target is valid and not invisible... - if(entity != null && !entity.isInvisible() - && WizardryUtilities.isValidTarget(EntityEvilWizard.this, entity)){ - - // ... and is a player, a summoned creature, another (non-evil) wizard ... - if(entity instanceof EntityPlayer - || (entity instanceof ISummonedCreature || entity instanceof EntityWizard - // ... or in the whitelist ... - || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) - .contains(EntityList.getKey(entity.getClass()))) - // ... and isn't in the blacklist ... - && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) - .contains(EntityList.getKey(entity.getClass()))){ - // ... it can be attacked. - return true; - } + // ... and is a player, a summoned creature, another (non-evil) wizard ... + if(entity instanceof EntityPlayer + || (entity instanceof ISummonedCreature || entity instanceof EntityWizard + // ... or in the whitelist ... + || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) + .contains(EntityList.getKey(entity.getClass()))) + // ... and isn't in the blacklist ... + && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) + .contains(EntityList.getKey(entity.getClass()))){ + // ... it can be attacked. + return true; } - - return false; } + + return false; }; this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true)); - this.targetTasks.addTask(0, new EntityAINearestAttackableTarget(this, EntityLivingBase.class, + this.targetTasks.addTask(0, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 0, false, true, this.targetSelector)); } @@ -151,7 +142,8 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity } public Element getElement(){ - return Element.values()[this.dataManager.get(ELEMENT)]; + int n = this.dataManager.get(ELEMENT); + return n == -1 ? null : Element.values()[n]; } public void setElement(Element element){ @@ -177,6 +169,22 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity public Spell getContinuousSpell(){ return this.continuousSpell; } + + @Override + public int getAimingError(EnumDifficulty difficulty){ + // Being more intelligent than skeletons, wizards are a little more accurate. + switch(difficulty){ + case EASY: return 7; + case NORMAL: return 4; + case HARD: return 1; + default: return 7; // Peaceful counts as easy + } + } + + @Override + public void setRevengeTarget(@Nullable EntityLivingBase target){ + if(target == null || !groupUUIDs.contains(target.getUniqueID())) super.setRevengeTarget(target); + } @Override public void onLivingUpdate(){ @@ -199,26 +207,25 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity // deathTime == 0 checks the wizard isn't currently dying }else if(healCooldown == -1 && this.deathTime == 0){ - // Heal particles + // Heal particles TODO: Change this so it uses the heal spell directly if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)this.posX + rand.nextFloat() * 2 - 1.0F); + for(int i=0; i<10; i++){ + double x = (double)((float)this.posX + rand.nextFloat() * 2 - 1.0F); // Apparently the client side spawns the particles 1 block higher than it should... hence the - // 0.5F. - double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat()); - double d2 = (double)((float)this.posZ + rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, 0, - 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f); + double y = (double)((float)this.posY - 0.5F + rand.nextFloat()); + double z = (double)((float)this.posZ + rand.nextFloat() * 2 - 1.0F); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1F, 0).clr(1, 1, 0.3f).spawn(world); } }else{ if(this.getHealth() < 10){ - // Wizard heals himself more often if he has low health + // Wizards heal themselves more often if they have low health this.setHealCooldown(150); }else{ this.setHealCooldown(400); } - this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F); + this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F); } } if(healCooldown > 0){ @@ -237,9 +244,13 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity // Spell.get(spells[3]).getDisplayName())); // When right-clicked with a spell book in creative, sets one of the spells to that spell - if(player.capabilities.isCreativeMode && stack.getItem() instanceof ItemSpellBook){ - if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){ - this.spells.set(rand.nextInt(3) + 1, Spell.get(stack.getItemDamage())); + if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){ + Spell spell = Spell.byMetadata(stack.getItemDamage()); + if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){ + // The set(...) method returns the element that was replaced - neat! + player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard", + this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(), + spell.getNameForTranslationFormatted())); return true; } } @@ -252,8 +263,9 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity super.writeEntityToNBT(nbt); nbt.setInteger("element", this.getElement().ordinal()); nbt.setInteger("skin", this.textureIndex); - nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id()))); - nbt.setBoolean("hasTower", this.hasTower); + nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata()))); + nbt.setBoolean("hasStructure", this.hasStructure); + nbt.setTag("groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag)); } @Override @@ -261,9 +273,10 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity super.readEntityFromNBT(nbt); this.setElement(Element.values()[nbt.getInteger("element")]); this.textureIndex = nbt.getInteger("skin"); - this.spells = (List)WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT), - (NBTTagInt tag) -> Spell.get(tag.getInt())); - this.hasTower = nbt.getBoolean("hasTower"); + this.spells = (List)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT), + (NBTTagInt tag) -> Spell.byMetadata(tag.getInt())); + this.hasStructure = nbt.getBoolean("hasStructure"); + this.groupUUIDs.addAll(NBTExtras.NBTToList(nbt.getTagList("groupUUIDs", NBT.TAG_COMPOUND), NBTUtil::getUUIDFromTag)); } @Override @@ -274,7 +287,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity @Override public boolean getCanSpawnHere(){ // Evil wizards can only spawn in the specified dimensions - for(int id : Wizardry.settings.evilWizardDimensions){ + for(int id : Wizardry.settings.mobSpawnDimensions){ if(this.dimension == id) return super.getCanSpawnHere(); } @@ -284,27 +297,27 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity @Override protected boolean canDespawn(){ // Evil wizards can only despawn if they don't have a tower (i.e. if they spawned naturally at night) - return !this.hasTower; + return !this.hasStructure; } - @Override - protected float getSoundPitch(){ - return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F; - } +// @Override +// protected float getSoundPitch(){ +// return (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 0.6F; +// } @Override protected SoundEvent getAmbientSound(){ - return SoundEvents.ENTITY_WITCH_AMBIENT; + return WizardrySounds.ENTITY_EVIL_WIZARD_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source){ - return SoundEvents.ENTITY_WITCH_HURT; + return WizardrySounds.ENTITY_EVIL_WIZARD_HURT; } @Override protected SoundEvent getDeathSound(){ - return SoundEvents.ENTITY_WITCH_DEATH; + return WizardrySounds.ENTITY_EVIL_WIZARD_DEATH; } // Although it *looks* like this is still called, in actual fact the only method that calls it is overridden in @@ -323,7 +336,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity // the dropRareDrop method because that would be just as rare as normal mobs; instead this is half as rare. if(this.spells.size() > 0 && rand.nextInt(100) - lootingLevel < 5) this.entityDropItem(new ItemStack(WizardryItems.spell_book, 1, - this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).id()), 0); + this.spells.get(1 + rand.nextInt(this.spells.size() - 1)).metadata()), 0); } @Override @@ -338,17 +351,19 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity textureIndex = this.rand.nextInt(6); - if(rand.nextBoolean()){ - this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]); - }else{ - this.setElement(Element.MAGIC); + if(getElement() == null){ + if(rand.nextBoolean()){ + this.setElement(Element.values()[rand.nextInt(Element.values().length - 1) + 1]); + }else{ + this.setElement(Element.MAGIC); + } } Element element = this.getElement(); // Adds armour. for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ - this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot))); + this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot))); } // Default chance is 0.085f, for reference. @@ -358,12 +373,12 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity // All wizards know magic missile, even if it is disabled. spells.add(Spells.magic_missile); - Tier maxTier = EntityWizard.populateSpells(spells, element, 3, rand); + Tier maxTier = EntityWizard.populateSpells(spells, element, hasStructure, 3, rand); // Now done after the spells so it can take the tier into account. For evil wizards this is slightly different; // it picks a random wand which is at least a high enough tier for the spells the wizard has. Tier tier = Tier.values()[maxTier.ordinal() + rand.nextInt(Tier.values().length - maxTier.ordinal())]; - this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryUtilities.getWand(tier, element))); + this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(WizardryItems.getWand(tier, element))); return data; } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityHuskMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityHuskMinion.java new file mode 100644 index 00000000..3a146734 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/living/EntityHuskMinion.java @@ -0,0 +1,42 @@ +package electroblob.wizardry.entity.living; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.MobEffects; +import net.minecraft.init.SoundEvents; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class EntityHuskMinion extends EntityZombieMinion { + + /** Creates a new husk minion in the given world. */ + public EntityHuskMinion(World world){ + super(world); + } + + @Override + protected boolean shouldBurnInDay(){ + return false; + } + + @Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_HUSK_AMBIENT; } + @Override protected SoundEvent getHurtSound(DamageSource damageSourceIn){ return SoundEvents.ENTITY_HUSK_HURT; } + @Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_HUSK_DEATH; } + @Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_HUSK_STEP; } + + @Override + public boolean attackEntityAsMob(Entity target){ + + boolean flag = super.attackEntityAsMob(target); + + if(flag && this.getHeldItemMainhand().isEmpty() && target instanceof EntityLivingBase){ + float f = this.world.getDifficultyForLocation(new BlockPos(this)).getAdditionalDifficulty(); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.HUNGER, 140 * (int)f)); + } + + return flag; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java b/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java index 423b42e0..c3b118e7 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java @@ -1,23 +1,15 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.ai.EntityAIAttackMelee; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAIMoveTowardsTarget; -import net.minecraft.entity.ai.EntityAINearestAttackableTarget; -import net.minecraft.entity.ai.EntityAIWatchClosest; +import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.EntityIronGolem; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; @@ -26,71 +18,30 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.village.Village; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } - - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new ice giant in the given world. */ public EntityIceGiant(World world){ super(world); this.setSize(1.4F, 2.9F); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntityIceGiant(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setSize(1.4F, 2.9F); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - @Override protected void initEntityAI(){ this.getNavigator().getNodeProcessor().setCanSwim(false); @@ -107,19 +58,9 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature // EntityIronGolem overrides - @Override - protected void updateAITasks(){ - } // Disables home-checking - - @Override - public Village getVillage(){ - return null; - } - - @Override - public int getHoldRoseTick(){ - return 0; - } + @Override protected void updateAITasks(){} // Disables home-checking + @Override public Village getVillage(){ return null; } + @Override public int getHoldRoseTick(){ return 0; } // Implementations @@ -136,18 +77,21 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature @Override public void onSpawn(){ + this.spawnParticleEffect(); } @Override public void onDespawn(){ - this.playSound(WizardrySounds.SPELL_FREEZE, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_ICE_GIANT_DESPAWN, 1.0f, 1.0f); + this.spawnParticleEffect(); + } + + private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 30; i++){ float brightness = 0.5f + (rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, this.world, - this.posX - 1 + rand.nextDouble() * 2, this.posY + rand.nextDouble() * 3, - this.posZ - 1 + rand.nextDouble() * 2, 0, -0.02, 0, 12 + rand.nextInt(8), brightness, - brightness + 0.1f, 1.0f); + ParticleBuilder.create(Type.SPARKLE, this).vel(0, -0.02, 0).time(12 + rand.nextInt(8)) + .clr(brightness, brightness + 0.1f, 1.0f).spawn(world); } } } @@ -158,9 +102,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature super.onLivingUpdate(); if(this.world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, this.world, this.posX - 1 + rand.nextDouble() * 2, - this.posY + rand.nextDouble() * 3, this.posZ - 1 + rand.nextDouble() * 2, 0, -0.02, 0, - 40 + rand.nextInt(10)); + ParticleBuilder.create(Type.SNOW, this).spawn(world); } } @@ -175,7 +117,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature this.applyEnchantments(this, target); - this.playSound(SoundEvents.ENTITY_IRONGOLEM_ATTACK, 1.0F, 1.0F); + this.playSound(WizardrySounds.ENTITY_ICE_GIANT_ATTACK, 1.0F, 1.0F); } @Override @@ -204,35 +146,20 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } + + // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; } @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } - - // This vanilla method has nothing to do with the custom onDespawn() method. - @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override @@ -254,7 +181,7 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java index e1d9caa1..467fc3ad 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java @@ -3,14 +3,11 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.ai.EntityAIBase; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction; -import net.minecraft.entity.ai.EntityAIWander; -import net.minecraft.entity.ai.EntityAIWatchClosest; +import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; @@ -25,12 +22,9 @@ public class EntityIceWraith extends EntityBlazeMinion { /** The version from EntityLivingBase is only used in onLivingUpdate, so it can safely be copied. */ private int jumpTicks; + /** Creates a new ice wraith in the given world. */ public EntityIceWraith(World world){ super(world); - } - - public EntityIceWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); this.isImmuneToFire = false; } @@ -50,9 +44,13 @@ public class EntityIceWraith extends EntityBlazeMinion { if(this.world.isRemote){ for(int i = 0; i < 15; i++){ float brightness = 0.5f + (rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX - 0.5d + rand.nextDouble(), - this.posY + this.height / 2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, - 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.1f, 1.0f); + ParticleBuilder.create(Type.SPARKLE) + .pos(this.posX - 0.5d + rand.nextDouble(), this.posY + this.height / 2 - 0.5d + rand.nextDouble(), + this.posZ - 0.5d + rand.nextDouble()) + .vel(0, 0.05f, 0) + .time(20 + rand.nextInt(10)) + .clr(brightness, brightness + 0.1f, 1.0f) + .spawn(world); } } } @@ -65,7 +63,7 @@ public class EntityIceWraith extends EntityBlazeMinion { } if(this.rand.nextInt(24) == 0){ - this.playSound(WizardrySounds.SPELL_LOOP_WIND, 0.3F + this.rand.nextFloat() / 4, + this.playSound(WizardrySounds.ENTITY_ICE_WRAITH_AMBIENT, 0.3F + this.rand.nextFloat() / 4, this.rand.nextFloat() * 0.7F + 1.4F); } @@ -180,14 +178,24 @@ public class EntityIceWraith extends EntityBlazeMinion { @Override public boolean isBurning(){ // Uses the datawatcher on both sides because fire is private to Entity (and I'm not using reflection here). - // TESTME: This should work, but there may be some issues with updating, so if it doesn't work, copy the + // This should work, but there may be some issues with updating, so if it doesn't work, copy the // version from Entity and use reflection to access the fire field. return this.getFlag(0); } + @Override + public boolean getCanSpawnHere(){ + // Only spawns in the specified dimensions + for(int id : Wizardry.settings.mobSpawnDimensions){ + if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel(); + } + + return false; + } + /** * Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball spawning - * with a one-liner call to WizardryRegistry.iceShard.cast(...) and the removal of redundant local variables. + * with a one-liner call to WizardryLoot.iceShard.cast(...) and the removal of redundant local variables. */ static class AIIceShardAttack extends EntityAIBase { @@ -200,35 +208,28 @@ public class EntityIceWraith extends EntityBlazeMinion { this.setMutexBits(3); } - /** - * Returns whether the EntityAIBase should begin execution. - */ + @Override public boolean shouldExecute(){ EntityLivingBase entitylivingbase = this.blaze.getAttackTarget(); return entitylivingbase != null && entitylivingbase.isEntityAlive(); } - /** - * Execute a one shot task or start executing a continuous task - */ + @Override public void startExecuting(){ this.attackStep = 0; } - /** - * Resets the task - */ + @Override public void resetTask(){ // This might be called setOnFire, but what it really controls is whether the wraith is in attack mode. this.blaze.setOnFire(false); } - /** - * Updates the task - */ + @Override public void updateTask(){ --this.attackTime; EntityLivingBase entitylivingbase = this.blaze.getAttackTarget(); + if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them double d0 = this.blaze.getDistanceSq(entitylivingbase); if(d0 < 4.0D){ @@ -258,7 +259,6 @@ public class EntityIceWraith extends EntityBlazeMinion { // Proof, if it were at all needed, of the elegance and versatility of the spell system. Spells.ice_shard.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers()); - // TODO: Decide if an event should be fired here. I'm guessing no. } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java index ba613e14..d396735f 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java @@ -2,14 +2,11 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.ai.EntityAIBase; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction; -import net.minecraft.entity.ai.EntityAIWander; -import net.minecraft.entity.ai.EntityAIWatchClosest; +import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; @@ -21,10 +18,6 @@ public class EntityLightningWraith extends EntityBlazeMinion { public EntityLightningWraith(World world){ super(world); - } - - public EntityLightningWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); this.isImmuneToFire = false; } @@ -44,9 +37,8 @@ public class EntityLightningWraith extends EntityBlazeMinion { if(this.world.isRemote){ for(int i = 0; i < 15; i++){ float brightness = 0.3f + (rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX - 0.5d + rand.nextDouble(), - this.posY + this.height / 2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, - 0.05f, 0, 20 + rand.nextInt(10), brightness, brightness + 0.2f, 1.0f); + ParticleBuilder.create(Type.SPARKLE, this).vel(0, 0.05, 0).time(20 + rand.nextInt(10)) + .clr(brightness, brightness + 0.2f, 1.0f).spawn(world); } } } @@ -56,10 +48,7 @@ public class EntityLightningWraith extends EntityBlazeMinion { // Fortunately, lightning wraiths don't replace any of blazes' particle effects or the fire sound, they only // add the sparks, so it's fine to call super here. if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0, 3); + ParticleBuilder.create(Type.SPARK, this).spawn(world); } super.onLivingUpdate(); } @@ -83,9 +72,21 @@ public class EntityLightningWraith extends EntityBlazeMinion { return this.getFlag(0); } + @Override + public boolean getCanSpawnHere(){ + // Only spawns in the specified dimensions, during thunderstorms + if(!world.isThundering()) return false; + + for(int id : Wizardry.settings.mobSpawnDimensions){ + if(this.dimension == id) return super.getCanSpawnHere() && this.isValidLightLevel(); + } + + return false; + } + /** * Copied straight from EntityBlaze.AIFireballAttack, with the only changes being replacement of fireball spawning - * with a one-liner call to WizardryRegistry.arc.cast(...) and the removal of redundant local variables. + * with a one-liner call to WizardryLoot.arc.cast(...) and the removal of redundant local variables. */ static class AILightningAttack extends EntityAIBase { @@ -98,35 +99,28 @@ public class EntityLightningWraith extends EntityBlazeMinion { this.setMutexBits(3); } - /** - * Returns whether the EntityAIBase should begin execution. - */ + @Override public boolean shouldExecute(){ EntityLivingBase entitylivingbase = this.blaze.getAttackTarget(); return entitylivingbase != null && entitylivingbase.isEntityAlive(); } - /** - * Execute a one shot task or start executing a continuous task - */ + @Override public void startExecuting(){ this.attackStep = 0; } - /** - * Resets the task - */ + @Override public void resetTask(){ // This might be called setOnFire, but what it really controls is whether the wraith is in attack mode. this.blaze.setOnFire(false); } - /** - * Updates the task - */ + @Override public void updateTask(){ --this.attackTime; EntityLivingBase entitylivingbase = this.blaze.getAttackTarget(); + if(entitylivingbase == null) return; // Dynamic stealth breaks things, let's un-break them double d0 = this.blaze.getDistanceSq(entitylivingbase); if(d0 < 4.0D){ @@ -154,9 +148,7 @@ public class EntityLightningWraith extends EntityBlazeMinion { if(this.attackStep > 1){ // Proof, if it were at all needed, of the elegance and versatility of the spell system. - Spells.arc.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, - new SpellModifiers()); - // TODO: Decide if an event should be fired here. I'm guessing no. + Spells.arc.cast(this.blaze.world, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers()); } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java b/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java index 3257701b..183fb155 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java @@ -1,16 +1,11 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - -import javax.annotation.Nullable; - +import electroblob.wizardry.registry.WizardrySounds; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; import net.minecraft.entity.monster.EntitySlime; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; @@ -22,44 +17,21 @@ import net.minecraft.world.DifficultyInstance; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import javax.annotation.Nullable; +import java.util.UUID; + /** As of Wizardry 1.2, this is now an ISummonedCreature like the rest of them, and it extends EntitySlime. */ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { // Field implementations private int lifetime = 200; - private WeakReference casterReference; private UUID casterUUID; // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } - - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } public EntityMagicSlime(World world){ super(world); @@ -80,7 +52,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { super(world); this.setPosition(target.posX, target.posY, target.posZ); this.startRiding(target); - this.casterReference = new WeakReference(caster); + this.setOwnerId(caster.getUniqueID()); this.setSlimeSize(2, false); // Needs to be called before setting the experience value to 0 this.experienceValue = 0; this.lifetime = lifetime; @@ -88,13 +60,8 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { // EntitySlime overrides - @Override - protected void initEntityAI(){ - } // Has no AI! - - @Override - protected void dealDamage(EntityLivingBase entity){ - } // Handles damage itself + @Override protected void initEntityAI(){} // Has no AI! + @Override protected void dealDamage(EntityLivingBase entity){} // Handles damage itself @Override public void setDead(){ @@ -111,8 +78,8 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { this.world.spawnParticle(EnumParticleTypes.SLIME, x, y, z, (x - this.posX) * 2, (y - this.posY) * 2, (z - this.posZ) * 2); } - this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 2.5f, 0.6f); - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 1.0f, 0.5f); + this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_SPLAT, 2.5f, 0.6f); + this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_EXPLODE, 1.0f, 0.5f); } @Override @@ -155,7 +122,7 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { this.getRidingEntity().attackEntityFrom(DamageSource.MAGIC, 1); ((EntityLivingBase)this.getRidingEntity()) .addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 20, 2)); - this.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_MAGIC_SLIME_ATTACK, 1.0f, 1.0f); this.squishAmount = 0.5F; } }else{ @@ -197,35 +164,15 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } - - // This vanilla method has nothing to do with the custom onDespawn() method. - @Override - protected boolean canDespawn(){ - return false; + // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java b/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java index 0414dfc8..e04428b0 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java @@ -1,9 +1,7 @@ package electroblob.wizardry.entity.living; -import java.util.Collections; -import java.util.List; - import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; @@ -13,7 +11,6 @@ import net.minecraft.entity.ai.EntityAIHurtByTarget; import net.minecraft.entity.ai.EntityAILookIdle; import net.minecraft.entity.ai.EntityAINearestAttackableTarget; import net.minecraft.entity.ai.EntityAIWatchClosest; -import net.minecraft.init.SoundEvents; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.SoundEvent; @@ -22,23 +19,23 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.Collections; +import java.util.List; + public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaster { private double AISpeed = 0.5; // Can attack for 7 seconds, then must cool down for 3. - private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 60, 140); + private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell<>(this, AISpeed, 15f, 60, 140); private Spell continuousSpell; private static final List attack = Collections.singletonList(Spells.flame_ray); + /** Creates a new phoenix in the given world. */ public EntityPhoenix(World world){ super(world); - } - - public EntityPhoenix(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); this.isImmuneToFire = true; this.height = 2.0f; // For some reason this can't be in initEntityAI @@ -100,17 +97,17 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste @Override protected SoundEvent getAmbientSound(){ - return SoundEvents.ENTITY_BLAZE_AMBIENT; + return WizardrySounds.ENTITY_PHOENIX_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source){ - return SoundEvents.ENTITY_BLAZE_HURT; + return WizardrySounds.ENTITY_PHOENIX_HURT; } @Override protected SoundEvent getDeathSound(){ - return SoundEvents.ENTITY_BLAZE_DEATH; + return WizardrySounds.ENTITY_PHOENIX_DEATH; } @Override @@ -147,9 +144,9 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste public void onLivingUpdate(){ // Makes the phoenix hover. - int floorLevel = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(this), 4); + Integer floorLevel = WizardryUtilities.getNearestFloor(world, new BlockPos(this), 4); - if(this.posY - floorLevel > 3){ + if(floorLevel == null || this.posY - floorLevel > 3){ this.motionY = -0.1; }else if(this.posY - floorLevel < 2){ this.motionY = 0.1; @@ -159,13 +156,13 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste // Living sound if(this.rand.nextInt(24) == 0){ - this.playSound(SoundEvents.BLOCK_FIRE_AMBIENT, 1.0F + this.rand.nextFloat(), + this.playSound(WizardrySounds.ENTITY_PHOENIX_BURN, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F); } // Flapping sound effect if(this.ticksExisted % 22 == 0){ - this.playSound(SoundEvents.ENTITY_ENDERDRAGON_FLAP, 1.0F, 1.0f); + this.playSound(WizardrySounds.ENTITY_PHOENIX_FLAP, 1.0F, 1.0f); } for(int i = 0; i < 2; i++){ @@ -185,8 +182,7 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste } @Override - public void fall(float distance, float damageMultiplier){ - } // Immune to fall damage + public void fall(float distance, float damageMultiplier){} // Immune to fall damage @Override public boolean isBurning(){ diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java index 48f78093..55963b90 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java @@ -1,22 +1,15 @@ package electroblob.wizardry.entity.living; -import java.util.Collections; -import java.util.List; - -import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.EntityAIAttackMelee; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAINearestAttackableTarget; -import net.minecraft.entity.ai.EntityAIWander; +import net.minecraft.entity.ai.*; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; @@ -25,22 +18,22 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.Collections; +import java.util.List; + public class EntityShadowWraith extends EntitySummonedCreature implements ISpellCaster { // TODO: This currently doesn't fly like it used to. Should it, or does it not matter? private double AISpeed = 1.0; - private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0); + private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0); private static final List attack = Collections.singletonList(Spells.darkness_orb); + /** Creates a new shadow wraith in the gievn world. */ public EntityShadowWraith(World world){ super(world); - } - - public EntityShadowWraith(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); // For some reason this can't be in initEntityAI this.tasks.addTask(0, this.spellAttackAI); } @@ -99,17 +92,17 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell @Override protected SoundEvent getAmbientSound(){ - return SoundEvents.ENTITY_BLAZE_AMBIENT; + return WizardrySounds.ENTITY_SHADOW_WRAITH_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source){ - return SoundEvents.ENTITY_BLAZE_HURT; + return WizardrySounds.ENTITY_SHADOW_WRAITH_HURT; } @Override protected SoundEvent getDeathSound(){ - return SoundEvents.ENTITY_BLAZE_DEATH; + return WizardrySounds.ENTITY_SHADOW_WRAITH_DEATH; } @Override @@ -128,9 +121,8 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell if(this.world.isRemote){ for(int i = 0; i < 15; i++){ float brightness = rand.nextFloat() * 0.4f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX - 0.5d + rand.nextDouble(), - this.posY + this.height / 2 - 0.5d + rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble(), 0, - 0.05f, 0, 20 + rand.nextInt(10), brightness, 0.0f, brightness); + ParticleBuilder.create(Type.SPARKLE, this).vel(0, 0.05, 0).time(20 + rand.nextInt(10)) + .clr(brightness, 0.0f, brightness).spawn(world); } } } @@ -139,7 +131,7 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell public void onLivingUpdate(){ if(this.rand.nextInt(24) == 0){ - this.playSound(SoundEvents.BLOCK_PORTAL_AMBIENT, 1.0F + this.rand.nextFloat(), + this.playSound(WizardrySounds.ENTITY_SHADOW_WRAITH_NOISE, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F); } @@ -149,26 +141,25 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell } if(world.isRemote){ - for(int i = 0; i < 2; i++){ + + for(int i=0; i<2; i++){ + world.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0); + world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0); + float brightness = rand.nextFloat() * 0.2f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0.05f, 0, - 20 + rand.nextInt(10), brightness, 0.0f, brightness); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0.1f, - 0.0f, 0.0f); + + ParticleBuilder.create(Type.SPARKLE, this).vel(0, 0.05, 0).time(20 + rand.nextInt(10)) + .clr(brightness, 0.0f, brightness).spawn(world); + + ParticleBuilder.create(Type.DARK_MAGIC, this).clr(0.1f, 0.0f, 0.0f).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySilverfishMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntitySilverfishMinion.java index 4cff78eb..d7d92d76 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySilverfishMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySilverfishMinion.java @@ -1,10 +1,8 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIAttackMelee; @@ -19,69 +17,33 @@ import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + public class EntitySilverfishMinion extends EntitySilverfish implements ISummonedCreature { + public static final int MAX_GENERATIONS = 5; + // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; + private int generation = 1; + // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } - - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new silverfish minion in the given world. */ public EntitySilverfishMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntitySilverfishMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // EntitySilverfish overrides @Override protected void initEntityAI(){ @@ -89,7 +51,7 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone this.tasks.addTask(1, new EntityAISwimming(this)); this.tasks.addTask(4, new EntityAIAttackMelee(this, 1.0D, false)); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); - this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityLivingBase.class, + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 0, false, true, this.getTargetSelector())); } @@ -119,9 +81,10 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, this.posX + this.rand.nextFloat(), - this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.3f, - 0.3f, 0.3f); + ParticleBuilder.create(Type.DARK_MAGIC) + .pos(this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat()) + .clr(0.3f, 0.3f, 0.3f) + .spawn(world); } } } @@ -137,13 +100,16 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone public void onKillEntity(EntityLivingBase victim){ // If the silverfish has a summoner, this is actually called from Wizardry's event handler rather than by // Minecraft itself, because the damagesource being changed causes it not to get called. - if(!this.world.isRemote){ + if(!this.world.isRemote && generation < MAX_GENERATIONS){ // Summons 1-4 more silverfish int alliesToSummon = rand.nextInt(4) + 1; for(int i = 0; i < alliesToSummon; i++){ - EntitySilverfishMinion silverfish = new EntitySilverfishMinion(this.world, victim.posX, victim.posY, - victim.posZ, this.getCaster(), this.lifetime); + EntitySilverfishMinion silverfish = new EntitySilverfishMinion(this.world); + silverfish.setPosition(victim.posX, victim.posY, victim.posZ); + silverfish.setCaster(this.getCaster()); + silverfish.setLifetime(this.getLifetime()); + silverfish.generation = this.generation + 1; this.world.spawnEntity(silverfish); } } @@ -165,45 +131,32 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone public void writeEntityToNBT(NBTTagCompound nbttagcompound){ super.writeEntityToNBT(nbttagcompound); this.writeNBTDelegate(nbttagcompound); + nbttagcompound.setInteger("generation", this.generation); } @Override public void readEntityFromNBT(NBTTagCompound nbttagcompound){ super.readEntityFromNBT(nbttagcompound); this.readNBTDelegate(nbttagcompound); + this.generation = nbttagcompound.getInteger("generation"); } // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } - - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override @@ -225,6 +178,6 @@ public class EntitySilverfishMinion extends EntitySilverfish implements ISummone @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java index 167d34f1..2d10fd34 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java @@ -1,105 +1,64 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.Calendar; -import java.util.UUID; - -import javax.annotation.Nullable; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAIHurtByTarget; import net.minecraft.entity.ai.EntityAINearestAttackableTarget; import net.minecraft.entity.ai.attributes.AttributeModifier; -import net.minecraft.entity.monster.EntitySkeleton; +import net.minecraft.entity.monster.AbstractSkeleton; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.Items; +import net.minecraft.init.SoundEvents; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; +import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.EnumHand; -import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.ResourceLocation; +import net.minecraft.util.*; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; -public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCreature { +import javax.annotation.Nullable; +import java.util.Calendar; +import java.util.UUID; + +// Extends AbstractSkeleton because EntitySkeleton drops skulls, which we don't want +public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } - - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new skeleton minion in the given world. */ public EntitySkeletonMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntitySkeletonMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // EntitySkeleton overrides // This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its - // targeting system with one that targets hostile mobs and takes the ADS into account. + // targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account. @Override protected void initEntityAI(){ super.initEntityAI(); this.targetTasks.taskEntries.clear(); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); - this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityLivingBase.class, + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 0, false, true, this.getTargetSelector())); } @@ -114,13 +73,13 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){ // Can't call super, so the code from the next level up (EntityLiving) had to be copied as well. this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE) - .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); }else{ this.setLeftHanded(false); - } + } // Halloween pumpkin heads! Why not? if(this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()){ @@ -136,6 +95,12 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre return livingdata; } + // Since we're extending AbstractSkeleton these aren't set by the superclass like normal + @Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_SKELETON_AMBIENT; } + @Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_SKELETON_HURT; } + @Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_SKELETON_DEATH; } + @Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_SKELETON_STEP; } + // Implementations @Override @@ -194,40 +159,26 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } - - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override public boolean canAttackClass(Class entityType){ - return true; + // Returns true unless the given entity type is a flying entity and this skeleton does not have a bow. + return !EntityFlying.class.isAssignableFrom(entityType) || this.getHeldItemMainhand().getItem() instanceof ItemBow; } @Override @@ -243,6 +194,6 @@ public class EntitySkeletonMinion extends EntitySkeleton implements ISummonedCre @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java index ea9d3cb3..b4efebc2 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java @@ -1,10 +1,9 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.WizardryUtilities.Operations; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; @@ -26,71 +25,30 @@ import net.minecraft.world.DifficultyInstance; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } - - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new spider minion in the given world. */ public EntitySpiderMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntitySpiderMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // EntitySpider overrides // This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its - // targeting system with one that targets hostile mobs and takes the ADS into account. + // targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account. @Override protected void initEntityAI(){ super.initEntityAI(); @@ -109,7 +67,7 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre // Can't call super, so the code from the next level up (EntityLiving) had to be copied as well. this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE) - .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); @@ -147,9 +105,10 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, this.posX + this.rand.nextFloat(), - this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0.0d, 0.0d, 0.0d, 0, 0.1f, - 0.2f, 0.0f); + ParticleBuilder.create(Type.DARK_MAGIC) + .pos(this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat()) + .clr(0.1f, 0.2f, 0.0f) + .spawn(world); } } } @@ -196,35 +155,20 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } - - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override @@ -246,6 +190,6 @@ public class EntitySpiderMinion extends EntityCaveSpider implements ISummonedCre @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java index 01fa6b7e..98deb764 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java @@ -1,10 +1,10 @@ package electroblob.wizardry.entity.living; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -15,7 +15,6 @@ import net.minecraft.entity.passive.EntityHorse; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; @@ -33,6 +32,10 @@ public class EntitySpiritHorse extends EntityHorse { private int idleTimer = 0; + private int dispelTimer = 0; + + private static final int DISPEL_TIME = 10; + public EntitySpiritHorse(World par1World){ super(par1World); } @@ -42,7 +45,7 @@ public class EntitySpiritHorse extends EntityHorse { if(this.hasCustomName()){ return this.getCustomNameTag(); }else{ - return I18n.translateToLocal("entity.wizardry.Spirit Horse.name"); + return I18n.translateToLocal("entity.wizardry.spirit_horse.name"); } } @@ -92,21 +95,13 @@ public class EntitySpiritHorse extends EntityHorse { // Allows the owner (but not other players) to dispel the spirit horse using a wand (shift-clicking, because // clicking mounts the horse in this case). - if(itemstack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){ + if(itemstack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){ // Prevents accidental double clicking. if(this.ticksExisted > 20){ - for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, - 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f); - } - this.setDead(); - if(WizardData.get(player) != null){ - WizardData.get(player).hasSpiritHorse = false; - } - this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F); + + this.dispelTimer++; + + this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F); // This is necessary to prevent the wand's spell being cast when performing this action. return true; } @@ -115,15 +110,13 @@ public class EntitySpiritHorse extends EntityHorse { return super.processInteract(player, hand); } - - @Override - public void onDeath(DamageSource par1DamageSource){ - - super.onDeath(par1DamageSource); - - // Allows player to summon another spirit horse once this one has died. - if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){ - WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false; + + private void spawnAppearParticles(){ + for(int i=0; i<15; i++){ + double x = this.posX - this.width / 2 + this.rand.nextFloat() * width; + double y = this.posY + this.height * this.rand.nextFloat() + 0.2f; + double z = this.posZ - this.width / 2 + this.rand.nextFloat() * width; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).spawn(world); } } @@ -140,17 +133,27 @@ public class EntitySpiritHorse extends EntityHorse { } } + public float getOpacity(){ + return 1 - (float)dispelTimer/DISPEL_TIME; + } + @Override public void onUpdate(){ super.onUpdate(); + if(dispelTimer > 0){ + if(dispelTimer++ > DISPEL_TIME){ + this.setDead(); + } + } + // Adds a dust particle effect if(this.world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f); + double x = this.posX - this.width / 2 + this.rand.nextFloat() * width; + double y = this.posY + this.height * this.rand.nextFloat() + 0.2f; + double z = this.posZ - this.width / 2 + this.rand.nextFloat() * width; + ParticleBuilder.create(Type.DUST).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).shaded(true).spawn(world); } // Spirit horse disappears a short time after being dismounted. @@ -161,21 +164,10 @@ public class EntitySpiritHorse extends EntityHorse { } if(this.idleTimer > 200){ - if(this.world.isRemote){ - for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, - 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f); - } - } - this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F); - // Allows player to summon another spirit horse once this one has disappeared. - if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){ - WizardData.get((EntityPlayer)this.getOwner()).hasSpiritHorse = false; - } - this.setDead(); + + this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F); + + this.dispelTimer++; } } @@ -189,13 +181,7 @@ public class EntitySpiritHorse extends EntityHorse { // Adds Particles on spawn. Due to client/server differences this cannot be done in the item. if(this.world.isRemote){ - for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, 48 + this.rand.nextInt(12), - 0.8f, 0.8f, 1.0f); - } + this.spawnAppearParticles(); } return super.onInitialSpawn(difficulty, data); @@ -214,7 +200,7 @@ public class EntitySpiritHorse extends EntityHorse { @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getOwner() != null; + return Wizardry.settings.summonedCreatureNames && getOwner() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritWolf.java b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritWolf.java index af5d4512..3c8929b5 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritWolf.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritWolf.java @@ -1,28 +1,17 @@ package electroblob.wizardry.entity.living; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityAgeable; import net.minecraft.entity.IEntityLivingData; -import net.minecraft.entity.ai.EntityAIAttackMelee; -import net.minecraft.entity.ai.EntityAIFollowOwner; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAILeapAtTarget; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget; -import net.minecraft.entity.ai.EntityAIOwnerHurtTarget; -import net.minecraft.entity.ai.EntityAISit; -import net.minecraft.entity.ai.EntityAISwimming; -import net.minecraft.entity.ai.EntityAIWander; -import net.minecraft.entity.ai.EntityAIWatchClosest; +import net.minecraft.entity.ai.*; import net.minecraft.entity.passive.EntityWolf; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; @@ -36,9 +25,12 @@ import net.minecraft.world.World; */ public class EntitySpiritWolf extends EntityWolf { - public EntitySpiritWolf(World par1World){ + private int dispelTimer = 0; - super(par1World); + private static final int DISPEL_TIME = 10; + + public EntitySpiritWolf(World world){ + super(world); this.experienceValue = 0; } @@ -59,47 +51,48 @@ public class EntitySpiritWolf extends EntityWolf { this.targetTasks.addTask(3, new EntityAIHurtByTarget(this, true, new Class[0])); } - @Override - public void onDeath(DamageSource source){ - - // Allows player to summon another spirit wolf once this one has died. - // NOTE: This has been known to work incorrectly. - if(this.getOwner() instanceof EntityPlayer && WizardData.get((EntityPlayer)this.getOwner()) != null){ - WizardData.get((EntityPlayer)this.getOwner()).hasSpiritWolf = false; - } - - super.onDeath(source); - } - @Override public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, IEntityLivingData livingdata){ // Adds Particles on spawn. Due to client/server differences this cannot be done // in the item. if(this.world.isRemote){ - for(int i = 0; i < 15; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, 48 + this.rand.nextInt(12), - 0.8f, 0.8f, 1.0f); - } + this.spawnAppearParticles(); } return livingdata; } + private void spawnAppearParticles(){ + for(int i=0; i<15; i++){ + double x = this.posX - this.width / 2 + this.rand.nextFloat() * width; + double y = this.posY + this.height * this.rand.nextFloat() + 0.2f; + double z = this.posZ - this.width / 2 + this.rand.nextFloat() * width; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).spawn(world); + } + } + + public float getOpacity(){ + return 1 - (float)dispelTimer/DISPEL_TIME; + } + @Override public void onUpdate(){ super.onUpdate(); + if(dispelTimer > 0){ + if(dispelTimer++ > DISPEL_TIME){ + this.setDead(); + } + } + // Adds a dust particle effect if(this.world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, 0, 0.8f, 0.8f, 1.0f); + double x = this.posX - this.width / 2 + this.rand.nextFloat() * width; + double y = this.posY + this.height * this.rand.nextFloat() + 0.2f; + double z = this.posZ - this.width / 2 + this.rand.nextFloat() * width; + ParticleBuilder.create(Type.DUST).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).shaded(true).spawn(world); } } @@ -112,21 +105,13 @@ public class EntitySpiritWolf extends EntityWolf { // Allows the owner (but not other players) to dispel the spirit wolf using a // wand. - if(stack.getItem() instanceof ItemWand && this.getOwner() == player && player.isSneaking()){ + if(stack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){ // Prevents accidental double clicking. if(this.ticksExisted > 20){ - for(int i = 0; i < 10; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX - this.width / 2 + this.rand.nextFloat() * width, - this.posY + this.height * this.rand.nextFloat() + 0.2f, - this.posZ - this.width / 2 + this.rand.nextFloat() * width, 0, 0, 0, - 48 + this.rand.nextInt(12), 0.8f, 0.8f, 1.0f); - } - this.setDead(); - if(WizardData.get(player) != null){ - WizardData.get(player).hasSpiritWolf = false; - } - this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F); + + this.dispelTimer++; + + this.playSound(WizardrySounds.ENTITY_SPIRIT_WOLF_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F); // This is necessary to prevent the wand's spell being cast when performing this // action. return true; @@ -177,7 +162,7 @@ public class EntitySpiritWolf extends EntityWolf { public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking // directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getOwner() != null; + return Wizardry.settings.summonedCreatureNames && getOwner() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java b/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java index 29e2c061..ce87a216 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java @@ -1,23 +1,15 @@ package electroblob.wizardry.entity.living; -import java.util.Collections; -import java.util.List; - -import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.EntityAIAttackMelee; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAILookIdle; -import net.minecraft.entity.ai.EntityAINearestAttackableTarget; -import net.minecraft.entity.ai.EntityAIWander; +import net.minecraft.entity.ai.*; import net.minecraft.entity.effect.EntityLightningBolt; -import net.minecraft.init.SoundEvents; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.SoundEvent; @@ -25,21 +17,22 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.Collections; +import java.util.List; + public class EntityStormElemental extends EntitySummonedCreature implements ISpellCaster { private double AISpeed = 1.0; - private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0); + private EntityAIAttackSpell spellAttackAI = new EntityAIAttackSpell(this, AISpeed, 15f, 30, 0); private static final List attack = Collections.singletonList(Spells.lightning_disc); + /** Creates a new storm elemental in the given world. */ public EntityStormElemental(World world){ super(world); - } - - public EntityStormElemental(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world, x, y, z, caster, lifetime); // For some reason this can't be in initEntityAI + // TESTME: May need to be inside a !world.isRemote check. this.tasks.addTask(0, this.spellAttackAI); } @@ -92,17 +85,17 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe @Override protected SoundEvent getAmbientSound(){ - return SoundEvents.ENTITY_BLAZE_AMBIENT; + return WizardrySounds.ENTITY_STORM_ELEMENTAL_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source){ - return SoundEvents.ENTITY_BLAZE_HURT; + return WizardrySounds.ENTITY_STORM_ELEMENTAL_HURT; } @Override protected SoundEvent getDeathSound(){ - return SoundEvents.ENTITY_BLAZE_DEATH; + return WizardrySounds.ENTITY_STORM_ELEMENTAL_DEATH; } @Override @@ -120,11 +113,11 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe public void onLivingUpdate(){ if(this.ticksExisted % 120 == 1){ - this.playSound(WizardrySounds.SPELL_LOOP_WIND, 1.0f, 1.0f); + this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_WIND, 1.0f, 1.0f); } if(this.rand.nextInt(24) == 0){ - this.playSound(SoundEvents.ENTITY_BLAZE_BURN, 1.0F + this.rand.nextFloat(), + this.playSound(WizardrySounds.ENTITY_STORM_ELEMENTAL_BURN, 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F); } @@ -135,22 +128,24 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe if(world.isRemote){ - for(int i = 0; i < 2; ++i){ + for(int i=0; i<2; ++i){ + world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0, 0, 0); + + ParticleBuilder.create(Type.SPARK, this).spawn(world); } - for(int i = 0; i < 10; i++){ + for(int i=0; i<10; i++){ + float brightness = rand.nextFloat() * 0.2f; double dy = this.rand.nextDouble() * (double)this.height; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE_ROTATING, world, this.posX, this.posY + dy, - this.posZ, 0, 0, 0, 20 + rand.nextInt(10), 0, brightness, brightness, false, 0.2f + 0.5f * dy); + + ParticleBuilder.create(Type.SPARKLE).pos(this.posX, this.posY + dy, this.posZ) + .time(20 + rand.nextInt(10)).clr(0, brightness, brightness)//.entity(this) + .spin(0.2 + 0.5 * dy, 0.1 + 0.05 * world.rand.nextDouble()).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityStrayMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityStrayMinion.java new file mode 100644 index 00000000..6edcc700 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/living/EntityStrayMinion.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.entity.living; + +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityTippedArrow; +import net.minecraft.init.MobEffects; +import net.minecraft.init.SoundEvents; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; +import net.minecraft.util.SoundEvent; +import net.minecraft.world.World; + +public class EntityStrayMinion extends EntitySkeletonMinion { + + /** Creates a new stray minion in the given world. */ + public EntityStrayMinion(World world){ + super(world); + } + + @Override protected SoundEvent getAmbientSound(){ return SoundEvents.ENTITY_STRAY_AMBIENT; } + @Override protected SoundEvent getHurtSound(DamageSource source){ return SoundEvents.ENTITY_STRAY_HURT; } + @Override protected SoundEvent getDeathSound(){ return SoundEvents.ENTITY_STRAY_DEATH; } + @Override protected SoundEvent getStepSound(){ return SoundEvents.ENTITY_STRAY_STEP; } + + @Override + protected EntityArrow getArrow(float distanceFactor){ + + EntityArrow entityarrow = super.getArrow(distanceFactor); + + if(entityarrow instanceof EntityTippedArrow){ + ((EntityTippedArrow)entityarrow).addEffect(new PotionEffect(MobEffects.SLOWNESS, 600)); + } + + return entityarrow; + } +} diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySummonedCreature.java b/src/main/java/electroblob/wizardry/entity/living/EntitySummonedCreature.java index 64d2e46b..12d623e7 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySummonedCreature.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySummonedCreature.java @@ -1,8 +1,5 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; import net.minecraft.entity.EntityCreature; import net.minecraft.entity.EntityFlying; @@ -14,8 +11,11 @@ import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + /** * Abstract base implementation of {@link ISummonedCreature} which is the superclass to all custom summoned entities * (i.e. entities that don't extend vanilla/mod creatures). Also serves as an example of how to correctly implement the @@ -29,8 +29,7 @@ import net.minecraft.world.World; public abstract class EntitySummonedCreature extends EntityCreature implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations @@ -45,48 +44,21 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I } @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ + public UUID getOwnerId(){ return casterUUID; } @Override - public void setCasterUUID(UUID uuid){ + public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new summoned creature in the given world. */ public EntitySummonedCreature(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntitySummonedCreature(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // Implementations @Override @@ -134,35 +106,20 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } + + // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; } @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } - - // This vanilla method has nothing to do with the custom onDespawn() method. - @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override @@ -171,7 +128,6 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I return !EntityFlying.class.isAssignableFrom(entityType) || this.hasRangedAttack(); } - // TODO: Backport the following two methods to 1.7.10. @Override public ITextComponent getDisplayName(){ if(getCaster() != null){ @@ -185,7 +141,7 @@ public abstract class EntitySummonedCreature extends EntityCreature implements I @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } // Specific to EntitySummonedCreature, remove if copying diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java new file mode 100644 index 00000000..73ed60ba --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java @@ -0,0 +1,151 @@ +package electroblob.wizardry.entity.living; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.ai.EntityAIHurtByTarget; +import net.minecraft.entity.ai.EntityAINearestAttackableTarget; +import net.minecraft.entity.monster.EntityVex; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.UUID; + +public class EntityVexMinion extends EntityVex implements ISummonedCreature { + + // Field implementations + private int lifetime = -1; + private UUID casterUUID; + + // Setter + getter implementations + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } + + /** Creates a new vex minion in the given world. */ + public EntityVexMinion(World world){ + super(world); + this.experienceValue = 0; + } + + // ISummonedCreature overrides + @Override + public void setCaster(@Nullable EntityLivingBase caster){ + // Integrates the summoned creature caster system with the (subtly different) vex owner system for NPC casters + ISummonedCreature.super.setCaster(caster); + if(caster instanceof EntityLiving) this.setOwner((EntityLiving)caster); + } + + // EntityVex overrides + @Override + protected void initEntityAI(){ + super.initEntityAI(); + this.targetTasks.taskEntries.clear(); + this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, + 0, false, false, this.getTargetSelector())); + } + + // Implementations + + @Override + public void setRevengeTarget(EntityLivingBase entity){ + if(this.shouldRevengeTarget(entity)) super.setRevengeTarget(entity); + } + + @Override + public void onUpdate(){ + super.onUpdate(); + this.updateDelegate(); + } + + @Override + public void onSpawn(){ + this.spawnParticleEffect(); + } + + @Override + public void onDespawn(){ + this.spawnParticleEffect(); + } + + private void spawnParticleEffect(){ + if(this.world.isRemote){ + for(int i = 0; i < 15; i++){ + ParticleBuilder.create(Type.DARK_MAGIC) + .pos(this.posX + this.rand.nextFloat(), this.posY + this.rand.nextFloat(), this.posZ + this.rand.nextFloat()) + .clr(0.3f, 0.3f, 0.3f) + .spawn(world); + } + } + } + + @Override + public boolean hasParticleEffect(){ + return true; + } + + @Override + protected boolean processInteract(EntityPlayer player, EnumHand hand){ + // In this case, the delegate method determines whether super is called. + // Rather handily, we can make use of Java's short-circuiting method of evaluating OR statements. + return this.interactDelegate(player, hand) || super.processInteract(player, hand); + } + + @Override + public void writeEntityToNBT(NBTTagCompound nbttagcompound){ + super.writeEntityToNBT(nbttagcompound); + this.writeNBTDelegate(nbttagcompound); + } + + @Override + public void readEntityFromNBT(NBTTagCompound nbttagcompound){ + super.readEntityFromNBT(nbttagcompound); + this.readNBTDelegate(nbttagcompound); + } + + // Recommended overrides + + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } + + // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + + @Override + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; + } + + @Override + public ITextComponent getDisplayName(){ + if(getCaster() != null){ + return new TextComponentTranslation(NAMEPLATE_TRANSLATION_KEY, getCaster().getName(), + new TextComponentTranslation("entity." + this.getEntityString() + ".name")); + }else{ + return super.getDisplayName(); + } + } + + @Override + public boolean hasCustomName(){ + // If this returns true, the renderer will show the nameplate when looking directly at the entity + return Wizardry.settings.summonedCreatureNames && getCaster() != null; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java index dee3d51e..4ec2238a 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java @@ -1,12 +1,8 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.Calendar; -import java.util.UUID; - -import javax.annotation.Nullable; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; import net.minecraft.entity.SharedMonsterAttributes; @@ -20,6 +16,7 @@ import net.minecraft.init.Items; import net.minecraft.init.MobEffects; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; +import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; @@ -29,73 +26,35 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import javax.annotation.Nullable; +import java.util.Calendar; +import java.util.UUID; + public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations - @Override - public int getLifetime(){ - return lifetime; - } + @Override public int getLifetime(){ return lifetime; } + @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - @Override - public void setLifetime(int lifetime){ - this.lifetime = lifetime; - } - - @Override - public WeakReference getCasterReference(){ - return casterReference; - } - - @Override - public void setCasterReference(WeakReference reference){ - casterReference = reference; - } - - @Override - public UUID getCasterUUID(){ - return casterUUID; - } - - @Override - public void setCasterUUID(UUID uuid){ - this.casterUUID = uuid; - } - - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new wither skeleton minion in the given world. */ public EntityWitherSkeletonMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntityWitherSkeletonMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - // EntitySkeleton overrides // This particular override is pretty standard: let the superclass handle basic AI like swimming, but replace its - // targeting system with one that targets hostile mobs and takes the ADS into account. + // targeting system with one that targets hostile mobs and takes the AllyDesignationSystem into account. @Override protected void initEntityAI(){ super.initEntityAI(); @@ -105,24 +64,27 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements 0, false, true, this.getTargetSelector())); } - // Shouldn't have randomised armour, but does still need a bow! + // Shouldn't have randomised armour, but does still need a sword! @Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){ - this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); + this.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); } - // Where the skeleton minion is summoned does not affect its type. @Override public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){ // Can't call super, so the code from the next level up (EntityLiving) had to be copied as well. this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE) - .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, 1)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); }else{ this.setLeftHanded(false); } + + this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); + this.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); // Halloween pumpkin heads! Why not? if(this.getItemStackFromSlot(EntityEquipmentSlot.HEAD).isEmpty()){ @@ -201,40 +163,26 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements // Recommended overrides - @Override - protected int getExperiencePoints(EntityPlayer player){ - return 0; - } - - @Override - protected boolean canDropLoot(){ - return false; - } - - @Override - protected Item getDropItem(){ - return null; - } - - @Override - protected ResourceLocation getLootTable(){ - return null; - } - - @Override - public boolean canPickUpLoot(){ - return false; - } + @Override protected int getExperiencePoints(EntityPlayer player){ return 0; } + @Override protected boolean canDropLoot(){ return false; } + @Override protected Item getDropItem(){ return null; } + @Override protected ResourceLocation getLootTable(){ return null; } + @Override public boolean canPickUpLoot(){ return false; } // This vanilla method has nothing to do with the custom despawn() method. + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + @Override - protected boolean canDespawn(){ - return false; + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; } @Override public boolean canAttackClass(Class entityType){ - return true; + // Returns true unless the given entity type is a flying entity and this skeleton does not have a bow. + return !EntityFlying.class.isAssignableFrom(entityType) || this.getHeldItemMainhand().getItem() instanceof ItemBow; } @Override @@ -250,6 +198,6 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java b/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java index 64123f0b..2d5ac7b0 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java @@ -1,75 +1,46 @@ package electroblob.wizardry.entity.living; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; - -import javax.annotation.Nullable; - import com.google.common.base.Predicate; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.event.DiscoverSpellEvent; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.item.ItemSpellBook; -import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.misc.WildcardTradeList; +import electroblob.wizardry.registry.*; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WildcardTradeList; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.*; import io.netty.buffer.ByteBuf; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityCreature; -import net.minecraft.entity.EntityList; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.IEntityLivingData; -import net.minecraft.entity.IMerchant; -import net.minecraft.entity.INpc; -import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.EntityAIBase; -import net.minecraft.entity.ai.EntityAIHurtByTarget; -import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction; -import net.minecraft.entity.ai.EntityAINearestAttackableTarget; -import net.minecraft.entity.ai.EntityAIOpenDoor; -import net.minecraft.entity.ai.EntityAIRestrictOpenDoor; -import net.minecraft.entity.ai.EntityAISwimming; -import net.minecraft.entity.ai.EntityAIWander; -import net.minecraft.entity.ai.EntityAIWatchClosest; -import net.minecraft.entity.ai.EntityAIWatchClosest2; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.IMob; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagInt; -import net.minecraft.nbt.NBTTagLong; +import net.minecraft.nbt.*; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.village.MerchantRecipe; import net.minecraft.village.MerchantRecipeList; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.world.BlockEvent; @@ -80,10 +51,13 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.oredict.OreDictionary; +import javax.annotation.Nullable; +import java.util.*; + @Mod.EventBusSubscriber public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISpellCaster, IEntityAdditionalSpawnData { - private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell(this, 0.5D, 14.0F, 30, 50); + private EntityAIAttackSpell spellCastingAI = new EntityAIAttackSpell<>(this, 0.5D, 14.0F, 30, 50); public int textureIndex = 0; @@ -132,7 +106,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp this.tasks.addTask(0, new EntityAISwimming(this)); // Why would you go to the effort of making the IMerchant interface and then have the AI classes only accept - // EntityVillager? N + // EntityVillager? this.tasks.addTask(1, new EntityAITradePlayer(this)); this.tasks.addTask(1, new EntityAILookAtTradePlayer(this)); this.tasks.addTask(4, new EntityAIRestrictOpenDoor(this)); @@ -143,29 +117,26 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp this.tasks.addTask(7, new EntityAIWander(this, 0.6D)); this.tasks.addTask(8, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F)); - this.targetSelector = new Predicate(){ + this.targetSelector = entity -> { - public boolean apply(Entity entity){ + // If the target is valid and not invisible... + if(entity != null && !entity.isInvisible() + && AllyDesignationSystem.isValidTarget(EntityWizard.this, entity)){ - // If the target is valid and not invisible... - if(entity != null && !entity.isInvisible() - && WizardryUtilities.isValidTarget(EntityWizard.this, entity)){ - - // ... and is a mob, a summoned creature ... - if((entity instanceof IMob || entity instanceof ISummonedCreature - // ... or in the whitelist ... - || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) - .contains(EntityList.getKey(entity.getClass()))) - // ... and isn't in the blacklist ... - && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) - .contains(EntityList.getKey(entity.getClass()))){ - // ... it can be attacked. - return true; - } + // ... and is a mob, a summoned creature ... + if((entity instanceof IMob || entity instanceof ISummonedCreature + // ... or in the whitelist ... + || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) + .contains(EntityList.getKey(entity.getClass()))) + // ... and isn't in the blacklist ... + && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) + .contains(EntityList.getKey(entity.getClass()))){ + // ... it can be attacked. + return true; } - - return false; } + + return false; }; this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true)); @@ -178,6 +149,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp protected void applyEntityAttributes(){ super.applyEntityAttributes(); this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5); + this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30); } private int getHealCooldown(){ @@ -215,6 +187,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp public Spell getContinuousSpell(){ return this.continuousSpell; } + + @Override + public int getAimingError(EnumDifficulty difficulty){ + // Being more intelligent than skeletons, wizards are a little more accurate. + switch(difficulty){ + case EASY: return 7; + case NORMAL: return 4; + case HARD: return 1; + default: return 7; // Peaceful counts as easy + } + } @Override public void setCustomer(EntityPlayer player){ @@ -235,7 +218,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Copied from EntityVillager if(!this.world.isRemote && this.livingSoundTime > -this.getTalkInterval() + 20){ this.livingSoundTime = -this.getTalkInterval(); - this.playSound(stack.isEmpty() ? SoundEvents.ENTITY_VILLAGER_NO : SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch()); + this.playSound(stack.isEmpty() ? WizardrySounds.ENTITY_WIZARD_NO : WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch()); } } @@ -255,8 +238,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Apparently nothing goes here, and nothing's here in EntityVillager either... } + // TESTME: Should this be getName instead? @Override public ITextComponent getDisplayName(){ + + if(this.hasCustomName()){ + return super.getDisplayName(); + } + return this.getElement().getWizardName(); } @@ -264,6 +253,21 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp protected boolean canDespawn(){ return false; } + + @Override + protected SoundEvent getAmbientSound(){ + return this.isTrading() ? WizardrySounds.ENTITY_WIZARD_TRADING : WizardrySounds.ENTITY_WIZARD_AMBIENT; + } + + @Override + protected SoundEvent getHurtSound(DamageSource source){ + return WizardrySounds.ENTITY_WIZARD_HURT; + } + + @Override + protected SoundEvent getDeathSound(){ + return WizardrySounds.ENTITY_WIZARD_DEATH; + } @Override public void onLivingUpdate(){ @@ -287,26 +291,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // deathTime == 0 checks the wizard isn't currently dying }else if(healCooldown == -1 && this.deathTime == 0){ - // Heal particles + // Heal particles TODO: Change this so it uses the heal spell directly if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)this.posX + rand.nextFloat() * 2 - 1.0F); - // Apparently the client side spawns the particles 1 block higher than it should... hence the - - // 0.5F. - double d1 = (double)((float)this.posY - 0.5F + rand.nextFloat()); - double d2 = (double)((float)this.posZ + rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, 0, - 48 + rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } + ParticleBuilder.spawnHealParticles(world, this); }else{ if(this.getHealth() < 10){ - // Wizard heals himself more often if he has low health + // Wizards heal themseselves more often if they have low health this.setHealCooldown(150); }else{ this.setHealCooldown(400); } - this.playSound(WizardrySounds.SPELL_HEAL, 0.7F, rand.nextFloat() * 0.4F + 1.0F); + this.playSound(Spells.heal.getSounds()[0], 0.7F, rand.nextFloat() * 0.4F + 1.0F); } } @@ -359,9 +355,13 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Spell.get(spells[3]).getDisplayName())); // When right-clicked with a spell book in creative, sets one of the spells to that spell - if(player.capabilities.isCreativeMode && stack.getItem() instanceof ItemSpellBook){ - if(this.spells.size() >= 4 && Spell.get(stack.getItemDamage()).canBeCastByNPCs()){ - this.spells.set(rand.nextInt(3) + 1, Spell.get(stack.getItemDamage())); + if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){ + Spell spell = Spell.byMetadata(stack.getItemDamage()); + if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){ + // The set(...) method returns the element that was replaced - neat! + player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard", + this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(), + spell.getNameForTranslationFormatted())); return true; } } @@ -392,11 +392,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp nbt.setInteger("element", this.getElement().ordinal()); nbt.setInteger("skin", this.textureIndex); - nbt.setTag("spells", WizardryUtilities.listToNBT(spells, spell -> new NBTTagInt(spell.id()))); + nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata()))); if(this.towerBlocks != null && this.towerBlocks.size() > 0){ - nbt.setTag("towerBlocks", - WizardryUtilities.listToNBT(this.towerBlocks, pos -> new NBTTagLong(pos.toLong()))); + nbt.setTag("towerBlocks", NBTExtras.listToNBT(this.towerBlocks, NBTUtil::createPosTag)); } } @@ -412,11 +411,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp this.setElement(Element.values()[nbt.getInteger("element")]); this.textureIndex = nbt.getInteger("skin"); - this.spells = (List)WizardryUtilities.NBTToList(nbt.getTagList("spells", NBT.TAG_INT), - (NBTTagInt tag) -> Spell.get(tag.getInt())); + this.spells = (List)NBTExtras.NBTToList(nbt.getTagList("spells", NBT.TAG_INT), + (NBTTagInt tag) -> Spell.byMetadata(tag.getInt())); - this.towerBlocks = new HashSet(WizardryUtilities.NBTToList( - nbt.getTagList("towerBlocks", NBT.TAG_LONG), (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong()))); + NBTTagList tagList = nbt.getTagList("towerBlocks", NBT.TAG_LONG); + if(!tagList.isEmpty()){ + this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(tagList, NBTUtil::getPosFromTag)); + }else{ + // Fallback to old packed long format + this.towerBlocks = new HashSet<>(NBTExtras.NBTToList(nbt.getTagList("towerBlocks", NBT.TAG_LONG), + (NBTTagLong tag) -> BlockPos.fromLong(tag.getLong()))); + } } @Override @@ -424,20 +429,42 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp merchantrecipe.incrementToolUses(); this.livingSoundTime = -this.getTalkInterval(); - this.playSound(SoundEvents.ENTITY_VILLAGER_YES, this.getSoundVolume(), this.getSoundPitch()); + this.playSound(WizardrySounds.ENTITY_WIZARD_YES, this.getSoundVolume(), this.getSoundPitch()); - // Achievements if(this.getCustomer() != null){ + + // Achievements WizardryAdvancementTriggers.wizard_trade.triggerFor(this.getCustomer()); - if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook - && Spell.get(merchantrecipe.getItemToSell().getItemDamage()).tier == Tier.MASTER){ - WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer()); + if(merchantrecipe.getItemToSell().getItem() instanceof ItemSpellBook){ + + Spell spell = Spell.byMetadata(merchantrecipe.getItemToSell().getItemDamage()); + + if(spell.getTier() == Tier.MASTER) WizardryAdvancementTriggers.buy_master_spell.triggerFor(this.getCustomer()); + + // Spell discovery (a lot of this is the same as in the event handler) + WizardData data = WizardData.get(this.getCustomer()); + + if(data != null){ + + if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(this.getCustomer(), spell, + DiscoverSpellEvent.Source.PURCHASE)) && data.discoverSpell(spell)){ + + data.sync(); + + if(!world.isRemote && !this.getCustomer().isCreative() && Wizardry.settings.discoveryMode){ + // Sound and text only happen server-side, in survival, with discovery mode on + WizardryUtilities.playSoundAtPlayer(this.getCustomer(), WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1); + this.getCustomer().sendMessage(new TextComponentTranslation("spell.discover", + spell.getNameForTranslationFormatted())); + } + } + } } } // Changed to a 4 in 5 chance of unlocking a new recipe. - if(this.rand.nextInt(5) > 0){ + if(this.rand.nextInt(5) > 0 || ItemArtefact.isArtefactActive(customer, WizardryItems.charm_haggler)){ this.timeUntilReset = 40; this.updateRecipes = true; @@ -485,7 +512,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp boolean itemAlreadySold = true; - Tier tier = Tier.BASIC; + Tier tier = Tier.NOVICE; while(itemAlreadySold){ @@ -500,7 +527,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp double tierIncreaseChance = 0.5 + 0.04 * (Math.max(this.trades.size() - 4, 0)); - tier = Tier.BASIC; + tier = Tier.NOVICE; if(rand.nextDouble() < tierIncreaseChance){ tier = Tier.APPRENTICE; @@ -530,8 +557,10 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Don't know how it can ever be empty here, but it's a failsafe. if(itemToSell.isEmpty()) return; - merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), - new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4)), itemToSell)); + ItemStack secondItemToBuy = tier == Tier.MASTER ? new ItemStack(WizardryItems.astral_diamond) + : new ItemStack(WizardryItems.magic_crystal, tier.ordinal() * 3 + 1 + rand.nextInt(4)); + + merchantrecipelist.add(new MerchantRecipe(this.getRandomPrice(tier), secondItemToBuy, itemToSell)); } Collections.shuffle(merchantrecipelist); @@ -540,27 +569,31 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp this.trades = new WildcardTradeList(); } - for(int j1 = 0; j1 < merchantrecipelist.size(); ++j1){ - this.trades.add(merchantrecipelist.get(j1)); - } + this.trades.addAll(merchantrecipelist); } // TODO: Switch all of this over to some kind of loot pool system? - + private ItemStack getRandomPrice(Tier tier){ - ItemStack itemstack = ItemStack.EMPTY; - switch(this.rand.nextInt(3)){ - case 0: - itemstack = new ItemStack(Items.GOLD_INGOT, (tier.ordinal() + 1) * 8 - 1 + rand.nextInt(6)); - break; - case 1: - itemstack = new ItemStack(Items.DIAMOND, (tier.ordinal() + 1) * 4 - 2 + rand.nextInt(3)); - break; - case 2: - itemstack = new ItemStack(Items.EMERALD, (tier.ordinal() + 1) * 6 - 1 + rand.nextInt(3)); - break; + + Map map = Wizardry.settings.currencyItems; + // This isn't that efficient but it's not called very often really so it doesn't matter + ResourceLocation itemName = map.keySet().toArray(new ResourceLocation[0])[rand.nextInt(map.size())]; + Item item = Item.REGISTRY.getObject(itemName); + int value; + + if(item == null){ + Wizardry.logger.warn("Invalid item in currency items: {}", itemName); + item = Items.EMERALD; // Fallback item + value = 6; + }else{ + value = map.get(itemName); } - return itemstack; + + // ((tier.ordinal() + 1) * 16 + rand.nextInt(6)) gives a 'value' for the item being bought + // This is then divided by the value of the currency item to give a price + // The absolute maximum stack size that can result from this calculation (with value = 1) is 64. + return new ItemStack(item, (8 + tier.ordinal() * 16 + rand.nextInt(9)) / value); } private ItemStack getRandomItemOfTier(Tier tier){ @@ -568,32 +601,36 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp int randomiser; // All enabled spells of the given tier - List spells = Spell.getSpells(new Spell.TierElementFilter(tier, null)); + List spells = Spell.getSpells(new Spell.TierElementFilter(tier, null, SpellProperties.Context.TRADES)); // All enabled spells of the given tier that match this wizard's element - List specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement())); + List specialismSpells = Spell.getSpells(new Spell.TierElementFilter(tier, this.getElement(), SpellProperties.Context.TRADES)); + + // Wizards don't sell scrolls + spells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK)); + specialismSpells.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK)); // This code is sooooooo much neater with the new filter system! switch(tier){ - case BASIC: + case NOVICE: randomiser = rand.nextInt(5); if(randomiser < 4 && !spells.isEmpty()){ if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0 && !specialismSpells.isEmpty()){ // This means it is more likely for spell books sold to be of the same element as the wizard if the // wizard has an element. return new ItemStack(WizardryItems.spell_book, 1, - specialismSpells.get(rand.nextInt(specialismSpells.size())).id()); + specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata()); }else{ - return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id()); + return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata()); } }else{ if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){ // This means it is more likely for wands sold to be of the same element as the wizard if the wizard // has an element. - return new ItemStack(WizardryUtilities.getWand(tier, this.getElement())); + return new ItemStack(WizardryItems.getWand(tier, this.getElement())); }else{ return new ItemStack( - WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); + WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); } } @@ -604,31 +641,30 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // This means it is more likely for spell books sold to be of the same element as the wizard if the // wizard has an element. return new ItemStack(WizardryItems.spell_book, 1, - specialismSpells.get(rand.nextInt(specialismSpells.size())).id()); + specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata()); }else{ - return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id()); + return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata()); } }else if(randomiser < 6){ if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){ // This means it is more likely for wands sold to be of the same element as the wizard if the wizard // has an element. - return new ItemStack(WizardryUtilities.getWand(tier, this.getElement())); + return new ItemStack(WizardryItems.getWand(tier, this.getElement())); }else{ return new ItemStack( - WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); + WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); } }else if(randomiser < 8){ return new ItemStack(WizardryItems.arcane_tome, 1, 1); }else if(randomiser < 10){ - EntityEquipmentSlot slot = WizardryUtilities.ARMOUR_SLOTS[rand - .nextInt(WizardryUtilities.ARMOUR_SLOTS.length)]; + EntityEquipmentSlot slot = WizardryUtilities.ARMOUR_SLOTS[rand.nextInt(WizardryUtilities.ARMOUR_SLOTS.length)]; if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){ // This means it is more likely for armour sold to be of the same element as the wizard if the // wizard has an element. - return new ItemStack(WizardryUtilities.getArmour(this.getElement(), slot)); + return new ItemStack(WizardryItems.getArmour(this.getElement(), slot)); }else{ return new ItemStack( - WizardryUtilities.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot)); + WizardryItems.getArmour(Element.values()[rand.nextInt(Element.values().length)], slot)); } }else{ // Don't need to check for discovery mode here since it is done above @@ -642,18 +678,18 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // This means it is more likely for spell books sold to be of the same element as the wizard if the // wizard has an element. return new ItemStack(WizardryItems.spell_book, 1, - specialismSpells.get(rand.nextInt(specialismSpells.size())).id()); + specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata()); }else{ - return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).id()); + return new ItemStack(WizardryItems.spell_book, 1, spells.get(rand.nextInt(spells.size())).metadata()); } }else if(randomiser < 6){ if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){ // This means it is more likely for wands sold to be of the same element as the wizard if the wizard // has an element. - return new ItemStack(WizardryUtilities.getWand(tier, this.getElement())); + return new ItemStack(WizardryItems.getWand(tier, this.getElement())); }else{ return new ItemStack( - WizardryUtilities.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); + WizardryItems.getWand(tier, Element.values()[rand.nextInt(Element.values().length)])); } }else if(randomiser < 8){ return new ItemStack(WizardryItems.arcane_tome, 1, 2); @@ -670,12 +706,12 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp if(randomiser < 5 && this.getElement() != Element.MAGIC && !specialismSpells.isEmpty()){ // Master spells can only be sold by a specialist in that element. return new ItemStack(WizardryItems.spell_book, 1, - specialismSpells.get(rand.nextInt(specialismSpells.size())).id()); + specialismSpells.get(rand.nextInt(specialismSpells.size())).metadata()); }else if(randomiser < 6){ if(this.getElement() != Element.MAGIC && rand.nextInt(4) > 0){ // Master elemental wands can only be sold by a specialist in that element. - return new ItemStack(WizardryUtilities.getWand(tier, this.getElement())); + return new ItemStack(WizardryItems.getWand(tier, this.getElement())); }else{ return new ItemStack(WizardryItems.master_wand); } @@ -704,7 +740,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Adds armour. for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ - this.setItemStackToSlot(slot, new ItemStack(WizardryUtilities.getArmour(element, slot))); + this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot))); } // Default chance is 0.085f, for reference. @@ -714,11 +750,16 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // All wizards know magic missile, even if it is disabled. spells.add(Spells.magic_missile); - Tier maxTier = populateSpells(spells, element, 3, rand); + Tier maxTier = populateSpells(spells, element, false, 3, rand); // Now done after the spells so it can take the tier into account. - this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, - new ItemStack(WizardryUtilities.getWand(maxTier, element))); + ItemStack wand = new ItemStack(WizardryItems.getWand(maxTier, element)); + ArrayList list = new ArrayList<>(spells); + list.add(Spells.heal); + WandHelper.setSpells(wand, list.toArray(new Spell[5])); + this.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, wand); + + this.setHealCooldown(50); return livingdata; } @@ -733,14 +774,14 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp * @param random A random number generator to use. * @return The tier of the highest-tier spell that was added to the list. */ - static Tier populateSpells(List spells, Element e, int n, Random random){ + static Tier populateSpells(List spells, Element e, boolean master, int n, Random random){ // This is the tier of the highest tier spell added. - Tier maxTier = Tier.BASIC; + Tier maxTier = Tier.NOVICE; List npcSpells = Spell.getSpells(Spell.npcSpells); - for(int i = 0; i < 3; i++){ + for(int i = 0; i < n; i++){ Tier tier; // If the wizard has no element, it picks a random one each time. @@ -750,17 +791,19 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Uses its own special weighting if(randomiser < 10){ - tier = Tier.BASIC; + tier = Tier.NOVICE; }else if(randomiser < 16){ tier = Tier.APPRENTICE; - }else{ + }else if(randomiser < 19 || !master){ tier = Tier.ADVANCED; + }else{ + tier = Tier.MASTER; } if(tier.ordinal() > maxTier.ordinal()) maxTier = tier; // Finds all the spells of the chosen tier and element - List list = Spell.getSpells(new Spell.TierElementFilter(tier, element)); + List list = Spell.getSpells(new Spell.TierElementFilter(tier, element, SpellProperties.Context.NPCS)); // Keeps only spells which can be cast by NPCs list.retainAll(npcSpells); // Removes spells that the wizard already has @@ -814,12 +857,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp this.towerBlocks = blocks; } - /** - * Tests whether the block at the given coordinates is part of this wizard's tower. - * - * @param pos - * @return - */ + /** Tests whether the block at the given coordinates is part of this wizard's tower. */ public boolean isBlockPartOfTower(BlockPos pos){ if(this.towerBlocks == null) return false; // Uses .equals() rather than == so this will work fine. diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java index 7a7b10d8..f61e33a3 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java @@ -1,8 +1,5 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.UUID; - import electroblob.wizardry.Wizardry; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; @@ -12,6 +9,7 @@ import net.minecraft.entity.ai.EntityAINearestAttackableTarget; import net.minecraft.entity.monster.EntityZombie; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; @@ -19,53 +17,36 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; +import java.util.UUID; + public class EntityZombieMinion extends EntityZombie implements ISummonedCreature { // Field implementations - private int lifetime = 600; - private WeakReference casterReference; + private int lifetime = -1; private UUID casterUUID; // Setter + getter implementations @Override public int getLifetime(){ return lifetime; } @Override public void setLifetime(int lifetime){ this.lifetime = lifetime; } - @Override public WeakReference getCasterReference(){ return casterReference; } - @Override public void setCasterReference(WeakReference reference){ casterReference = reference; } - @Override public UUID getCasterUUID(){ return casterUUID; } - @Override public void setCasterUUID(UUID uuid){ this.casterUUID = uuid; } + @Override public UUID getOwnerId(){ return casterUUID; } + @Override public void setOwnerId(UUID uuid){ this.casterUUID = uuid; } - /** - * Default shell constructor, only used by client. Lifetime defaults arbitrarily to 600, but this doesn't matter - * because the client side entity immediately gets the lifetime value copied over to it by this class anyway. When - * extending this class, you must override this constructor or Minecraft won't like it, but there's no need to do - * anything inside it other than call super(). - */ + /** Creates a new zombie minion in the given world. */ public EntityZombieMinion(World world){ super(world); this.experienceValue = 0; } - /** - * Set lifetime to -1 to allow this creature to last forever. This constructor should be overridden when extending - * this class (be sure to call super()) so that AI and other things can be added. - */ - public EntityZombieMinion(World world, double x, double y, double z, EntityLivingBase caster, int lifetime){ - super(world); - this.setPosition(x, y, z); - this.casterReference = new WeakReference(caster); - this.experienceValue = 0; - this.lifetime = lifetime; - } - - // EntityZombie overrides (EntityZombie is a long class so there are lots of these) + // EntityZombie overrides (EntityZombie is a complex class so there are lots of these) @Override protected void applyEntityAI(){ this.tasks.addTask(6, new EntityAIMoveThroughVillage(this, 1.0D, false)); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); - this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityLivingBase.class, + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 0, false, true, this.getTargetSelector())); } @@ -74,6 +55,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur @Override protected void setEquipmentBasedOnDifficulty(DifficultyInstance difficulty){} // They don't have equipment! @Override public void onKillEntity(EntityLivingBase entityLivingIn){} // Turns villagers to zombies in EntityZombie @Override public void setChildSize(boolean isChild){} + @Override protected ItemStack getSkullDrop(){ return ItemStack.EMPTY; } // Implementations @@ -138,8 +120,16 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur @Override protected Item getDropItem(){ return null; } @Override protected ResourceLocation getLootTable(){ return null; } @Override public boolean canPickUpLoot(){ return false; } + // This vanilla method has nothing to do with the custom despawn() method. - @Override protected boolean canDespawn(){ return false; } + @Override protected boolean canDespawn(){ + return getCaster() == null && getOwnerId() == null; + } + + @Override + public boolean getCanSpawnHere(){ + return this.world.getDifficulty() != EnumDifficulty.PEACEFUL; + } @Override public boolean canAttackClass(Class entityType){ @@ -160,7 +150,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur @Override public boolean hasCustomName(){ // If this returns true, the renderer will show the nameplate when looking directly at the entity - return Wizardry.settings.showSummonedCreatureNames && getCaster() != null; + return Wizardry.settings.summonedCreatureNames && getCaster() != null; } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/living/IIntelligentSpellCaster.java b/src/main/java/electroblob/wizardry/entity/living/IIntelligentSpellCaster.java index 978f5457..939414d5 100644 --- a/src/main/java/electroblob/wizardry/entity/living/IIntelligentSpellCaster.java +++ b/src/main/java/electroblob/wizardry/entity/living/IIntelligentSpellCaster.java @@ -1,9 +1,9 @@ package electroblob.wizardry.entity.living; -import java.util.List; - import electroblob.wizardry.spell.Spell; +import java.util.List; + /** * [NYI] Interface for entities that can select between spells based on their current circumstances, to be used in * conjunction with {@link EntityAISelectSpell}. diff --git a/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java b/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java index a43c0153..6b336783 100644 --- a/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java +++ b/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java @@ -1,22 +1,23 @@ package electroblob.wizardry.entity.living; -import java.util.List; - -import javax.annotation.Nonnull; - import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.world.EnumDifficulty; + +import javax.annotation.Nonnull; +import java.util.List; /** * Interface for entities that can cast spells. Mainly intended for use by wizard-type entities, but can be implemented * by any subclass of EntityLiving. Designed to be as flexible as possible - ranging from the simplest use of giving an * entity a specific spell as an attack, to a complex AI which selects different spell types depending on the situation. * The only restriction is that the spells must be castable by NPCs. - *

    + *

    * This is intended for entities that use {@link EntityAIAttackSpell}. If so, all the spell casting code (including * packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select. - *

    + *

    * This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor * needs to do is store the actual fields involved. */ @@ -65,4 +66,13 @@ public interface ISpellCaster { * to NBT is up to you. If the implementing class does not deal with continuous spells, leave this method blank. */ public void setContinuousSpell(Spell spell); + + /** + * Returns the aiming arror for the given difficulty, used in projectile spells. Defaults to the values used by + * skeletons, which are: Easy - 10, Normal - 6, Hard - 2, Peaceful - 10 (rarely used). + */ + // This is what default methods are actually intended for! + public default int getAimingError(EnumDifficulty difficulty) { + return WizardryUtilities.getDefaultAimingError(difficulty); + } } diff --git a/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java b/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java index 3c7e9a12..5b9e06da 100644 --- a/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java +++ b/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java @@ -1,26 +1,15 @@ package electroblob.wizardry.entity.living; -import java.lang.ref.WeakReference; -import java.util.Arrays; -import java.util.UUID; - -import javax.annotation.Nullable; - import com.google.common.base.Predicate; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.util.IElementalDamage; -import electroblob.wizardry.util.IndirectMinionDamage; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.MinionDamage; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import io.netty.buffer.ByteBuf; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityList; -import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.*; import net.minecraft.entity.monster.IMob; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -34,28 +23,33 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.UUID; + /** * Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 2.1, and this * interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so * EntitySummonedZombie now extends EntityZombie, for example. This change has two major * benefits: - *

    + *

    * - There is no longer any need for separate render classes, because summoned creatures are now instances of vanilla * types. You don't even need to assign a render class because the supertype should already be assigned the * correct one.
    * - Summoned creature classes are now much more robust when it comes to changes between Minecraft versions, since none * of the vanilla code needs to be copied. - *

    + *

    * Summoned creatures that do not emulate vanilla entities do not directly implement this interface. Instead, * they should extend the abstract base implementation, {@link EntitySummonedCreature}. - *

    + *

    * All damage dealt by ISummonedCreature instances is redirected via - * {@link ISummonedCreature#onLivingAttackEvent(net.minecraftforge.event.entity.living.LivingAttackEvent) + * {@link ISummonedCreature#onLivingAttackEvent(LivingAttackEvent) * ISummonedCreature.onLivingAttackEvent(LivingAttackEvent)} and replaced by an instance of - * {@link electroblob.wizardry.util.IElementalDamage IElementalDamage} with the summoner of that creature as the source + * {@link IElementalDamage IElementalDamage} with the summoner of that creature as the source * rather than the creature itself. This means that kills by summoned creatures register as kills for their owner, * dropping xp and rare loot if that owner is a player. - *

    + *

    * Though this system is a lot better than the previous system, it is not a perfect solution. The old * EntitySummonedCreature class overrode some methods from Entity in order to add shared functionality, but this cannot * be done with an interface. To get around this problem, this interface contains 5 delegate methods that do the same @@ -65,12 +59,12 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; * work properly unless it is adhered to. The position of the delegate method call is unimportant, but by convention * it is usually at the start of the calling method, which avoids it being unintentionally skipped by a return * statement (except for methods where the result of the delegate method should itself be returned). - *

    + *

    * It is recommended that when implementing this interface, you begin by copying {@link EntitySummonedCreature} to * ensure all the relevant methods are duplicated. You can then change the superclass, override any additional methods * and add functionality to any that are already overridden. You will always want to override the AI methods at the very * least. - *

    + *

    * Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly * marked as 'Internal, DO NOT CALL'. Don't call them, only implement them. * @@ -81,7 +75,7 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; * sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a good * way of doing this sort of thing anyway, but we have no choice about that! */ @Mod.EventBusSubscriber -public interface ISummonedCreature extends IEntityAdditionalSpawnData { +public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOwnable { // Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that. String NAMEPLATE_TRANSLATION_KEY = "entity." + Wizardry.MODID + ":summonedcreature.nameplate"; @@ -99,35 +93,51 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { */ int getLifetime(); - /** - * Sets the WeakReference object which refers to the owner of this summoned creature. Internal, don't call unless - * you know what you are doing. - */ - void setCasterReference(WeakReference reference); + /** Internal, do not use. Implementing classes should implement this to set their owner UUID field. */ + void setOwnerId(UUID uuid); - /** - * Returns a WeakReference object which refers to the owner of this summoned creature. Subclasses should store this - * as a private field. This may be null; as such it is preferable to use {@link ISummonedCreature#getCaster()} to - * get the caster object itself. - */ + /** Returns the UUID of the owner of this summoned creature, or null if it does not have an owner. + * Implementing classes should implement this to return their owner UUID field. */ @Nullable - WeakReference getCasterReference(); + @Override + UUID getOwnerId(); // Only overridden because I wanted to add javadoc! - /** Internal, DO NOT CALL. */ - void setCasterUUID(UUID uuid); - - /** Internal, DO NOT CALL. This is for loading purposes only and is not usually synchronised. */ - UUID getCasterUUID(); + @Nullable + @Override + default Entity getOwner(){ + return getCaster(); // Delegate to getCaster + } /** * Returns the EntityLivingBase that summoned this creature, or null if it no longer exists. Cases where the entity * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to - * another dimension, or this creature simply had no caster in the first place. This is the correct method to use - * to get the owner of this summoned creature. + * another dimension, or this creature simply had no caster in the first place. */ @Nullable - default EntityLivingBase getCaster(){ - return getCasterReference() == null ? null : getCasterReference().get(); + default EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase + + if(this instanceof Entity){ // Bit of a cheat but it saves having yet another method just to get the world + + Entity entity = WizardryUtilities.getEntityByUUID(((Entity)this).world, getOwnerId()); + + if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen + Wizardry.logger.warn("{} has a non-living owner!", this); + return null; + } + + return (EntityLivingBase)entity; + + }else{ + Wizardry.logger.warn("{} implements ISummonedCreature but is not an SoundLoopSpellEntity!", this.getClass()); + return null; + } + } + + /** + * Sets the EntityLivingBase that summoned this creature. + */ + default void setCaster(@Nullable EntityLivingBase caster){ + setOwnerId(caster == null ? null : caster.getUniqueID()); } // Miscellaneous @@ -148,23 +158,62 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { * Called by the client when it receives a Entity spawn packet. Data should be read out of the stream in the same * way as it was written. Implementors must call super when overriding. * - * @param additionalData The packet data stream + * @param buffer The packet data stream */ @Override default void readSpawnData(ByteBuf buffer){ int id = buffer.readInt(); // We're on the client side here, so we can safely use Minecraft.getMinecraft().world via proxies. - if(id > -1) setCasterReference( - new WeakReference((EntityLivingBase)Wizardry.proxy.getTheWorld().getEntityByID(id))); + if(id > -1){ + Entity entity = Wizardry.proxy.getTheWorld().getEntityByID(id); + if(entity instanceof EntityLivingBase) setCaster((EntityLivingBase)entity); + else Wizardry.logger.warn("Received a spawn packet for entity {}, but no living entity matched the supplied ID", this); + } setLifetime(buffer.readInt()); } /** - * Shorthand for {@link WizardryUtilities#isValidTarget(Entity, Entity)}, with the owner of this creature as the - * attacker. Also allows implementors to override it if they wish to do so. + * Determines whether the given target is valid. Used by the default target selector (see + * {@link ISummonedCreature#getTargetSelector()}) and revenge targeting checks. This method is responsible for the + * ally designation system, default classes that may be targeted and the config whitelist/blacklist. + * Implementors may override this if they want to do something different or add their own checks. + * @see AllyDesignationSystem#isValidTarget(Entity, Entity) */ default boolean isValidTarget(Entity target){ - return WizardryUtilities.isValidTarget(this.getCaster(), target); + // If the target is valid based on the ADS... + if(AllyDesignationSystem.isValidTarget(this.getCaster(), target)){ + + // ...and is a player, they can be attacked, since players can't be in the whitelist or the + // blacklist... + if(target instanceof EntityPlayer){ + // ...unless the creature was summoned by a good wizard who the player has not angered. + if(getCaster() instanceof EntityWizard){ + if(getCaster().getRevengeTarget() != target + && ((EntityWizard)getCaster()).getAttackTarget() != target) { + return false; + } + } + + return true; + } + + // ...and is a mob, a summoned creature, a wizard... + if((target instanceof IMob || target instanceof ISummonedCreature + || (target instanceof EntityWizard && !(getCaster() instanceof EntityWizard)) + // ...or something that's attacking the owner... + || (target instanceof EntityLiving && ((EntityLiving)target).getAttackTarget() == getCaster()) + // ...or in the whitelist... + || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) + .contains(EntityList.getKey(target.getClass()))) + // ...and isn't in the blacklist... + && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) + .contains(EntityList.getKey(target.getClass()))){ + // ...it can be attacked. + return true; + } + } + + return false; } /** @@ -172,45 +221,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { * possible for implementors to override this in order to do something special when selecting a target. */ default Predicate getTargetSelector(){ - - return new Predicate(){ - - public boolean apply(Entity entity){ - // TODO: Backport invisibility check (also in wizards) - // If the target is valid and not invisible... - if(!entity.isInvisible() && isValidTarget(entity)){ - - // ... and is a player, they can be attacked, since players can't be in the whitelist or the - // blacklist ... - if(entity instanceof EntityPlayer){ - // ... unless the creature was summoned by a good wizard who the player has not angered. - if(getCaster() instanceof EntityWizard){ - if(((EntityWizard)getCaster()).getRevengeTarget() != entity - && ((EntityWizard)getCaster()).getAttackTarget() != entity) { - return false; - } - } - - return true; - } - - // ... and is a mob, a summoned creature, a wizard ... - if((entity instanceof IMob || entity instanceof ISummonedCreature - || (entity instanceof EntityWizard && !(getCaster() instanceof EntityWizard)) - // ... or in the whitelist ... - || Arrays.asList(Wizardry.settings.summonedCreatureTargetsWhitelist) - .contains(EntityList.getKey(entity.getClass()))) - // ... and isn't in the blacklist ... - && !Arrays.asList(Wizardry.settings.summonedCreatureTargetsBlacklist) - .contains(EntityList.getKey(entity.getClass()))){ - // ... it can be attacked. - return true; - } - } - - return false; - } - }; + return entity -> getCaster() == null ? entity instanceof EntityPlayer : !entity.isInvisible() && isValidTarget(entity); } /** @@ -231,12 +242,12 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { * Called from the event handler after the damage change is applied. Does nothing by default, but can be overridden * to do something when a successful attack is made. This was added because the event-based damage source system can * cause parts of attackEntityAsMob not to fire, since attackEntityFrom is intercepted and canceled. - *

    - * Usage examples: {@link EntitySliverfishMinion} uses this to summon more silverfish if the target is killed, + *

    + * Usage examples: {@link EntitySilverfishMinion} uses this to summon more silverfish if the target is killed, * {@link EntitySkeletonMinion} and {@link EntitySpiderMinion} use this to add potion effects to the target. */ default void onSuccessfulAttack(EntityLivingBase target){ - }; + } // Delegates @@ -256,7 +267,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { * very little point in doing that since anything extra could just be added to readEntityFromNBT anyway. */ default void readNBTDelegate(NBTTagCompound tagcompound){ - this.setCasterUUID(tagcompound.getUniqueId("casterUUID")); + this.setOwnerId(tagcompound.getUniqueId("casterUUID")); this.setLifetime(tagcompound.getInteger("lifetime")); } @@ -265,8 +276,8 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { * returns true. */ default boolean shouldRevengeTarget(EntityLivingBase entity){ - // Allows the config to prevent minions from revenge-targeting their owners. - return entity != this.getCaster() || Wizardry.settings.minionRevengeTargeting; + // Allows the config to prevent minions from revenge-targeting their owners (or anything else, for that matter) + return Wizardry.settings.minionRevengeTargeting || isValidTarget(entity); } /** @@ -276,30 +287,26 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { default void updateDelegate(){ if(!(this instanceof Entity)) - throw new ClassCastException("Implementations of ISummonedCreature must extend Entity!"); + throw new ClassCastException("Implementations of ISummonedCreature must extend SoundLoopSpellEntity!"); Entity thisEntity = ((Entity)this); - if(this.getCaster() == null && this.getCasterUUID() != null){ - Entity entity = WizardryUtilities.getEntityByUUID(thisEntity.world, getCasterUUID()); - if(entity instanceof EntityLivingBase){ - this.setCasterReference(new WeakReference((EntityLivingBase)entity)); - } - } - if(thisEntity.ticksExisted == 1){ this.onSpawn(); } - if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() != -1){ + // For some reason Minecraft reads the entity from NBT just after the entity is created, so setting -1 as a + // default lifetime doesn't work. The easiest way around this is to use 0 - nobody's going to need it! + if(thisEntity.ticksExisted > this.getLifetime() && this.getLifetime() > 0){ this.onDespawn(); thisEntity.setDead(); } if(this.hasParticleEffect() && thisEntity.world.isRemote && thisEntity.world.rand.nextInt(8) == 0) - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, thisEntity.world, thisEntity.posX, - thisEntity.posY + thisEntity.world.rand.nextDouble() * 1.5, thisEntity.posZ, 0.0d, 0.0d, 0.0d, 0, - 0.1f, 0.0f, 0.0f); + ParticleBuilder.create(Type.DARK_MAGIC) + .pos(thisEntity.posX, thisEntity.posY + thisEntity.world.rand.nextDouble() * 1.5, thisEntity.posZ) + .clr(0.1f, 0.0f, 0.0f) + .spawn(thisEntity.world); } @@ -311,20 +318,20 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { ItemStack stack = player.getHeldItem(hand); - WizardData properties = WizardData.get(player); + WizardData data = WizardData.get(player); // Selects one of the player's minions. - if(player.isSneaking() && stack.getItem() instanceof ItemWand){ + if(player.isSneaking() && stack.getItem() instanceof ISpellCastingItem){ - if(!player.world.isRemote && properties != null && this.getCaster() == player){ + if(!player.world.isRemote && data != null && this.getCaster() == player){ - if(properties.selectedMinion != null && properties.selectedMinion.get() == this){ + if(data.selectedMinion != null && data.selectedMinion.get() == this){ // Deselects the selected minion if right-clicked again - properties.selectedMinion = null; + data.selectedMinion = null; }else{ // Selects this minion - properties.selectedMinion = new WeakReference(this); + data.selectedMinion = new WeakReference<>(this); } - properties.sync(); + data.sync(); } return true; } @@ -335,7 +342,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { // Damage system @SubscribeEvent - public static void onLivingAttackEvent(LivingAttackEvent event){ + static void onLivingAttackEvent(LivingAttackEvent event){ // Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and // more robust) to use LivingAttackEvent to modify the damage source. @@ -373,8 +380,10 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData { // For some reason Minecraft calculates knockback relative to DamageSource#getTrueSource. In vanilla this // is unnoticeable, but it looks a bit weird with summoned creatures involved - so this fixes that. - if(WizardryUtilities.attackEntityWithoutKnockback(event.getEntity(), newSource, event.getAmount())){ - // Using event.getSource().getTrueSource() as this means the target is knocked back from the minion + // Damage safety checker falls back to the original damage source, so it behaves as if the creature has + // no summoner. + if(DamageSafetyChecker.attackEntitySafely(event.getEntity(), newSource, event.getAmount(), event.getSource(), false)){ + // Uses event.getSource().getTrueSource() as this means the target is knocked back from the minion WizardryUtilities.applyStandardKnockback(event.getSource().getTrueSource(), event.getEntityLiving()); ((ISummonedCreature)event.getSource().getTrueSource()).onSuccessfulAttack(event.getEntityLiving()); // If the target revenge-targeted the summoner, make it revenge-target the minion instead diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityBomb.java index d7ae1a3b..6f4af21a 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityBomb.java @@ -1,7 +1,6 @@ package electroblob.wizardry.entity.projectile; import io.netty.buffer.ByteBuf; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; @@ -21,19 +20,6 @@ public abstract class EntityBomb extends EntityMagicProjectile { super(world); } - public EntityBomb(World world, EntityLivingBase thrower){ - super(world, thrower); - } - - public EntityBomb(World world, EntityLivingBase thrower, float damageMultiplier, float blastMultiplier){ - super(world, thrower, damageMultiplier); - this.blastMultiplier = blastMultiplier; - } - - public EntityBomb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - @Override public void writeSpawnData(ByteBuf buffer){ buffer.writeFloat(blastMultiplier); diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityDarknessOrb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityDarknessOrb.java index 90292a5f..484457ba 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityDarknessOrb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityDarknessOrb.java @@ -1,54 +1,44 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityDarknessOrb extends EntityMagicProjectile { - public EntityDarknessOrb(World par1World){ - super(par1World); - } - - public EntityDarknessOrb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntityDarknessOrb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - } - - public EntityDarknessOrb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); + + public EntityDarknessOrb(World world){ + super(world); } @Override - protected float getSpeed(){ - return 0.5F; - } - - @Override - protected void onImpact(RayTraceResult RayTraceResult){ - Entity target = RayTraceResult.entityHit; + protected void onImpact(RayTraceResult rayTrace){ + + Entity target = rayTrace.entityHit; if(target != null && !MagicDamage.isEntityImmune(DamageType.WITHER, target)){ - float damage = 8 * damageMultiplier; + + float damage = Spells.darkness_orb.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.WITHER).setProjectile(), damage); if(target instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.WITHER, target)) - ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER, 150, 1)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER, + Spells.darkness_orb.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.darkness_orb.getProperty(Spell.EFFECT_STRENGTH).intValue())); - this.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_DARKNESS_ORB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } this.setDead(); @@ -59,21 +49,13 @@ public class EntityDarknessOrb extends EntityMagicProjectile { super.onUpdate(); if(world.isRemote){ + float brightness = rand.nextFloat() * 0.2f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0, 20 + rand.nextInt(10), - brightness, 0.0f, brightness); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, - this.posY + this.rand.nextDouble() * (double)this.height, - this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0.0d, 0.0d, 0.0d, 0, 0.1f, 0.0f, - 0.0f); - } - - if(this.ticksExisted > 150){ - this.setDead(); + + ParticleBuilder.create(Type.SPARKLE, this).time(20 + rand.nextInt(10)) + .clr(brightness, 0.0f, brightness).spawn(world); + + ParticleBuilder.create(Type.DARK_MAGIC, this).clr(0.1f, 0.0f, 0.0f).spawn(world); } // Cancels out the slowdown effect in EntityThrowable @@ -82,10 +64,13 @@ public class EntityDarknessOrb extends EntityMagicProjectile { this.motionZ /= 0.99; } - /** - * Gets the amount of gravity to apply to the thrown entity with each tick. - */ - protected float getGravityVelocity(){ - return 0.0F; + @Override + public boolean hasNoGravity(){ + return true; + } + + @Override + public int getLifetime(){ + return 60; } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityDart.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityDart.java index 0d51c5df..6fd4cb3c 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityDart.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityDart.java @@ -1,65 +1,46 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityDart extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + + /** Creates a new dart in the given world. */ public EntityDart(World world){ super(world); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityDart(World world, double x, double y, double z){ - super(world, x, y, z); - } + @Override public double getDamage(){ return Spells.dart.getProperty(Spell.DAMAGE).doubleValue(); } - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityDart(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - } + @Override public boolean doGravity(){ return true; } - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityDart(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); - } + @Override public boolean doDeceleration(){ return true; } @Override public void onEntityHit(EntityLivingBase entityHit){ // Adds a weakness effect to the target. - entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 200, 1, false, false)); - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + entityHit.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, Spells.dart.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.dart.getProperty(Spell.EFFECT_STRENGTH).intValue(), false, false)); + this.playSound(WizardrySounds.ENTITY_DART_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } @Override - public void onBlockHit(){ - this.playSound(SoundEvents.ENTITY_ARROW_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + public void onBlockHit(RayTraceResult hit){ + this.playSound(WizardrySounds.ENTITY_DART_HIT_BLOCK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } @Override public void tickInAir(){ - if(this.world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, world, this.posX, this.posY, this.posZ, 0, -0.03, 0, - 10 + rand.nextInt(5)); + ParticleBuilder.create(Type.LEAF, this).time(10 + rand.nextInt(5)).spawn(world); } } @@ -72,28 +53,10 @@ public class EntityDart extends EntityMagicArrow { } @Override - public double getDamage(){ - return 4.0d; - } + protected void entityInit(){} @Override - public DamageType getDamageType(){ - return DamageType.MAGIC; + public int getLifetime(){ + return -1; } - - @Override - public boolean doGravity(){ - return true; - } - - @Override - public boolean doDeceleration(){ - return true; - } - - @Override - protected void entityInit(){ - - } - } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java new file mode 100644 index 00000000..13325283 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java @@ -0,0 +1,87 @@ +package electroblob.wizardry.entity.projectile; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Disintegration; +import electroblob.wizardry.spell.Spell; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; + +public class EntityEmber extends EntityMagicProjectile { + + private int extraLifetime; + + public EntityEmber(World world){ + super(world); + } + + public EntityEmber(World world, EntityLivingBase caster){ + super(world); + this.thrower = caster; + extraLifetime = rand.nextInt(30); + this.setSize(0.1f, 0.1f); + } + + @Override + public AxisAlignedBB getCollisionBoundingBox(){ + return null;//this.getEntityBoundingBox(); + } + + @Override + public int getLifetime(){ + return Spells.disintegration.getProperty(Disintegration.EMBER_LIFETIME).intValue() + extraLifetime; + } + + @Override + protected void onImpact(RayTraceResult result){ + + if(result.entityHit != null){ + result.entityHit.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()); + } + + if(result.typeOfHit == RayTraceResult.Type.BLOCK){ + this.inGround = true; + this.collided = true; + if(result.sideHit.getAxis() == EnumFacing.Axis.X) motionX = 0; + if(result.sideHit.getAxis() == EnumFacing.Axis.Y){ + motionY = 0; + this.collidedVertically = true; + } + if(result.sideHit.getAxis() == EnumFacing.Axis.Z) motionZ = 0; + } + } + + @Override + public void applyEntityCollision(Entity entity){ + + super.applyEntityCollision(entity); + + if(entity instanceof EntityLivingBase){ + entity.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()); + } + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(this.collidedVertically){ + this.motionY += this.getGravityVelocity(); + this.motionX *= 0.5; + this.motionZ *= 0.5; + } + + world.getEntitiesInAABBexcluding(thrower, this.getEntityBoundingBox(), e -> e instanceof EntityLivingBase) + .forEach(e -> e.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue())); + + // Copied from ParticleLava + if(this.rand.nextFloat() > (float)this.ticksExisted / this.getLifetime()){ + this.world.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, this.posX, this.posY, this.posZ, this.motionX, this.motionY, this.motionZ); + } + } +} diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebolt.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebolt.java index 268a15c2..454aad3e 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebolt.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebolt.java @@ -1,53 +1,46 @@ package electroblob.wizardry.entity.projectile; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityFirebolt extends EntityMagicProjectile { - public EntityFirebolt(World par1World){ - super(par1World); - } - - public EntityFirebolt(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntityFirebolt(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - } - - public EntityFirebolt(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); + + public EntityFirebolt(World world){ + super(world); } @Override protected void onImpact(RayTraceResult rayTrace){ + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ - float damage = 5 * damageMultiplier; + + float damage = Spells.firebolt.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(), damage); - if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(5); + if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) + entityHit.setFire(Spells.firebolt.getProperty(Spell.BURN_DURATION).intValue()); } - this.playSound(SoundEvents.BLOCK_LAVA_POP, 2, 0.8f + rand.nextFloat() * 0.3f); + this.playSound(WizardrySounds.ENTITY_FIREBOLT_HIT, 2, 0.8f + rand.nextFloat() * 0.3f); // Particle effect if(world.isRemote){ for(int i = 0; i < 8; i++){ world.spawnParticle(EnumParticleTypes.LAVA, this.posX + rand.nextFloat() - 0.5, - this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, - 0); + this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0); } } @@ -60,29 +53,27 @@ public class EntityFirebolt extends EntityMagicProjectile { super.onUpdate(); if(world.isRemote){ - for(int i = 0; i < 4; i++){ - world.spawnParticle(EnumParticleTypes.FLAME, this.posX + rand.nextFloat() * 0.2 - 0.1, - this.posY + this.height / 2 + rand.nextFloat() * 0.2 - 0.1, - this.posZ + rand.nextFloat() * 0.2 - 0.1, 0, 0, 0); + ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE, this).time(14).spawn(world); + + if(this.ticksExisted > 1){ // Don't spawn particles behind where it started! + double x = posX - motionX/2 + rand.nextFloat() * 0.2 - 0.1; + double y = posY + this.height/2 - motionY/2 + rand.nextFloat() * 0.2 - 0.1; + double z = posZ - motionZ/2 + rand.nextFloat() * 0.2 - 0.1; + ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE).pos(x, y, z).time(14).spawn(world); } } - - if(this.ticksExisted > 8){ - this.setDead(); - } } - /** - * Gets the amount of gravity to apply to the thrown entity with each tick. - */ @Override - protected float getGravityVelocity(){ - return 0.0F; + public int getLifetime(){ + return 6; + } + + @Override + public boolean hasNoGravity(){ + return true; } - /** - * Return whether this entity should be rendered as on fire. - */ @Override public boolean canRenderOnFire(){ return false; diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java index 296af6ff..0c4693ce 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java @@ -1,81 +1,73 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; +import java.util.List; + public class EntityFirebomb extends EntityBomb { - public EntityFirebomb(World par1World){ - super(par1World); + public EntityFirebomb(World world){ + super(world); } - public EntityFirebomb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); + @Override + public int getLifetime(){ + return -1; } - public EntityFirebomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier); - } - - public EntityFirebomb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ - protected void onImpact(RayTraceResult par1RayTraceResult){ - Entity entityHit = par1RayTraceResult.entityHit; + @Override + protected void onImpact(RayTraceResult rayTrace){ + + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ // This is if the firebomb gets a direct hit - float damage = 5 * damageMultiplier; + float damage = Spells.firebomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier; entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(), damage); - if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) entityHit.setFire(10); + if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit)) + entityHit.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue()); } // Particle effect if(world.isRemote){ - this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); + + ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier).clr(1, 0.6f, 0) + .spawn(world); + for(int i = 0; i < 60 * blastMultiplier; i++){ - // this.world.spawnParticle(EnumParticleTypes.FLAME, this.posX + (this.rand.nextDouble()*4 - - // 2)*blastMultiplier, this.posY + (this.rand.nextDouble()*4 - 2)*blastMultiplier, this.posZ + - // (this.rand.nextDouble()*4 - 2)*blastMultiplier, 0, 0, 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0, 0, 0, 15 + rand.nextInt(5), - 2 + rand.nextFloat(), 0, 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 0, 1.0f, - 0.2f + rand.nextFloat() * 0.4f, 0.0f); + + ParticleBuilder.create(Type.MAGIC_FIRE, rand, posX, posY, posZ, 2*blastMultiplier, false) + .time(10 + rand.nextInt(4)).scale(2 + rand.nextFloat()).spawn(world); + + ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false) + .clr(1.0f, 0.2f + rand.nextFloat() * 0.4f, 0.0f).spawn(world); } + + this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); } if(!this.world.isRemote){ - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F); - this.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); + this.playSound(WizardrySounds.ENTITY_FIREBOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F); + this.playSound(WizardrySounds.ENTITY_FIREBOMB_FIRE, 1, 1); - double range = 3.0d * blastMultiplier; + double range = Spells.firebomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.world); @@ -86,8 +78,8 @@ public class EntityFirebomb extends EntityBomb { // Splash damage does not count as projectile damage target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE), - 4.0f * damageMultiplier); - target.setFire(7); + Spells.firebomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier); + target.setFire(Spells.firebomb.getProperty(Spell.BURN_DURATION).intValue()); } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java index c1b6f1be..0b5bb9cb 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java @@ -1,69 +1,100 @@ package electroblob.wizardry.entity.projectile; +import electroblob.wizardry.item.IManaStoringItem; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage.DamageType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import java.util.Arrays; + public class EntityForceArrow extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + /** The mana used to cast this force arrow, used for artefacts. */ + private int mana = 0; + + /** Creates a new force arrow in the given world. */ public EntityForceArrow(World world){ super(world); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityForceArrow(World world, double x, double y, double z){ - super(world, x, y, z); - } - - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityForceArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - } - - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityForceArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); + public void setMana(int mana){ + this.mana = mana; } @Override public void onEntityHit(EntityLivingBase entityHit){ - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F); + this.playSound(WizardrySounds.ENTITY_FORCE_ARROW_HIT, 1.0F, 1.0F); + if(this.world.isRemote) + ParticleBuilder.create(Type.FLASH).pos(posX, posY, posZ).scale(1.3f).clr(0.75f, 1, 0.85f).spawn(world); } @Override public void tickInGround(){ + returnManaToCaster(); this.setDead(); } @Override - public void onBlockHit(){ - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.0F, 1.0F); + public void onUpdate(){ + + if(getLifetime() >=0 && this.ticksExisted > getLifetime()){ // The last tick before it disappears + returnManaToCaster(); + } + + super.onUpdate(); } - @Override - public void tickInAir(){ - if(this.ticksExisted > 20){ - this.setDead(); + private void returnManaToCaster(){ + + if(mana > 0 && getCaster() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)getCaster(); + + if(!player.capabilities.isCreativeMode && ItemArtefact.isArtefactActive(player, WizardryItems.ring_mana_return)){ + + for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ + if(stack.getItem() instanceof ISpellCastingItem && stack.getItem() instanceof IManaStoringItem + && Arrays.asList(((ISpellCastingItem)stack.getItem()).getSpells(stack)).contains(Spells.force_arrow)){ + ((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana); + } + } + } } } + @Override + public void onBlockHit(RayTraceResult hit){ + this.playSound(WizardrySounds.ENTITY_FORCE_ARROW_HIT, 1.0F, 1.0F); + if(this.world.isRemote){ + // Gets a position slightly away from the block hit so the particle doesn't get cut in half by the block face + Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(0.15)); + ParticleBuilder.create(Type.FLASH).pos(vec).scale(1.3f).clr(0.75f, 1, 0.85f).spawn(world); + //vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); + //ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0, 1, 0.5f).spawn(world); + } + } + + @Override + public int getLifetime(){ + return 20; + } + @Override public double getDamage(){ - return 7.0d; + return Spells.force_arrow.getProperty(Spell.DAMAGE).floatValue(); } @Override diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java index 825db7fc..3967b0ba 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java @@ -1,64 +1,45 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; -public class EntityForceOrb extends EntityMagicProjectile { +import java.util.List; - /** - * The entity blast multiplier. In this particular case, it doesn't need syncing, so this class doesn't extend - * EntityBlastProjectile. - */ - public float blastMultiplier; - - public EntityForceOrb(World par1World){ - super(par1World); +public class EntityForceOrb extends EntityBomb { + + public EntityForceOrb(World world){ + super(world); } - public EntityForceOrb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); + @Override + public int getLifetime(){ + return -1; } - public EntityForceOrb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - this.blastMultiplier = blastMultiplier; - } - - public EntityForceOrb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ + @Override protected void onImpact(RayTraceResult par1RayTraceResult){ if(par1RayTraceResult.entityHit != null){ // This is if the force orb gets a direct hit - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } // Particle effect if(this.world.isRemote){ for(int j = 0; j < 20; j++){ float brightness = 0.5f + (rand.nextFloat() / 2); - double x = this.posX - 0.25d + (rand.nextDouble() / 2); - double y = this.posY - 0.25d + (rand.nextDouble() / 2); - double z = this.posZ - 0.25d + (rand.nextDouble() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x, y, z, (x - this.posX) * 2, - (y - this.posY) * 2, (z - this.posZ) * 2, 6, brightness, 1.0f, brightness + 0.2f); + ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 0.25, true).time(6) + .clr(brightness, 1.0f, brightness + 0.2f).spawn(world); } this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); } @@ -67,10 +48,10 @@ public class EntityForceOrb extends EntityMagicProjectile { // 2 gives a cool flanging effect! float pitch = this.rand.nextFloat() * 0.2F + 0.3F; - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.5F, pitch); - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST, 1.5F, pitch - 0.01f); + this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT_BLOCK, 1.5F, pitch); + this.playSound(WizardrySounds.ENTITY_FORCE_ORB_HIT_BLOCK, 1.5F, pitch - 0.01f); - double blastRadius = 4.0d * blastMultiplier; + double blastRadius = Spells.force_orb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(blastRadius, this.posX, this.posY, this.posZ, this.world); @@ -85,7 +66,7 @@ public class EntityForceOrb extends EntityMagicProjectile { double dz = this.posZ - target.posZ > 0 ? -0.5 - (this.posZ - target.posZ) / 8 : 0.5 - (this.posZ - target.posZ) / 8; - float damage = 4 * damageMultiplier; + float damage = Spells.force_orb.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.BLAST), damage); @@ -99,16 +80,5 @@ public class EntityForceOrb extends EntityMagicProjectile { this.setDead(); } } - - @Override - public void readEntityFromNBT(NBTTagCompound nbttagcompound){ - super.readEntityFromNBT(nbttagcompound); - blastMultiplier = nbttagcompound.getFloat("blastMultiplier"); - } - - @Override - public void writeEntityToNBT(NBTTagCompound nbttagcompound){ - super.writeEntityToNBT(nbttagcompound); - nbttagcompound.setFloat("blastMultiplier", blastMultiplier); - } + } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java index 3288e4fa..048d47c9 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java @@ -1,84 +1,78 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.init.Blocks; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; +import java.util.List; + public class EntityIceCharge extends EntityBomb { - public EntityIceCharge(World par1World){ - super(par1World); + public static final String ICE_SHARDS = "ice_shards"; + + public EntityIceCharge(World world){ + super(world); } - public EntityIceCharge(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); + @Override + public int getLifetime(){ + return -1; } - public EntityIceCharge(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier); - } + @Override + protected void onImpact(RayTraceResult rayTrace){ - public EntityIceCharge(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ - protected void onImpact(RayTraceResult par1RayTraceResult){ - Entity entityHit = par1RayTraceResult.entityHit; + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ // This is if the ice charge gets a direct hit - float damage = 4 * damageMultiplier; + float damage = Spells.ice_charge.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FROST).setProjectile(), damage); if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit)) - ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost, 120, 1)); + ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(), + Spells.ice_charge.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue())); } // Particle effect if(world.isRemote){ this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); for(int i = 0; i < 30 * blastMultiplier; i++){ + + ParticleBuilder.create(Type.ICE, rand, this.posX, this.posY, this.posZ, 2 * blastMultiplier, false) + .time(35).gravity(true).spawn(world); + float brightness = 0.4f + rand.nextFloat() * 0.5f; - Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 35); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 0, brightness, - brightness + 0.1f, 1.0f); + ParticleBuilder.create(Type.DARK_MAGIC, rand, this.posX, this.posY, this.posZ, 2 * blastMultiplier, false) + .clr(brightness, brightness + 0.1f, 1.0f).spawn(world); } } if(!this.world.isRemote){ - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5f, rand.nextFloat() * 0.4f + 0.6f); - this.playSound(WizardrySounds.SPELL_ICE, 1.2f, rand.nextFloat() * 0.4f + 1.2f); + this.playSound(WizardrySounds.ENTITY_ICE_CHARGE_SMASH, 1.5f, rand.nextFloat() * 0.4f + 0.6f); + this.playSound(WizardrySounds.ENTITY_ICE_CHARGE_ICE, 1.2f, rand.nextFloat() * 0.4f + 1.2f); - double radius = 3.0d * blastMultiplier; + double radius = Spells.ice_charge.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); @@ -87,7 +81,9 @@ public class EntityIceCharge extends EntityBomb { for(EntityLivingBase target : targets){ if(target != entityHit && target != this.getThrower()){ if(!MagicDamage.isEntityImmune(DamageType.FROST, target)) - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0)); + target.addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.ice_charge.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(), + Spells.ice_charge.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue())); } } @@ -97,35 +93,40 @@ public class EntityIceCharge extends EntityBomb { BlockPos pos = new BlockPos(this.posX + i, this.posY, this.posZ + j); - int y = WizardryUtilities.getNearestFloorLevelB(world, pos, 7); + Integer y = WizardryUtilities.getNearestSurface(world, pos, EnumFacing.UP, 7, true, + WizardryUtilities.SurfaceCriteria.SOLID_LIQUID_TO_AIR); - pos = new BlockPos(pos.getX(), y, pos.getZ()); + if(y != null){ - double dist = this.getDistance(pos.getX(), pos.getY(), pos.getZ()); + pos = new BlockPos(pos.getX(), y, pos.getZ()); - // Randomised with weighting so that the nearer the block the more likely it is to be snowed. - if(y != -1 && rand.nextInt((int)dist * 2 + 1) < 1 && dist < 2){ - if(world.getBlockState(pos.down()).getBlock() == Blocks.WATER){ - world.setBlockState(pos.down(), Blocks.ICE.getDefaultState()); - }else{ - // Don't need to check whether the block at pos can be replaced since getNearestFloorLevelB - // only ever returns floors with air above them. - world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState()); + double dist = this.getDistance(pos.getX(), pos.getY(), pos.getZ()); + + // Randomised with weighting so that the nearer the block the more likely it is to be snowed. + if(rand.nextInt((int)dist * 2 + 1) < 1 && dist < 2){ + if(world.getBlockState(pos.down()).getBlock() == Blocks.WATER){ + world.setBlockState(pos.down(), Blocks.ICE.getDefaultState()); + }else{ + // Don't need to check whether the block at pos can be replaced since getNearestFloorLevelB + // only ever returns floors with air above them. + world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState()); + } } } } } // Releases shards - for(int i = 0; i < 10; i++){ + for(int i = 0; i < Spells.ice_charge.getProperty(ICE_SHARDS).intValue(); i++){ double dx = rand.nextDouble() - 0.5; double dy = rand.nextDouble() - 0.5; double dz = rand.nextDouble() - 0.5; - EntityIceShard iceshard = new EntityIceShard(world, this.posX + dx, this.posY + dy, this.posZ + dz); - iceshard.motionX = dx; - iceshard.motionY = dy; - iceshard.motionZ = dz; - iceshard.setShootingEntity(this.getThrower()); + EntityIceShard iceshard = new EntityIceShard(world); + iceshard.setPosition(this.posX + dx, this.posY + dy, this.posZ + dz); + iceshard.motionX = dx * 1.5; + iceshard.motionY = dy * 1.5; + iceshard.motionZ = dz * 1.5; + iceshard.setCaster(this.getThrower()); iceshard.damageMultiplier = this.damageMultiplier; world.spawnEntity(iceshard); } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceLance.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceLance.java index afbfa94c..a9c2032e 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceLance.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceLance.java @@ -1,123 +1,67 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityIceLance extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + /** Creates a new ice lance in the given world. */ public EntityIceLance(World world){ super(world); this.setKnockbackStrength(1); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityIceLance(World world, double x, double y, double z){ - super(world, x, y, z); - this.setKnockbackStrength(1); - } + @Override public double getDamage(){ return Spells.ice_lance.getProperty(Spell.DAMAGE).floatValue(); } - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityIceLance(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - this.setKnockbackStrength(1); - } + @Override public int getLifetime(){ return -1; } - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityIceLance(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); - this.setKnockbackStrength(1); - } + @Override public DamageType getDamageType(){ return DamageType.FROST; } + + @Override public boolean doGravity(){ return true; } + + @Override public boolean doDeceleration(){ return true; } + + @Override public boolean doOverpenetration(){ return true; } + + @Override public boolean canRenderOnFire(){ return false; } @Override public void onEntityHit(EntityLivingBase entityHit){ // Adds a freeze effect to the target. if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit)) - entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 0)); + entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.ice_lance.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.ice_lance.getProperty(Spell.EFFECT_STRENGTH).intValue())); - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_ICE_LANCE_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } @Override - public void tickInAir(){ - - } - - @Override - public void onBlockHit(){ + public void onBlockHit(RayTraceResult hit){ // Adds a particle effect when the ice lance hits a block. if(this.world.isRemote){ for(int j = 0; j < 10; j++){ - double x = this.posX - 0.25d + (rand.nextDouble() / 2); - double y = this.posY - 0.25d + (rand.nextDouble() / 2); - double z = this.posZ - 0.25d + (rand.nextDouble() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, world, x, y, z, x - this.posX, y - this.posY, - z - this.posZ, 20 + rand.nextInt(10)); + ParticleBuilder.create(Type.ICE, this.rand, this.posX, this.posY, this.posZ, 0.5, true) + .time(20 + rand.nextInt(10)).gravity(true).spawn(world); } } - // Parameters for sound: sound event name, volume, pitch. - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F); + + this.playSound(WizardrySounds.ENTITY_ICE_LANCE_SMASH, 1.0F, rand.nextFloat() * 0.4F + 1.2F); } @Override - public void tickInGround(){ - this.setDead(); - } - - @Override - public double getDamage(){ - return 10.0d; - } - - @Override - public DamageType getDamageType(){ - return DamageType.FROST; - } - - @Override - public boolean doGravity(){ - return true; - } - - @Override - public boolean doDeceleration(){ - return true; - } - - @Override - public boolean doOverpenetration(){ - return true; - } - - @Override - protected void entityInit(){ - - } - - @Override - public boolean canRenderOnFire(){ - return false; - } + protected void entityInit(){} } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceShard.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceShard.java index 2d5e2f39..72a2cfdf 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceShard.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceShard.java @@ -1,109 +1,70 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class EntityIceShard extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + /** Creates a new ice shard in the given world. */ public EntityIceShard(World world){ super(world); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityIceShard(World world, double x, double y, double z){ - super(world, x, y, z); - } + @Override public double getDamage(){ return Spells.ice_shard.getProperty(Spell.DAMAGE).floatValue(); } - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityIceShard(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - } + @Override public int getLifetime(){ return -1; } - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityIceShard(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); - } + @Override public DamageType getDamageType(){ return DamageType.FROST; } + + @Override public boolean doGravity(){ return true; } + + @Override public boolean doDeceleration(){ return true; } + + @Override public boolean canRenderOnFire(){ return false; } @Override public void onEntityHit(EntityLivingBase entityHit){ // Adds a freeze effect to the target. if(!MagicDamage.isEntityImmune(DamageType.FROST, entityHit)) - entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0)); + entityHit.addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.ice_shard.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.ice_shard.getProperty(Spell.EFFECT_STRENGTH).intValue())); - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_ICE_SHARD_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); } @Override - public void tickInAir(){ - - } - - @Override - public void onBlockHit(){ + public void onBlockHit(RayTraceResult hit){ + // Adds a particle effect when the ice shard hits a block. if(this.world.isRemote){ + // Gets a position slightly away from the block hit so the particle doesn't get cut in half by the block face + Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(0.15)); + ParticleBuilder.create(Type.FLASH).pos(vec).clr(0.75f, 1, 1).spawn(world); + for(int j = 0; j < 10; j++){ - double x = this.posX - 0.25d + (rand.nextDouble() / 2); - double y = this.posY - 0.25d + (rand.nextDouble() / 2); - double z = this.posZ - 0.25d + (rand.nextDouble() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.ICE, world, x, y, z, x - this.posX, y - this.posY, - z - this.posZ, 20 + rand.nextInt(10)); + ParticleBuilder.create(Type.ICE, this.rand, this.posX, this.posY, this.posZ, 0.5, true) + .time(20 + rand.nextInt(10)).gravity(true).spawn(world); } } // Parameters for sound: sound event name, volume, pitch. - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.0F, rand.nextFloat() * 0.4F + 1.2F); + this.playSound(WizardrySounds.ENTITY_ICE_SHARD_SMASH, 1.0F, rand.nextFloat() * 0.4F + 1.2F); } @Override - public double getDamage(){ - return 6.0d; - } - - @Override - public DamageType getDamageType(){ - return DamageType.FROST; - } - - @Override - public boolean doGravity(){ - return true; - } - - @Override - public boolean doDeceleration(){ - return true; - } - - @Override - protected void entityInit(){ - - } - - @Override - public boolean canRenderOnFire(){ - return false; - } + protected void entityInit(){} } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java new file mode 100644 index 00000000..7a34b09d --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java @@ -0,0 +1,117 @@ +package electroblob.wizardry.entity.projectile; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.Blocks; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; + +public class EntityIceball extends EntityMagicProjectile { + + public EntityIceball(World world){ + super(world); + this.setSize(0.5f, 0.5f); + } + + @Override + protected void onImpact(RayTraceResult rayTrace){ + + if(!world.isRemote){ + + Entity entityHit = rayTrace.entityHit; + + if(entityHit != null){ + + float damage = Spells.iceball.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + + entityHit.attackEntityFrom( + MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FROST).setProjectile(), + damage); + + if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.FROST, entityHit)){ + ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(WizardryPotions.frost, + Spells.iceball.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.iceball.getProperty(Spell.EFFECT_STRENGTH).intValue())); + } + + }else{ + + boolean flag = true; + + if(this.getThrower() != null && this.getThrower() instanceof EntityLiving){ + flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.getThrower()); + } + + if(flag){ + + BlockPos pos = rayTrace.getBlockPos(); + + if(rayTrace.sideHit == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP) + && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ + world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState()); + } + } + } + + this.playSound(WizardrySounds.ENTITY_ICEBALL_HIT, 2, 0.8f + rand.nextFloat() * 0.3f); + + this.setDead(); + } + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(world.isRemote){ + + for(int i=0; i<5; i++){ + + double dx = (rand.nextDouble() - 0.5) * width; + double dy = (rand.nextDouble() - 0.5) * height + this.height/2; + double dz = (rand.nextDouble() - 0.5) * width; + double v = 0.06; + ParticleBuilder.create(ParticleBuilder.Type.SNOW) + .pos(this.getPositionVector().add(dx - this.motionX/2, dy, dz - this.motionZ/2)) + .vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(8 + rand.nextInt(4)).spawn(world); + + if(ticksExisted > 1){ + dx = (rand.nextDouble() - 0.5) * width; + dy = (rand.nextDouble() - 0.5) * height + this.height / 2; + dz = (rand.nextDouble() - 0.5) * width; + ParticleBuilder.create(ParticleBuilder.Type.SNOW) + .pos(this.getPositionVector().add(dx - this.motionX, dy, dz - this.motionZ)) + .vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(8 + rand.nextInt(4)).spawn(world); + } + } + } + } + + @Override + public int getLifetime(){ + return 16; + } + + @Override + public boolean hasNoGravity(){ + return true; + } + + @Override + public boolean canRenderOnFire(){ + return false; + } +} diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java new file mode 100644 index 00000000..e78574a4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java @@ -0,0 +1,106 @@ +package electroblob.wizardry.entity.projectile; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.projectile.EntityLargeFireball; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * It's like {@link EntityMagicFireball}, but bigger... the wizardry version of vanilla's + * {@link net.minecraft.entity.projectile.EntityLargeFireball} + */ +@Mod.EventBusSubscriber +public class EntityLargeMagicFireball extends EntityMagicFireball { + + public static final String EXPLOSION_POWER = "explosion_power"; + + /** The entity blast multiplier. This is now synced and saved centrally from {@link EntityBomb}. */ + public float blastMultiplier = 1.0f; + + /** The explosion power of this fireball. If this is -1, the damage for the fireball + * spell will be used instead; this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */ + protected float explosionPower = -1; + + public EntityLargeMagicFireball(World world){ + super(world); + this.setSize(1, 1); + } + + public void setExplosionPower(float explosionPower){ + this.explosionPower = explosionPower; + } + + public float getExplosionPower(){ + return explosionPower == -1 ? Spells.greater_fireball.getProperty(EXPLOSION_POWER).floatValue() : explosionPower; + } + + @Override + public float getDamage(){ + return damage == -1 ? Spells.greater_fireball.getProperty(Spell.DAMAGE).floatValue() : damage; + } + + @Override + protected void onImpact(RayTraceResult rayTrace){ + + if(!world.isRemote){ + boolean flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.thrower); + this.world.newExplosion(null, this.posX, this.posY, this.posZ, getExplosionPower() * blastMultiplier, flag, flag); + } + + super.onImpact(rayTrace); + } + + @Override + public void writeSpawnData(ByteBuf buffer){ + buffer.writeFloat(blastMultiplier); + super.writeSpawnData(buffer); + } + + @Override + public void readSpawnData(ByteBuf buffer){ + blastMultiplier = buffer.readFloat(); + super.readSpawnData(buffer); + } + + @Override + public void readEntityFromNBT(NBTTagCompound nbttagcompound){ + super.readEntityFromNBT(nbttagcompound); + blastMultiplier = nbttagcompound.getFloat("blastMultiplier"); + } + + @Override + public void writeEntityToNBT(NBTTagCompound nbttagcompound){ + super.writeEntityToNBT(nbttagcompound); + nbttagcompound.setFloat("blastMultiplier", blastMultiplier); + } + + @SubscribeEvent + public static void onEntityJoinWorldEvent(EntityJoinWorldEvent event){ + // Replaces all vanilla large fireballs with wizardry ones + if(Wizardry.settings.replaceVanillaFireballs && event.getEntity() instanceof EntityLargeFireball){ + + event.setCanceled(true); + + EntityLargeMagicFireball fireball = new EntityLargeMagicFireball(event.getWorld()); + fireball.thrower = ((EntityLargeFireball)event.getEntity()).shootingEntity; + fireball.setPosition(event.getEntity().posX, event.getEntity().posY, event.getEntity().posZ); + fireball.setDamage(6); + // Don't set the burn duration because vanilla large fireballs don't set mobs on fire directly + fireball.setExplosionPower(((EntityLargeFireball)event.getEntity()).explosionPower); + fireball.setLifetime(75); + + fireball.motionX = ((EntityLargeFireball)event.getEntity()).accelerationX * ACCELERATION_CONVERSION_FACTOR; + fireball.motionY = ((EntityLargeFireball)event.getEntity()).accelerationY * ACCELERATION_CONVERSION_FACTOR; + fireball.motionZ = ((EntityLargeFireball)event.getEntity()).accelerationZ * ACCELERATION_CONVERSION_FACTOR; + + event.getWorld().spawnEntity(fireball); + } + } +} diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningArrow.java index f19c4dc2..2f166bfd 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningArrow.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningArrow.java @@ -1,100 +1,59 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; import net.minecraft.world.World; public class EntityLightningArrow extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + /** Creates a new lightning arrow in the given world. */ public EntityLightningArrow(World world){ super(world); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityLightningArrow(World world, double x, double y, double z){ - super(world, x, y, z); - } + @Override public double getDamage(){ return Spells.lightning_arrow.getProperty(Spell.DAMAGE).doubleValue(); } - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityLightningArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - } + @Override public int getLifetime(){ return 20; } - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityLightningArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); - } + @Override public DamageType getDamageType(){ return DamageType.SHOCK; } + + @Override public boolean doGravity(){ return false; } + + @Override public boolean doDeceleration(){ return false; } @Override public void onEntityHit(EntityLivingBase entityHit){ if(world.isRemote){ for(int j = 0; j < 8; j++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + rand.nextFloat() - 0.5, - this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, - 0, 3); + ParticleBuilder.create(Type.SPARK, rand, posX, posY + height / 2, posZ, 1, false).spawn(world); } } - /* Pretty sure this needn't be here, probably missed it when I implemented the damage type system. if(entityHit - * instanceof EntityCreeper && !((EntityCreeper)entityHit).getPowered()){ - * entityHit.getDataWatcher().updateObject(17, Byte.valueOf((byte)1)); if(this.getShootingEntity() instanceof - * EntityPlayer) ((EntityPlayer)this.getShootingEntity()).addStat(Wizardry.chargeCreeper); } */ - this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.0F); + + this.playSound(WizardrySounds.ENTITY_LIGHTNING_ARROW_HIT, 1.0F, 1.0F); } + +// @Override +// public void onBlockHit(RayTraceResult hit){ +// if(this.world.isRemote){ +// Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); +// ParticleBuilder.create(Type.SCORCH).pos(vec).face(hit.sideHit).clr(0.4f, 0.8f, 1).scale(0.6f).spawn(world); +// } +// } @Override public void tickInAir(){ - - if(this.ticksExisted > 20){ - this.setDead(); - } - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX, this.posY, this.posZ, 0, 0, 0, - 3); + ParticleBuilder.create(Type.SPARK).pos(posX, posY, posZ).spawn(world); } - } @Override - public double getDamage(){ - return 7.0d; - } - - @Override - public DamageType getDamageType(){ - return DamageType.SHOCK; - } - - @Override - public boolean doGravity(){ - return false; - } - - @Override - public boolean doDeceleration(){ - return false; - } - - @Override - protected void entityInit(){ - - } + protected void entityInit(){} } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningDisc.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningDisc.java index 6340f99f..7f745f87 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningDisc.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityLightningDisc.java @@ -1,56 +1,38 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityLightningDisc extends EntityMagicProjectile { - public EntityLightningDisc(World par1World){ - super(par1World); - } - - public EntityLightningDisc(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntityLightningDisc(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - } - - public EntityLightningDisc(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); + + public EntityLightningDisc(World world){ + super(world); this.width = 2.0f; this.height = 0.5f; } @Override - protected float getSpeed(){ - return 1.2f; - } - - @Override - protected void onImpact(RayTraceResult mop){ - Entity entityHit = mop.entityHit; + protected void onImpact(RayTraceResult result){ + + Entity entityHit = result.entityHit; if(entityHit != null){ - float damage = 12 * damageMultiplier; - - entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), - damage); + float damage = Spells.lightning_disc.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), + DamageType.SHOCK), damage); } - this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_LIGHTNING_DISC_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); - if(mop.typeOfHit == RayTraceResult.Type.BLOCK) this.setDead(); + if(result.typeOfHit == RayTraceResult.Type.BLOCK) this.setDead(); } @Override @@ -61,42 +43,11 @@ public class EntityLightningDisc extends EntityMagicProjectile { // Particle effect if(world.isRemote){ for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + rand.nextFloat() * 2 - 1, - this.posY, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3); - // world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + rand.nextFloat() - 0.5, this.posY + - // this.height/2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, 0); + ParticleBuilder.create(Type.SPARK).pos(this.posX + rand.nextFloat() * 2 - 1, + this.posY, this.posZ + rand.nextFloat() * 2 - 1).spawn(world); } } - if(!this.collided && !world.isRemote){ - - double seekingRange = 5.0d; - - List entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX, - this.posY, this.posZ, this.world); - Entity target = null; - - for(Entity possibleTarget : entities){ - // Decides if current entity should be replaced. - if(target == null || this.getDistance(target) > this.getDistance(possibleTarget)){ - // Decides if new entity is a valid target. - if(WizardryUtilities.isValidTarget(this.getThrower(), possibleTarget)){ - target = possibleTarget; - } - } - } - - if(target != null && Math.abs(this.motionX) < 1 && Math.abs(this.motionY) < 1 - && Math.abs(this.motionZ) < 1){ - this.addVelocity((target.posX - this.posX) / 30, (target.posY + target.height / 2 - this.posY) / 30, - (target.posZ - this.posZ) / 30); - } - } - - if(this.ticksExisted > 50){ - this.setDead(); - } - // Cancels out the slowdown effect in EntityThrowable this.motionX /= 0.99; this.motionY /= 0.99; @@ -104,8 +55,18 @@ public class EntityLightningDisc extends EntityMagicProjectile { } @Override - protected float getGravityVelocity(){ - return 0.0F; + public float getSeekingStrength(){ + return Spells.lightning_disc.getProperty(Spell.SEEKING_STRENGTH).floatValue(); + } + + @Override + public int getLifetime(){ + return 30; + } + + @Override + public boolean hasNoGravity(){ + return true; } @Override diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java index db78638f..2dd166e6 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java @@ -1,11 +1,12 @@ package electroblob.wizardry.entity.projectile; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.UUID; - +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.RayTracer; import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.block.Block; @@ -23,22 +24,23 @@ import net.minecraft.network.play.server.SPacketChangeGameState; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.*; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.UUID; + /** * This class was copied from EntityArrow in the 1.7.10 update as part of the overhaul and major cleanup of the code for * the projectiles. It provides a unifying superclass for all directed projectiles (i.e. not spherical stuff like * snowballs), namely magic missile, ice shard, force arrow, lightning arrow and dart. All spherical projectiles should * extend {@link EntityMagicProjectile}. - *

    + *

    * This class handles saving of the damage multiplier and all shared logic. Methods are provided which are triggered at * useful points during the entity update cycle as well as a few getters for various properties. Override any of these * to change the behaviour (no need to call super for any of them). @@ -46,8 +48,12 @@ import net.minecraftforge.fml.relauncher.SideOnly; * @since Wizardry 1.0 * @author Electroblob */ +// TODO: Might be a good idea to have this implement IEntityOwnable as well public abstract class EntityMagicArrow extends Entity implements IProjectile, IEntityAdditionalSpawnData { + public static final double LAUNCH_Y_OFFSET = 0.1; + public static final int SEEKING_TIME = 15; + private int blockX = -1; private int blockY = -1; private int blockZ = -1; @@ -59,7 +65,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE /** Seems to be some sort of timer for animating an arrow. */ public int arrowShake; /** The owner of this arrow. */ - private WeakReference shootingEntity; + private WeakReference caster; /** * The UUID of the caster. Note that this is only for loading purposes; during normal updates the actual entity * instance is stored (so that getEntityByUUID is not called constantly), so this will not always be synced (this is @@ -70,90 +76,81 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE int ticksInAir; /** The amount of knockback an arrow applies when it hits a mob. */ private int knockbackStrength; - /** - * The damage multiplier for the arrow. Normally this isn't set directly, since it can be done via the constructor. - * An exception is where other entities need to pass in their multipliers, e.g. ice charge. - */ + /** The damage multiplier for the projectile. */ public float damageMultiplier = 1.0f; - /** Basic shell constructor. Should only be used by the client. */ + /** Creates a new projectile in the given world. */ public EntityMagicArrow(World world){ super(world); this.setSize(0.5F, 0.5F); } - - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityMagicArrow(World world, double x, double y, double z){ - super(world); - this.setSize(0.5F, 0.5F); - this.setPosition(x, y, z); - // yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway. - } - - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the aimingError parameter. For reference, skeletons set this to - * 10 on easy, 6 on normal and 2 on hard difficulty. - */ - public EntityMagicArrow(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world); - this.shootingEntity = new WeakReference(caster); - this.damageMultiplier = damageMultiplier; - - this.posY = caster.posY + (double)caster.getEyeHeight() - 0.10000000149011612D; - double d0 = target.posX - caster.posX; - double d1 = this.doGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0F) - this.posY - : target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) - this.posY; - double d2 = target.posZ - caster.posZ; - double d3 = (double)MathHelper.sqrt(d0 * d0 + d2 * d2); - - if(d3 >= 1.0E-7D){ - float f2 = (float)(Math.atan2(d2, d0) * 180.0D / Math.PI) - 90.0F; - float f3 = (float)(-(Math.atan2(d1, d3) * 180.0D / Math.PI)); - double d4 = d0 / d3; - double d5 = d2 / d3; - this.setLocationAndAngles(caster.posX + d4, this.posY, caster.posZ + d5, f2, f3); - // yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway. - - // f4 depends on the horizontal distance between the two entities and accounts for bullet drop, - // but of course if gravity is ignored this should be 0. - float bulletDropCompensation = this.doGravity() ? (float)d3 * 0.2F : 0; - this.shoot(d0, d1 + (double)bulletDropCompensation, d2, speed, aimingError); - } - } - - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. Use this - * constructor for normal, player-cast spells. - */ - public EntityMagicArrow(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world); - this.shootingEntity = new WeakReference(caster); - this.damageMultiplier = damageMultiplier; - - this.setSize(0.5F, 0.5F); - this.setLocationAndAngles(caster.posX, caster.posY + (double)caster.getEyeHeight(), caster.posZ, - caster.rotationYaw, caster.rotationPitch); + + // Initialiser methods + + /** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and + * aims it in the direction they are looking with the given speed. */ + public void aim(EntityLivingBase caster, float speed){ + + this.setCaster(caster); + + this.setLocationAndAngles(caster.posX, caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET, + caster.posZ, caster.rotationYaw, caster.rotationPitch); + this.posX -= (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F); this.posY -= 0.10000000149011612D; this.posZ -= (double)(MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F); + this.setPosition(this.posX, this.posY, this.posZ); + // yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway. this.motionX = (double)(-MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI)); + this.motionY = (double)(-MathHelper.sin(this.rotationPitch / 180.0F * (float)Math.PI)); this.motionZ = (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI)); - this.motionY = (double)(-MathHelper.sin(this.rotationPitch / 180.0F * (float)Math.PI)); + this.shoot(this.motionX, this.motionY, this.motionZ, speed * 1.5F, 1.0F); } + /** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and + * aims it at the given target with the given speed. The trajectory will be altered slightly by a random amount + * determined by the aimingError parameter. For reference, skeletons set this to 10 on easy, 6 on normal and 2 on hard + * difficulty. */ + public void aim(EntityLivingBase caster, Entity target, float speed, float aimingError){ + + this.setCaster(caster); + + this.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET; + double dx = target.posX - caster.posX; + double dy = this.doGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY + : target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY; + double dz = target.posZ - caster.posZ; + double horizontalDistance = (double)MathHelper.sqrt(dx * dx + dz * dz); + + if(horizontalDistance >= 1.0E-7D){ + float yaw = (float)(Math.atan2(dz, dx) * 180.0d / Math.PI) - 90.0f; + float pitch = (float)(-(Math.atan2(dy, horizontalDistance) * 180.0d / Math.PI)); + double dxNormalised = dx / horizontalDistance; + double dzNormalised = dz / horizontalDistance; + this.setLocationAndAngles(caster.posX + dxNormalised, this.posY, caster.posZ + dzNormalised, yaw, pitch); + // yOffset was set to 0 here, but that has been replaced by getYOffset(), which returns 0 in Entity anyway. + + // Depends on the horizontal distance between the two entities and accounts for bullet drop, + // but of course if gravity is ignored this should be 0 since there is no bullet drop. + float bulletDropCompensation = this.doGravity() ? (float)horizontalDistance * 0.2f : 0; + this.shoot(dx, dy + (double)bulletDropCompensation, dz, speed, aimingError); + } + } + + // Property getters (to be overridden by subclasses) + /** Subclasses must override this to set their own base damage. */ public abstract double getDamage(); + /** Returns the maximum flight time in ticks before this projectile disappears, or -1 if it can continue + * indefinitely until it hits something. This should be constant. */ + public abstract int getLifetime(); + /** Override this to specify the damage type dealt. Defaults to {@link DamageType#MAGIC}. */ public DamageType getDamageType(){ return DamageType.MAGIC; @@ -181,85 +178,68 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE } /** - * Similar to setArrowHeading, it's point the throwable entity to a x, y, z direction. + * Returns the seeking strength of this projectile, or the maximum distance from a target the projectile can be + * heading for that will make it curve towards that target. By default, this is 2 if the caster is wearing a ring + * of attraction, otherwise it is 0. */ - @Override - public void shoot(double x, double y, double z, float speed, float randomness){ - float f2 = MathHelper.sqrt(x * x + y * y + z * z); - x /= (double)f2; - y /= (double)f2; - z /= (double)f2; - x += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D - * (double)randomness; - y += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D - * (double)randomness; - z += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D - * (double)randomness; - x *= (double)speed; - y *= (double)speed; - z *= (double)speed; - this.motionX = x; - this.motionY = y; - this.motionZ = z; - float f3 = MathHelper.sqrt(x * x + z * z); - this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(x, z) * 180.0D / Math.PI); - this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(y, (double)f3) * 180.0D / Math.PI); - this.ticksInGround = 0; + public float getSeekingStrength(){ + return getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), + WizardryItems.ring_seeking) ? 2 : 0; } - // There was an override for setPositionAndRotationDirect here, but it was exactly the same as the superclass - // method (in Entity), so it was removed since it was redundant. + // Setters and getters + /** Sets the amount of knockback the projectile applies when it hits a mob. */ + public void setKnockbackStrength(int knockback){ + this.knockbackStrength = knockback; + } + /** - * Sets the velocity to the args. Args: x, y, z. THIS IS CLIENT SIDE ONLY! DO NOT USE IN COMMON OR SERVER CODE! + * Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the entity + * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to + * another dimension, or this construct simply had no caster in the first place. */ - @Override - @SideOnly(Side.CLIENT) - public void setVelocity(double p_70016_1_, double p_70016_3_, double p_70016_5_){ - this.motionX = p_70016_1_; - this.motionY = p_70016_3_; - this.motionZ = p_70016_5_; - - if(this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F){ - float f = MathHelper.sqrt(p_70016_1_ * p_70016_1_ + p_70016_5_ * p_70016_5_); - this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(p_70016_1_, p_70016_5_) * 180.0D / Math.PI); - this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(p_70016_3_, (double)f) * 180.0D / Math.PI); - this.prevRotationPitch = this.rotationPitch; - this.prevRotationYaw = this.rotationYaw; - this.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch); - this.ticksInGround = 0; - } + public EntityLivingBase getCaster(){ + return caster == null ? null : caster.get(); } - /** - * Called each tick when the projectile is in a block. Defaults to setDead(), but can be overridden to change the - * behaviour. - */ - public void tickInGround(){ + public void setCaster(EntityLivingBase entity){ + caster = new WeakReference<>(entity); + } + + // Methods triggered during the update cycle + + /** Called each tick when the projectile is in a block. Defaults to setDead(), but can be overridden to change the + * behaviour. */ + protected void tickInGround(){ this.setDead(); } /** Called each tick when the projectile is in the air. Override to add particles and such like. */ - public void tickInAir(){ - } + protected void tickInAir(){} /** Called when the projectile hits an entity. Override to add potion effects and such like. */ - public void onEntityHit(EntityLivingBase entityHit){ - } + protected void onEntityHit(EntityLivingBase entityHit){} - /** Called when the projectile hits a block. Override to add sound effects and such like. */ - public void onBlockHit(){ - } + /** Called when the projectile hits a block. Override to add sound effects and such like. + * @param hit A vector representing the exact coordinates of the hit; use this to centre particle effects, for + * example. */ + protected void onBlockHit(RayTraceResult hit){} @Override public void onUpdate(){ super.onUpdate(); - if(this.getShootingEntity() == null && this.casterUUID != null){ + // Projectile disappears after its lifetime (if it has one) has elapsed + if(getLifetime() >=0 && this.ticksExisted > getLifetime()){ + this.setDead(); + } + + if(this.getCaster() == null && this.casterUUID != null){ Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); if(entity instanceof EntityLivingBase){ - this.shootingEntity = new WeakReference((EntityLivingBase)entity); + this.caster = new WeakReference<>((EntityLivingBase)entity); } } @@ -299,6 +279,9 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.ticksInGround = 0; ++this.ticksInAir; + + // Does a ray trace to determine whether the projectile will hit a block in the next tick + Vec3d vec3d1 = new Vec3d(this.posX, this.posY, this.posZ); Vec3d vec3d = new Vec3d(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ); RayTraceResult raytraceresult = this.world.rayTraceBlocks(vec3d1, vec3d, false, true, false); @@ -309,6 +292,9 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE vec3d = new Vec3d(raytraceresult.hitVec.x, raytraceresult.hitVec.y, raytraceresult.hitVec.z); } + + // Uses bounding boxes to determine whether the projectile will hit an entity in the next tick, and if so + // overwrites the block hit with an entity Entity entity = null; List list = this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox() @@ -320,7 +306,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE for(i = 0; i < list.size(); ++i){ Entity entity1 = (Entity)list.get(i); - if(entity1.canBeCollidedWith() && (entity1 != this.getShootingEntity() || this.ticksInAir >= 5)){ + if(entity1.canBeCollidedWith() && (entity1 != this.getCaster() || this.ticksInAir >= 5)){ f1 = 0.3F; AxisAlignedBB axisalignedbb1 = entity1.getEntityBoundingBox().grow((double)f1, (double)f1, (double)f1); @@ -347,8 +333,8 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE && raytraceresult.entityHit instanceof EntityPlayer){ EntityPlayer entityplayer = (EntityPlayer)raytraceresult.entityHit; - if(entityplayer.capabilities.disableDamage || this.getShootingEntity() instanceof EntityPlayer - && !((EntityPlayer)this.getShootingEntity()).canAttackPlayer(entityplayer)){ + if(entityplayer.capabilities.disableDamage || this.getCaster() instanceof EntityPlayer + && !((EntityPlayer)this.getCaster()).canAttackPlayer(entityplayer)){ raytraceresult = null; } } @@ -359,11 +345,10 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE if(raytraceresult.entityHit != null){ DamageSource damagesource = null; - if(this.getShootingEntity() == null){ + if(this.getCaster() == null){ damagesource = DamageSource.causeThrownDamage(this, this); }else{ - damagesource = MagicDamage.causeIndirectMagicDamage(this, - (EntityLivingBase)this.getShootingEntity(), this.getDamageType()).setProjectile(); + damagesource = MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), this.getDamageType()).setProjectile(); } if(raytraceresult.entityHit.attackEntityFrom(damagesource, @@ -386,17 +371,15 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE } // Thorns enchantment - if(this.getShootingEntity() != null - && this.getShootingEntity() instanceof EntityLivingBase){ - EnchantmentHelper.applyThornEnchantments(entityHit, this.getShootingEntity()); - EnchantmentHelper.applyArthropodEnchantments((EntityLivingBase)this.getShootingEntity(), - entityHit); + if(this.getCaster() != null){ + EnchantmentHelper.applyThornEnchantments(entityHit, this.getCaster()); + EnchantmentHelper.applyArthropodEnchantments(this.getCaster(), entityHit); } - if(this.getShootingEntity() != null && raytraceresult.entityHit != this.getShootingEntity() + if(this.getCaster() != null && raytraceresult.entityHit != this.getCaster() && raytraceresult.entityHit instanceof EntityPlayer - && this.getShootingEntity() instanceof EntityPlayerMP){ - ((EntityPlayerMP)this.getShootingEntity()).connection + && this.getCaster() instanceof EntityPlayerMP){ + ((EntityPlayerMP)this.getCaster()).connection .sendPacket(new SPacketChangeGameState(6, 0.0F)); } } @@ -431,7 +414,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.inGround = true; this.arrowShake = 7; - this.onBlockHit(); + this.onBlockHit(raytraceresult); if(this.stuckInBlock.getMaterial() != Material.AIR){ this.stuckInBlock.getBlock().onEntityCollision(this.world, raytraceresult.getBlockPos(), @@ -440,6 +423,29 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE } } + // Seeking + if(getSeekingStrength() > 0){ + + Vec3d velocity = new Vec3d(motionX, motionY, motionZ); + + RayTraceResult hit = RayTracer.rayTrace(world, this.getPositionVector(), + this.getPositionVector().add(velocity.scale(SEEKING_TIME)), getSeekingStrength(), false, + true, false, EntityLivingBase.class, RayTracer.ignoreEntityFilter(null)); + + if(hit != null && hit.entityHit != null){ + + if(AllyDesignationSystem.isValidTarget(getCaster(), hit.entityHit)){ + + Vec3d direction = new Vec3d(hit.entityHit.posX, hit.entityHit.posY + hit.entityHit.height/2, + hit.entityHit.posZ).subtract(this.getPositionVector()).normalize().scale(velocity.length()); + + motionX = motionX + 2 * (direction.x - motionX) / SEEKING_TIME; + motionY = motionY + 2 * (direction.y - motionY) / SEEKING_TIME; + motionZ = motionZ + 2 * (direction.z - motionZ) / SEEKING_TIME; + } + } + } + this.posX += this.motionX; this.posY += this.motionY; this.posZ += this.motionZ; @@ -497,6 +503,51 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE } } + @Override + public void shoot(double x, double y, double z, float speed, float randomness){ + float f2 = MathHelper.sqrt(x * x + y * y + z * z); + x /= (double)f2; + y /= (double)f2; + z /= (double)f2; + x += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness; + y += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness; + z += this.rand.nextGaussian() * (double)(this.rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)randomness; + x *= (double)speed; + y *= (double)speed; + z *= (double)speed; + this.motionX = x; + this.motionY = y; + this.motionZ = z; + float f3 = MathHelper.sqrt(x * x + z * z); + this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(x, z) * 180.0D / Math.PI); + this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(y, (double)f3) * 180.0D / Math.PI); + this.ticksInGround = 0; + } + + // There was an override for setPositionAndRotationDirect here, but it was exactly the same as the superclass + // method (in Entity), so it was removed since it was redundant. + + /** Sets the velocity to the args. Args: x, y, z. THIS IS CLIENT SIDE ONLY! DO NOT USE IN COMMON OR SERVER CODE! */ + @Override + @SideOnly(Side.CLIENT) + public void setVelocity(double p_70016_1_, double p_70016_3_, double p_70016_5_){ + this.motionX = p_70016_1_; + this.motionY = p_70016_3_; + this.motionZ = p_70016_5_; + + if(this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F){ + float f = MathHelper.sqrt(p_70016_1_ * p_70016_1_ + p_70016_5_ * p_70016_5_); + this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(p_70016_1_, p_70016_5_) * 180.0D / Math.PI); + this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(p_70016_3_, (double)f) * 180.0D / Math.PI); + this.prevRotationPitch = this.rotationPitch; + this.prevRotationYaw = this.rotationYaw; + this.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotationYaw, this.rotationPitch); + this.ticksInGround = 0; + } + } + + // Data reading and writing + @Override public void writeEntityToNBT(NBTTagCompound tag){ tag.setShort("xTile", (short)this.blockX); @@ -504,16 +555,15 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE tag.setShort("zTile", (short)this.blockZ); tag.setShort("life", (short)this.ticksInGround); if(this.stuckInBlock != null){ - ResourceLocation resourcelocation = (ResourceLocation)Block.REGISTRY - .getNameForObject(this.stuckInBlock.getBlock()); + ResourceLocation resourcelocation = Block.REGISTRY.getNameForObject(this.stuckInBlock.getBlock()); tag.setString("inTile", resourcelocation == null ? "" : resourcelocation.toString()); } tag.setByte("inData", (byte)this.inData); tag.setByte("shake", (byte)this.arrowShake); tag.setByte("inGround", (byte)(this.inGround ? 1 : 0)); tag.setFloat("damageMultiplier", this.damageMultiplier); - if(this.getShootingEntity() != null){ - tag.setUniqueId("casterUUID", this.getShootingEntity().getUniqueID()); + if(this.getCaster() != null){ + tag.setUniqueId("casterUUID", this.getCaster().getUniqueID()); } } @@ -523,7 +573,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.blockY = tag.getShort("yTile"); this.blockZ = tag.getShort("zTile"); this.ticksInGround = tag.getShort("life"); - // Commented out for now because there's some funny stuff going on with blockstates and metadata. + // Commented out for now because there's some funny stuff going on with blockstates and id. // this.stuckInBlock = Block.getBlockById(tag.getByte("inTile") & 255); this.inData = tag.getByte("inData") & 255; this.arrowShake = tag.getByte("shake") & 255; @@ -531,58 +581,40 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.damageMultiplier = tag.getFloat("damageMultiplier"); casterUUID = tag.getUniqueId("casterUUID"); } + + @Override + public void writeSpawnData(ByteBuf buffer){ + if(this.getCaster() != null) buffer.writeInt(this.getCaster().getEntityId()); + } - /** - * returns if this entity triggers Block.onEntityWalking on the blocks they walk on. used for spiders and wolves to - * prevent them from trampling crops - */ + @Override + public void readSpawnData(ByteBuf buffer){ + if(buffer.isReadable()) this.caster = new WeakReference<>( + (EntityLivingBase)this.world.getEntityByID(buffer.readInt())); + } + + // Miscellaneous overrides + + @Override protected boolean canTriggerWalking(){ return false; } + + @Override + public boolean canBeAttackedWithItem(){ + return false; + } @SideOnly(Side.CLIENT) public float getShadowSize(){ return 0.0F; } - - /** - * Sets the amount of knockback the arrow applies when it hits a mob. - */ - public void setKnockbackStrength(int p_70240_1_){ - this.knockbackStrength = p_70240_1_; - } - - /** - * If returns false, the item will not inflict any damage against entities. - */ - public boolean canAttackWithItem(){ - return false; - } - - public void writeSpawnData(ByteBuf buffer){ - if(this.getShootingEntity() != null) buffer.writeInt(this.getShootingEntity().getEntityId()); - } - - public void readSpawnData(ByteBuf buffer){ - if(buffer.isReadable()) this.shootingEntity = new WeakReference( - (EntityLivingBase)this.world.getEntityByID(buffer.readInt())); - } - - /** - * Returns the EntityLivingBase that created this construct, or null if it no longer exists. Cases where the entity - * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to - * another dimension, or this construct simply had no caster in the first place. - */ - public EntityLivingBase getShootingEntity(){ - return shootingEntity == null ? null : shootingEntity.get(); - } - - public void setShootingEntity(EntityLivingBase entity){ - shootingEntity = new WeakReference(entity); + + @Override + public SoundCategory getSoundCategory(){ + return WizardrySounds.SPELLS; } @Override - protected void entityInit() { - // TODO Auto-generated method stub - } + protected void entityInit(){} } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java new file mode 100644 index 00000000..0089966a --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java @@ -0,0 +1,198 @@ +package electroblob.wizardry.entity.projectile; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.projectile.EntitySmallFireball; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * It's a fireball - but unlike vanilla fireballs, it actually looks like a fireball, and isn't completely useless for + * attacking things (acceleration from stationary? Really, Mojang? No wonder I had so many blaze rods back in the day...) + */ +@Mod.EventBusSubscriber +public class EntityMagicFireball extends EntityMagicProjectile { + + protected static final int ACCELERATION_CONVERSION_FACTOR = 10; + + /** The damage dealt by this fireball. If this is -1, the damage for the fireball spell will be used instead; + * this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */ + protected float damage = -1; + /** The number of seconds entities are set on fire by this fireball. If this is -1, the damage for the fireball + * spell will be used instead; this is for when the fireball is not from a spell (i.e. a vanilla fireball replacement). */ + protected int burnDuration = -1; + /** The lifetime of this fireball in ticks. This needs to be stored so that it can be changed for vanilla replacements, + * or mobs that shoot fireballs would have severely reduced range! */ + protected int lifetime = 16; + + public EntityMagicFireball(World world){ + super(world); + this.setSize(0.5f, 0.5f); + } + + public void setDamage(float damage){ + this.damage = damage; + } + + public void setBurnDuration(int burnDuration){ + this.burnDuration = burnDuration; + } + + public float getDamage(){ + // I'm lazy, I'd rather not have an entire fireball spell class just to set two fields on the entity + return damage == -1 ? Spells.fireball.getProperty(Spell.DAMAGE).floatValue() : damage; + } + + public int getBurnDuration(){ + return burnDuration == -1 ? Spells.fireball.getProperty(Spell.BURN_DURATION).intValue() : burnDuration; + } + + @Override + protected void onImpact(RayTraceResult rayTrace){ + + if(!world.isRemote){ + + Entity entityHit = rayTrace.entityHit; + + if(entityHit != null){ + + float damage = getDamage() * damageMultiplier; + + entityHit.attackEntityFrom( + MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.FIRE).setProjectile(), + damage); + + if(!MagicDamage.isEntityImmune(DamageType.FIRE, entityHit) && getBurnDuration() > 0) + entityHit.setFire(getBurnDuration()); + + }else{ + + boolean flag = true; + + if(this.getThrower() != null && this.getThrower() instanceof EntityLiving){ + flag = net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.world, this.getThrower()); + } + + if(flag){ + + BlockPos blockpos = rayTrace.getBlockPos().offset(rayTrace.sideHit); + + if(this.world.isAirBlock(blockpos)){ + this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); + } + } + } + + //this.playSound(WizardrySounds.ENTITY_MAGIC_FIREBALL_HIT, 2, 0.8f + rand.nextFloat() * 0.3f); + + this.setDead(); + } + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(world.isRemote){ + + for(int i=0; i<5; i++){ + + double dx = (rand.nextDouble() - 0.5) * width; + double dy = (rand.nextDouble() - 0.5) * height + this.height/2 - 0.1; // -0.1 because flames aren't centred + double dz = (rand.nextDouble() - 0.5) * width; + double v = 0.06; + ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE) + .pos(this.getPositionVector().add(dx - this.motionX/2, dy, dz - this.motionZ/2)) + .vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(10).spawn(world); + + if(ticksExisted > 1){ + dx = (rand.nextDouble() - 0.5) * width; + dy = (rand.nextDouble() - 0.5) * height + this.height / 2 - 0.1; + dz = (rand.nextDouble() - 0.5) * width; + ParticleBuilder.create(ParticleBuilder.Type.MAGIC_FIRE) + .pos(this.getPositionVector().add(dx - this.motionX, dy, dz - this.motionZ)) + .vel(-v * dx, -v * dy, -v * dz).scale(width*2).time(10).spawn(world); + } + } + } + } + + public void setLifetime(int lifetime){ + this.lifetime = lifetime; + } + + @Override + public int getLifetime(){ + return lifetime; + } + + @Override + public boolean hasNoGravity(){ + return true; + } + + @Override + public boolean canRenderOnFire(){ + return false; + } + + @Override + public void writeSpawnData(ByteBuf buffer){ + buffer.writeInt(lifetime); + super.writeSpawnData(buffer); + } + + @Override + public void readSpawnData(ByteBuf buffer){ + lifetime = buffer.readInt(); + super.readSpawnData(buffer); + } + + @Override + public void readEntityFromNBT(NBTTagCompound nbttagcompound){ + super.readEntityFromNBT(nbttagcompound); + lifetime = nbttagcompound.getInteger("lifetime"); + } + + @Override + public void writeEntityToNBT(NBTTagCompound nbttagcompound){ + super.writeEntityToNBT(nbttagcompound); + nbttagcompound.setInteger("lifetime", lifetime); + } + + @SubscribeEvent + public static void onEntityJoinWorldEvent(EntityJoinWorldEvent event){ + // Replaces all vanilla fireballs with wizardry ones + if(Wizardry.settings.replaceVanillaFireballs && event.getEntity() instanceof EntitySmallFireball){ + + event.setCanceled(true); + + EntityMagicFireball fireball = new EntityMagicFireball(event.getWorld()); + fireball.thrower = ((EntitySmallFireball)event.getEntity()).shootingEntity; + fireball.setPosition(event.getEntity().posX, event.getEntity().posY, event.getEntity().posZ); + fireball.setDamage(5); + fireball.setBurnDuration(5); + fireball.setLifetime(40); + + fireball.motionX = ((EntitySmallFireball)event.getEntity()).accelerationX * ACCELERATION_CONVERSION_FACTOR; + fireball.motionY = ((EntitySmallFireball)event.getEntity()).accelerationY * ACCELERATION_CONVERSION_FACTOR; + fireball.motionZ = ((EntitySmallFireball)event.getEntity()).accelerationZ * ACCELERATION_CONVERSION_FACTOR; + + event.getWorld().spawnEntity(fireball); + } + } +} diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicMissile.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicMissile.java index 41351d98..9649b84a 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicMissile.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicMissile.java @@ -1,89 +1,63 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryParticleType; -import net.minecraft.entity.Entity; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class EntityMagicMissile extends EntityMagicArrow { - /** Basic shell constructor. Should only be used by the client. */ + /** Creates a new magic missile in the given world. */ public EntityMagicMissile(World world){ super(world); } - /** - * Creates a projectile at position xyz in world, with no motion. Do not create a projectile with this constructor - * and then call setVelocity() as that method is, bizarrely, client-side only. - */ - public EntityMagicMissile(World world, double x, double y, double z){ - super(world, x, y, z); - } + @Override public double getDamage(){ return Spells.magic_missile.getProperty(Spell.DAMAGE).floatValue(); } - /** - * Creates a projectile at the position of the caster, pointing at the given target. The trajectory seems to be - * altered slightly by a random amount determined by the last parameter. For reference, skeletons set this to 10 on - * easy, 6 on normal and 2 on hard difficulty. - */ - public EntityMagicMissile(World world, EntityLivingBase caster, Entity target, float speed, float aimingError, - float damageMultiplier){ - super(world, caster, target, speed, aimingError, damageMultiplier); - } + @Override public int getLifetime(){ return 12; } - /** - * Creates a projectile pointing in the direction the caster is looking, with the given speed. USE THIS CONSTRUCTOR - * FOR NORMAL SPELLS. - */ - public EntityMagicMissile(World world, EntityLivingBase caster, float speed, float damageMultiplier){ - super(world, caster, speed, damageMultiplier); - } + @Override public boolean doGravity(){ return false; } + + @Override public boolean doDeceleration(){ return false; } @Override public void onEntityHit(EntityLivingBase entityHit){ - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_MAGIC_MISSILE_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + if(this.world.isRemote) ParticleBuilder.create(Type.FLASH).pos(posX, posY, posZ).clr(1, 1, 0.65f).spawn(world); + } + + @Override + public void onBlockHit(RayTraceResult hit){ + if(this.world.isRemote){ + // Gets a position slightly away from the block hit so the particle doesn't get cut in half by the block face + Vec3d vec = hit.hitVec.add(new Vec3d(hit.sideHit.getDirectionVec()).scale(0.15)); + ParticleBuilder.create(Type.FLASH).pos(vec).clr(1, 1, 0.65f).fade(0.85f, 0.5f, 0.8f).spawn(world); + } } @Override public void tickInAir(){ - if(this.ticksExisted > 20){ - this.setDead(); - } - if(this.world.isRemote){ - - if(this.ticksExisted % 2 == 1){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX, this.posY, this.posZ, 0, 0, - 0, 20 + rand.nextInt(10), 0.5f + (rand.nextFloat() / 2), 0.5f + (rand.nextFloat() / 2), - 0.5f + (rand.nextFloat() / 2)); - }else{ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, this.posX, this.posY, this.posZ, 0, 0, - 0, 20 + rand.nextInt(10), 0.5f + (rand.nextFloat() / 2), 0.5f + (rand.nextFloat() / 2), - 0.5f + (rand.nextFloat() / 2)); + ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 0.03, true).clr(1, 1, 0.65f).fade(0.7f, 0, 1) + .time(20 + rand.nextInt(10)).spawn(world); + + if(this.ticksExisted > 1){ // Don't spawn particles behind where it started! + double x = posX - motionX/2; + double y = posY - motionY/2; + double z = posZ - motionZ/2; + ParticleBuilder.create(Type.SPARKLE, rand, x, y, z, 0.03, true).clr(1, 1, 0.65f).fade(0.7f, 0, 1) + .time(20 + rand.nextInt(10)).spawn(world); } } } @Override - public double getDamage(){ - return 4.0d; - } - - @Override - public boolean doGravity(){ - return false; - } - - @Override - public boolean doDeceleration(){ - return false; - } - - @Override - protected void entityInit(){ - - } + protected void entityInit(){ } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java index 337776ac..3246d818 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java @@ -1,10 +1,20 @@ package electroblob.wizardry.entity.projectile; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.RayTracer; import io.netty.buffer.ByteBuf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntityThrowable; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; @@ -12,13 +22,10 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; * This class is a generic superclass for all non-directed projectiles, namely: darkness orb, firebolt, firebomb, * force orb, ice charge, lightning disc, poison bomb, spark, spark bomb and thunderbolt. Directed (arrow-like) * projectiles should instead extend {@link EntityMagicArrow}. - *

    + *

    * This class purely handles saving of the damage multiplier; EntityThrowable is pretty well suited to my purposes as it * is. Range is done via the velocity when the constructor is called. Caster is already handled by - * EntityThrowable.getThrower(). - * - * Note that this class does not implement {@link IEntityAdditionalSpawnData}; subclasses that need to transfer extra - * data to the client should implement that interface themselves. See {@link EntityBomb} for an example. + * EntityThrowable.getThrower(), though due to a bug in vanilla it has to be synced by this class. * * @since Wizardry 1.0 * @author Electroblob @@ -26,72 +33,106 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; */ public abstract class EntityMagicProjectile extends EntityThrowable implements IEntityAdditionalSpawnData { + public static final double LAUNCH_Y_OFFSET = 0.1; + public static final int SEEKING_TIME = 15; + public float damageMultiplier = 1.0f; + /** Creates a new projectile in the given world. */ public EntityMagicProjectile(World world){ super(world); } - public EntityMagicProjectile(World world, EntityLivingBase thrower){ - super(world, thrower); + // Initialiser methods + + /** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and + * aims it in the direction they are looking with the given speed. */ + public void aim(EntityLivingBase caster, float speed){ + this.setPosition(caster.posX, caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET, caster.posZ); + // This is the standard set of parameters for this method, used by snowballs and ender pearls amongst others. + this.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, speed, 1.0f); + this.thrower = caster; + // Mojang's 'fix' for the projectile-hitting-thrower bug actually made the problem worse, hence the following line. + this.ignoreEntity = caster; } - public EntityMagicProjectile(World world, EntityLivingBase thrower, float damageMultiplier){ - super(world, thrower); - // This is the standard set of parameters for this method, used by snowballs and ender pearls amongst others. - this.shoot(thrower, thrower.rotationPitch, thrower.rotationYaw, 0.0f, this.getSpeed(), 1.0f); - this.damageMultiplier = damageMultiplier; + /** Sets the shooter of the projectile to the given caster, positions the projectile at the given caster's eyes and + * aims it at the given target with the given speed. The trajectory will be altered slightly by a random amount + * determined by the aimingError parameter. For reference, skeletons set this to 10 on easy, 6 on normal and 2 on hard + * difficulty. */ + public void aim(EntityLivingBase caster, Entity target, float speed, float aimingError){ + + this.thrower = caster; // Mojang's 'fix' for the projectile-hitting-thrower bug actually made the problem worse, hence the following line. this.ignoreEntity = thrower; + + this.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET; + double dx = target.posX - caster.posX; + double dy = !this.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY + : target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY; + double dz = target.posZ - caster.posZ; + double horizontalDistance = (double)MathHelper.sqrt(dx * dx + dz * dz); + + if(horizontalDistance >= 1.0E-7D){ + + double dxNormalised = dx / horizontalDistance; + double dzNormalised = dz / horizontalDistance; + this.setPosition(caster.posX + dxNormalised, this.posY, caster.posZ + dzNormalised); + + // Depends on the horizontal distance between the two entities and accounts for bullet drop, + // but of course if gravity is ignored this should be 0 since there is no bullet drop. + float bulletDropCompensation = !this.hasNoGravity() ? (float)horizontalDistance * 0.2f : 0; + // It turns out that this method normalises the input (x, y, z) anyway + this.shoot(dx, dy + (double)bulletDropCompensation, dz, speed, aimingError); + } } - public EntityMagicProjectile(World world, double x, double y, double z){ - super(world, x, y, z); + public void setCaster(EntityLivingBase caster){ + this.thrower = caster; + this.ignoreEntity = caster; } - /** This got removed at some point since 1.7.10, but I liked it so I thought I'd add it back in again. */ - protected float getSpeed(){ - return 1.5f; + /** + * Returns the seeking strength of this projectile, or the maximum distance from a target the projectile can be + * heading for that will make it curve towards that target. By default, this is 2 if the caster is wearing a ring + * of attraction, otherwise it is 0. + */ + public float getSeekingStrength(){ + return getThrower() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getThrower(), + WizardryItems.ring_seeking) ? 2 : 0; } - /** Sets this projectile's velocity as a normalised vector towards the target. */ - public void directTowards(Entity target, float velocity){ - - double dx = target.posX - this.posX; - double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) - - (this.posY + (double)(this.height / 2.0F)); - double dz = target.posZ - this.posZ; - - this.motionX = dx / this.getDistance(target) * velocity; - this.motionY = dy / this.getDistance(target) * velocity; - this.motionZ = dz / this.getDistance(target) * velocity; - } - @Override public void onUpdate(){ - // This fixes the client-side projectile-hitting-thrower bug. Comparing with 1.10.2, this was caused by a change - // to the line EntityThrowable:215, where a thrower != null check was added. Since the thrower field is not synced, - // this fails and the ignoreEntity field is never set, causing the projectile to hit its thrower client-side. - // The 'proper' way to fix this is to use IEntityAdditionalSpawnData to sync the thrower field, but I don't really - // want to waste packets like that, so, since things worked just fine in 1.10.2 without the thrower != null check, - // it makes sense to just duplicate that block of code and remove the offending check. - // The only side-effect (and probably why the change was made to vanilla) is that if this entity is summoned - // inside a mob using commands, it wouldn't hit that mob. This is so minor that it's not worth sending a packet - // for, though it may become more noticeable if spells firing from blocks are added. - // TODO: Investigate whether this is still necessary in 1.12 -// if(this.world.isRemote){ -// -// List list = this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox().expand(this.motionX, this.motionY, this.motionZ).grow(1.0D)); -// -// for(Entity entity : list){ // Why does vanilla still not use a for-each loop? -// if(entity.canBeCollidedWith() && this.ticksExisted < 2 && this.ignoreEntity == null){ -// this.ignoreEntity = entity; -// } -// } -// // Pretty sure EntityThrowable handles the rest. -// } - + super.onUpdate(); + + if(getLifetime() >=0 && this.ticksExisted > getLifetime()){ + this.setDead(); + } + + // Seeking + if(getSeekingStrength() > 0){ + + Vec3d velocity = new Vec3d(motionX, motionY, motionZ); + + RayTraceResult hit = RayTracer.rayTrace(world, this.getPositionVector(), + this.getPositionVector().add(velocity.scale(SEEKING_TIME)), getSeekingStrength(), false, + true, false, EntityLivingBase.class, RayTracer.ignoreEntityFilter(null)); + + if(hit != null && hit.entityHit != null){ + + if(AllyDesignationSystem.isValidTarget(getThrower(), hit.entityHit)){ + + Vec3d direction = new Vec3d(hit.entityHit.posX, hit.entityHit.posY + hit.entityHit.height/2, + hit.entityHit.posZ).subtract(this.getPositionVector()).normalize().scale(velocity.length()); + + motionX = motionX + 2 * (direction.x - motionX) / SEEKING_TIME; + motionY = motionY + 2 * (direction.y - motionY) / SEEKING_TIME; + motionZ = motionZ + 2 * (direction.z - motionZ) / SEEKING_TIME; + } + } + } } @Override @@ -107,20 +148,26 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I } @Override - // For now, we're only writing when the thrower exists, so subclasses MUST CALL SUPER LAST. - // TODO: Figure out whether there's a default value we can write that is never used as an entity id (0? -1? +/-MAX_VALUE?) public void writeSpawnData(ByteBuf data){ - if(this.getThrower() != null) data.writeInt(this.getThrower().getEntityId()); + data.writeInt(this.getThrower() == null ? -1 : this.getThrower().getEntityId()); } @Override - // For now, we're only writing when the thrower exists, so subclasses MUST CALL SUPER LAST. public void readSpawnData(ByteBuf data){ - if(data.isReadable()){ - Entity entity = this.world.getEntityByID(data.readInt()); - if(entity instanceof EntityLivingBase) this.thrower = (EntityLivingBase)entity; - this.ignoreEntity = this.thrower; - } + int id = data.readInt(); + if(id == -1) return; + Entity entity = this.world.getEntityByID(id); + if(entity instanceof EntityLivingBase) this.thrower = (EntityLivingBase)entity; + this.ignoreEntity = this.thrower; + } + + @Override + public SoundCategory getSoundCategory(){ + return WizardrySounds.SPELLS; } + /** Returns the maximum flight time in ticks before this projectile disappears, or -1 if it can continue + * indefinitely until it hits something. This should be constant. */ + public abstract int getLifetime(); + } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java index af033b37..286e7259 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java @@ -1,69 +1,66 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; +import java.util.List; + public class EntityPoisonBomb extends EntityBomb { - public EntityPoisonBomb(World par1World){ - super(par1World); - } - - public EntityPoisonBomb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntityPoisonBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier); - } - - public EntityPoisonBomb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); + public EntityPoisonBomb(World world){ + super(world); } @Override - protected void onImpact(RayTraceResult par1RayTraceResult){ - Entity entityHit = par1RayTraceResult.entityHit; + public int getLifetime(){ + return -1; + } + + @Override + protected void onImpact(RayTraceResult rayTrace){ + + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ // This is if the poison bomb gets a direct hit - float damage = 5 * damageMultiplier; + float damage = Spells.poison_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier; entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON).setProjectile(), damage); if(entityHit instanceof EntityLivingBase && !MagicDamage.isEntityImmune(DamageType.POISON, entityHit)) - ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON, 120, 1)); + ((EntityLivingBase)entityHit).addPotionEffect(new PotionEffect(MobEffects.POISON, + Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_DURATION).intValue(), + Spells.poison_bomb.getProperty(Spell.DIRECT_EFFECT_STRENGTH).intValue())); } // Particle effect if(world.isRemote){ + + ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier) + .clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world); + for(int i = 0; i < 60 * blastMultiplier; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 35, - 0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 0, - 0.2f + rand.nextFloat() * 0.2f, 0.8f, 0.0f); + + ParticleBuilder.create(Type.SPARKLE, rand, posX, posY, posZ, 2*blastMultiplier, false).time(35) + .scale(2).clr(0.2f + rand.nextFloat() * 0.3f, 0.6f, 0.0f).spawn(world); + + ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false) + .clr(0.2f + rand.nextFloat() * 0.2f, 0.8f, 0.0f).spawn(world); } // Spawning this after the other particles fixes the rendering colour bug. It's a bit of a cheat, but it // works pretty well. @@ -72,10 +69,10 @@ public class EntityPoisonBomb extends EntityBomb { if(!this.world.isRemote){ - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F); - this.playSound(SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.2F, 1.0f); + this.playSound(WizardrySounds.ENTITY_POISON_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F); + this.playSound(WizardrySounds.ENTITY_POISON_BOMB_POISON, 1.2F, 1.0f); - double range = 3.0d * blastMultiplier; + double range = Spells.poison_bomb.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.world); @@ -85,8 +82,10 @@ public class EntityPoisonBomb extends EntityBomb { && !MagicDamage.isEntityImmune(DamageType.POISON, target)){ target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.POISON), - 4.0f * damageMultiplier); - target.addPotionEffect(new PotionEffect(MobEffects.POISON, 100, 1)); + Spells.poison_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier); + target.addPotionEffect(new PotionEffect(MobEffects.POISON, + Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_DURATION).intValue(), + Spells.poison_bomb.getProperty(Spell.SPLASH_EFFECT_STRENGTH).intValue())); } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java index d2491481..cbdb5315 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java @@ -1,81 +1,79 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; +import java.util.List; + public class EntitySmokeBomb extends EntityBomb { - public EntitySmokeBomb(World par1World){ - super(par1World); - } - - public EntitySmokeBomb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntitySmokeBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier); - } - - public EntitySmokeBomb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); + public EntitySmokeBomb(World world){ + super(world); } @Override - protected void onImpact(RayTraceResult par1RayTraceResult){ + public int getLifetime(){ + return -1; + } + + @Override + protected void onImpact(RayTraceResult rayTrace){ // Particle effect if(world.isRemote){ + + ParticleBuilder.create(Type.FLASH).pos(this.getPositionVector()).scale(5 * blastMultiplier).clr(0, 0, 0).spawn(world); + this.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, this.posX, this.posY, this.posZ, 0, 0, 0); + for(int i = 0; i < 60 * blastMultiplier; i++){ + this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0, 0, 0); + float brightness = rand.nextFloat() * 0.3f; - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - this.posX + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posY + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, - this.posZ + (this.rand.nextDouble() * 4 - 2) * blastMultiplier, 0.0d, 0.0d, 0.0d, 0, brightness, - brightness, brightness); + ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false) + .clr(brightness, brightness, brightness).spawn(world); } } if(!this.world.isRemote){ - this.playSound(SoundEvents.ENTITY_SPLASH_POTION_BREAK, 1.5F, rand.nextFloat() * 0.4F + 0.6F); - this.playSound(SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.2F, 1.0f); + this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMASH, 1.5F, rand.nextFloat() * 0.4F + 0.6F); + this.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_SMOKE, 1.2F, 1.0f); - double range = 3.0d * blastMultiplier; + double range = Spells.smoke_bomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, this.posZ, this.world); + int duration = Spells.smoke_bomb.getProperty(Spell.EFFECT_DURATION).intValue(); + for(EntityLivingBase target : targets){ if(target != this.getThrower()){ // Gives the target blindness if it is a player, mind trick otherwise (since this has the desired // effect of preventing targeting) if(target instanceof EntityPlayer){ - target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 120, 0)); + target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0)); }else if(target instanceof EntityLiving){ // New AI ((EntityLiving)target).setAttackTarget(null); - - target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, 120, 0)); + target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, duration, 0)); } } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntitySpark.java b/src/main/java/electroblob/wizardry/entity/projectile/EntitySpark.java index d05d4878..93d1d756 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntitySpark.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntitySpark.java @@ -1,113 +1,66 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntitySpark extends EntityMagicProjectile { - public EntitySpark(World par1World){ - super(par1World); + public EntitySpark(World world){ + super(world); } - public EntitySpark(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } - - public EntitySpark(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - } - - public EntitySpark(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** This is the speed */ - protected float getSpeed(){ - return 0.5F; - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ - protected void onImpact(RayTraceResult par1RayTraceResult){ - Entity entityHit = par1RayTraceResult.entityHit; + @Override + protected void onImpact(RayTraceResult rayTrace){ + + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ - float damage = 6 * damageMultiplier; - entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), - damage); + float damage = Spells.homing_spark.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + entityHit.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), + DamageType.SHOCK), damage); } - this.playSound(WizardrySounds.SPELL_SPARK, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_HOMING_SPARK_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); // Particle effect if(world.isRemote){ for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + rand.nextFloat() - 0.5, - this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, - 0, 3); + double x = this.posX + rand.nextDouble() - 0.5; + double y = this.posY + this.height / 2 + rand.nextDouble() - 0.5; + double z = this.posZ + rand.nextDouble() - 0.5; + ParticleBuilder.create(Type.SPARK).pos(x, y, z).spawn(world); } } this.setDead(); } - public void onUpdate(){ - - super.onUpdate(); - - if(!this.collided && !world.isRemote){ - - double seekingRange = 5.0d; - - List entities = WizardryUtilities.getEntitiesWithinRadius(seekingRange, this.posX, - this.posY, this.posZ, this.world); - Entity target = null; - - for(Entity possibleTarget : entities){ - // Decides if current entity should be replaced. - if(target == null || this.getDistance(target) > this.getDistance(possibleTarget)){ - // Decides if new entity is a valid target. - if(WizardryUtilities.isValidTarget(this.getThrower(), possibleTarget)){ - target = possibleTarget; - } - } - } - - if(target != null && Math.abs(this.motionX) < 1 && Math.abs(this.motionY) < 1 - && Math.abs(this.motionZ) < 1){ - this.addVelocity((target.posX - this.posX) / 30, (target.posY + target.height / 2 - this.posY) / 30, - (target.posZ - this.posZ) / 30); - } - } - - if(this.ticksExisted > 100){ - this.setDead(); - } + @Override + public float getSeekingStrength(){ + return Spells.homing_spark.getProperty(Spell.SEEKING_STRENGTH).floatValue(); } - /** - * Gets the amount of gravity to apply to the thrown entity with each tick. - */ - protected float getGravityVelocity(){ - return 0.0F; + @Override + public int getLifetime(){ + return 50; } - /** - * Return whether this entity should be rendered as on fire. - */ + @Override + public boolean hasNoGravity(){ + return true; + } + + @Override public boolean canRenderOnFire(){ return false; } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java index d8836e4c..721a06a2 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java @@ -1,54 +1,46 @@ package electroblob.wizardry.entity.projectile; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.EntityArc; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; +import java.util.List; + public class EntitySparkBomb extends EntityBomb { - public EntitySparkBomb(World par1World){ - super(par1World); + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; + + public EntitySparkBomb(World world){ + super(world); } - public EntitySparkBomb(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); + @Override + public int getLifetime(){ + return -1; } - public EntitySparkBomb(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier, - float blastMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier, blastMultiplier); - } + @Override + protected void onImpact(RayTraceResult rayTrace){ + + this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT_BLOCK, 0.5f, 0.5f); - public EntitySparkBomb(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ - protected void onImpact(RayTraceResult par1RayTraceResult){ - this.playSound(SoundEvents.ENTITY_FIREWORK_BLAST_FAR, 0.5f, 0.5f); - - Entity entityHit = par1RayTraceResult.entityHit; + Entity entityHit = rayTrace.entityHit; if(entityHit != null){ // This is if the spark bomb gets a direct hit - float damage = 6 * damageMultiplier; + float damage = Spells.spark_bomb.getProperty(Spell.DIRECT_DAMAGE).floatValue() * damageMultiplier; - this.playSound(SoundEvents.ENTITY_GENERIC_HURT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); + this.playSound(WizardrySounds.ENTITY_SPARK_BOMB_HIT, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F)); entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(), @@ -58,29 +50,22 @@ public class EntitySparkBomb extends EntityBomb { // Particle effect if(world.isRemote){ - for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + rand.nextFloat() - 0.5, - this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, - 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + rand.nextFloat() - 0.5, - this.posY + this.height / 2 + rand.nextFloat() - 0.5, this.posZ + rand.nextFloat() - 0.5, 0, 0, - 0); - } + ParticleBuilder.spawnShockParticles(world, posX, posY + height/2, posZ); } - double seekerRange = 5.0d * blastMultiplier; + double seekerRange = Spells.spark_bomb.getProperty(Spell.EFFECT_RADIUS).doubleValue() * blastMultiplier; List targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, this.posY, this.posZ, this.world); - for(int i = 0; i < Math.min(targets.size(), 4); i++){ + for(int i = 0; i < Math.min(targets.size(), Spells.spark_bomb.getProperty(SECONDARY_MAX_TARGETS).intValue()); i++){ boolean flag = targets.get(i) != entityHit && targets.get(i) != this.getThrower() && !(targets.get(i) instanceof EntityPlayer - && ((EntityPlayer)targets.get(i)).capabilities.isCreativeMode); + && ((EntityPlayer)targets.get(i)).isCreative()); // Detects (client side) if target is the thrower, to stop particles being spawned around them. - //if(flag && world.isRemote && targets.get(i).getEntityId() == this.casterID) flag = false; + //if(flag && world.isRemote && targets.get(i).getEntityId() == this.playerID) flag = false; if(flag){ @@ -88,28 +73,15 @@ public class EntitySparkBomb extends EntityBomb { if(!this.world.isRemote){ - EntityArc arc = new EntityArc(this.world); - arc.setEndpointCoords(this.posX, this.posY, this.posZ, target.posX, target.posY + target.height / 2, - target.posZ); - this.world.spawnEntity(arc); - - target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, rand.nextFloat() * 0.4F + 1.5F); + target.playSound(WizardrySounds.ENTITY_SPARK_BOMB_CHAIN, 1.0F, rand.nextFloat() * 0.4F + 1.5F); target.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK), - 5.0f * damageMultiplier); + Spells.spark_bomb.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier); }else{ - // Particle effect - for(int j = 0; j < 8; j++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height * rand.nextFloat(), - target.posZ + rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height * rand.nextFloat(), - target.posZ + rand.nextFloat() - 0.5, 0, 0, 0); - } + ParticleBuilder.create(Type.LIGHTNING).pos(this.getPositionVector()).target(target).spawn(world); + ParticleBuilder.spawnShockParticles(world, target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ); } } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityThunderbolt.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityThunderbolt.java index 48e2a4d0..48e90dd8 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityThunderbolt.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityThunderbolt.java @@ -1,59 +1,49 @@ package electroblob.wizardry.entity.projectile; -import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.init.SoundEvents; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; public class EntityThunderbolt extends EntityMagicProjectile { + public static final String KNOCKBACK_STRENGTH = "knockback_strength"; + public EntityThunderbolt(World par1World){ super(par1World); } - public EntityThunderbolt(World par1World, EntityLivingBase par2EntityLivingBase){ - super(par1World, par2EntityLivingBase); - } + @Override public boolean hasNoGravity(){ return true; } - public EntityThunderbolt(World par1World, EntityLivingBase par2EntityLivingBase, float damageMultiplier){ - super(par1World, par2EntityLivingBase, damageMultiplier); - } - - public EntityThunderbolt(World par1World, double par2, double par4, double par6){ - super(par1World, par2, par4, par6); - } - - /** This is the speed */ - protected float getSpeed(){ - return 2.5F; - } - - /** - * Called when this EntityThrowable hits a block or entity. - */ + @Override public boolean canRenderOnFire(){ return false; } + + @Override protected void onImpact(RayTraceResult par1RayTraceResult){ Entity entityHit = par1RayTraceResult.entityHit; if(entityHit != null){ - float damage = 3 * damageMultiplier; + float damage = Spells.thunderbolt.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; entityHit.attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, this.getThrower(), DamageType.SHOCK).setProjectile(), damage); + float knockbackStrength = Spells.thunderbolt.getProperty(KNOCKBACK_STRENGTH).floatValue(); + // Knockback - entityHit.addVelocity(this.motionX * 0.2, this.motionY * 0.2, this.motionZ * 0.2); + entityHit.addVelocity(this.motionX * knockbackStrength, this.motionY * knockbackStrength, this.motionZ * knockbackStrength); } - this.playSound(SoundEvents.ENTITY_FIREWORK_LARGE_BLAST, 1.4F, 0.5f + this.rand.nextFloat() * 0.1F); + this.playSound(WizardrySounds.ENTITY_THUNDERBOLT_HIT, 1.4F, 0.5f + this.rand.nextFloat() * 0.1F); // Particle effect if(world.isRemote){ @@ -63,37 +53,24 @@ public class EntityThunderbolt extends EntityMagicProjectile { this.setDead(); } + @Override public void onUpdate(){ super.onUpdate(); if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, this.posX + rand.nextFloat() * 0.2 - 0.1, - this.posY + this.height / 2 + rand.nextFloat() * 0.2 - 0.1, - this.posZ + rand.nextFloat() * 0.2 - 0.1, 0, 0, 0, 3); + ParticleBuilder.create(Type.SPARK, rand, posX, posY + height/2, posZ, 0.1, false).spawn(world); for(int i = 0; i < 4; i++){ world.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, this.posX + rand.nextFloat() * 0.2 - 0.1, this.posY + this.height / 2 + rand.nextFloat() * 0.2 - 0.1, this.posZ + rand.nextFloat() * 0.2 - 0.1, 0, 0, 0); } } - - if(this.ticksExisted > 8){ - this.setDead(); - } } - /** - * Gets the amount of gravity to apply to the thrown entity with each tick. - */ - protected float getGravityVelocity(){ - return 0.0F; + @Override + public int getLifetime(){ + return 8; } - /** - * Return whether this entity should be rendered as on fire. - */ - public boolean canRenderOnFire(){ - return false; - } } diff --git a/src/main/java/electroblob/wizardry/event/DiscoverSpellEvent.java b/src/main/java/electroblob/wizardry/event/DiscoverSpellEvent.java index 8e22c499..79fb9916 100644 --- a/src/main/java/electroblob/wizardry/event/DiscoverSpellEvent.java +++ b/src/main/java/electroblob/wizardry/event/DiscoverSpellEvent.java @@ -6,6 +6,8 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.fml.common.eventhandler.Cancelable; +import javax.annotation.Nullable; + /** * DiscoverSpellEvent is fired when a player discovers a spell by any method.
    *
    @@ -41,13 +43,30 @@ public class DiscoverSpellEvent extends PlayerEvent { public enum Source { /** Signifies that the spell was discovered by trying to cast it. */ - CASTING, + CASTING("casting"), /** Signifies that the spell was discovered using a scroll of identification. */ - IDENTIFICATION_SCROLL, + IDENTIFICATION_SCROLL("identification_scroll"), /** Signifies that the spell was discovered using commands. */ - COMMAND, + COMMAND("command"), + /** Signifies that the spell was discovered by purchasing it from a wizard. */ + PURCHASE("purchase"), /** Signifies that the spell was discovered by some other means. */ - OTHER + OTHER("other"); + + String name; + + Source(String name){ + this.name = name; + } + + /** Returns the spell discovery source with the given name, or null if no such source exists. */ + @Nullable + public static Source byName(String name){ + for(Source source : values()){ + if(source.name.equals(name)) return source; + } + return null; + } } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/event/SpellCastEvent.java b/src/main/java/electroblob/wizardry/event/SpellCastEvent.java index 3cd0a88e..ec471598 100644 --- a/src/main/java/electroblob/wizardry/event/SpellCastEvent.java +++ b/src/main/java/electroblob/wizardry/event/SpellCastEvent.java @@ -1,34 +1,71 @@ package electroblob.wizardry.event; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.entity.living.LivingEvent; import net.minecraftforge.fml.common.eventhandler.Cancelable; +import net.minecraftforge.fml.common.eventhandler.Event; + +import javax.annotation.Nullable; /** * SpellCastEvent is the parent event class for all spell casting events. Methods which subscribe to this event will - * receive all three child events (it is recommended that you use {@link SpellCastEvent.Pre}, - * {@link SpellCastEvent.Post} or {@link SpellCastEvent.Tick}, depending on the application).
    + * receive all four child events (it is recommended that you use {@link SpellCastEvent.Pre}, + * {@link SpellCastEvent.Post}, {@link SpellCastEvent.Tick} or {@link SpellCastEvent.Finish}, depending on the application).
    *
    + * A note about spell modifiers: As of Wizardry 4.2, item-based spell casting has been reorganised, a notable change + * being that spell modifiers are no longer recalculated each tick for continuous spells. Instead, spell modifiers + * are stored in {@link WizardData WizardData} at the start (after {@code SpellCastEvent.Pre}) + * and simply passed in each tick. This means the {@code SpellModifiers} object received by {@code SpellCastEvent.Tick} + * contains those modified values, and any changes made to them will not persist between ticks. + *

    + * This means that where before you had to change the modifiers in {@code Pre} and {@code Tick}, you now only + * need to change them in {@code Pre} unless you want them to change partway through casting.
    + *

    * This event is fired on the {@link MinecraftForge#EVENT_BUS}. * * @author Electroblob * @since Wizardry 2.1 */ -public abstract class SpellCastEvent extends LivingEvent { +public abstract class SpellCastEvent extends Event { private final Spell spell; private final SpellModifiers modifiers; private final Source source; + private final EntityLivingBase caster; + private final World world; + private final double x, y, z; + private final EnumFacing direction; - public SpellCastEvent(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source){ - super(caster); + public SpellCastEvent(Source source, Spell spell, EntityLivingBase caster, SpellModifiers modifiers){ + super(); this.spell = spell; this.modifiers = modifiers; this.source = source; + this.caster = caster; + this.world = caster.world; // World is required for the position-based casting, but we may as well set it here + this.x = Double.NaN; // Better to use NaN than some arbitrary number, because NaN will throw an exception when + this.y = Double.NaN; // someone tries to operate on it whereas 0, for example, will likely just cause strange + this.z = Double.NaN; // behaviour - the cause of which may not be immediately obvious. + this.direction = null; + } + + public SpellCastEvent(Source source, Spell spell, World world, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers){ + super(); + this.spell = spell; + this.modifiers = modifiers; + this.source = source; + this.caster = null; + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.direction = direction; } /** Returns the spell being cast. */ @@ -45,6 +82,39 @@ public abstract class SpellCastEvent extends LivingEvent { public Source getSource(){ return source; } + + /** Returns the entity that cast this spell, or null if it was cast from a dispenser or a command with coordinates. */ + @Nullable + public EntityLivingBase getCaster(){ + return caster; + } + + /** Returns the world in which this spell was cast. If the spell was cast by an entity, this is equivalent to + * {@code getCaster().world}. */ + public World getWorld(){ + return world; + } + + /** Returns the x coordinate at which this spell was cast, or NaN if the spell was not cast from a position. */ + public double getX(){ + return x; + } + + /** Returns the y coordinate at which this spell was cast, or NaN if the spell was not cast from a position. */ + public double getY(){ + return y; + } + + /** Returns the z coordinate at which this spell was cast, or NaN if the spell was not cast from a position. */ + public double getZ(){ + return z; + } + + /** Returns the direction in which this spell was cast, or null if the spell was not cast from a position. */ + @Nullable + public EnumFacing getDirection(){ + return direction; + } public enum Source { /** Signifies that the spell was cast using a wand. */ @@ -55,6 +125,8 @@ public abstract class SpellCastEvent extends LivingEvent { COMMAND, /** Signifies that the spell was cast by an {@link ISpellCaster}. */ NPC, + /** Signifies that the spell was cast from a dispenser. */ + DISPENSER, /** Signifies that the spell was cast by some other means. */ OTHER } @@ -70,6 +142,13 @@ public abstract class SpellCastEvent extends LivingEvent { * right-click action that caused it (if any) returns a result of FAIL, meaning that the right-click is passed to * the block/entity in front of the player, if any.
    *
    + * Priority convention for {@code SpellCastEvent.Pre}:
    + * {@code HIGHEST} - General spell prevention e.g. arcane jammer
    + * {@code HIGH} - Specific spell prevention e.g. spells disabled in config/JSONs
    + * {@code NORMAL} - Everything else e.g. forfeits
    + * {@code LOW} - Changes to modifiers e.g. from potion effects
    + * {@code LOWEST} - Anything that requires modifiers to be at their final values, unused in wizardry
    + *
    * This event does not have a result. {@link HasResult}
    *
    * This event is fired on the {@link MinecraftForge#EVENT_BUS}. @@ -80,10 +159,13 @@ public abstract class SpellCastEvent extends LivingEvent { @Cancelable public static class Pre extends SpellCastEvent { - public Pre(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source){ - super(caster, spell, modifiers, source); + public Pre(Source source, Spell spell, EntityLivingBase caster, SpellModifiers modifiers){ + super(source, spell, caster, modifiers); + } + + public Pre(Source source, Spell spell, World world, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers){ + super(source, spell, world, x, y, z, direction, modifiers); } - } /** @@ -91,7 +173,8 @@ public abstract class SpellCastEvent extends LivingEvent { * whether the spell succeeds, and does not affect the spell itself. For example, wizardry uses this event to keep * track of spellcasting stats. Note that although this event is fired from both sides, it is not fired from common * code; rather, both sides fire it separately, so timing is not guaranteed. Also note that this event is only fired - * once for continuous spells, after the first casting tick.
    + * once for continuous spells, after the first casting tick. Changing the modifiers within this event will likely + * have no effect, with the exception of cooldown modifiers.
    *
    * This event is not {@link Cancelable}.
    *
    @@ -104,8 +187,12 @@ public abstract class SpellCastEvent extends LivingEvent { */ public static class Post extends SpellCastEvent { - public Post(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source){ - super(caster, spell, modifiers, source); + public Post(Source source, Spell spell, EntityLivingBase caster, SpellModifiers modifiers){ + super(source, spell, caster, modifiers); + } + + public Post(Source source, Spell spell, World world, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers){ + super(source, spell, world, x, y, z, direction, modifiers); } } @@ -114,8 +201,9 @@ public abstract class SpellCastEvent extends LivingEvent { * SpellCastEvent.Tick is fired each tick while a continuous spell is being cast.
    *
    * This event is {@link Cancelable}. If this event is canceled, the spell is not cast, mana is not consumed, and the - * spell casting is interrupted. Cancelling this event on the client side will stop particles being spawned, but - * will not interrupt spell casting.
    + * spell casting is interrupted. Cancelling this event on the client side only will stop particles being spawned, but + * will not interrupt spell casting. Cancelling this event on the server side only may have different results + * depending on the source of the spell; as such it is recommended that this event is cancelled on both sides.
    *
    * This event does not have a result. {@link HasResult}
    *
    @@ -129,8 +217,13 @@ public abstract class SpellCastEvent extends LivingEvent { private final int count; - public Tick(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source, int count){ - super(caster, spell, modifiers, source); + public Tick(Source source, Spell spell, EntityLivingBase caster, SpellModifiers modifiers, int count){ + super(source, spell, caster, modifiers); + this.count = count; + } + + public Tick(Source source, Spell spell, World world, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers, int count){ + super(source, spell, world, x, y, z, direction, modifiers); this.count = count; } @@ -141,4 +234,39 @@ public abstract class SpellCastEvent extends LivingEvent { } + /** + * SpellCastEvent.Finish is fired just after a continuous spell stops being cast. Use this event for anything + * that needs to know the total time for which a continuous spell has been cast, or should only happen when the + * spell finishes (as opposed to just after it starts, as in {@link Post}).
    + *
    + * This event is not {@link Cancelable}.
    + *
    + * This event does not have a result. {@link HasResult}
    + *
    + * This event is fired on the {@link MinecraftForge#EVENT_BUS}. + * + * @author Electroblob + * @since Wizardry 2.1 + */ + public static class Finish extends SpellCastEvent { + + private final int count; + + public Finish(Source source, Spell spell, EntityLivingBase caster, SpellModifiers modifiers, int count){ + super(source, spell, caster, modifiers); + this.count = count; + } + + public Finish(Source source, Spell spell, World world, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers, int count){ + super(source, spell, world, x, y, z, direction, modifiers); + this.count = count; + } + + /** Returns the total number of ticks this (continuous) spell was cast for. */ + public int getCount(){ + return count; + } + + } + } diff --git a/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java b/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java new file mode 100644 index 00000000..2bbfb421 --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java @@ -0,0 +1,153 @@ +package electroblob.wizardry.integration; + +import com.google.common.collect.ImmutableSet; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.util.DamageSource; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import java.util.Set; + +/** + * This class implements an 'if-all-else-fails' fix for cross-mod infinite looping caused by re-applying damage in + * attack events (see GitHub issue #72 for details). + * The methods in this class should only be used when intercepting one of the attack/damage events and + * dealing damage from within it. + */ +@Mod.EventBusSubscriber +public final class DamageSafetyChecker { + + /** We don't want to tell users to add any of these to the blacklist, as that would be problematic. */ + // NOTE: Make sure this is updated for each new version of Minecraft + private static final Set VANILLA_DAMAGE_NAMES = ImmutableSet.of("inFire", "lightningBolt", "onFire", + "lava", "hotFloor", "inWall", "cramming", "drown", "starve", "cactus", "fall", "flyIntoWall", "outOfWorld", + "generic", "magic", "wither", "anvil", "fallingBlock", "dragonBreath", "fireworks", "mob", "player", "arrow", + "thrown", "indirectMagic", "thorns", "explosion", "explosion.player"); + + /** + * Global counter which is incremented once for each call to + * {@link DamageSafetyChecker#attackEntitySafely(Entity, DamageSource, float, String, DamageSource, boolean)} + * This allows for detection and avoidance of imminent StackOverflowErrors caused by looping between mods. + */ + private static int attacksThisTick = 0; + + /** The number of calls per loaded entity after which damage will be reassigned. */ + // It's a fair bet that if an entity is being damaged 15 times in a single tick, then something is wrong! + // Based on the crash report in issue #72, there were approximately 38 calls before the error was thrown. + // The number of calls will vary depending on the stack size and possibly what else is happening at the time. + private static final int EXCESSIVE_CALL_THRESHOLD = 15; + /** The number of calls per loaded entity after which damage will be cancelled entirely. */ + private static final int EXCESSIVE_CALL_LIMIT = 25; + + /** + * Attacks the specified target with specified damage source and damage amount, checking for the blacklist and + * excessive looping in the process. Under normal circumstances, this method simply calls + * {@code target.attackEntityFrom(...)}. If excessive looping is detected, the damage source is substituted for + * the given fallback instead, and a warning is printed to the console. + *

    + * This method should only be used within the attack events (LivingAttackEvent, LivingHurtEvent, LivingDamageEvent + * and possibly LivingKnockBackEvent, depending on the circumstances). + * @param target The target to apply the damage to. + * @param source The source of the damage. + * @param damage The amount of damage to be applied. + * @param originalSourceName The string identifier for the original damage source (i.e. the one being replaced). + * This allows wizardry to request that users add it to the blacklist. + * @param fallback The fallback damage source for when excessive looping is detected. This must not be the + * same as the re-applied source, or this method is pointless! Usually it will be more general. + * @param knockback True to apply knockback as normal, false to use the knockback-free methods in WizardryUtilities + * (see {@link WizardryUtilities#attackEntityWithoutKnockback(Entity, DamageSource, float)}). + */ + public static boolean attackEntitySafely(Entity target, DamageSource source, float damage, + String originalSourceName, DamageSource fallback, boolean knockback){ + + for(String sourceName : Wizardry.settings.damageSourceBlacklist){ + if(originalSourceName.equals(sourceName)){ + // Blacklist behaviour + // Same as fallback behaviour, but without the log message + // No harm in still incrementing the counter + attacksThisTick++; + return knockback ? target.attackEntityFrom(fallback, damage) + : WizardryUtilities.attackEntityWithoutKnockback(target, fallback, damage); + } + } + + if(attacksThisTick > EXCESSIVE_CALL_LIMIT * target.world.loadedEntityList.size()){ + // This should never ever happen unless another mod is intercepting non-entity-based damage and damaging + // the same target. + logInterception(originalSourceName, true); + return false; + } + + if(attacksThisTick > EXCESSIVE_CALL_THRESHOLD * target.world.loadedEntityList.size()){ + // Sometimes this is unavoidable, it's neither mod's fault but without some kind of forge standard or + // universal cooperation there's no easy way to prevent it. + logInterception(originalSourceName, false); + // Fallback behaviour + attacksThisTick++; + return knockback ? target.attackEntityFrom(fallback, damage) + : WizardryUtilities.attackEntityWithoutKnockback(target, fallback, damage); + + }else{ + // Normal behaviour + attacksThisTick++; + return knockback ? target.attackEntityFrom(source, damage) + : WizardryUtilities.attackEntityWithoutKnockback(target, source, damage); + } + } + + /** + * See {@link DamageSafetyChecker#attackEntitySafely(Entity, DamageSource, float, String, DamageSource, boolean)}. + * This version is for when the source being replaced is used as a fallback, i.e. when a damage source is being + * replaced for technical reasons (e.g. summoned creatures) rather than as part of a game mechanic (e.g. shadow ward). + * This means that if excessive looping is detected, the code will work as if the event was never intercepted. + */ + public static boolean attackEntitySafely(Entity target, DamageSource source, float damage, DamageSource originalSource, boolean knockback){ + return attackEntitySafely(target, source, damage, originalSource.getDamageType(), originalSource, knockback); + } + + /** + * See {@link DamageSafetyChecker#attackEntitySafely(Entity, DamageSource, float, String, DamageSource, boolean)}. + * Fallback defaults to {@link DamageSource#MAGIC} and knockback defaults to true. + */ + public static boolean attackEntitySafely(Entity target, DamageSource source, float damage, String originalSourceName){ + return attackEntitySafely(target, source, damage, originalSourceName, DamageSource.MAGIC, true); + } + + /** Prints the appropriate message about the damage interception to the console. */ + private static void logInterception(String originalSourceName, boolean aborted){ + + if(!Wizardry.settings.compatibilityWarnings) return; // No warnings if they're disabled! + + boolean vanillaName = VANILLA_DAMAGE_NAMES.contains(originalSourceName); + + if(aborted){ + Wizardry.logger.warn("SoundLoopSpellEntity attack excessive call limit reached, aborting entity damage entirely!"); + }else{ + Wizardry.logger.warn("SoundLoopSpellEntity attack excessive call threshold reached, substituting for non-entity-based " + + "damage to avert a crash."); + } + + if(vanillaName){ + Wizardry.logger.info("The damage source in question had a vanilla identifier. If you know which mod may " + + "have caused this, consider asking the author to add a custom identifier so it may be blacklisted. " + + "You can turn this warning off using the compatibilityWarnings config option. Please do not report " + + "it to wizardry's author."); + }else{ + Wizardry.logger.info("To prevent this message and improve efficiency, add \"" + originalSourceName + "\" " + + "(without quotes) to the damage source blacklist in the config. Please do not report " + + "this warning unless you have added the damage source to the blacklist already."); + } + } + + @SubscribeEvent + public static void tick(TickEvent event){ + // We actually want this to fire on both sides, because attacks are common code. + if(event.phase == TickEvent.Phase.START && event.type == TickEvent.Type.WORLD){ + attacksThisTick = 0; // Reset the attack call counter + } + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java b/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java new file mode 100644 index 00000000..8fb48157 --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java @@ -0,0 +1,71 @@ +package electroblob.wizardry.integration.antiqueatlas; + +import electroblob.wizardry.Wizardry; +import hunternif.mc.atlas.api.AtlasAPI; +import hunternif.mc.atlas.registry.MarkerType; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.Loader; + +/** + * This class handles all of wizardry's integration with the Antique Atlas mod. This class contains only the code + * that requires Antique Atlas to be loaded in order to run. Conversely, all code that requires Antique Atlas to be + * loaded is located within this class or another class in the package {@code electroblob.wizardry.integration.antiqueatlas}. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +public class WizardryAntiqueAtlasIntegration { + + public static final String ANTIQUE_ATLAS_MOD_ID = "antiqueatlas"; + + private static final ResourceLocation TOWER_MARKER = new ResourceLocation(Wizardry.MODID, "wizard_tower"); + private static final ResourceLocation SHRINE_MARKER = new ResourceLocation(Wizardry.MODID, "shrine"); + private static final ResourceLocation OBELISK_MARKER = new ResourceLocation(Wizardry.MODID, "obelisk"); + + private static boolean antiqueAtlasLoaded; + + public static void init(){ + antiqueAtlasLoaded = Loader.isModLoaded(ANTIQUE_ATLAS_MOD_ID); + Wizardry.proxy.registerAtlasMarkers(); // Needs routing through the proxies to make sure it's only client-side + } + + public static boolean enabled(){ + return Wizardry.settings.antiqueAtlasIntegration && antiqueAtlasLoaded; + } + + /** Places a global wizard tower marker in all antique atlases at the given coordinates in the given world if + * {@link electroblob.wizardry.Settings#autoTowerMarkers} is enabled. Server side only! */ + public static void markTower(World world, int x, int z){ + if(enabled() && Wizardry.settings.autoTowerMarkers){ + AtlasAPI.getMarkerAPI().putGlobalMarker(world, false, TOWER_MARKER.toString(), "integration.antiqueatlas.marker." + TOWER_MARKER.toString(), x, z); + } + } + + /** Places a global obelisk marker in all antique atlases at the given coordinates in the given world if + * {@link electroblob.wizardry.Settings#autoObeliskMarkers} is enabled. Server side only! */ + public static void markObelisk(World world, int x, int z){ + if(enabled() && Wizardry.settings.autoObeliskMarkers){ + AtlasAPI.getMarkerAPI().putGlobalMarker(world, false, OBELISK_MARKER.toString(), "integration.antiqueatlas.marker." + OBELISK_MARKER.toString(), x, z); + } + } + + /** Places a global shrine marker in all antique atlases at the given coordinates in the given world if + * {@link electroblob.wizardry.Settings#autoShrineMarkers} is enabled. Server side only! */ + public static void markShrine(World world, int x, int z){ + if(enabled() && Wizardry.settings.autoShrineMarkers){ + AtlasAPI.getMarkerAPI().putGlobalMarker(world, false, SHRINE_MARKER.toString(), "integration.antiqueatlas.marker." + SHRINE_MARKER.toString(), x, z); + } + } + + /** Registers the marker icons with Antique Atlas. Client side only! */ + public static void registerMarkers(){ + + if(!enabled()) return; + + AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(TOWER_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/wizard_tower.png"))); + AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(SHRINE_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/shrine.png"))); + AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(OBELISK_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/obelisk.png"))); + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java b/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java new file mode 100644 index 00000000..11f56ce2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java @@ -0,0 +1,110 @@ +package electroblob.wizardry.integration.baubles; + +import baubles.api.BaubleType; +import baubles.api.BaublesApi; +import baubles.api.IBauble; +import baubles.api.cap.BaublesCapabilities; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.fml.common.Loader; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +/** + * This class handles all of wizardry's integration with the Baubles mod. This class contains only the code + * that requires Baubles to be loaded in order to run. Conversely, all code that requires Baubles to be loaded is + * located within this class or another class in the package {@code electroblob.wizardry.integration.baubles}. + * + * @since Wizardry 4.2 + * @author Electroblob + */ +public final class WizardryBaublesIntegration { + + public static final String BAUBLES_MOD_ID = "baubles"; + + private static final Map ARTEFACT_TYPE_MAP = new EnumMap<>(ItemArtefact.Type.class); + + private static boolean baublesLoaded; + + public static void init(){ + + baublesLoaded = Loader.isModLoaded(BAUBLES_MOD_ID); + + if(!enabled()) return; + + ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.RING, BaubleType.RING); + ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.AMULET, BaubleType.AMULET); + ARTEFACT_TYPE_MAP.put(ItemArtefact.Type.CHARM, BaubleType.CHARM); + } + + public static boolean enabled(){ + return Wizardry.settings.baublesIntegration && baublesLoaded; + } + + // Wrappers for BaublesApi methods + + /** + * Return true if the given item is equipped in any bauble slot. + * @param player The player whose inventory is to be checked. + * @param item The item to check for. + * @return True if the given item is equipped in a bauble slot, false otherwise. + */ + public static boolean isBaubleEquipped(EntityPlayer player, Item item){ + return BaublesApi.isBaubleEquipped(player, item) >= 0; + } + + /** + * Returns a list of artefact stacks equipped of the given types. + * @param player The player whose inventory is to be checked. + * @param types Zero or more artefact types to check for. If omitted, searches for all types. + * @return A list of equipped artefact {@code ItemStacks}. + */ + // This could return all ItemStacks, but if an artefact type is given this doesn't really make sense. + public static List getEquippedArtefacts(EntityPlayer player, ItemArtefact.Type... types){ + + List artefacts = new ArrayList<>(); + + for(ItemArtefact.Type type : types){ + for(int slot : ARTEFACT_TYPE_MAP.get(type).getValidSlots()){ + ItemStack stack = BaublesApi.getBaublesHandler(player).getStackInSlot(slot); + if(stack.getItem() instanceof ItemArtefact) artefacts.add((ItemArtefact)stack.getItem()); + } + } + + return artefacts; + } + + // Shamelessly copied from The Twilight Forest, with a few modifications + @SuppressWarnings("unchecked") + public static final class ArtefactBaubleProvider implements ICapabilityProvider { + + private BaubleType type; + + public ArtefactBaubleProvider(ItemArtefact.Type type){ + this.type = ARTEFACT_TYPE_MAP.get(type); + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing){ + return capability == BaublesCapabilities.CAPABILITY_ITEM_BAUBLE; + } + + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing){ + // This lambda expression is an implementation of the entire IBauble interface + return capability == BaublesCapabilities.CAPABILITY_ITEM_BAUBLE ? (T)(IBauble)itemStack -> type : null; + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/IConjuredItem.java b/src/main/java/electroblob/wizardry/item/IConjuredItem.java index 1ab78fe2..eadfe591 100644 --- a/src/main/java/electroblob/wizardry/item/IConjuredItem.java +++ b/src/main/java/electroblob/wizardry/item/IConjuredItem.java @@ -1,59 +1,118 @@ package electroblob.wizardry.item; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.spell.SpellConjuration; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.IItemPropertyGetter; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; import net.minecraftforge.event.entity.item.ItemTossEvent; import net.minecraftforge.event.entity.living.LivingDropsEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.UUID; /** * Allows wizardry to identify items that are conjured (and therefore need destroying if they leave the inventory) - * without explicitly referencing each, thereby allowing for better expandibility. + * without explicitly referencing each, thereby allowing for better expandability. */ @Mod.EventBusSubscriber public interface IConjuredItem { /** The NBT tag key used to store the duration multiplier for conjured items. */ - public static final String DURATION_MULTIPLIER_KEY = "durationMultiplier"; + String DURATION_MULTIPLIER_KEY = "durationMultiplier"; + /** The NBT tag key used to store the damage multiplier for conjured items. */ + String DAMAGE_MULTIPLIER = "damageMultiplier"; + + UUID POTENCY_MODIFIER = UUID.fromString("da067ea6-0b35-4140-8436-5476224de9dd"); /** Helper method for setting the duration multiplier (via NBT) for conjured items. */ - public static void setDurationMultiplier(ItemStack stack, float multiplier){ + static void setDurationMultiplier(ItemStack stack, float multiplier){ if(!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound()); stack.getTagCompound().setFloat(DURATION_MULTIPLIER_KEY, multiplier); } + /** Helper method for setting the damage multiplier (via NBT) for conjured items. */ + static void setDamageMultiplier(ItemStack stack, float multiplier){ + if(!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound()); + stack.getTagCompound().setFloat(DAMAGE_MULTIPLIER, multiplier); + } + + /** Helper method for getting the damage multiplier (via NBT) for conjured items. */ + static float getDamageMultiplier(ItemStack stack){ + if(!stack.hasTagCompound()) return 1; + return stack.getTagCompound().getFloat(DAMAGE_MULTIPLIER); + } + /** * Helper method for returning the max damage of a conjured item based on its NBT data. Centralises the code. * Implementors will almost certainly want to call this from {@link Item#getMaxDamage(ItemStack stack)}. */ - public default int getMaxDamageFromNBT(ItemStack stack){ + default int getMaxDamageFromNBT(ItemStack stack, Spell spell){ + + float baseDuration = spell.getProperty(SpellConjuration.ITEM_LIFETIME).floatValue(); + if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DURATION_MULTIPLIER_KEY)){ - return (int)(this.getBaseDuration() * stack.getTagCompound().getFloat(DURATION_MULTIPLIER_KEY)); + return (int)(baseDuration * stack.getTagCompound().getFloat(DURATION_MULTIPLIER_KEY)); } - return this.getBaseDuration(); + + return (int)baseDuration; } /** - * Returns the base duration in ticks for this conjured item. Should be a constant (commonly 600). Implementors may - * want to call this when setting an item's max damage in its constructor. + * Adds property overrides to define the conjuring/vanishing animation. Call this from the item's constructor. */ - public int getBaseDuration(); + default void addAnimationPropertyOverrides(){ + + if(!(this instanceof Item)) throw new ClassCastException("Cannot set up conjuring animations for a non-item!"); + + Item item = (Item)this; + + final int frames = getAnimationFrames(); + + item.addPropertyOverride(new ResourceLocation("conjure"), new IItemPropertyGetter(){ + @SideOnly(Side.CLIENT) + public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){ + return stack.getItemDamage() < frames ? (float)stack.getItemDamage() / frames + : (float)(stack.getMaxDamage() - stack.getItemDamage()) / frames; + } + }); + item.addPropertyOverride(new ResourceLocation("conjuring"), new IItemPropertyGetter(){ + @SideOnly(Side.CLIENT) + public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){ + return stack.getItemDamage() < frames + || stack.getItemDamage() > stack.getMaxDamage() - frames ? 1.0F : 0.0F; + } + }); + } + + /** Returns the number of frames in the conjuring/vanishing animation. Override to change the number of frames + * set by {@link IConjuredItem#addAnimationPropertyOverrides()}. */ + default int getAnimationFrames(){ + return 8; + } @SubscribeEvent - public static void onLivingDropsEvent(LivingDropsEvent event){ + static void onLivingDropsEvent(LivingDropsEvent event){ // Destroys conjured items if their caster dies. for(EntityItem item : event.getDrops()){ - if(item.getItem().getItem() instanceof IConjuredItem){ + // Apparently some mods don't behave and shove null items in the list, quite why I have no idea + if(item != null && item.getItem() != null && item.getItem().getItem() instanceof IConjuredItem){ item.setDead(); } } } @SubscribeEvent - public static void onItemTossEvent(ItemTossEvent event){ + static void onItemTossEvent(ItemTossEvent event){ // Prevents conjured items being thrown by dragging and dropping outside the inventory. if(event.getEntityItem().getItem().getItem() instanceof IConjuredItem){ event.setCanceled(true); diff --git a/src/main/java/electroblob/wizardry/item/IManaStoringItem.java b/src/main/java/electroblob/wizardry/item/IManaStoringItem.java new file mode 100644 index 00000000..de93846d --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/IManaStoringItem.java @@ -0,0 +1,72 @@ +package electroblob.wizardry.item; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +/** + * Interface for any items that store mana. This interface simply specifies methods for setting and getting the amount + * of mana held in the item (plus a few convenience methods); implementations may differ between items. + *

    + * In wizardry itself, mana is still implemented as durability, however, as of wizardry 4.2, the vanilla method + * {@code Item.setDamage()} has been overridden to do nothing. Instead, mana must be interacted with using the methods + * in this interface. This means operating via the item rather than the stack. + *

    + * This change prevents general item repair methods working on mana items (see issues #66 and #153), and also allows + * other items to implement mana differently if they wish. For example, a weapon that can cast spells as an ability + * might want regular durability in addition to mana, so the mana might be stored in NBT instead. Items that do not use + * durability to represent mana may need to do zero-checking themselves, as appropriate. + *

    + * Wizardry's items implement mana as the inverse of item damage; i.e. the more damaged the item, the + * less mana it has. Beware of this when converting to the new system. + *

    + * @author Electroblob + * @since Wizardry 4.2 + */ +public interface IManaStoringItem { + + /** Returns the amount of mana contained in the given item stack. */ + int getMana(ItemStack stack); + + /** Sets the amount of mana contained in the given item stack to the given value. This method does not perform any + * checks for creative mode, etc. */ + void setMana(ItemStack stack, int mana); + + /** Returns the maximum amount of mana that the given item stack can hold. */ + int getManaCapacity(ItemStack stack); + + /** + * Returns whether this item's mana should be displayed in the arcane workbench tooltip. Only called client-side. + * Ignore this method if this item is not an {@link IWorkbenchItem}. + * @param player The player using the workbench. + * @param stack The itemstack to query. + * @return True if the mana should be shown, false if not. Returns true by default. + */ + default boolean showManaInWorkbench(EntityPlayer player, ItemStack stack){ + return true; + } + + /** Convenience method that decreases the amount of mana contained in the given item stack by the given value. This + * method automatically limits the mana to a minimum of 0 and performs the relevant checks for creative mode, etc. */ + default void consumeMana(ItemStack stack, int mana, EntityLivingBase wielder){ + if(wielder instanceof EntityPlayer && ((EntityPlayer)wielder).isCreative()) return; // Mana isn't consumed in creative + setMana(stack, Math.max(getMana(stack) - mana, 0)); + } + + /** Convenience method that increases the amount of mana contained in the given item stack by the given value. + * This method automatically limits the mana to within the item's capacity. */ + // We don't really need to limit this one because Item#setDamage() ultimately limits it anyway, but we may as well + default void rechargeMana(ItemStack stack, int mana){ + setMana(stack, Math.min(getMana(stack) + mana, getManaCapacity(stack))); + } + + /** Convenience method that returns true if the given stack contains the maximum amount of mana, false otherwise. */ + default boolean isManaFull(ItemStack stack){ + return getMana(stack) == getManaCapacity(stack); + } + + /** Convenience method that returns true if the given stack contains no mana, false otherwise. */ + default boolean isManaEmpty(ItemStack stack){ + return getMana(stack) == 0; + } +} diff --git a/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java b/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java new file mode 100644 index 00000000..849f9ae8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java @@ -0,0 +1,23 @@ +package electroblob.wizardry.item; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; + +/** + * Interface for items that change their texture depending on their metadata. This is mainly to facilitate use of the + * convenience method {@link electroblob.wizardry.client.model.WizardryModels#registerMultiTexturedModel(Item) WizardryModels.registerMultiTexturedModel(T)}. Also works well for {@code ItemBlock}s! + * @author Electroblob + * @since Wizardry 4.2 + * @see ItemBlockMultiTexturedElemental + */ +public interface IMultiTexturedItem { + + /** + * Returns the appropriate {@code ResourceLocation} for this item's model, based on the given itemstack. + * @param stack The itemstack to return the model name for. + * @return A {@code ResourceLocation} pointing to the appropriate model file. As with any other model, this should + * include the domain (mod ID) and filename, without the rest of the filepath. + */ + ResourceLocation getModelName(ItemStack stack); +} diff --git a/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java b/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java new file mode 100644 index 00000000..0d532200 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java @@ -0,0 +1,129 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +/** + * Interface for items that can hold and cast one or more spells. These may be consumables, like scrolls, or they may be + * durability-based, like wands. Custom spell casting items should implement this interface to integrate properly into + * wizardry. It is no longer necessary to extend {@code ItemWand}, but you may still do so instead of implementing + * this interface if appropriate. + *

    + * This interface is used for the following:
    + * - General-purpose detection of continuous spell casting (see {@link electroblob.wizardry.util.WizardryUtilities#isCasting(EntityLivingBase, Spell)})
    + * - Display of the arcane workbench tooltip (in conjunction with {@link IManaStoringItem})
    + * - Spell HUD visibility
    + * - Spell switching controls (they won't do anything unless the player is holding an {@code ISpellCastingItem})
    + * - Artefacts that trigger a player's wands/scrolls to cast spells + * @author Electroblob + * @since Wizardry 4.2 + */ +// This could probably be turned into a capability at some point, but for the moment it's fine like this +// As we've already noted, capabilities are only useful for optional dependencies anyway +public interface ISpellCastingItem { + + /** + * Returns the spell currently equipped on the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + * @return The currently equipped spell, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell + * is equipped. + */ + @Nonnull + Spell getCurrentSpell(ItemStack stack); + + /** + * Returns all the spells currently bound to the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + * @return The bound spells, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell + * is equipped. + */ + default Spell[] getSpells(ItemStack stack){ + return new Spell[]{getCurrentSpell(stack)}; // Default implementation for single-spell items, because I'm lazy + } + + /** + * Selects the next spell bound to the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + */ + default void selectNextSpell(ItemStack stack){ + // If it doesn't need spell-switching then don't bother the implementor with it + } + + /** + * Selects the previous spell bound to the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + */ + default void selectPreviousSpell(ItemStack stack){ + // Nothing here either + } + + /** + * Returns whether the spell HUD should be shown when a player is holding this item. Only called client-side. + * @param player The player holding the item. + * @param stack The itemstack to query. + * @return True if the spell HUD should be shown, false if not. + */ + boolean showSpellHUD(EntityPlayer player, ItemStack stack); + + /** + * Returns whether this item's spells should be displayed in the arcane workbench tooltip. Only called client-side. + * Ignore this method if this item is not an {@link IWorkbenchItem}. + * @param player The player using the workbench. + * @param stack The itemstack to query. + * @return True if the spells should be shown, false if not. Returns true by default. + */ + default boolean showSpellsInWorkbench(EntityPlayer player, ItemStack stack){ + return true; + } + + // These methods were made with intention of standardising the code for casting spells using items. + // For most external uses there's no reason for them to be separate, however, it makes more sense to do so because + // then we can eliminate a bit of duplicate code from continuous vs. non-continuous spell casting. Otherwise, we'd + // need a separate method for casting continuous spells anyway. + + /** + * Returns whether the given spell can be cast by the given stack in its current state. Does not perform any actual + * spellcasting. + * + * @param stack The stack being queried; will be of this item. + * @param spell The spell to be cast. + * @param caster The player doing the casting. + * @param hand The hand in which the casting item is being held. + * @param castingTick For continuous spells, the number of ticks the spell has already been cast for. For all other + * spells, this will be zero. + * @param modifiers The modifiers with which the spell is being cast. + * @return True if the spell can be cast, false if not. + */ + boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers); + + /** + * Casts the given spell using the given item stack. This method does not perform any checks; these are done + * in {@link ISpellCastingItem#canCast(ItemStack, Spell, EntityPlayer, EnumHand, int, SpellModifiers)}. This method + * also performs any post-casting logic, such as mana costs and cooldowns. + *

    + * N.B. Continuous spell casting from outside of the items requires a bit of extra legwork, see + * {@link WizardData} for an example. + * + * @param stack The stack being queried; will be of this item. + * @param spell The spell to be cast. + * @param caster The player doing the casting. + * @param hand The hand in which the casting item is being held. + * @param castingTick For continuous spells, the number of ticks the spell has already been cast for. For all other + * spells, this will be zero. + * @param modifiers The modifiers with which the spell is being cast. + * @return True if the spell succeeded, false if not. This is only really for the purpose of returning a result from + * {@link net.minecraft.item.Item#onItemRightClick(World, EntityPlayer, EnumHand)} and similar methods; mana costs, + * cooldowns and whatever else you might want to do post-spellcasting should be done within this method so that + * external sources don't allow spells to be cast for free, for example. + */ + boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers); + +} diff --git a/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java b/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java new file mode 100644 index 00000000..e85fd314 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java @@ -0,0 +1,64 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.event.SpellBindEvent; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +/** + * Items that implement this interface may be placed in the central slot of the arcane workbench as long as + * {@link IWorkbenchItem#canPlace(ItemStack)} returns true. The number of spell book slots displayed is also specified + * using {@link IWorkbenchItem#getSpellSlotCount(ItemStack)}. + *

    + * Items that implement this interface define what happens if they are in the central slot of the arcane workbench and + * the apply button is pressed, in {@link IWorkbenchItem#onApplyButtonPressed(EntityPlayer, Slot, Slot, Slot, Slot[])}. + * This is a core part of the arcane workbench refactoring in version 4.2 and allows for custom spell casting items and + * chargeable armour without requiring that they extend {@link ItemWand} or {@link ItemWizardArmour}. + * @author Electroblob + * @since Wizardry 4.2 + */ +public interface IWorkbenchItem { + + /** + * Returns true if the item can be placed in the central slot of an arcane workbench, false otherwise. Allows + * for itemstack-sensitive behaviour. Returns true by default. + * @param stack The stack that is being placed into the workbench. + * @return True to allow the item to be placed into the workbench, false to prevent that from happening. + */ + default boolean canPlace(ItemStack stack){ + return true; + } + + /** + * Returns the number of spell book slots that should appear in the workbench when this item is placed into it, + * based on the given itemstack. + * @param stack The stack that is being placed into the workbench. + * @return The number of spell book slots that should appear around this item when it is placed into the workbench. + * Can be 0, but must not be negative. + */ + int getSpellSlotCount(ItemStack stack); + + /** + * Called when this item is in the central slot of an arcane workbench and the apply button is pressed. Items must + * implement this method to define what happens when the apply button is pressed. Note that {@link SpellBindEvent} + * is fired before this method is called. + * @param player The player that pressed the apply button. + * @param centre The central slot in the arcane workbench. This slot will always contain a stack of the implementing + * item, or in other words, it is guaranteed that {@code this == centre.getStack().getItem()}. + * @param crystals The magic crystal slot of the arcane workbench. + * @param upgrade The upgrade slot of the arcane workbench. + * @param spellBooks An array of the active (visible) spell book slots in the arcane workbench. The length of + * the array will be equal to the value returned by {@link IWorkbenchItem#getSpellSlotCount(ItemStack)}. + * @return True if anything changed, false if not. + */ + boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks); + + /** + * Returns whether the tooltip (dark grey box) should be drawn when this item is in an arcane workbench. Only + * called client-side. + * @param stack The itemstack to query. + * @return True if the workbench tooltip should be shown, false if not. + */ + boolean showTooltip(ItemStack stack); + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemArcaneTome.java b/src/main/java/electroblob/wizardry/item/ItemArcaneTome.java index 07b0406d..3e40abd8 100644 --- a/src/main/java/electroblob/wizardry/item/ItemArcaneTome.java +++ b/src/main/java/electroblob/wizardry/item/ItemArcaneTome.java @@ -1,11 +1,8 @@ package electroblob.wizardry.item; -import java.util.List; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryTabs; -import net.minecraft.client.util.ITooltipFlag; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.EnumRarity; import net.minecraft.item.Item; @@ -15,6 +12,8 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.List; + public class ItemArcaneTome extends Item { public ItemArcaneTome(){ @@ -26,7 +25,7 @@ public class ItemArcaneTome extends Item { @Override public void getSubItems(CreativeTabs tab, NonNullList list){ - if (isInCreativeTab(tab)) { + if(tab == WizardryTabs.WIZARDRY){ // Don't use isInCreativeTab here. for(int i = 1; i < Tier.values().length; i++){ list.add(new ItemStack(this, 1, i)); } @@ -54,7 +53,12 @@ public class ItemArcaneTome extends Item { @SideOnly(Side.CLIENT) @Override - public void addInformation(ItemStack stack, World world, List tooltip, ITooltipFlag showAdvanced){ + public void addInformation(ItemStack stack, World world, List tooltip, net.minecraft.client.util.ITooltipFlag showAdvanced){ + + if(stack.getItemDamage() < 1){ + return; // If something's up with the metadata it will display a 'generic' tome of arcana with no info + } + Tier tier = Tier.values()[stack.getItemDamage()]; Tier tier2 = Tier.values()[stack.getItemDamage() - 1]; tooltip.add(tier.getDisplayNameWithFormatting()); diff --git a/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java b/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java index 39eaef6e..b7307b15 100644 --- a/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java +++ b/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java @@ -1,12 +1,7 @@ package electroblob.wizardry.item; -import java.util.List; - -import javax.annotation.Nullable; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryTabs; -import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.EnumRarity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -14,6 +9,9 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; +import java.util.List; + public class ItemArmourUpgrade extends Item { public ItemArmourUpgrade(){ @@ -35,7 +33,7 @@ public class ItemArmourUpgrade extends Item { @Override @SideOnly(Side.CLIENT) - public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, ITooltipFlag flagIn) { + public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, net.minecraft.client.util.ITooltipFlag flagIn) { tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":armour_upgrade.desc1", "\u00A77")); tooltip.add( net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":armour_upgrade.desc2", "\u00A77", "\u00A7d")); diff --git a/src/main/java/electroblob/wizardry/item/ItemArtefact.java b/src/main/java/electroblob/wizardry/item/ItemArtefact.java new file mode 100644 index 00000000..be1e987c --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemArtefact.java @@ -0,0 +1,875 @@ +package electroblob.wizardry.item; + +import com.google.common.collect.Streams; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.entity.construct.EntityFireRing; +import electroblob.wizardry.entity.living.ISummonedCreature; +import electroblob.wizardry.entity.projectile.EntityDart; +import electroblob.wizardry.entity.projectile.EntityForceOrb; +import electroblob.wizardry.entity.projectile.EntityIceShard; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration; +import electroblob.wizardry.registry.*; +import electroblob.wizardry.spell.*; +import electroblob.wizardry.util.*; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.IProjectile; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.MobEffects; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraftforge.common.BiomeDictionary; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.entity.living.LivingEvent; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import net.minecraftforge.event.entity.living.PotionEvent; +import net.minecraftforge.event.entity.player.PlayerDropsEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * Base class for all artefact items, which handles effects, textures and so on. The majority of artefacts are + * event-driven so it is unlikely that this class will need to be extended, unless other {@code Item} methods are to be + * overridden. + *

    + * This class contains methods and an enum that mirror those in {@code IBauble} from the Baubles mod. If Baubles is + * loaded, these are called via the bauble capability; otherwise, they are called from regular {@link Item} methods + * or events with appropriate checks. This allows wizardry to run with Baubles as an optional dependency. + *

    + * Do not reference any Baubles classes from subclasses of this, or the dependency will no longer be optional! + * Use {@link ItemArtefact#isArtefactActive(EntityPlayer, Item)} to test if a particular artefact is active. Use + * {@link ItemArtefact#getActiveArtefacts(EntityPlayer, Type...)} to get a list of active artefacts. + *

    + * @author Electroblob + * @since Wizardry 4.2 + * @see electroblob.wizardry.integration.baubles.WizardryBaublesIntegration + */ +@Mod.EventBusSubscriber +public class ItemArtefact extends Item { + + // Artefact checklist: + // - Create and register item, add model and texture + // - Program effect, using events if possible (if it only affects a specific spell or entity, in there is ok) + // - Add name AND description to lang files + // - Add to loot_tables/subsets/[rarity]_artefacts.json + // - Add to advancements/artefact.json and advancements/all_artefacts.json + + public enum Type { + + /** An artefact that improves attacking spells. Two of these can be active at any one time. */ RING(2), + /** An artefact that improves defensive spells. One of these can be active at any one time. */ AMULET(1), + /** An artefact that improves utility spells. One of these can be active at any one time. */ CHARM(1); + + public final int maxAtOnce; + + Type(int maxAtOnce){ + this.maxAtOnce = maxAtOnce; + } + } + + // If Baubles is not installed, artefacts will still work, but must instead be + // on the player's hotbar (and only the first n of a given type will work, where n is the number of baubles slots of that + // artefact's type). + + // Rarity was chosen over Tier here for a couple of reasons: firstly, displaying a tier would add unnecessary clutter + // to a potentially already-long tooltip whereas rarity provides a compact, neat way of displaying it that everyone is + // reasonably familiar with. Secondly, rarity makes more sense for an artefact, since it's something you find rather + // than upgrade to, and artefacts are not tied to tiers of wand/spell/whatever - they can be used whenever. + private final EnumRarity rarity; + private final Type type; + + public ItemArtefact(EnumRarity rarity, Type type){ + setMaxStackSize(1); + setCreativeTab(WizardryTabs.GEAR); + this.rarity = rarity; + this.type = type; + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return rarity; + } + + public Type getType(){ + return type; + } + + @Override + public boolean hasEffect(ItemStack stack){ + return rarity == EnumRarity.EPIC; + } + + @Override + @SideOnly(Side.CLIENT) + public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, net.minecraft.client.util.ITooltipFlag flagIn){ + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc"); + } + + @Nullable + @Override + public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable NBTTagCompound nbt){ + return WizardryBaublesIntegration.enabled() ? new WizardryBaublesIntegration.ArtefactBaubleProvider(type) : null; + } + + // IBauble does of course have an onWornTick method. However, because it's an optional dependency, it doesn't really + // make sense to use that method when it's easier to just use the isBaubleEquipped method in the same + // place as the non-baubles check. In other words, most artefacts are event-driven anyway so I'd rather have the + // tick-driven ones use events as well for the sake of consistency. + + /** + * Returns whether the given artefact is active for the given player. If Baubles is loaded, an artefact is active + * when it is equipped in an appropriate bauble slot. If Baubles is not loaded, an artefact is active if it is one + * of the first n of its type on the player's hands/hotbar, where n is the number of bauble slots of that type. + *

    + * N.B. This method is inefficient if you are defining multiple artefact behaviours in the same place. In this use + * case, it is preferable to use {@link ItemArtefact#getActiveArtefacts(EntityPlayer, Type...)}. + * + * @param player The player whose inventory is to be checked. + * @param artefact The artefact to check for. + * @return True if the player has the artefact and it is active, false if not. Always returns false if the given + * item is not an instance of {@code ItemArtefact}. + * @throws IllegalArgumentException If the given item is not an artefact. + */ + // It's cleaner to cast to ItemArtefact here than wherever it is used - items can't be stored as ItemWhatever objects + public static boolean isArtefactActive(EntityPlayer player, Item artefact){ + + if(!(artefact instanceof ItemArtefact)) throw new IllegalArgumentException("Not an artefact!"); + + if(WizardryBaublesIntegration.enabled()){ + return WizardryBaublesIntegration.isBaubleEquipped(player, artefact); + }else{ + // To find out if the given artefact is one of the first n on the player's hotbar (where n is the maximum + // number of that kind of artefact that can be active at once): + return WizardryUtilities.getPrioritisedHotbarAndOffhand(player).stream() // Retrieve the stacks in question + // Filter out all except artefacts of the same type as the given one (preserving order) + .filter(s -> s.getItem() instanceof ItemArtefact && ((ItemArtefact)s.getItem()).type == ((ItemArtefact)artefact).type) + .limit(((ItemArtefact)artefact).type.maxAtOnce) // Ignore all but the first n + .anyMatch(s -> s.getItem() == artefact); // Check if the remaining stacks contain the artefact + // Note that streaming a list DOES retain the order (unless you call unordered(), obviously) + } + } + + /** + * Returns the currently active artefacts for the given player. If Baubles is loaded, an artefact is active + * when it is equipped in an appropriate bauble slot. If Baubles is not loaded, an artefact is active if it is one + * of the first n of its type on the player's hands/hotbar, where n is the number of bauble slots of that type. + *

    + * This method is more efficient for processing multiple artefact behaviours at once. + * + * @param player The player whose inventory is to be checked. + * @param types The artefact types to check for. If omitted, all artefact types will be checked. + * @return True if the player has the artefact and it is active, false if not. Always returns false if the given + * item is not an instance of {@code ItemArtefact}. + */ + public static List getActiveArtefacts(EntityPlayer player, Type... types){ + + if(types.length == 0) types = Type.values(); + + if(WizardryBaublesIntegration.enabled()){ + return WizardryBaublesIntegration.getEquippedArtefacts(player, types); + }else{ + + List artefacts = new ArrayList<>(); + + for(Type type : types){ + artefacts.addAll(WizardryUtilities.getPrioritisedHotbarAndOffhand(player).stream() + .filter(s -> s.getItem() instanceof ItemArtefact) + .map(s -> (ItemArtefact)s.getItem()) + .filter(i -> type == i.type) + .limit(type.maxAtOnce) + .collect(Collectors.toList())); + } + + return artefacts; + } + } + + /** + * Helper method that scans through all wands on the given player's hotbar and offhand and executes the given action + * if any of them have the given spell bound to them. This is a useful code pattern for artefact effects. + * + * @param player The player whose hotbar is to be checked + * @param spell The spell to search for + * @param action A {@link Consumer} specifying the action to be performed if a wand with the given spell is found. + * The stack passed to this consumer will be the wand in question. + * @return True if the action was executed, false otherwise. + */ + public static boolean findMatchingWandAndExecute(EntityPlayer player, Spell spell, Consumer action){ + + List hotbar = WizardryUtilities.getPrioritisedHotbarAndOffhand(player); + + Optional stack = hotbar.stream().filter(s -> s.getItem() instanceof ISpellCastingItem + && Arrays.asList(((ISpellCastingItem)s.getItem()).getSpells(s)).contains(spell)).findFirst(); + + stack.ifPresent(action); + return stack.isPresent(); + } + + /** + * Helper method that scans through all wands on the given player's hotbar and offhand and casts the given spell if + * it is bound to any of them. This is a useful code pattern for artefact effects. + * + * @param player The player whose hotbar is to be checked + * @param spell The spell to search for and cast + * @return True if the spell was cast, false otherwise. + */ + public static boolean findMatchingWandAndCast(EntityPlayer player, Spell spell){ + + return findMatchingWandAndExecute(player, spell, wand -> { + + SpellModifiers modifiers = new SpellModifiers(); + + if(((ISpellCastingItem)wand.getItem()).canCast(wand, spell, player, EnumHand.MAIN_HAND, 0, modifiers)){ + ((ISpellCastingItem)wand.getItem()).cast(wand, spell, player, EnumHand.MAIN_HAND, 0, modifiers); + } + }); + } + + // ================================================ Event Handlers ================================================ + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.phase == TickEvent.Phase.START){ + + EntityPlayer player = event.player; + World world = player.world; + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + if(artefact == WizardryItems.ring_condensing){ + + if(world.isRemote && player.ticksExisted % 150 == 0){ + for(ItemStack stack : WizardryUtilities.getHotbar(player)){ + // Needs to be both of these interfaces because this ring only recharges wands + // (or more accurately, chargeable spellcasting items) + if(stack.getItem() instanceof ISpellCastingItem && stack.getItem() instanceof IManaStoringItem) + ((IManaStoringItem)stack.getItem()).rechargeMana(stack, 1); + } + } + + }else if(artefact == WizardryItems.amulet_arcane_defence){ + + if(world.isRemote && player.ticksExisted % 300 == 0){ + for(ItemStack stack : player.getArmorInventoryList()){ + // IManaStoringItem is sufficient, since anything in the armour slots is probably armour + if(stack.getItem() instanceof IManaStoringItem) + ((IManaStoringItem)stack.getItem()).rechargeMana(stack, 1); + } + } + + }else if(artefact == WizardryItems.amulet_recovery){ + + if(player.shouldHeal() && player.getHealth() < player.getMaxHealth()/2 + && player.ticksExisted % 50 == 0){ + + int totalArmourMana = Streams.stream(player.getArmorInventoryList()) + .filter(s -> s.getItem() instanceof IManaStoringItem) + .mapToInt(s -> ((IManaStoringItem)s.getItem()).getMana(s)) + .sum(); + + if(totalArmourMana >= 2){ + player.heal(1); + // 2 mana per half-heart, randomly distributed + List chargedArmour = Streams.stream(player.getArmorInventoryList()) + .filter(s -> s.getItem() instanceof IManaStoringItem) + .filter(s -> !((IManaStoringItem)s.getItem()).isManaEmpty(s)) + .collect(Collectors.toList()); + + if(chargedArmour.size() == 1){ + ((IManaStoringItem)chargedArmour.get(0).getItem()).consumeMana(chargedArmour.get(0), 2, player); + }else{ + Collections.shuffle(chargedArmour); + ((IManaStoringItem)chargedArmour.get(0).getItem()).consumeMana(chargedArmour.get(0), 1, player); + ((IManaStoringItem)chargedArmour.get(1).getItem()).consumeMana(chargedArmour.get(1), 1, player); + } + } + } + + }else if(artefact == WizardryItems.amulet_glide){ + // This should be a chance per fall, so we can't just check fall distance is greater than 3 each tick + // Based on a stationary start and a gravity acceleration of 0.02 blocks/tick^2, at 3 blocks of fall + // distance the player should be falling at about 0.35b/t, so 0.5 blocks should be enough of a window + if(player.fallDistance > 3f && player.fallDistance < 3.5f && player.world.rand.nextFloat() < 0.5f){ + if(!WizardData.get(player).isCasting()) WizardData.get(player).startCastingContinuousSpell(Spells.glide, new SpellModifiers(), 600); + }else if(player.onGround){ + WizardData data = WizardData.get(player); + if(data.currentlyCasting() == Spells.glide) data.stopCastingContinuousSpell(); + } + + }else if(artefact == WizardryItems.amulet_auto_shield){ + + findMatchingWandAndExecute(player, Spells.shield, wand -> { + + List projectiles = WizardryUtilities.getEntitiesWithinRadius(5, player.posX, player.posY, player.posZ, world, Entity.class); + projectiles.removeIf(e -> !(e instanceof IProjectile)); + Vec3d look = player.getLookVec(); + Vec3d playerPos = player.getPositionVector().add(0, player.height/2, 0); + + for(Entity projectile : projectiles){ + Vec3d vec = playerPos.subtract(projectile.getPositionVector()).normalize(); + double angle = Math.acos(vec.scale(-1).dotProduct(look)); + if(angle > Math.PI * 0.4f) continue; // (Roughly) the angle the shield will protect + Vec3d velocity = new Vec3d(projectile.motionX, projectile.motionY, projectile.motionZ).normalize(); + double angle1 = Math.acos(vec.dotProduct(velocity)); + if(angle1 < Math.PI * 0.2f){ + SpellModifiers modifiers = new SpellModifiers(); + if(((ISpellCastingItem)wand.getItem()).canCast(wand, Spells.shield, player, EnumHand.MAIN_HAND, 0, modifiers)){ + ((ISpellCastingItem)wand.getItem()).cast(wand, Spells.shield, player, EnumHand.MAIN_HAND, 0, modifiers); + } + break; + } + } + }); + + }else if(artefact == WizardryItems.charm_feeding){ + // Every 5 seconds, feed the player if they are near starving + if(player.ticksExisted % 100 == 0 && player.getFoodStats().getFoodLevel() < 2){ + findMatchingWandAndCast(player, Spells.replenish_hunger); + } + } + } + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + + if(event.getCaster() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getCaster(); + SpellModifiers modifiers = event.getModifiers(); + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + float potency = modifiers.get(SpellModifiers.POTENCY); + float cooldown = modifiers.get(WizardryItems.cooldown_upgrade); + Biome biome = player.world.getBiome(player.getPosition()); + + if(artefact == WizardryItems.ring_battlemage){ + + if(player.getHeldItemOffhand().getItem() instanceof ISpellCastingItem + && ImbueWeapon.isSword(player.getHeldItemMainhand().getItem())){ + modifiers.set(SpellModifiers.POTENCY, 1.1f * potency, false); + } + + }else if(artefact == WizardryItems.ring_fire_biome){ + + if(event.getSpell().getElement() == Element.FIRE + && BiomeDictionary.hasType(biome, BiomeDictionary.Type.HOT) + && BiomeDictionary.hasType(biome, BiomeDictionary.Type.DRY)){ + modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false); + } + + }else if(artefact == WizardryItems.ring_ice_biome){ + + if(event.getSpell().getElement() == Element.ICE + && BiomeDictionary.hasType(biome, BiomeDictionary.Type.SNOWY)){ + modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false); + } + + }else if(artefact == WizardryItems.ring_earth_biome){ + + if(event.getSpell().getElement() == Element.EARTH + // If it was any forest that would be far too many, so taigas and jungles are excluded + && BiomeDictionary.hasType(biome, BiomeDictionary.Type.FOREST) + && !BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS) + && !BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)){ + modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false); + } + + }else if(artefact == WizardryItems.ring_storm){ + + if(event.getSpell().getElement() == Element.LIGHTNING && player.world.isThundering()){ + modifiers.set(WizardryItems.cooldown_upgrade, cooldown * 0.3f, false); + } + + }else if(artefact == WizardryItems.ring_full_moon){ + + if(event.getSpell().getElement() == Element.EARTH && !player.world.isDaytime() + && player.world.provider.getMoonPhase(player.world.getWorldTime()) == 0){ + modifiers.set(WizardryItems.cooldown_upgrade, cooldown * 0.3f, false); + } + + }else if(artefact == WizardryItems.ring_blockwrangler){ + + if(event.getSpell() == Spells.greater_telekinesis){ + modifiers.set(SpellModifiers.POTENCY, modifiers.get(SpellModifiers.POTENCY) * 2, false); + } + + }else if(artefact == WizardryItems.ring_conjurer){ + + if(event.getSpell() instanceof SpellConjuration){ + modifiers.set(WizardryItems.duration_upgrade, modifiers.get(WizardryItems.duration_upgrade) * 2, false); + } + + }else if(artefact == WizardryItems.charm_minion_health){ + // We COULD check the spell is a SpellMinion here, but there's really no point + modifiers.set(SpellMinion.HEALTH_MODIFIER, 1.25f * modifiers.get(SpellMinion.HEALTH_MODIFIER), true); + + }else if(artefact == WizardryItems.charm_flight){ + + if(event.getSpell() == Spells.flight || event.getSpell() == Spells.glide){ + // FIXME: Does not appear to be working, for some reason + modifiers.set(SpellModifiers.POTENCY, 1.5f * potency, true); + } + + }else if(artefact == WizardryItems.charm_experience_tome){ + + modifiers.set(SpellModifiers.PROGRESSION, modifiers.get(SpellModifiers.PROGRESSION) * 1.5f, false); + } + } + } + } + + @SubscribeEvent + public static void onSpellCastPostEvent(SpellCastEvent.Pre event){ + + if(event.getCaster() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getCaster(); + + if(isArtefactActive(player, WizardryItems.ring_paladin)){ + + if(event.getSpell() instanceof Heal || event.getSpell() instanceof HealAlly || event.getSpell() instanceof GreaterHeal){ + // Spell properties allow all three of the above spells to be dealt with the same way - neat! + float healthGained = event.getSpell().getProperty(Spell.HEALTH).floatValue() * event.getModifiers().get(SpellModifiers.POTENCY); + + List nearby = WizardryUtilities.getEntitiesWithinRadius(4, player.posX, player.posY, player.posZ, event.getWorld()); + + for(EntityLivingBase entity : nearby){ + if(AllyDesignationSystem.isAllied(player, entity) && entity.getHealth() > 0 && entity.getHealth() < entity.getMaxHealth()){ + entity.heal(healthGained * 0.2f); // 1/5 of the amount healed by the spell itself + if(event.getWorld().isRemote) ParticleBuilder.spawnHealParticles(event.getWorld(), entity); + } + } + } + } + } + } + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingEvent.LivingUpdateEvent event){ + + EntityLivingBase entity = event.getEntityLiving(); + + // No point doing this every tick, every 2.5 seconds should be enough + if(entity.ticksExisted % 50 == 0 && entity.isPotionActive(WizardryPotions.mind_control)){ + + NBTTagCompound entityNBT = entity.getEntityData(); + + if(entityNBT.hasUniqueId(MindControl.NBT_KEY)){ + + Entity caster = WizardryUtilities.getEntityByUUID(entity.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); + + if(caster instanceof EntityPlayer){ + + if(isArtefactActive((EntityPlayer)caster, WizardryItems.ring_mind_control)){ + + WizardryUtilities.getEntitiesWithinRadius(3, entity.posX, entity.posY, entity.posZ, entity.world, EntityLiving.class).stream() + .filter(e -> e.world.rand.nextInt(10) == 0) + .filter(MindControl::canControl) + .filter(e -> AllyDesignationSystem.isValidTarget(caster, e)) + .forEach(target -> MindControl.startControlling(target, (EntityPlayer)caster, + // Control the new target for only the remaining duration, otherwise it could go on forever! + entity.getActivePotionEffect(WizardryPotions.mind_control).getDuration())); + } + } + } + } + } + + @SubscribeEvent + public static void onLivingHurtEvent(LivingHurtEvent event){ + + if(event.getEntity() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getEntity(); + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + if(artefact == WizardryItems.amulet_warding){ + + if(!event.getSource().isUnblockable() && event.getSource().isMagicDamage()){ + event.setAmount(event.getAmount() * 0.9f); + } + + }else if(artefact == WizardryItems.amulet_fire_protection){ + + if(event.getSource().isFireDamage()) event.setAmount(event.getAmount() * 0.7f); + + }else if(artefact == WizardryItems.amulet_ice_protection){ + + if(event.getSource() instanceof IElementalDamage + && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FROST) + event.setAmount(event.getAmount() * 0.7f); + + }else if(artefact == WizardryItems.amulet_channeling){ + + if(player.world.rand.nextFloat() < 0.3f && event.getSource() instanceof IElementalDamage + && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.SHOCK){ + event.setCanceled(true); + return; + } + + }else if(artefact == WizardryItems.amulet_fire_cloaking){ + + if(!event.getSource().isUnblockable()){ + + List fireRings = player.world.getEntitiesWithinAABB(EntityFireRing.class, player.getEntityBoundingBox()); + + for(EntityFireRing fireRing : fireRings){ + if(fireRing.getCaster() instanceof EntityPlayer && (fireRing.getCaster() == player + || AllyDesignationSystem.isOwnerAlly(player, fireRing))){ + event.setAmount(event.getAmount() * 0.25f); + } + } + } + + }else if(artefact == WizardryItems.amulet_potential){ + + if(player.world.rand.nextFloat() < 0.2f && WizardryUtilities.isMeleeDamage(event.getSource()) + && event.getSource().getTrueSource() instanceof EntityLivingBase){ + + EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource(); + + if(player.world.isRemote){ + + ParticleBuilder.create(ParticleBuilder.Type.LIGHTNING).entity(event.getEntity()) + .pos(0, event.getEntity().height/2, 0).target(target).spawn(player.world); + + ParticleBuilder.spawnShockParticles(player.world, target.posX, + target.getEntityBoundingBox().minY + target.height/2, target.posZ); + } + + DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player, + MagicDamage.DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType()); + target.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, player.world.rand.nextFloat() * 0.4F + 1.5F); + + } + + }else if(artefact == WizardryItems.amulet_lich){ + + if(!event.getSource().isUnblockable() && player.world.rand.nextFloat() < 0.15f){ + + List nearbyMobs = WizardryUtilities.getEntitiesWithinRadius(5, player.posX, player.posY, player.posZ, player.world, EntityLiving.class); + nearbyMobs.removeIf(e -> !(e instanceof ISummonedCreature && ((ISummonedCreature)e).getCaster() == player)); + + if(!nearbyMobs.isEmpty()){ + Collections.shuffle(nearbyMobs); + // Even though we're passing the same damage source through, we still need the safety check + DamageSafetyChecker.attackEntitySafely(nearbyMobs.get(0), event.getSource(), event.getAmount(), event.getSource().getDamageType()); + event.setCanceled(true); + return; // Standard practice: stop as soon as the event is canceled + } + } + + }else if(artefact == WizardryItems.amulet_banishing){ + + if(player.world.rand.nextFloat() < 0.2f && WizardryUtilities.isMeleeDamage(event.getSource()) + && event.getSource().getTrueSource() instanceof EntityLivingBase){ + + EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource(); + ((Banish)Spells.banish).teleport(target, target.world, 8 + target.world.rand.nextDouble() * 8); + } + + }else if(artefact == WizardryItems.amulet_transience){ + + if(player.getHealth() <= 2 && player.world.rand.nextFloat() < 0.25f){ + player.addPotionEffect(new PotionEffect(WizardryPotions.transience, 300)); + player.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, 300, 0, false, false)); + } + } + } + } + + if(event.getSource().getTrueSource() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource(); + ItemStack mainhandItem = player.getHeldItemMainhand(); + World world = player.world; + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + if(artefact == WizardryItems.ring_fire_melee){ + // Used ItemWand intentionally because we need the element + // Other mods can always make their own events if they want their own spellcasting items to do this + if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + && ((ItemWand)mainhandItem.getItem()).element == Element.FIRE){ + event.getEntity().setFire(5); + } + + }else if(artefact == WizardryItems.ring_ice_melee){ + + if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + && ((ItemWand)mainhandItem.getItem()).element == Element.ICE){ + event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0)); + } + + }else if(artefact == WizardryItems.ring_lightning_melee){ + + if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + && ((ItemWand)mainhandItem.getItem()).element == Element.LIGHTNING){ + + WizardryUtilities.getEntitiesWithinRadius(3, player.posX, player.posY, player.posZ, world).stream() + .filter(WizardryUtilities::isLiving) + .min(Comparator.comparingDouble(player::getDistanceSq)) + .ifPresent(target -> { + + if(world.isRemote){ + + ParticleBuilder.create(ParticleBuilder.Type.LIGHTNING).entity(event.getEntity()) + .pos(0, event.getEntity().height/2, 0).target(target).spawn(world); + + ParticleBuilder.spawnShockParticles(world, target.posX, + target.getEntityBoundingBox().minY + target.height/2, target.posZ); + } + + DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player, + MagicDamage.DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType()); + target.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); + }); + } + + }else if(artefact == WizardryItems.ring_necromancy_melee){ + + if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + && ((ItemWand)mainhandItem.getItem()).element == Element.NECROMANCY){ + event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.WITHER, 200, 0)); + } + + }else if(artefact == WizardryItems.ring_earth_melee){ + + if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + && ((ItemWand)mainhandItem.getItem()).element == Element.EARTH){ + event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0)); + } + + }else if(artefact == WizardryItems.ring_shattering){ + + if(!player.world.isRemote && player.world.rand.nextFloat() < 0.15f + && event.getEntityLiving().getHealth() < 12f // Otherwise it's a bit overpowered! + && event.getEntityLiving().isPotionActive(WizardryPotions.frost) + && WizardryUtilities.isMeleeDamage(event.getSource())){ + + event.setAmount(12f); + + for(int i = 0; i < 8; i++){ + double dx = event.getEntity().world.rand.nextDouble() - 0.5; + double dy = event.getEntity().world.rand.nextDouble() - 0.5; + double dz = event.getEntity().world.rand.nextDouble() - 0.5; + EntityIceShard iceshard = new EntityIceShard(event.getEntity().world); + iceshard.setPosition(event.getEntity().posX + dx + Math.signum(dx) * event.getEntity().width, + event.getEntity().posY + event.getEntity().height/2 + dy, + event.getEntity().posZ + dz + Math.signum(dz) * event.getEntity().width); + iceshard.motionX = dx * 1.5; + iceshard.motionY = dy * 1.5; + iceshard.motionZ = dz * 1.5; + iceshard.setCaster(player); + event.getEntity().world.spawnEntity(iceshard); + } + } + + }else if(artefact == WizardryItems.ring_soulbinding){ + + // Best guess at necromancy spell damage: either it's wither damage... + if((event.getSource() instanceof IElementalDamage + && (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.WITHER)) + // or it's direct, non-melee damage and the player is holding a wand with a necromancy spell selected + || (event.getSource().getImmediateSource() == player && !WizardryUtilities.isMeleeDamage(event.getSource()) + && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem + && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY))){ + + CurseOfSoulbinding.getSoulboundCreatures(WizardData.get(player)).add(event.getEntity().getUniqueID()); + } + + }else if(artefact == WizardryItems.ring_leeching){ + + // Best guess at necromancy spell damage: either it's wither damage... + if(player.world.rand.nextFloat() < 0.3f && ((event.getSource() instanceof IElementalDamage + && (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.WITHER)) + // ...or it's direct, non-melee damage and the player is holding a wand with a necromancy spell selected + || (event.getSource().getImmediateSource() == player && !WizardryUtilities.isMeleeDamage(event.getSource()) + && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem + && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY + && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s) != Spells.life_drain)))){ + + if(player.shouldHeal()){ + player.heal(event.getAmount() * Spells.life_drain.getProperty(LifeDrain.HEAL_FACTOR).floatValue()); + } + } + + }else if(artefact == WizardryItems.ring_poison){ + + // Best guess at earth spell damage: either it's poison damage... + if((event.getSource() instanceof IElementalDamage + && (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.POISON)) + // ...or it was from a dart... + || event.getSource().getImmediateSource() instanceof EntityDart + // ...or it's direct, non-melee damage and the player is holding a wand with an earth spell selected + || (event.getSource().getImmediateSource() == player && !WizardryUtilities.isMeleeDamage(event.getSource()) + && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem + && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.EARTH))){ + + event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0)); + } + + }else if(artefact == WizardryItems.ring_extraction){ + + // Best guess at sorcery spell damage: either it's force damage... + if((event.getSource() instanceof IElementalDamage + && (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FORCE)) + // ...or it was from a force orb... + || event.getSource().getImmediateSource() instanceof EntityForceOrb + // ...or it's direct, non-melee damage and the player is holding a wand with a sorcery spell selected + || (event.getSource().getImmediateSource() == player && !WizardryUtilities.isMeleeDamage(event.getSource()) + && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem + && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.SORCERY))){ + + WizardryUtilities.getPrioritisedHotbarAndOffhand(player).stream() + .filter(s -> s.getItem() instanceof ISpellCastingItem && s.getItem() instanceof IManaStoringItem + && !((IManaStoringItem)s.getItem()).isManaFull(s)) + .findFirst() + .ifPresent(s -> ((IManaStoringItem)s.getItem()).rechargeMana(s, 4 + world.rand.nextInt(3))); + } + + } + + } + } + } + + @SubscribeEvent + public static void onLivingDeathEvent(LivingDeathEvent event){ + + if(event.getSource().getTrueSource() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource(); + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + if(artefact == WizardryItems.ring_combustion){ + + if(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FIRE){ + event.getEntity().world.createExplosion(event.getEntity(), event.getEntity().posX, event.getEntity().posY, + event.getEntity().posZ, 1.5f, false); + } + + }else if(artefact == WizardryItems.ring_disintegration){ + + if(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FIRE){ + Disintegration.spawnEmbers(event.getEntity().world, player, event.getEntity(), + Spells.disintegration.getProperty(Disintegration.EMBER_COUNT).intValue()); + } + + }else if(artefact == WizardryItems.ring_arcane_frost){ + + if(!player.world.isRemote && event.getSource() instanceof IElementalDamage + && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FROST){ + + for(int i = 0; i < 8; i++){ + double dx = event.getEntity().world.rand.nextDouble() - 0.5; + double dy = event.getEntity().world.rand.nextDouble() - 0.5; + double dz = event.getEntity().world.rand.nextDouble() - 0.5; + EntityIceShard iceshard = new EntityIceShard(event.getEntity().world); + iceshard.setPosition(event.getEntity().posX + dx + Math.signum(dx) * event.getEntity().width, + event.getEntity().posY + event.getEntity().height/2 + dy, + event.getEntity().posZ + dz + Math.signum(dz) * event.getEntity().width); + iceshard.motionX = dx * 1.5; + iceshard.motionY = dy * 1.5; + iceshard.motionZ = dz * 1.5; + iceshard.setCaster(player); + event.getEntity().world.spawnEntity(iceshard); + } + } + } + } + + } + } + + @SubscribeEvent(priority = EventPriority.HIGH) // Needs to happen before gravestones, etc. + public static void onPlayerDropsEvent(PlayerDropsEvent event){ + // Amulet of the immortal allows players to hold onto a wand with resurrection + // This needs to happen or we can't cast the spell with it and use up the mana + if(isArtefactActive(event.getEntityPlayer(), WizardryItems.amulet_resurrection)){ + + EntityItem item = event.getDrops().stream() + .filter(e -> Resurrection.canStackResurrect(e.getItem(), event.getEntityPlayer())) + .findFirst().orElse(null); + + if(item == null) return; // The player didn't have a wand with resurrection on it + if(!WizardryUtilities.getHotbar(event.getEntityPlayer()).contains(ItemStack.EMPTY)) return; // No space on hotbar + + event.getDrops().remove(item); + // At this point the player probably has nothing in their hand, but if not just find a free space somewhere + if(event.getEntityPlayer().getHeldItemMainhand().isEmpty()) event.getEntityPlayer().setHeldItem(EnumHand.MAIN_HAND, item.getItem()); + else event.getEntityPlayer().addItemStackToInventory(item.getItem()); // Always chooses hotbar slots first + } + } + + @SubscribeEvent + public static void onPotionApplicableEvent(PotionEvent.PotionApplicableEvent event){ + + if(event.getEntity() instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)event.getEntity(); + + for(ItemArtefact artefact : getActiveArtefacts(player)){ + + if(artefact == WizardryItems.amulet_ice_immunity){ + + if(event.getPotionEffect().getPotion() == WizardryPotions.frost) event.setResult(Event.Result.DENY); + + }else if(artefact == WizardryItems.amulet_wither_immunity){ + + if(event.getPotionEffect().getPotion() == MobEffects.WITHER) event.setResult(Event.Result.DENY); + } + } + } + } + + @SubscribeEvent + public static void onItemPickupEvent(PlayerEvent.ItemPickupEvent event){ + + // ItemPickupEvent is just a convenient trigger for this; we don't actually care what got picked up + if(isArtefactActive(event.player, WizardryItems.charm_auto_smelt)){ + + // So this doesn't waste mana, only cast pocket furnace when it would smelt the maximum number of items + if(event.player.inventory.mainInventory.stream() + .filter(s -> !FurnaceRecipes.instance().getSmeltingResult(s).isEmpty()) + .mapToInt(ItemStack::getCount) + .sum() >= Spells.pocket_furnace.getProperty(PocketFurnace.ITEMS_SMELTED).intValue()){ + + findMatchingWandAndCast(event.player, Spells.pocket_furnace); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java b/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java new file mode 100644 index 00000000..dc15754e --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java @@ -0,0 +1,62 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellProperties; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +public class ItemBlankScroll extends Item implements IWorkbenchItem { + + public ItemBlankScroll(){ + this.setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public int getSpellSlotCount(ItemStack stack){ + return 1; + } + + @Override + public boolean showTooltip(ItemStack stack){ + return false; + } + + @Override + public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ + + if(!spellBooks[0].getStack().isEmpty() && !crystals.getStack().isEmpty()){ + + Spell spell = Spell.byMetadata(spellBooks[0].getStack().getItemDamage()); + WizardData data = WizardData.get(player); + + // Spells can only be bound to scrolls if the player has already cast them (prevents casting of master + // spells without getting a master wand) + // This restriction does not apply in creative mode + if(spell != Spells.none && player.isCreative() || (data != null + && data.hasSpellBeenDiscovered(spell)) && spell.isEnabled(SpellProperties.Context.SCROLL)){ + + int cost = spell.getCost() * centre.getStack().getCount(); + // Continuous spell scrolls require enough mana to cast them for the duration defined in ItemScroll. + if(spell.isContinuous) cost *= ItemScroll.CASTING_TIME / 20; + + if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL > cost){ + // Rounds up to the nearest whole crystal + crystals.decrStackSize(cost / Constants.MANA_PER_CRYSTAL + 1); + centre.putStack(new ItemStack(WizardryItems.scroll, centre.getStack().getCount(), spell.metadata())); + return true; + } + + } + } + + return false; + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java b/src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java new file mode 100644 index 00000000..0ed575cf --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java @@ -0,0 +1,42 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.constants.Element; +import net.minecraft.block.Block; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; + +public class ItemBlockMultiTexturedElemental extends ItemBlock implements IMultiTexturedItem { + + private final boolean separateNames; + + public ItemBlockMultiTexturedElemental(Block block, boolean separateNames){ + super(block); + this.setHasSubtypes(true); + this.setMaxDamage(0); + this.separateNames = separateNames; + } + + @Override + public ResourceLocation getModelName(ItemStack stack){ + int metadata = stack.getMetadata(); + if(metadata >= Element.values().length) metadata = 0; + return getModelName(metadata); + } + + public ResourceLocation getModelName(int metadata){ + return new ResourceLocation(this.block.getRegistryName().getNamespace(), + Element.values()[metadata].getName() + "_" + this.block.getRegistryName().getPath()); + } + + @Override + public String getTranslationKey(ItemStack stack){ + return this.separateNames ? "tile." + this.getModelName(stack).toString() : super.getTranslationKey(stack); + } + + @Override + public int getMetadata(int metadata){ + return metadata; + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemCrystal.java b/src/main/java/electroblob/wizardry/item/ItemCrystal.java new file mode 100644 index 00000000..88e4c96d --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemCrystal.java @@ -0,0 +1,44 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; + +/** Note that in 1.13, the flattening will make this class redundant, much like ItemCoal, which is probably its + * closest analog in vanilla. */ +public class ItemCrystal extends Item implements IMultiTexturedItem { + + public ItemCrystal(){ + super(); + this.setHasSubtypes(true); + this.setMaxDamage(0); + this.setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public ResourceLocation getModelName(ItemStack stack){ + int metadata = stack.getMetadata(); + if(metadata >= Element.values().length) metadata = 0; + return new ResourceLocation(Wizardry.MODID, "crystal_" + Element.values()[metadata].getName()); + } + + @Override + public String getTranslationKey(ItemStack stack){ + return "item." + this.getModelName(stack).toString(); + } + + @Override + public void getSubItems(CreativeTabs tab, NonNullList items){ + if(tab == WizardryTabs.WIZARDRY){ + for(Element element : Element.values()){ + items.add(new ItemStack(this, 1, element.ordinal())); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemFirebomb.java b/src/main/java/electroblob/wizardry/item/ItemFirebomb.java index 27cb433c..c3404136 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFirebomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemFirebomb.java @@ -1,9 +1,9 @@ package electroblob.wizardry.item; import electroblob.wizardry.entity.projectile.EntityFirebomb; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; @@ -23,16 +23,15 @@ public class ItemFirebomb extends Item { ItemStack stack = player.getHeldItem(hand); - if(!player.capabilities.isCreativeMode){ + if(!player.isCreative()){ stack.shrink(1); } - player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.playSound(WizardrySounds.ENTITY_FIREBOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); if(!world.isRemote){ - EntityFirebomb firebomb = new EntityFirebomb(world, player); - // This is the standard set of parameters for this method, used by snowballs and ender pearls. - firebomb.shoot(player, player.rotationPitch, player.rotationYaw, 0.0f, 1.5f, 1.0f); + EntityFirebomb firebomb = new EntityFirebomb(world); + firebomb.aim(player, 1); world.spawnEntity(firebomb); } diff --git a/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java b/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java index af776bab..dcfe3110 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java @@ -1,10 +1,19 @@ package electroblob.wizardry.item; +import com.google.common.collect.Multimap; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; import net.minecraft.item.ItemAxe; import net.minecraft.item.ItemStack; import net.minecraft.world.World; @@ -13,21 +22,42 @@ import net.minecraftforge.fml.relauncher.SideOnly; public class ItemFlamingAxe extends ItemAxe implements IConjuredItem { + private EnumRarity rarity = EnumRarity.COMMON; + public ItemFlamingAxe(ToolMaterial material){ super(material, 8, -3); - setMaxDamage(getBaseDuration()); + setMaxDamage(1200); // Might cause problems if removed, the actual number is irrelevant as long as it's > 0 setNoRepair(); setCreativeTab(null); + addAnimationPropertyOverrides(); } @Override - public int getBaseDuration(){ - return 600; + public Multimap getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){ + + Multimap multimap = super.getItemAttributeModifiers(slot); + + if(slot == EntityEquipmentSlot.MAINHAND){ + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + } + + return multimap; + } + + public Item setRarity(EnumRarity rarity){ + this.rarity = rarity; + return this; + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return rarity; } @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.flaming_axe); } @Override @@ -50,9 +80,16 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem { stack.setItemDamage(damage + 1); } + @Override + public Multimap getItemAttributeModifiers(EntityEquipmentSlot equipmentSlot){ + attackDamage = Spells.flaming_axe.getProperty(Spell.DAMAGE).floatValue(); + return super.getItemAttributeModifiers(equipmentSlot); + } + @Override public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase wielder){ - if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) target.setFire(8); + if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) + target.setFire(Spells.flaming_axe.getProperty(Spell.BURN_DURATION).intValue()); return false; } @@ -72,6 +109,16 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ diff --git a/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java b/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java index 2f57f20b..cca0d046 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java @@ -1,11 +1,19 @@ package electroblob.wizardry.item; +import com.google.common.collect.Multimap; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; import net.minecraft.item.ItemAxe; import net.minecraft.item.ItemStack; import net.minecraft.potion.PotionEffect; @@ -15,21 +23,42 @@ import net.minecraftforge.fml.relauncher.SideOnly; public class ItemFrostAxe extends ItemAxe implements IConjuredItem { + private EnumRarity rarity = EnumRarity.COMMON; + public ItemFrostAxe(ToolMaterial material){ super(material, 8, -3); - setMaxDamage(getBaseDuration()); + setMaxDamage(1200); setNoRepair(); setCreativeTab(null); + addAnimationPropertyOverrides(); } @Override - public int getBaseDuration(){ - return 600; + public Multimap getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){ + + Multimap multimap = super.getItemAttributeModifiers(slot); + + if(slot == EntityEquipmentSlot.MAINHAND){ + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + } + + return multimap; + } + + public Item setRarity(EnumRarity rarity){ + this.rarity = rarity; + return this; + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return rarity; } @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.frost_axe); } @Override @@ -75,6 +104,16 @@ public class ItemFrostAxe extends ItemAxe implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ diff --git a/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java b/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java index 8a560c5b..61375b1e 100644 --- a/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java +++ b/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java @@ -1,19 +1,14 @@ package electroblob.wizardry.item; -import java.util.List; - -import javax.annotation.Nullable; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.event.DiscoverSpellEvent; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; +import net.minecraft.item.EnumRarity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; @@ -25,6 +20,9 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; +import java.util.List; + public class ItemIdentificationScroll extends Item { public ItemIdentificationScroll(){ @@ -38,11 +36,15 @@ public class ItemIdentificationScroll extends Item { return true; } + @Override + public EnumRarity getRarity(ItemStack stack){ + return EnumRarity.UNCOMMON; + } + @Override @SideOnly(Side.CLIENT) - public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, ITooltipFlag flagIn) { - tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":identification_scroll.desc1", "\u00A77")); - tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":identification_scroll.desc2", "\u00A77")); + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, net.minecraft.client.util.ITooltipFlag flag) { + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc"); } @Override @@ -52,27 +54,26 @@ public class ItemIdentificationScroll extends Item { if(WizardData.get(player) != null){ - WizardData properties = WizardData.get(player); + WizardData data = WizardData.get(player); for(ItemStack stack1 : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ if(!stack1.isEmpty()){ - Spell spell = Spell.get(stack1.getItemDamage()); + Spell spell = Spell.byMetadata(stack1.getItemDamage()); if((stack1.getItem() instanceof ItemSpellBook || stack1.getItem() instanceof ItemScroll) - && !properties.hasSpellBeenDiscovered(spell)){ + && !data.hasSpellBeenDiscovered(spell)){ if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, spell, DiscoverSpellEvent.Source.IDENTIFICATION_SCROLL))){ // Identification scrolls give the chat readout in creative mode, otherwise it looks like // nothing happens! - properties.discoverSpell(spell); - WizardryAdvancementTriggers.identify_spell.triggerFor(player); - player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1); - if(!player.capabilities.isCreativeMode) stack.shrink(1); + data.discoverSpell(spell); + player.playSound(WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1); + if(!player.isCreative()) stack.shrink(1); if(!world.isRemote) player.sendMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted())); - return new ActionResult(EnumActionResult.SUCCESS, stack); + return new ActionResult<>(EnumActionResult.SUCCESS, stack); } } } @@ -82,7 +83,7 @@ public class ItemIdentificationScroll extends Item { new TextComponentTranslation("item." + Wizardry.MODID + ":identification_scroll.nothing_to_identify")); } - return new ActionResult(EnumActionResult.FAIL, stack); + return new ActionResult<>(EnumActionResult.FAIL, stack); } } diff --git a/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java b/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java new file mode 100644 index 00000000..0b7d40cd --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java @@ -0,0 +1,220 @@ +package electroblob.wizardry.item; + +import com.google.common.collect.Multimap; +import electroblob.wizardry.entity.construct.EntityHammer; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.LightningHammer; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.List; +import java.util.UUID; + +@Mod.EventBusSubscriber +public class ItemLightningHammer extends Item implements IConjuredItem { + + public static final String DURATION_NBT_KEY = "duration"; + // Annoyingly we can't implement this for attack damage, but at least it gets saved for when the hammer is thrown + public static final String DAMAGE_MULTIPLIER_NBT_KEY = "damageMultiplier"; + + public static final UUID MOVEMENT_SPEED_MODIFIER = UUID.fromString("d4c3bd93-c8e3-49c5-b35b-9356663bad1b"); + + private static final double ATTACK_SPEED = -3.2; + private static final double CHAINING_RANGE = 4; + private static final float CHAINING_DAMAGE = 4; + private static final double THROW_SPEED = 0.75; + private static final double MOVEMENT_SPEED_REDUCTION = -0.25; + + public ItemLightningHammer(){ + super(); + setMaxDamage(600); + setMaxStackSize(1); + setNoRepair(); + setCreativeTab(null); + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return EnumRarity.EPIC; + } + + @Override + public int getMaxDamage(ItemStack stack){ + if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DURATION_NBT_KEY)){ + return stack.getTagCompound().getInteger(DURATION_NBT_KEY); + } + return super.getMaxDamage(stack); + } + + private float getDamageMultiplier(ItemStack stack){ + if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DAMAGE_MULTIPLIER_NBT_KEY)){ + return stack.getTagCompound().getFloat(DAMAGE_MULTIPLIER_NBT_KEY); + } + return 1; + } + + @Override + public Multimap getItemAttributeModifiers(EntityEquipmentSlot slot){ + + Multimap multimap = super.getItemAttributeModifiers(slot); + + if(slot == EntityEquipmentSlot.MAINHAND){ + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", Spells.lightning_hammer.getProperty(Spell.DIRECT_DAMAGE).floatValue(), WizardryUtilities.Operations.ADD)); + multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Weapon modifier", ATTACK_SPEED, WizardryUtilities.Operations.ADD)); + multimap.put(SharedMonsterAttributes.MOVEMENT_SPEED.getName(), new AttributeModifier(MOVEMENT_SPEED_MODIFIER, "Weapon modifier", MOVEMENT_SPEED_REDUCTION, WizardryUtilities.Operations.MULTIPLY_FLAT)); + } + + return multimap; + } + + @Override + // This method allows the code for the item's timer to be greatly simplified by damaging it directly from + // onUpdate() and removing the workaround that involved WizardData and all sorts of crazy stuff. + public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){ + + if(!oldStack.isEmpty() || !newStack.isEmpty()){ + // We only care about the situation where we specifically want the animation NOT to play. + if(oldStack.getItem() == newStack.getItem() && !slotChanged) return false; + } + + return super.shouldCauseReequipAnimation(oldStack, newStack, slotChanged); + } + + @Override + public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean selected){ + int damage = stack.getItemDamage(); + if(damage > stack.getMaxDamage()) entity.replaceItemInInventory(slot, ItemStack.EMPTY); + stack.setItemDamage(damage + 1); + } + + @Override + public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand){ + + ItemStack stack = player.getHeldItem(hand); + + if(!world.isRemote){ + EntityHammer hammer = new EntityHammer(world); + Vec3d look = player.getLookVec(); + Vec3d vec = player.getPositionEyes(1).add(look); + hammer.setPositionAndRotation(vec.x, vec.y - hammer.height/2, vec.z, player.rotationYawHead - 90, 0); + // For some reason the above method insists on clamping the pitch to between -90 and 90 + hammer.rotationPitch = 180 + player.rotationPitch; + hammer.prevRotationPitch = hammer.rotationPitch; + + float attackStrength = player.getCooledAttackStrength(0); + double speed = THROW_SPEED * attackStrength; // Throw distance depends on the attack meter + hammer.addVelocity(look.x * speed, look.y * speed, look.z * speed); + hammer.lifetime = stack.getMaxDamage() - stack.getItemDamage(); + hammer.setCaster(player); + hammer.damageMultiplier = getDamageMultiplier(stack); + hammer.spin = true; + world.spawnEntity(hammer); + } + + WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.ENTITY_HAMMER_THROW, 1.0F, 0.8f); + + //player.swingArm(hand); + + // Use this instead of stack.shrink so it works regardless of whether the player is in creative mode or not + player.setHeldItem(hand, ItemStack.EMPTY); + + return ActionResult.newResult(EnumActionResult.SUCCESS, stack); + } + + @Override + public boolean getIsRepairable(ItemStack stack, ItemStack par2ItemStack){ + return false; + } + + @Override + public int getItemEnchantability(){ + return 0; + } + + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + + // Cannot be dropped + @Override + public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ + return false; + } + + // Can't be done in hitEntity because that's only called server-side, and after the cooldown is reset + @SubscribeEvent + public static void onAttackEntityEvent(AttackEntityEvent event){ + + ItemStack stack = event.getEntityPlayer().getHeldItemMainhand(); + + if(stack.getItem() instanceof ItemLightningHammer && event.getTarget() instanceof EntityLivingBase){ + + EntityPlayer wielder = event.getEntityPlayer(); + EntityLivingBase hit = (EntityLivingBase)event.getTarget(); + + float attackStrength = wielder.getCooledAttackStrength(0); + + double dx = wielder.posX - hit.posX; + double dz; + for(dz = wielder.posZ - hit.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) + * 0.01D){ + dx = (Math.random() - Math.random()) * 0.01D; + } + + hit.knockBack(wielder, 2 * attackStrength, dx, dz); + + if(attackStrength == 1){ // Only chains when the attack meter is full + + List nearby = WizardryUtilities.getEntitiesWithinRadius(CHAINING_RANGE, hit.posX, hit.posY, hit.posZ, hit.world); + + nearby.remove(hit); + nearby.remove(wielder); + // When held, the number of chaining targets is halved + int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue() / 2; + while(nearby.size() > maxTargets) nearby.remove(nearby.size() - 1); + + for(EntityLivingBase target : nearby){ + + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(wielder, DamageType.SHOCK), CHAINING_DAMAGE * ((ItemLightningHammer)stack.getItem()).getDamageMultiplier(stack)); + + if(hit.world.isRemote){ + ParticleBuilder.create(Type.LIGHTNING).pos(hit.getPositionVector().add(0, hit.height / 2, 0)) + .target(target).spawn(hit.world); + ParticleBuilder.spawnShockParticles(hit.world, target.posX, target.getEntityBoundingBox().minY + target.height / 2, target.posZ); + } + + //target.playSound(WizardrySounds.SPELL_SPARK, 1, 1.5f + 0.4f * world.rand.nextFloat()); + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemManaFlask.java b/src/main/java/electroblob/wizardry/item/ItemManaFlask.java new file mode 100644 index 00000000..8823b632 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemManaFlask.java @@ -0,0 +1,37 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +public class ItemManaFlask extends Item { + + public enum Size { + + SMALL(75, EnumRarity.COMMON), + MEDIUM(700, EnumRarity.COMMON), + LARGE(1400, EnumRarity.RARE); + + public int capacity; + public EnumRarity rarity; + + Size(int capacity, EnumRarity rarity){ + this.capacity = capacity; + this.rarity = rarity; + } + } + + public final Size size; + + public ItemManaFlask(Size size){ + super(); + this.size = size; + this.setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return size.rarity; + } +} diff --git a/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java b/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java index 3613e0e7..a68c8aa4 100644 --- a/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java @@ -1,9 +1,9 @@ package electroblob.wizardry.item; import electroblob.wizardry.entity.projectile.EntityPoisonBomb; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; @@ -23,16 +23,15 @@ public class ItemPoisonBomb extends Item { ItemStack stack = player.getHeldItem(hand); - if(!player.capabilities.isCreativeMode){ + if(!player.isCreative()){ stack.shrink(1); } - player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.playSound(WizardrySounds.ENTITY_POISON_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); if(!world.isRemote){ - EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world, player); - // This is the standard set of parameters for this method, used by snowballs and ender pearls. - poisonbomb.shoot(player, player.rotationPitch, player.rotationYaw, 0.0f, 1.5f, 1.0f); + EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world); + poisonbomb.aim(player, 1); world.spawnEntity(poisonbomb); } diff --git a/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java b/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java new file mode 100644 index 00000000..785ed409 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java @@ -0,0 +1,99 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Items; +import net.minecraft.item.EnumAction; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.List; + +public class ItemPurifyingElixir extends Item { + + public ItemPurifyingElixir(){ + this.setMaxStackSize(1); + this.setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public boolean hasEffect(ItemStack stack){ + return true; + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return EnumRarity.RARE; + } + + @Override + @SideOnly(Side.CLIENT) + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, net.minecraft.client.util.ITooltipFlag flag) { + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc"); + } + + @Override + public ItemStack onItemUseFinish(ItemStack stack, World world, EntityLivingBase entity){ + + if(!world.isRemote){ + entity.curePotionEffects(stack); + }else{ + + ParticleBuilder.spawnHealParticles(world, entity); + + for(int i = 0; i < 20; i++){ + double x = entity.posX + world.rand.nextDouble() * 2 - 1; + double y = entity.getEntityBoundingBox().minY + entity.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = entity.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.14, 0).clr(0x0f001b) + .time(20 + world.rand.nextInt(12)).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x0f001b).spawn(world); + } + } + + world.playSound(entity.posX, entity.posY, entity.posZ, WizardrySounds.ITEM_PURIFYING_ELIXIR_DRINK, SoundCategory.PLAYERS, 1, 1, false); + + if(entity instanceof EntityPlayerMP){ + EntityPlayerMP entityplayermp = (EntityPlayerMP)entity; + CriteriaTriggers.CONSUME_ITEM.trigger(entityplayermp, stack); + } + + if(entity instanceof EntityPlayer && !((EntityPlayer)entity).capabilities.isCreativeMode){ + stack.shrink(1); + } + + return stack.isEmpty() ? new ItemStack(Items.GLASS_BOTTLE) : stack; + } + + @Override + public int getMaxItemUseDuration(ItemStack stack){ + return 32; + } + + @Override + public EnumAction getItemUseAction(ItemStack stack){ + return EnumAction.DRINK; + } + + @Override + public ActionResult onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand handIn){ + playerIn.setActiveHand(handIn); + return new ActionResult<>(EnumActionResult.SUCCESS, playerIn.getHeldItem(handIn)); + } +} diff --git a/src/main/java/electroblob/wizardry/item/ItemScroll.java b/src/main/java/electroblob/wizardry/item/ItemScroll.java index 079927d5..d793782b 100644 --- a/src/main/java/electroblob/wizardry/item/ItemScroll.java +++ b/src/main/java/electroblob/wizardry/item/ItemScroll.java @@ -8,8 +8,8 @@ import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; -import net.minecraft.client.gui.FontRenderer; import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -23,20 +23,37 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -public class ItemScroll extends Item { +public class ItemScroll extends Item implements ISpellCastingItem { + + /** The maximum number of ticks a continuous spell scroll can be cast for (by holding the use item button). */ + public static final int CASTING_TIME = 120; public ItemScroll(){ super(); setHasSubtypes(true); - setMaxStackSize(1); + setMaxStackSize(16); setCreativeTab(WizardryTabs.SPELLS); } + + @Override + public Spell getCurrentSpell(ItemStack stack){ + return Spell.byMetadata(stack.getItemDamage()); + } + + @Override + public boolean showSpellHUD(EntityPlayer player, ItemStack stack){ + return false; + } @Override public void getSubItems(CreativeTabs tab, NonNullList list){ - if (isInCreativeTab(tab)) { - for(Spell spell : Spell.getSpells(Spell.nonContinuousSpells)){ - list.add(new ItemStack(this, 1, spell.id())); + if(tab == WizardryTabs.SPELLS){ + // In this particular case, getTotalSpellCount() is a more efficient way of doing this since the spell instance + // is not required, only the metadata. + for(int i = 0; i < Spell.getTotalSpellCount(); i++){ + // i+1 is used so that the metadata ties up with the metadata() method. In other words, the none spell has metadata + // 0 and since this is not used as a spell book the metadata starts at 1. + list.add(new ItemStack(this, 1, i + 1)); } } } @@ -60,63 +77,129 @@ public class ItemScroll extends Item { * server side, but the result to then be sent to the client, which means broken discovery system. Simply put, I * can't predict that, and it's not my job to cater for other people's incorrect usage of code, especially when * that might compromise some perfectly reasonable use (think Bibliocraft's 'best guess' book detection). */ - // TODO: Backport this proxy-based fix. return Wizardry.proxy.getScrollDisplayName(stack); } + + @Override + public int getMaxItemUseDuration(ItemStack stack){ + return CASTING_TIME; + } @Override public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand){ ItemStack stack = player.getHeldItem(hand); - Spell spell = Spell.get(stack.getItemDamage()); + Spell spell = Spell.byMetadata(stack.getItemDamage()); // By default, scrolls have no modifiers - but with the event system, they could be added. SpellModifiers modifiers = new SpellModifiers(); - // If anything stops the spell working at this point, nothing else happens. - if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(player, spell, modifiers, Source.SCROLL))){ - return new ActionResult(EnumActionResult.FAIL, stack); - } - - if(!spell.isContinuous){ - - if(!world.isRemote){ - - if(spell.cast(world, player, hand, 0, new SpellModifiers())){ - - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.SCROLL)); - - if(spell.doesSpellRequirePacket()){ - // Sends a packet to all players in dimension to tell them to spawn particles. - IMessage msg = new PacketCastSpell.Message(player.getEntityId(), hand, spell.id(), modifiers); - WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); - } - - // Scrolls are consumed upon successful use in survival mode - if(!player.capabilities.isCreativeMode) stack.shrink(1); - - return new ActionResult(EnumActionResult.SUCCESS, stack); + if(canCast(stack, spell, player, hand, 0, modifiers)){ + // Now we can cast continuous spells with scrolls! + if(spell.isContinuous){ + if(!player.isHandActive()){ + player.setActiveHand(hand); + return new ActionResult<>(EnumActionResult.SUCCESS, stack); } - - // This else if check was bugging me for AGES! I can't believe I didn't compare to ItemWand before. - }else if(!spell.doesSpellRequirePacket()){ - // Client-inconsistent spell casting. This code only runs client-side. - if(spell.cast(world, player, hand, 0, modifiers)){ - // This is all that needs to happen, because everything above works fine on just the server side. - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.SCROLL)); - return new ActionResult(EnumActionResult.SUCCESS, stack); + }else{ + if(cast(stack, spell, player, hand, 0, modifiers)){ + return new ActionResult<>(EnumActionResult.SUCCESS, stack); } } } - return new ActionResult(EnumActionResult.FAIL, stack); + return new ActionResult<>(EnumActionResult.FAIL, stack); + } + + // For continuous spells. The count argument actually decrements by 1 each tick. + @Override + public void onUsingTick(ItemStack stack, EntityLivingBase user, int count){ + if(user instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)user; + + Spell spell = Spell.byMetadata(stack.getItemDamage()); + // By default, scrolls have no modifiers - but with the event system, they could be added. + SpellModifiers modifiers = new SpellModifiers(); + int castingTick = stack.getMaxItemUseDuration() - count; + + // Continuous spells (these must check if they can be cast each tick since the mana changes) + // In theory the spell is always continuous here but just in case it isn't... + if(spell.isContinuous && canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers)){ + cast(stack, spell, player, player.getActiveHand(), castingTick, modifiers); + }else{ + // Scrolls normally work on the max use duration so this isn't ever reached by wizardry, but if the + // casting was interrupted by SpellCastEvent.Tick it will be used + player.stopActiveHand(); + } + } + } + + @Override + public boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){ + // Even neater! + if(castingTick == 0){ + return !MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.SCROLL, spell, caster, modifiers)); + }else{ + return !MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.SCROLL, spell, caster, modifiers, castingTick)); + } + } + + @Override + public boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){ + + World world = caster.world; + + if(world.isRemote && !spell.isContinuous && spell.requiresPacket()) return false; + + if(spell.cast(world, caster, hand, castingTick, modifiers)){ + + if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.SCROLL, spell, caster, modifiers)); + + if(!world.isRemote){ + + // Continuous spells never require packets so don't rely on the requiresPacket method to specify it + if(!spell.isContinuous && spell.requiresPacket()){ + // Sends a packet to all players in dimension to tell them to spawn particles. + IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), hand, spell, modifiers); + WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); + } + + // Scrolls are consumed upon successful use in survival mode + if(!spell.isContinuous && !caster.isCreative()) stack.shrink(1); + } + + return true; + } + + return false; + } + + @Override + public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase user, int timeLeft){ + // Consumes a continuous spell scroll when a player in survival mode stops using it. + if(Spell.byMetadata(stack.getItemDamage()).isContinuous + && (!(user instanceof EntityPlayer) || !((EntityPlayer)user).isCreative())){ + stack.shrink(1); + } + } + + @Override + public ItemStack onItemUseFinish(ItemStack stack, World world, EntityLivingBase user){ + // Consumes a continuous spell scroll when the casting elapses whilst in use by a player in survival mode. + if(Spell.byMetadata(stack.getItemDamage()).isContinuous + && (!(user instanceof EntityPlayer) || !((EntityPlayer)user).isCreative())){ + stack.shrink(1); + } + + return stack; } @Override @SideOnly(Side.CLIENT) - public FontRenderer getFontRenderer(ItemStack stack){ + public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){ return Wizardry.proxy.getFontRenderer(stack); } } diff --git a/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java b/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java index 023fe59f..a62a1179 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java @@ -1,9 +1,9 @@ package electroblob.wizardry.item; import electroblob.wizardry.entity.projectile.EntitySmokeBomb; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; @@ -23,16 +23,15 @@ public class ItemSmokeBomb extends Item { ItemStack stack = player.getHeldItem(hand); - if(!player.capabilities.isCreativeMode){ + if(!player.isCreative()){ stack.shrink(1); } - player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); if(!world.isRemote){ - EntitySmokeBomb smokebomb = new EntitySmokeBomb(world, player); - // This is the standard set of parameters for this method, used by snowballs and ender pearls. - smokebomb.shoot(player, player.rotationPitch, player.rotationYaw, 0.0f, 1.5f, 1.0f); + EntitySmokeBomb smokebomb = new EntitySmokeBomb(world); + smokebomb.aim(player, 1); world.spawnEntity(smokebomb); } diff --git a/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java b/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java new file mode 100644 index 00000000..319b3dae --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java @@ -0,0 +1,41 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.entity.projectile.EntitySparkBomb; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.world.World; + +public class ItemSparkBomb extends Item { + + public ItemSparkBomb(){ + setMaxStackSize(16); + setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand){ + + ItemStack stack = player.getHeldItem(hand); + + if(!player.isCreative()){ + stack.shrink(1); + } + + player.playSound(WizardrySounds.ENTITY_SPARK_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + + if(!world.isRemote){ + EntitySparkBomb sparkBomb = new EntitySparkBomb(world); + sparkBomb.aim(player, 1); + world.spawnEntity(sparkBomb); + } + + return ActionResult.newResult(EnumActionResult.SUCCESS, stack); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java b/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java index 1250c083..afa653b8 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java @@ -1,7 +1,6 @@ package electroblob.wizardry.item; -import net.minecraft.client.model.ModelBiped; -import net.minecraft.client.renderer.GlStateManager; +import electroblob.wizardry.registry.Spells; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -18,17 +17,12 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem { public ItemSpectralArmour(ArmorMaterial material, int renderIndex, EntityEquipmentSlot armourType){ super(material, renderIndex, armourType); setCreativeTab(null); - setMaxDamage(getBaseDuration()); - } - - @Override - public int getBaseDuration(){ - return 1200; + setMaxDamage(1200); } @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.conjure_armour); } // Overridden to stop the enchantment trick making the name turn blue. @@ -73,6 +67,16 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ @@ -89,14 +93,14 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem { @Override @SideOnly(Side.CLIENT) - public ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack, - EntityEquipmentSlot armorSlot, ModelBiped _default){ - GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate( - GlStateManager.SourceFactor.SRC_ALPHA, - GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, - GlStateManager.SourceFactor.ONE, - GlStateManager.DestFactor.ZERO + public net.minecraft.client.model.ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack, + EntityEquipmentSlot armorSlot, net.minecraft.client.model.ModelBiped _default){ + net.minecraft.client.renderer.GlStateManager.enableBlend(); + net.minecraft.client.renderer.GlStateManager.tryBlendFuncSeparate( + net.minecraft.client.renderer.GlStateManager.SourceFactor.SRC_ALPHA, + net.minecraft.client.renderer.GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, + net.minecraft.client.renderer.GlStateManager.SourceFactor.ONE, + net.minecraft.client.renderer.GlStateManager.DestFactor.ZERO ); return super.getArmorModel(entityLiving, itemStack, armorSlot, _default); } diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java b/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java index 2426baec..421b5b8c 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java @@ -1,9 +1,7 @@ package electroblob.wizardry.item; -import javax.annotation.Nullable; - import electroblob.wizardry.Wizardry; -import net.minecraft.client.Minecraft; +import electroblob.wizardry.registry.Spells; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -17,20 +15,18 @@ import net.minecraft.item.ItemArrow; import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; import net.minecraft.stats.StatList; -import net.minecraft.util.ActionResult; -import net.minecraft.util.EnumActionResult; -import net.minecraft.util.EnumHand; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.SoundCategory; +import net.minecraft.util.*; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; + public class ItemSpectralBow extends ItemBow implements IConjuredItem { public ItemSpectralBow(){ super(); - setMaxDamage(getBaseDuration()); + setMaxDamage(1200); setNoRepair(); setCreativeTab(null); this.addPropertyOverride(new ResourceLocation("pull"), new IItemPropertyGetter(){ @@ -40,7 +36,6 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { return 0.0F; }else{ ItemStack itemstack = entityIn.getActiveItemStack(); - // Mojang, observe - hardcoding item references into their own classes is NOT good Java. return itemstack.getItem() == ItemSpectralBow.this ? (float)(stack.getMaxItemUseDuration() - entityIn.getItemInUseCount()) / 20.0F : 0.0F; @@ -54,6 +49,7 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { : 0.0F; } }); + addAnimationPropertyOverrides(); } @Override @@ -63,14 +59,9 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { return true; } - @Override - public int getBaseDuration(){ - return 600; - } - @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.conjure_bow); } @Override @@ -78,27 +69,38 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { // onUpdate() and removing the workaround that involved WizardData and all sorts of crazy stuff. public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){ - // TODO: For some reason there used to be an && here instead of an ||, which makes me wonder if there's a weird - // fix I did that needs removing. if(!oldStack.isEmpty() || !newStack.isEmpty()){ // We only care about the situation where we specifically want the animation NOT to play. if(oldStack.getItem() == newStack.getItem() && !slotChanged // This code should only run on the client side, so using Minecraft is ok. - && !Minecraft.getMinecraft().player.isHandActive()) + && !net.minecraft.client.Minecraft.getMinecraft().player.isHandActive()) return false; } return super.shouldCauseReequipAnimation(oldStack, newStack, slotChanged); } + // Copied fixes from ItemWand made possible by recently-added Forge hooks + + @Override + public boolean canContinueUsing(ItemStack oldStack, ItemStack newStack){ + // Ignore durability changes + if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return true; + return super.canContinueUsing(oldStack, newStack); + } + + @Override + public boolean shouldCauseBlockBreakReset(ItemStack oldStack, ItemStack newStack){ + // Ignore durability changes + if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return false; + return super.shouldCauseBlockBreakReset(oldStack, newStack); + } + @Override public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean selected){ int damage = stack.getItemDamage(); if(damage > stack.getMaxDamage()) entity.replaceItemInInventory(slot, ItemStack.EMPTY); - // Can't damage it whilst in use because for some reason it causes the item use to constantly reset. - if(!(entity instanceof EntityLivingBase) || !((EntityLivingBase)entity).isHandActive()){ - stack.setItemDamage(damage + 1); - } + stack.setItemDamage(damage + 1); } // The following two methods re-route the displayed durability through the proxies in order to override the pausing @@ -145,19 +147,29 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ return false; } - @Override - public void onUsingTick(ItemStack stack, EntityLivingBase player, int count){ - // player.getItemInUseMaxCount() is named incorrectly; you only have to look at the method to see what it really - // does. - if(stack.getItemDamage() + player.getItemInUseMaxCount() > stack.getMaxDamage()) - player.replaceItemInInventory(player.getActiveHand() == EnumHand.MAIN_HAND ? 98 : 99, ItemStack.EMPTY); - } +// @Override +// public void onUsingTick(ItemStack stack, EntityLivingBase player, int count){ +// // player.getItemInUseMaxCount() is named incorrectly; you only have to look at the method to see what it really +// // does. +// if(stack.getItemDamage() + player.getItemInUseMaxCount() > stack.getMaxDamage()) +// player.replaceItemInInventory(player.getActiveHand() == EnumHand.MAIN_HAND ? 98 : 99, ItemStack.EMPTY); +// } @Override public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase entity, int timeLeft){ @@ -205,10 +217,12 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { entityarrow.pickupStatus = EntityArrow.PickupStatus.DISALLOWED; + entityarrow.setDamage(entityarrow.getDamage() * IConjuredItem.getDamageMultiplier(stack)); + world.spawnEntity(entityarrow); } - world.playSound((EntityPlayer)null, entityplayer.posX, entityplayer.posY, entityplayer.posZ, + world.playSound(null, entityplayer.posX, entityplayer.posY, entityplayer.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + f * 0.5F); diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java b/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java index 6e2a71b2..3f40556e 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java @@ -1,30 +1,44 @@ package electroblob.wizardry.item; +import electroblob.wizardry.registry.Spells; +import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; import net.minecraft.item.ItemPickaxe; import net.minecraft.item.ItemStack; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; + public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem { + private EnumRarity rarity = EnumRarity.COMMON; + public ItemSpectralPickaxe(ToolMaterial material){ super(material); - setMaxDamage(getBaseDuration()); + setMaxDamage(1200); setNoRepair(); setCreativeTab(null); + addAnimationPropertyOverrides(); + } + + public Item setRarity(EnumRarity rarity){ + this.rarity = rarity; + return this; } @Override - public int getBaseDuration(){ - return 600; + public EnumRarity getRarity(ItemStack stack){ + return rarity; } @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.conjure_pickaxe); } @Override @@ -47,6 +61,18 @@ public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem { stack.setItemDamage(damage + 1); } + @Override + public float getDestroySpeed(ItemStack stack, IBlockState state){ + float speed = super.getDestroySpeed(stack, state); + return speed > 1 ? speed * IConjuredItem.getDamageMultiplier(stack) : speed; + } + + @Override + public int getHarvestLevel(ItemStack stack, String toolClass, @Nullable EntityPlayer player, @Nullable IBlockState blockState){ + // Reuses the standard bonus amplifier calculation from SpellBuff to increase the mining level at advanced and master tier + return super.getHarvestLevel(stack, toolClass, player, blockState) + (int)((IConjuredItem.getDamageMultiplier(stack) - 1) / 0.4); + } + @Override @SideOnly(Side.CLIENT) public boolean hasEffect(ItemStack stack){ @@ -63,10 +89,19 @@ public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ return false; } - } diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java b/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java index d8125f9e..8a14bbab 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java @@ -1,7 +1,15 @@ package electroblob.wizardry.item; +import com.google.common.collect.Multimap; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemSword; import net.minecraft.world.World; @@ -10,21 +18,42 @@ import net.minecraftforge.fml.relauncher.SideOnly; public class ItemSpectralSword extends ItemSword implements IConjuredItem { + private EnumRarity rarity = EnumRarity.COMMON; + public ItemSpectralSword(ToolMaterial material){ super(material); - setMaxDamage(getBaseDuration()); + setMaxDamage(1200); setNoRepair(); setCreativeTab(null); + addAnimationPropertyOverrides(); } @Override - public int getBaseDuration(){ - return 600; + public Multimap getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){ + + Multimap multimap = super.getItemAttributeModifiers(slot); + + if(slot == EntityEquipmentSlot.MAINHAND){ + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + } + + return multimap; + } + + public Item setRarity(EnumRarity rarity){ + this.rarity = rarity; + return this; + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return rarity; } @Override public int getMaxDamage(ItemStack stack){ - return this.getMaxDamageFromNBT(stack); + return this.getMaxDamageFromNBT(stack, Spells.conjure_sword); } @Override @@ -63,6 +92,16 @@ public class ItemSpectralSword extends ItemSword implements IConjuredItem { return 0; } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + // Cannot be dropped @Override public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){ diff --git a/src/main/java/electroblob/wizardry/item/ItemSpellBook.java b/src/main/java/electroblob/wizardry/item/ItemSpellBook.java index 8e7cc4c3..a99dcd81 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpellBook.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpellBook.java @@ -1,17 +1,10 @@ package electroblob.wizardry.item; -import java.util.List; - -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; import electroblob.wizardry.WizardryGuiHandler; +import electroblob.wizardry.data.SpellGlyphData; import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.spell.Spell; -import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.EntityPlayerSP; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.util.ITooltipFlag; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; @@ -25,22 +18,24 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.oredict.OreDictionary; +import java.util.List; + public class ItemSpellBook extends Item { public ItemSpellBook(){ super(); setHasSubtypes(true); - setMaxStackSize(1); + setMaxStackSize(16); setCreativeTab(WizardryTabs.SPELLS); } @Override public void getSubItems(CreativeTabs tab, NonNullList list){ - if (isInCreativeTab(tab)) { + if(tab == WizardryTabs.SPELLS){ // In this particular case, getTotalSpellCount() is a more efficient way of doing this since the spell instance - // is not required, only the id. + // is not required, only the metadata. for(int i = 0; i < Spell.getTotalSpellCount(); i++){ - // i+1 is used so that the metadata ties up with the id() method. In other words, the none spell has id + // i+1 is used so that the metadata ties up with the metadata() method. In other words, the none spell has metadata // 0 and since this is not used as a spell book the metadata starts at 1. list.add(new ItemStack(this, 1, i + 1)); } @@ -56,23 +51,19 @@ public class ItemSpellBook extends Item { @Override @SideOnly(Side.CLIENT) - public void addInformation(ItemStack itemstack, World world, List tooltip, ITooltipFlag advanced){ + public void addInformation(ItemStack itemstack, World world, List tooltip, net.minecraft.client.util.ITooltipFlag advanced){ // Tooltip is left blank for wizards buying generic spell books. - if(itemstack.getItemDamage() != OreDictionary.WILDCARD_VALUE){ - EntityPlayerSP player = Minecraft.getMinecraft().player; + if(world != null && itemstack.getItemDamage() != OreDictionary.WILDCARD_VALUE){ - Spell spell = Spell.get(itemstack.getItemDamage()); + Spell spell = Spell.byMetadata(itemstack.getItemDamage()); - boolean discovered = true; - if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null - && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ - discovered = false; - } + boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(spell, itemstack); // Element colour is not given for undiscovered spells tooltip.add(discovered ? "\u00A77" + spell.getDisplayNameWithFormatting() - : "#\u00A79" + SpellGlyphData.getGlyphName(spell, player.world)); - tooltip.add(spell.tier.getDisplayNameWithFormatting()); + : "#\u00A79" + SpellGlyphData.getGlyphName(spell, world)); + + tooltip.add(spell.getTier().getDisplayNameWithFormatting()); } /* Removed to streamline the tooltip a bit. Information is now within the book. if(spell.isContinuous){ * tooltip.add("\u00A79Mana Cost: " + spell.cost + " per second"); }else{ tooltip.add("\u00A79Mana Cost: " + @@ -81,7 +72,7 @@ public class ItemSpellBook extends Item { @Override @SideOnly(Side.CLIENT) - public FontRenderer getFontRenderer(ItemStack stack){ + public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){ return Wizardry.proxy.getFontRenderer(stack); } diff --git a/src/main/java/electroblob/wizardry/item/ItemWand.java b/src/main/java/electroblob/wizardry/item/ItemWand.java index 0d146a49..1f663ff7 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWand.java +++ b/src/main/java/electroblob/wizardry/item/ItemWand.java @@ -1,65 +1,80 @@ package electroblob.wizardry.item; -import java.util.List; - -import electroblob.wizardry.SpellGlyphData; -import electroblob.wizardry.WizardData; +import com.google.common.collect.Multimap; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.ISummonedCreature; import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent.Source; import electroblob.wizardry.packet.PacketCastSpell; import electroblob.wizardry.packet.WizardryPacketHandler; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.registry.*; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.EntityPlayerSP; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.util.ITooltipFlag; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.inventory.Slot; import net.minecraft.item.EnumAction; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.List; +import java.util.Random; + /** - * This class is (literally) where the magic happens! All wand types are single instances of this class. There's a lot - * of quite hard-to-read code in here, but unfortunately there's not much I can do about that. For this reason, I have - * written the {@link WandHelper} class. I strongly recommend you use it for interacting with wand items wherever - * possible. - *

    - * It's unikely that anything in this class will be of much use externally, but should you wish to use it for whatever - * reason (perhaps if you extend it), it works as follows: - *

    - * - onItemRightClick is where non-continuous spells are cast, and it sets the item in use for continuous spells
    - * - onUsingTick does the casting for continuous spells
    - * - onUpdate deals with the cooldowns for the spells - * + * This class is (literally) where the magic happens! All of wizardry's wand items are instances of this class. As of + * wizardry 4.2, it is no longer necessary to extend {@code ItemWand} thanks to {@link ISpellCastingItem}, though + * extending {@code ItemWand} may still be more appropriate for items using the same casting implementation. + *

    + * This class handles spell casting as follows: + *

    + * - {@code onItemRightClick} is where non-continuous spells are cast, and it sets the item in use for continuous spells
    + * - {@code onUsingTick} does the casting for continuous spells
    + * - {@code onUpdate} deals with the cooldowns for the spells
    + *
    + * See {@link ISpellCastingItem} for more detail on the {@code canCast(...)} and {@code cast(...)} methods.
    + * See {@link WandHelper} for everything related to wand NBT. + * * @since Wizardry 1.0 */ -public class ItemWand extends Item { +@Mod.EventBusSubscriber +public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, IManaStoringItem { + + /** The number of spell slots a wand has with no attunement upgrades applied. */ + public static final int BASE_SPELL_SLOTS = 5; + + /** The number of ticks between each time a continuous spell is added to the player's recently-cast spells. */ + private static final int CONTINUOUS_TRACKING_INTERVAL = 20; + /** The increase in progression for casting spells of the matching element. */ + private static final float ELEMENTAL_PROGRESSION_MODIFIER = 1.2f; + /** The fraction of progression lost when all recently-cast spells are the same as the one being cast. */ + private static final float MAX_PROGRESSION_REDUCTION = 0.75f; public Tier tier; public Element element; @@ -67,12 +82,63 @@ public class ItemWand extends Item { public ItemWand(Tier tier, Element element){ super(); setMaxStackSize(1); - if(element == null || tier == Tier.BASIC){ - setCreativeTab(WizardryTabs.WIZARDRY); - } + setCreativeTab(WizardryTabs.GEAR); this.tier = tier; this.element = element; setMaxDamage(this.tier.maxCharge); + WizardryRecipes.addToManaFlaskCharging(this); + } + + @Override + public Spell getCurrentSpell(ItemStack stack){ + return WandHelper.getCurrentSpell(stack); + } + + @Override + public Spell[] getSpells(ItemStack stack){ + return WandHelper.getSpells(stack); + } + + @Override + public void selectNextSpell(ItemStack stack){ + WandHelper.selectNextSpell(stack); + } + + @Override + public void selectPreviousSpell(ItemStack stack){ + WandHelper.selectPreviousSpell(stack); + } + + @Override + public boolean showSpellHUD(EntityPlayer player, ItemStack stack){ + return true; + } + + @Override + public boolean showTooltip(ItemStack stack){ + return true; + } + + /** Does nothing, use {@link ItemWand#setMana(ItemStack, int)} to modify wand mana. */ + @Override + public void setDamage(ItemStack stack, int damage){ + // Overridden to do nothing to stop repair things from 'repairing' the mana in a wand + } + + @Override + public void setMana(ItemStack stack, int mana){ + // Using super (which can only be done from in here) bypasses the above override + super.setDamage(stack, getManaCapacity(stack) - mana); + } + + @Override + public int getMana(ItemStack stack){ + return getManaCapacity(stack) - getDamage(stack); + } + + @Override + public int getManaCapacity(ItemStack stack){ + return this.getMaxDamage(stack); } @Override @@ -83,40 +149,111 @@ public class ItemWand extends Item { @Override @SideOnly(Side.CLIENT) - public FontRenderer getFontRenderer(ItemStack stack){ + public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){ return Wizardry.proxy.getFontRenderer(stack); } + @Override + public boolean isEnchantable(ItemStack stack){ + return false; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book){ + return false; + } + + @Override + public boolean hasEffect(ItemStack stack){ + return !Wizardry.settings.legacyWandLevelling && this.tier.level < Tier.MASTER.level + && WandHelper.getProgression(stack) >= Tier.values()[tier.ordinal() + 1].progression; + } + // Max damage is modifiable with upgrades. @Override - public int getMaxDamage(ItemStack itemstack){ + public int getMaxDamage(ItemStack stack){ // + 0.5f corrects small float errors rounding down - return (int)(super.getMaxDamage(itemstack) * (1.0f + Constants.STORAGE_INCREASE_PER_LEVEL - * WandHelper.getUpgradeLevel(itemstack, WizardryItems.storage_upgrade)) + 0.5f); + return (int)(super.getMaxDamage(stack) * (1.0f + Constants.STORAGE_INCREASE_PER_LEVEL + * WandHelper.getUpgradeLevel(stack, WizardryItems.storage_upgrade)) + 0.5f); } @Override - public void onUpdate(ItemStack itemstack, World world, Entity entity, int slot, boolean isHeld){ + public void onCreated(ItemStack stack, World worldIn, EntityPlayer playerIn){ + setMana(stack, 0); // Wands are empty when first crafted + } - WandHelper.decrementCooldowns(itemstack); + @Override + public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isHeld){ + + WandHelper.decrementCooldowns(stack); // Decrements wand damage (increases mana) every 1.5 seconds if it has a condenser upgrade - if(!world.isRemote && itemstack.isItemDamaged() - && world.getWorldTime() % Constants.CONDENSER_TICK_INTERVAL == 0){ + if(!world.isRemote && !this.isManaFull(stack) && world.getTotalWorldTime() % Constants.CONDENSER_TICK_INTERVAL == 0){ // If the upgrade level is 0, this does nothing anyway. - itemstack.setItemDamage( - itemstack.getItemDamage() - WandHelper.getUpgradeLevel(itemstack, WizardryItems.condenser_upgrade)); - } - - if(entity instanceof EntityPlayer && this.element != null && this.element != Element.MAGIC){ - // As it stands, this will trigger every tick. Not ideal, but I can't find a way to detect if a player - // has a certain achievement. - // TODO: check if this is somehow triggerable via JSON conditions. - WizardryAdvancementTriggers.element_master.triggerFor((EntityPlayer)entity); + this.rechargeMana(stack, WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade)); } } @Override + public Multimap getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){ + + Multimap multimap = super.getAttributeModifiers(slot, stack); + + if(slot == EntityEquipmentSlot.MAINHAND){ + int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade); + // This check doesn't affect the damage output, but it does stop a blank line from appearing in the tooltip. + if(level > 0 && !this.isManaEmpty(stack)){ + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), + new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Melee upgrade modifier", 2 * level, 0)); + multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Melee upgrade modifier", -2.4000000953674316D, 0)); + } + } + + return multimap; + } + + @Override + public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase wielder){ + + int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade); + int mana = this.getMana(stack); + + if(level > 0 && mana > 0) this.consumeMana(stack, level * 4, wielder); + + return true; + } + + @Override + public boolean canDestroyBlockInCreative(World world, BlockPos pos, ItemStack stack, EntityPlayer player){ + return WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade) == 0; + } + + // A proper hook was introduced for this in Forge build 14.23.5.2805 - Hallelujah, finally! + // The discussion about this was quite interesting, see the following: + // https://github.com/TeamTwilight/twilightforest/blob/1.12.x/src/main/java/twilightforest/item/ItemTFScepterLifeDrain.java + // https://github.com/MinecraftForge/MinecraftForge/pull/4834 + // Among the things mentioned were that it can be 'fixed' by doing the exact same hacks that I did, and that + // returning a result of PASS rather than SUCCESS from onItemRightClick also solves the problem (not sure why + // though, and again it's not a perfect solution) + // Edit: It seems that the hacky fix in previous versions actually introduced a wand duplication bug... oops + + @Override + public boolean canContinueUsing(ItemStack oldStack, ItemStack newStack){ + // Ignore durability changes + if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return true; + return super.canContinueUsing(oldStack, newStack); + } + + @Override + public boolean shouldCauseBlockBreakReset(ItemStack oldStack, ItemStack newStack){ + // Ignore durability changes + if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return false; + return super.shouldCauseBlockBreakReset(oldStack, newStack); + } + + @Override + // Only called client-side + // This method is always called on the item in oldStack, meaning that oldStack.getItem() == this public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){ // This method does some VERY strange things! Despite its name, it also seems to affect the updating of NBT... @@ -144,28 +281,40 @@ public class ItemWand extends Item { @SideOnly(Side.CLIENT) @Override - public void addInformation(ItemStack itemstack, World world, List text, ITooltipFlag advanced){ - EntityPlayerSP player = Minecraft.getMinecraft().player; + public void addInformation(ItemStack stack, World world, List text, net.minecraft.client.util.ITooltipFlag advanced){ + + EntityPlayer player = net.minecraft.client.Minecraft.getMinecraft().player; if (player == null) { return; } // +0.5f is necessary due to the error in the way floats are calculated. if(element != null) text.add("\u00A78" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.buff", - (int)((tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER * 100 + 0.5f) + "%", + (int)((tier.level + 1) * Constants.POTENCY_INCREASE_PER_TIER * 100 + 0.5f) + "%", element.getDisplayName())); - Spell spell = WandHelper.getCurrentSpell(itemstack); + Spell spell = WandHelper.getCurrentSpell(stack); boolean discovered = true; - if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null + if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ discovered = false; } text.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.spell", discovered ? "\u00A77" + spell.getDisplayNameWithFormatting() - : "#\u00A79" + SpellGlyphData.getGlyphName(spell, player.world))); + : "#\u00A79" + SpellGlyphData.getGlyphName(spell, player.world))); - text.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.mana", - (this.getMaxDamage(itemstack) - this.getDamage(itemstack)), this.getMaxDamage(itemstack))); + if(advanced.isAdvanced()){ + // Advanced tooltips for debugging + text.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.mana", + this.getMana(stack), this.getManaCapacity(stack))); + + text.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.progression", + WandHelper.getProgression(stack), this.tier.level < Tier.MASTER.level ? Tier.values()[tier.ordinal() + 1].progression : 0)); + +// }else{ +// +// ChargeStatus status = ChargeStatus.getChargeStatus(stack); +// text.add(status.getFormattingCode() + status.getDisplayName()); + } } @Override @@ -183,89 +332,29 @@ public class ItemWand extends Item { ItemStack stack = player.getHeldItem(hand); // Alternate right-click function; overrides spell casting. - if(this.selectMinionTarget(player, world)) return new ActionResult(EnumActionResult.SUCCESS, stack); + if(this.selectMinionTarget(player, world)) return new ActionResult<>(EnumActionResult.SUCCESS, stack); Spell spell = WandHelper.getCurrentSpell(stack); - SpellModifiers modifiers = this.calculateModifiers(stack, spell); - - // If anything stops the spell working at this point, nothing else happens. - if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(player, spell, modifiers, Source.WAND))){ - return new ActionResult(EnumActionResult.FAIL, stack); - } - - // This is here to start the inUse thing, otherwise the onUsingTick method will not fire. - if(spell.isContinuous && !player.isHandActive()){ - player.setActiveHand(hand); - // Probably ought to be here. (Does it succeed though?) - return new ActionResult(EnumActionResult.SUCCESS, stack); - } - - // Conditions for the spell to be attempted. The tier check is a failsafe; it should never be false unless the - // NBT is modified directly. - if(!spell.isContinuous && spell.tier.level <= this.tier.level - // Checks that the wand has enough mana to cast the spell - && spell.cost <= (stack.getMaxDamage() - stack.getItemDamage()) - // Checks that the spell is not in cooldown or that the player is in creative mode - && (WandHelper.getCurrentCooldown(stack) == 0 || player.capabilities.isCreativeMode)){ - - // If the spell does not require a packet, the code is run in the old client-inconsistent way, since this - // means that swingItem() doesn't need packets in order to work, improving performance. - if(!world.isRemote){ - - if(spell.cast(world, player, hand, 0, modifiers)){ - - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND)); - - // = Packets = - if(spell.doesSpellRequirePacket()){ - // Sends a packet to all players in dimension to tell them to spawn particles. - // Only sent if the spell succeeded, because if the spell failed, you wouldn't - // need to spawn any particles! - IMessage msg = new PacketCastSpell.Message(player.getEntityId(), hand, spell.id(), modifiers); - WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); - } + SpellModifiers modifiers = this.calculateModifiers(stack, player, spell); + if(canCast(stack, spell, player, hand, 0, modifiers)){ + // Now we can cast continuous spells with scrolls! + if(spell.isContinuous){ + if(!player.isHandActive()){ player.setActiveHand(hand); - - // = Cooldown = - // Spells only have a cooldown in survival - if(!player.capabilities.isCreativeMode){ - - float cooldownMultiplier = 1.0f - - WandHelper.getUpgradeLevel(stack, WizardryItems.cooldown_upgrade) - * Constants.COOLDOWN_REDUCTION_PER_LEVEL; - - if(player.isPotionActive(WizardryPotions.font_of_mana)){ - // Dividing by this rather than setting it takes upgrades and font of mana into account - // simultaneously - cooldownMultiplier /= 2 - + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier(); - } - - WandHelper.setCurrentCooldown(stack, (int)(spell.cooldown * cooldownMultiplier)); - } - - // = Mana cost = - // The spell costs 20% less for every armour piece of the matching element. - int armourPieces = getMatchingArmourCount(player, spell); - - stack.damageItem((int)(spell.cost * (1.0f - armourPieces * Constants.COST_REDUCTION_PER_ARMOUR)), - player); - - return new ActionResult(EnumActionResult.SUCCESS, stack); + // Store the modifiers for use each tick + if(WizardData.get(player) != null) WizardData.get(player).itemCastingModifiers = modifiers; + // Return the player's held item so spells can change it if they wish (e.g. possession) + return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand)); } - - }else if(!spell.doesSpellRequirePacket()){ - // Client-inconsistent spell casting. This code only runs client-side. - if(spell.cast(world, player, hand, 0, modifiers)){ - // This is all that needs to happen, because everything above works fine on just the server side. - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND)); - return new ActionResult(EnumActionResult.SUCCESS, stack); + }else{ + if(cast(stack, spell, player, hand, 0, modifiers)){ + return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand)); } } } - return new ActionResult(EnumActionResult.FAIL, stack); + return new ActionResult<>(EnumActionResult.FAIL, stack); } // For continuous spells. The count argument actually decrements by 1 each tick. @@ -277,72 +366,155 @@ public class ItemWand extends Item { EntityPlayer player = (EntityPlayer)user; Spell spell = WandHelper.getCurrentSpell(stack); - SpellModifiers modifiers = this.calculateModifiers(stack, spell); + + SpellModifiers modifiers; + + if(WizardData.get(player) != null){ + modifiers = WizardData.get(player).itemCastingModifiers; + }else{ + modifiers = this.calculateModifiers(stack, (EntityPlayer)user, spell); // Fallback to the old way, should never be used + } + int castingTick = stack.getMaxItemUseDuration() - count; - if(MinecraftForge.EVENT_BUS - .post(new SpellCastEvent.Tick(player, spell, modifiers, Source.WAND, castingTick))) - return; - // Continuous spells (these must check if they can be cast each tick since the mana changes) - if(spell.isContinuous && spell.tier.level <= this.tier.level - && spell.cost / 5 <= (stack.getMaxDamage() - stack.getItemDamage())){ + // Don't call canCast when castingTick == 0 because we already did it in onItemRightClick + if(spell.isContinuous && (castingTick == 0 || canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers))){ + cast(stack, spell, player, player.getActiveHand(), castingTick, modifiers); + }else{ + // Stops the casting if it was interrupted, either by events or because the wand ran out of mana + player.stopActiveHand(); + } + } + } - if(spell.cast(player.world, player, player.getActiveHand(), castingTick, modifiers)){ + @Override + public boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){ - if(castingTick == 0) - MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND)); + // Spells can only be cast if the casting events aren't cancelled... + if(castingTick == 0){ + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.WAND, spell, caster, modifiers))) return false; + }else{ + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.WAND, spell, caster, modifiers, castingTick))) return false; + } - // = Mana cost = - // Divides the mana cost over a second appropriately; since damage is an integer it cannot - // just be divided by 20. - // Now does five times per second regardless of the spell cost, but each time it does 1/5 of the - // cost per second. - int tickNumber = (count % 20) + 1; - // Tests if the tick counter is a multiple of 4 plus 1, i.e. is true when tickNumber = 1, 5, 9, 13 - // or 17. - // Made a slight adjustment since the counter starts on 1 and not 4. - if(tickNumber % 4 == 1){ + int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST)); - int armourPieces = getMatchingArmourCount(player, spell); + // As of wizardry 4.2 mana cost is only divided over two intervals each second + if(spell.isContinuous) cost = getDistributedCost(cost, castingTick); - switch(armourPieces){ + // ...and the wand has enough mana to cast the spell... + return cost <= this.getMana(stack) // This comes first because it changes over time + // ...and the wand is the same tier as the spell or higher... + && spell.getTier().level <= this.tier.level + // ...and either the spell is not in cooldown or the player is in creative mode + && (WandHelper.getCurrentCooldown(stack) == 0 || caster.isCreative()); + } - case 0: - stack.damageItem(spell.cost / 5, player); - break; + @Override + public boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){ - case 1: - if(tickNumber != 17) stack.damageItem(spell.cost / 5, player); - break; + World world = caster.world; - case 2: - if(tickNumber != 9 && tickNumber != 17) stack.damageItem(spell.cost / 5, player); - break; + if(world.isRemote && !spell.isContinuous && spell.requiresPacket()) return false; - case 3: - if(tickNumber != 5 && tickNumber != 13 && tickNumber != 17) - stack.damageItem(spell.cost / 5, player); - break; + if(spell.cast(world, caster, hand, castingTick, modifiers)){ - case 4: - if(tickNumber == 1) stack.damageItem(spell.cost / 5, player); - break; + if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.WAND, spell, caster, modifiers)); - } + if(!world.isRemote){ + + // Continuous spells never require packets so don't rely on the requiresPacket method to specify it + if(!spell.isContinuous && spell.requiresPacket()){ + // Sends a packet to all players in dimension to tell them to spawn particles. + IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), hand, spell, modifiers); + WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); + } + + caster.setActiveHand(hand); + + // Mana cost + int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST)); + // As of wizardry 4.2 mana cost is only divided over two intervals each second + if(spell.isContinuous) cost = getDistributedCost(cost, castingTick); + + if(cost > 0) this.consumeMana(stack, cost, caster); + + } + + // Cooldown + if(!spell.isContinuous && !caster.isCreative()){ // Spells only have a cooldown in survival + WandHelper.setCurrentCooldown(stack, (int)(spell.getCooldown() * modifiers.get(WizardryItems.cooldown_upgrade))); + } + + // Progression + if(this.tier.level < Tier.MASTER.level && castingTick % CONTINUOUS_TRACKING_INTERVAL == 0){ + + // We don't care about cost modifiers here, otherwise players would be penalised for wearing robes! + int progression = (int)(spell.getCost() * modifiers.get(SpellModifiers.PROGRESSION)); + WandHelper.addProgression(stack, progression); + + if(!Wizardry.settings.legacyWandLevelling){ // Don't display the message if legacy wand levelling is enabled + // If the wand just gained enough progression to be upgraded... + Tier nextTier = Tier.values()[tier.ordinal() + 1]; + int excess = WandHelper.getProgression(stack) - nextTier.progression; + if(excess >= 0 && excess < progression){ + // ...display a message above the player's hotbar + caster.playSound(WizardrySounds.ITEM_WAND_LEVELUP, 1.25f, 1); + if(!world.isRemote) + caster.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":wand.levelup", + this.getItemStackDisplayName(stack), nextTier.getNameForTranslationFormatted())); } } + + WizardData.get(caster).trackRecentSpell(spell); + } + + return true; + } + + return false; + } + + @Override + public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase user, int timeLeft){ + + if(user instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)user; + + Spell spell = WandHelper.getCurrentSpell(stack); + + SpellModifiers modifiers; + + if(WizardData.get(player) != null){ + modifiers = WizardData.get(player).itemCastingModifiers; + }else{ + modifiers = this.calculateModifiers(stack, (EntityPlayer)user, spell); // Fallback to the old way, should never be used + } + + int castingTick = stack.getMaxItemUseDuration() - timeLeft; // Might as well include this + + int cost = getDistributedCost((int)(spell.getCost() * modifiers.get(SpellModifiers.COST)), castingTick); + + // Still need to check there's enough mana or the spell will finish twice, since running out of mana is + // handled separately. + if(spell.isContinuous && spell.getTier().level <= this.tier.level && cost <= this.getMana(stack)){ + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Finish(Source.WAND, spell, player, modifiers, castingTick)); + spell.finishCasting(world, player, Double.NaN, Double.NaN, Double.NaN, null, castingTick, modifiers); + + if(!player.isCreative()){ // Spells only have a cooldown in survival + WandHelper.setCurrentCooldown(stack, (int)(spell.getCooldown() * modifiers.get(WizardryItems.cooldown_upgrade))); + } } } } @Override - public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer player, EntityLivingBase entity, - EnumHand hand){ + public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer player, EntityLivingBase entity, EnumHand hand){ if(player.isSneaking() && entity instanceof EntityPlayer && WizardData.get(player) != null){ - // This is one of those "the method doing the work looks as if it's just returning a value" situations. - // ... I know, right?! I feel very programmer-y. But it's not too confusing here, and it looks neat. String string = WizardData.get(player).toggleAlly((EntityPlayer)entity) ? "item." + Wizardry.MODID + ":wand.addally" : "item." + Wizardry.MODID + ":wand.removeally"; if(!player.world.isRemote) player.sendMessage(new TextComponentTranslation(string, entity.getName())); @@ -352,8 +524,27 @@ public class ItemWand extends Item { return false; } + /** Distributes the given cost (which should be the per-second cost of a continuous spell) over a second and + * returns the appropriate cost to be applied for the given tick. Currently the cost is distributed over 2 + * intervals per second, meaning the returned value is 0 unless {@code castingTick} is a multiple of 10.*/ + protected static int getDistributedCost(int cost, int castingTick){ + + int partialCost; + + if(castingTick % 20 == 0){ // Whole number of seconds has elapsed + partialCost = cost / 2 + cost % 2; // Make sure cost adds up to the correct value by adding the remainder here + }else if(castingTick % 10 == 0){ // Something-and-a-half seconds has elapsed + partialCost = cost/2; + }else{ // Some other number of ticks has elapsed + partialCost = 0; // Wands aren't damaged within half-seconds + } + + return partialCost; + } + /** Returns a SpellModifiers object with the appropriate modifiers applied for the given ItemStack and Spell. */ - protected SpellModifiers calculateModifiers(ItemStack stack, Spell spell){ + // This is now public because artefacts use it + public SpellModifiers calculateModifiers(ItemStack stack, EntityPlayer player, Spell spell){ SpellModifiers modifiers = new SpellModifiers(); @@ -370,38 +561,26 @@ public class ItemWand extends Item { if(level > 0) modifiers.set(WizardryItems.blast_upgrade, 1.0f + level * Constants.BLAST_RADIUS_INCREASE_PER_LEVEL, true); - // I would have liked to have made potion effects increase in strength according to the damage multiplier, - // but the amplifier level is too discrete to make this work. For example, wither 3 for 10 seconds will kill a - // normal mob on full 20 health, but wither 2 for the same duration only deals about 6 hearts of damage in - // total. - if(this.element == spell.element){ - modifiers.set(SpellModifiers.DAMAGE, 1.0f + (this.tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER, - true); + level = WandHelper.getUpgradeLevel(stack, WizardryItems.cooldown_upgrade); + if(level > 0) + modifiers.set(WizardryItems.cooldown_upgrade, 1.0f - level * Constants.COOLDOWN_REDUCTION_PER_LEVEL, true); + + float progressionModifier = 1.0f - ((float)WizardData.get(player).countRecentCasts(spell) / WizardData.MAX_RECENT_SPELLS) + * MAX_PROGRESSION_REDUCTION; + + if(this.element == spell.getElement()){ + modifiers.set(SpellModifiers.POTENCY, 1.0f + (this.tier.level + 1) * Constants.POTENCY_INCREASE_PER_TIER, true); + progressionModifier *= ELEMENTAL_PROGRESSION_MODIFIER; } + modifiers.set(SpellModifiers.PROGRESSION, progressionModifier, false); + return modifiers; } - /** Counts the number of armour pieces the given player is wearing that match the given spell's element. */ - private int getMatchingArmourCount(EntityPlayer player, Spell spell){ - - int armourPieces = 0; - - for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ - - ItemStack armour = player.getItemStackFromSlot(slot); - - if(armour != null && armour.getItem() instanceof ItemWizardArmour - && ((ItemWizardArmour)armour.getItem()).element == spell.element) - armourPieces++; - } - - return armourPieces; - } - private boolean selectMinionTarget(EntityPlayer player, World world){ - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, player, 16); + RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(world, player, 16, false); if(rayTrace != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ @@ -424,4 +603,192 @@ public class ItemWand extends Item { return false; } + + // Workbench stuff + + @Override + public int getSpellSlotCount(ItemStack stack){ + return BASE_SPELL_SLOTS + WandHelper.getUpgradeLevel(stack, WizardryItems.attunement_upgrade); + } + + @Override + public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ + + boolean changed = false; + + // Upgrades wand if necessary. Damage is copied, preserving remaining durability, + // and also the entire NBT tag compound. + if(upgrade.getStack().getItem() == WizardryItems.arcane_tome){ + + Tier tier = Tier.values()[upgrade.getStack().getItemDamage()]; + + // Checks the wand upgrade is for the tier above the wand's tier, and that either the wand has enough + // progression or the player is in creative mode. + // It is guaranteed that: this == centre.getStack().getItem() + if((player.isCreative() || Wizardry.settings.legacyWandLevelling + || WandHelper.getProgression(centre.getStack()) >= tier.progression) + && tier.ordinal() - 1 == this.tier.ordinal()){ + + // We're not carrying over excess progression for now, but if we do want to, this is how +// if(!Wizardry.settings.legacyWandLevelling){ +// // Easy way to carry excess progression over to the new stack +// WandHelper.setProgression(centre.getStack(), WandHelper.getProgression(centre.getStack()) - tier.progression); +// } + + ItemStack newWand = new ItemStack(WizardryItems.getWand(tier, this.element)); + newWand.setTagCompound(centre.getStack().getTagCompound()); + // This needs to be done after copying the tag compound so the mana capacity for the new wand + // takes storage upgrades into account + // Note the usage of the new wand item and not 'this' to ensure the correct capacity is used + ((IManaStoringItem)newWand.getItem()).setMana(newWand, this.getMana(centre.getStack())); + + centre.putStack(newWand); + upgrade.decrStackSize(1); + + changed = true; + } + + }else if(WandHelper.isWandUpgrade(upgrade.getStack().getItem())){ + + // Special upgrades + Item specialUpgrade = upgrade.getStack().getItem(); + + if(WandHelper.getTotalUpgrades(centre.getStack()) < this.tier.upgradeLimit + && WandHelper.getUpgradeLevel(centre.getStack(), specialUpgrade) < Constants.UPGRADE_STACK_LIMIT){ + + // Used to preserve existing mana when upgrading storage rather than creating free mana. + int prevMana = this.getMana(centre.getStack()); + + WandHelper.applyUpgrade(centre.getStack(), specialUpgrade); + + // Special behaviours for specific upgrades + if(specialUpgrade == WizardryItems.storage_upgrade){ + + this.setMana(centre.getStack(), prevMana); + + }else if(specialUpgrade == WizardryItems.attunement_upgrade){ + + int newSlotCount = BASE_SPELL_SLOTS + WandHelper.getUpgradeLevel(centre.getStack(), + WizardryItems.attunement_upgrade); + + Spell[] spells = WandHelper.getSpells(centre.getStack()); + Spell[] newSpells = new Spell[newSlotCount]; + + for(int i = 0; i < newSpells.length; i++){ + newSpells[i] = i < spells.length && spells[i] != null ? spells[i] : Spells.none; + } + + WandHelper.setSpells(centre.getStack(), newSpells); + + int[] cooldowns = WandHelper.getCooldowns(centre.getStack()); + int[] newCooldowns = new int[newSlotCount]; + + if(cooldowns.length > 0){ + System.arraycopy(cooldowns, 0, newCooldowns, 0, cooldowns.length); + } + + WandHelper.setCooldowns(centre.getStack(), newCooldowns); + } + + upgrade.decrStackSize(1); + WizardryAdvancementTriggers.special_upgrade.triggerFor(player); + + if(WandHelper.getTotalUpgrades(centre.getStack()) == Tier.MASTER.upgradeLimit){ + WizardryAdvancementTriggers.max_out_wand.triggerFor(player); + } + + changed = true; + } + } + + // Reads NBT spell metadata array to variable, edits this, then writes it back to NBT. + // Original spells are preserved; if a slot is left empty the existing spell binding will remain. + // Accounts for spells which cannot be applied because they are above the wand's tier; these spells + // will not bind but the existing spell in that slot will remain and other applicable spells will + // be bound as normal, along with any upgrades and crystals. + Spell[] spells = WandHelper.getSpells(centre.getStack()); + + if(spells.length <= 0){ + // Base value here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades + spells = new Spell[BASE_SPELL_SLOTS]; + } + + for(int i = 0; i < spells.length; i++){ + if(spellBooks[i].getStack() != ItemStack.EMPTY){ + + Spell spell = Spell.byMetadata(spellBooks[i].getStack().getItemDamage()); + // If the wand is powerful enough for the spell, it's not already bound to that slot and it's enabled for wands + if(!(spell.getTier().level > this.tier.level) && spells[i] != spell && spell.isEnabled(SpellProperties.Context.WANDS)){ + spells[i] = spell; + changed = true; + } + } + } + + WandHelper.setSpells(centre.getStack(), spells); + + // Charges wand by appropriate amount + if(crystals.getStack() != ItemStack.EMPTY && !this.isManaFull(centre.getStack())){ + + int chargeDepleted = this.getManaCapacity(centre.getStack()) - this.getMana(centre.getStack()); + + int manaPerItem = Constants.MANA_PER_CRYSTAL; + if(crystals.getStack().getItem() == WizardryItems.crystal_shard) manaPerItem = Constants.MANA_PER_SHARD; + if(crystals.getStack().getItem() == WizardryItems.grand_crystal) manaPerItem = Constants.GRAND_CRYSTAL_MANA; + + if(crystals.getStack().getCount() * manaPerItem < chargeDepleted){ + // If there aren't enough crystals to fully charge the wand + this.rechargeMana(centre.getStack(), crystals.getStack().getCount() * manaPerItem); + crystals.decrStackSize(crystals.getStack().getCount()); + + }else{ + // If there are excess crystals (or just enough) + this.setMana(centre.getStack(), this.getManaCapacity(centre.getStack())); + crystals.decrStackSize((int)Math.ceil(((double)chargeDepleted) / manaPerItem)); + } + + changed = true; + } + + return changed; + } + + // hitEntity is only called server-side, so we'll have to use events + @SubscribeEvent + public static void onAttackEntityEvent(AttackEntityEvent event){ + + EntityPlayer player = event.getEntityPlayer(); + ItemStack stack = player.getHeldItemMainhand(); // Can't melee with offhand items + + if(stack.getItem() instanceof IManaStoringItem){ + + // Nobody said it had to be a wand, as long as it's got a melee upgrade it counts + int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade); + int mana = ((IManaStoringItem)stack.getItem()).getMana(stack); + + if(level > 0 && mana > 0){ + + Random random = player.world.rand; + + player.world.playSound(player.posX, player.posY, player.posZ, WizardrySounds.ITEM_WAND_MELEE, SoundCategory.PLAYERS, 0.75f, 1, false); + + if(player.world.isRemote){ + + Vec3d origin = new Vec3d(player.posX, player.getEntityBoundingBox().minY + player.getEyeHeight(), player.posZ); + Vec3d hit = origin.add(player.getLookVec().scale(player.getDistance(event.getTarget()))); + // Generate two perpendicular vectors in the plane perpendicular to the look vec + Vec3d vec1 = player.getLookVec().rotatePitch(90); + Vec3d vec2 = player.getLookVec().crossProduct(vec1); + + for(int i = 0; i < 15; i++){ + ParticleBuilder.create(Type.SPARKLE).pos(hit) + .vel(vec1.scale(random.nextFloat() * 0.3f - 0.15f).add(vec2.scale(random.nextFloat() * 0.3f - 0.15f))) + .clr(1f, 1f, 1f).fade(0.3f, 0.5f, 1) + .time(8 + random.nextInt(4)).spawn(player.world); + } + } + } + } + } + } diff --git a/src/main/java/electroblob/wizardry/item/ItemWandUpgrade.java b/src/main/java/electroblob/wizardry/item/ItemWandUpgrade.java new file mode 100644 index 00000000..c9766b10 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemWandUpgrade.java @@ -0,0 +1,32 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.List; + +public class ItemWandUpgrade extends Item { + + public ItemWandUpgrade(){ + super(); + this.setCreativeTab(WizardryTabs.WIZARDRY); + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return EnumRarity.UNCOMMON; + } + + @Override + @SideOnly(Side.CLIENT) + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, net.minecraft.client.util.ITooltipFlag flag) { + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc"); + } +} diff --git a/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java b/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java index 63484b97..1e7c2708 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java +++ b/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java @@ -1,42 +1,50 @@ package electroblob.wizardry.item; -import java.util.List; -import java.util.UUID; - import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; - +import com.google.common.collect.Streams; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Element; +import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryRecipes; import electroblob.wizardry.registry.WizardryTabs; -import electroblob.wizardry.spell.Petrify; -import net.minecraft.client.model.ModelBiped; -import net.minecraft.client.util.ITooltipFlag; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttributeInstance; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.inventory.Slot; import net.minecraft.item.EnumAction; import net.minecraft.item.ItemArmor; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumHandSide; import net.minecraft.world.World; -import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -@Mod.EventBusSubscriber -public class ItemWizardArmour extends ItemArmor { +import java.util.Arrays; +import java.util.List; +import java.util.UUID; - //VanillaCopy, ItemArmor has this set to private for some reason. +@Mod.EventBusSubscriber +public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IManaStoringItem { + + // VanillaCopy, ItemArmor has this set to private for some reason. public static final UUID[] ARMOR_MODIFIERS = new UUID[] {UUID.fromString("845DB27C-C624-495F-8C9F-6020A9A58B6B"), UUID.fromString("D8499B04-0E66-4726-AB29-64469D734E0D"), UUID.fromString("9F3D476D-C118-4544-8365-64846904B48E"), UUID.fromString("2AD3F246-FEE1-4E67-B886-69FD380BB150")}; - //Damage reduction values that used to be in WizardryItems.SILK [feet, legs, chest, head] + // Damage reduction values that used to be in WizardryItems.SILK [feet, legs, chest, head] private static int[] reductions = new int[]{2, 4, 5, 2}; public Element element; @@ -44,20 +52,53 @@ public class ItemWizardArmour extends ItemArmor { public ItemWizardArmour(ArmorMaterial material, int renderIndex, EntityEquipmentSlot armourType, Element element){ super(material, renderIndex, armourType); this.element = element; - setCreativeTab(WizardryTabs.WIZARDRY); + setCreativeTab(WizardryTabs.GEAR); + WizardryRecipes.addToManaFlaskCharging(this); + } + + /** Should only be used by vanilla's armour damage calculations; use {@link ItemWizardArmour#setMana(ItemStack, int)} + * to modify wand mana from elsewhere. */ + @Override + public void setDamage(ItemStack stack, int damage){ + // Overridden to stop repair things from 'repairing' the mana in wizard armour + // This being armour, it's much easier to let its damage increase normally, but block it from being decreased + if(stack.getItemDamage() < damage) super.setDamage(stack, damage); + } + + @Override + public void setMana(ItemStack stack, int mana){ + // Using super (which can only be done from in here) bypasses the above override + super.setDamage(stack, getManaCapacity(stack) - mana); + } + + @Override + public int getMana(ItemStack stack){ + return getManaCapacity(stack) - getDamage(stack); + } + + @Override + public int getManaCapacity(ItemStack stack){ + return this.getMaxDamage(stack); } @Override @SideOnly(Side.CLIENT) - public void addInformation(ItemStack stack, World world, List tooltip, ITooltipFlag advanced){ + public void addInformation(ItemStack stack, World world, List tooltip, net.minecraft.client.util.ITooltipFlag advanced){ - if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary")) tooltip - .add("\u00A7d" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.legendary")); - if(element != null) + if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary")) + tooltip.add("\u00A7d" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.legendary")); + + if(element != null){ tooltip.add("\u00A78" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.buff", (int)(Constants.COST_REDUCTION_PER_ARMOUR * 100) + "%", element.getDisplayName())); - tooltip.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.mana", - (this.getMaxDamage(stack) - this.getDamage(stack)), this.getMaxDamage(stack))); + } + + // tooltip.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.mana", +// (this.getMaxDamage(stack) - this.getDamage(stack)), this.getMaxDamage(stack))); + +// ChargeStatus status = ChargeStatus.getChargeStatus(stack); +// +// tooltip.add(status.getFormattingCode() + status.getDisplayName()); } @Override @@ -67,32 +108,23 @@ public class ItemWizardArmour extends ItemArmor { @Override @SideOnly(Side.CLIENT) - public boolean hasEffect(ItemStack stack){ - return stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary"); - } - - @Override - @SideOnly(Side.CLIENT) - public ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack, - EntityEquipmentSlot armourSlot, ModelBiped _default){ - - ModelBiped model = Wizardry.proxy.getWizardArmourModel(); + public net.minecraft.client.model.ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack, + EntityEquipmentSlot armourSlot, net.minecraft.client.model.ModelBiped _default){ // Legs use modelBiped - if(armourSlot == EntityEquipmentSlot.LEGS) return null; + if(armourSlot == EntityEquipmentSlot.LEGS && !entityLiving.isInvisible()) return null; + + net.minecraft.client.model.ModelBiped model = Wizardry.proxy.getWizardArmourModel(); if(model != null){ model.bipedHead.showModel = armourSlot == EntityEquipmentSlot.HEAD; model.bipedHeadwear.showModel = false; - model.bipedBody.showModel = armourSlot == EntityEquipmentSlot.CHEST - || armourSlot == EntityEquipmentSlot.LEGS; + model.bipedBody.showModel = armourSlot == EntityEquipmentSlot.CHEST; model.bipedRightArm.showModel = armourSlot == EntityEquipmentSlot.CHEST; model.bipedLeftArm.showModel = armourSlot == EntityEquipmentSlot.CHEST; - model.bipedRightLeg.showModel = armourSlot == EntityEquipmentSlot.LEGS - || armourSlot == EntityEquipmentSlot.FEET; - model.bipedLeftLeg.showModel = armourSlot == EntityEquipmentSlot.LEGS - || armourSlot == EntityEquipmentSlot.FEET; + model.bipedRightLeg.showModel = armourSlot == EntityEquipmentSlot.FEET; + model.bipedLeftLeg.showModel = armourSlot == EntityEquipmentSlot.FEET; model.isSneak = entityLiving.isSneaking(); model.isRiding = entityLiving.isRiding(); @@ -104,29 +136,29 @@ public class ItemWizardArmour extends ItemArmor { ItemStack itemstackL = leftHanded ? entityLiving.getHeldItemMainhand() : entityLiving.getHeldItemOffhand(); if(!itemstackR.isEmpty()){ - model.rightArmPose = ModelBiped.ArmPose.ITEM; + model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM; if(entityLiving.getItemInUseCount() > 0){ EnumAction enumaction = itemstackR.getItemUseAction(); if(enumaction == EnumAction.BLOCK){ - model.rightArmPose = ModelBiped.ArmPose.BLOCK; + model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK; }else if(enumaction == EnumAction.BOW){ - model.rightArmPose = ModelBiped.ArmPose.BOW_AND_ARROW; + model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW; } } } if(!itemstackL.isEmpty()){ - model.leftArmPose = ModelBiped.ArmPose.ITEM; + model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM; if(entityLiving.getItemInUseCount() > 0){ EnumAction enumaction1 = itemstackL.getItemUseAction(); if(enumaction1 == EnumAction.BLOCK){ - model.leftArmPose = ModelBiped.ArmPose.BLOCK; + model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK; }else if(enumaction1 == EnumAction.BOW){ - model.leftArmPose = ModelBiped.ArmPose.BOW_AND_ARROW; + model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW; } } } @@ -141,33 +173,33 @@ public class ItemWizardArmour extends ItemArmor { // Returns a completely transparent texture if the player is invisible. This is such an annoyingly easy // fix, considering how long I spent trying to do this before - a bit of lateral thinking was all it took. // Do note however that a texture pack could override this. - if(entity instanceof EntityLivingBase && ((EntityLivingBase)entity).isInvisible() - && !entity.getEntityData().getBoolean(Petrify.NBT_KEY)) - return "ebwizardry:textures/armour/invisible_armour.png"; +// if(entity instanceof EntityLivingBase && entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)) +// return "ebwizardry:textures/armour/invisible_armour.png"; - if(slot == EntityEquipmentSlot.LEGS) - return this.element == null ? "ebwizardry:textures/armour/wizard_armour_legs.png" - : "ebwizardry:textures/armour/wizard_armour_" + this.element.getUnlocalisedName() + "_legs.png"; + String s = "wizard_armour"; - return this.element == null ? "ebwizardry:textures/armour/wizard_armour.png" - : "ebwizardry:textures/armour/wizard_armour_" + this.element.getUnlocalisedName() + ".png"; + if(this.element != null) s = s + "_" + this.element.getName(); + if(slot == EntityEquipmentSlot.LEGS) s = s + "_legs"; + if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary")) s = "legendary_" + s; + + return "ebwizardry:textures/armour/" + s + ".png"; } @Override - public boolean getIsRepairable(ItemStack stack, ItemStack par2ItemStack){ + public boolean getIsRepairable(ItemStack stack, ItemStack material){ return false; } - /* - * Properly handles the defense value of the armor. This method is responisble for the tooltip on top of - * the armor value. It is also what handles armor toughness, but the wizard armor had a value of 0 for that. + /** + * Properly handles the defence value of the armour. This method is responsible for the tooltip on top of + * the armour value. It is also what handles armour toughness. */ @Override public Multimap getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){ Multimap map = HashMultimap.create(); - if(stack.getItemDamage() < stack.getMaxDamage() && this.armorType == slot){ + if(!this.isManaEmpty(stack) && this.armorType == slot){ int defense = reductions[slot.getIndex()]; float toughness = 0f; @@ -186,28 +218,118 @@ public class ItemWizardArmour extends ItemArmor { return map; } - // Fixes wizard armor breaking by disallowing setting damage above the max, since damageArmor is not always called. - // Since ISpecialArmor has been removed from this class, this may no longer be necessary, but keeping it won't hurt. + // Workbench stuff + @Override - public void setDamage(ItemStack stack, int damage) { - if(damage <= stack.getMaxDamage()) super.setDamage(stack, damage); - else super.setDamage(stack, stack.getMaxDamage()); + public boolean showTooltip(ItemStack stack){ return true; } + + @Override + public int getSpellSlotCount(ItemStack stack){ + return 0; // Doesn't have any spell slots! + } + + @Override + public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ + + boolean changed = false; + + // Applies legendary upgrade + if(upgrade.getStack().getItem() == WizardryItems.armour_upgrade){ + + if(!centre.getStack().hasTagCompound()){ + centre.getStack().setTagCompound(new NBTTagCompound()); + } + + if(!centre.getStack().getTagCompound().hasKey("legendary")){ + + centre.getStack().getTagCompound().setBoolean("legendary", true); + upgrade.decrStackSize(1); + WizardryAdvancementTriggers.legendary.triggerFor(player); + changed = true; + } + } + + // Charges armour by appropriate amount + if(crystals.getStack() != ItemStack.EMPTY && !this.isManaFull(centre.getStack())){ + + int chargeDepleted = this.getManaCapacity(centre.getStack()) - this.getMana(centre.getStack()); + + if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){ + // If there aren't enough crystals to fully charge the armour + this.rechargeMana(centre.getStack(), crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL); + crystals.decrStackSize(crystals.getStack().getCount()); + + }else{ + // If there are excess crystals (or just enough) + this.setMana(centre.getStack(), this.getManaCapacity(centre.getStack())); + crystals.decrStackSize((int)Math.ceil(((double)chargeDepleted) / Constants.MANA_PER_CRYSTAL)); + } + + changed = true; + } + + return changed; + } + + // Event Handlers + +// @SubscribeEvent +// public static void onLivingUpdateEvent(LivingUpdateEvent event){ +// +// if(event.getEntityLiving() instanceof EntityPlayer){ +// +// EntityPlayer player = (EntityPlayer)event.getEntityLiving(); +// +// for(ItemStack stack : player.getArmorInventoryList()){ +// if(!(stack.getItem() instanceof ItemWizardArmour)){ +// return; // If any of the armour slots doesn't contain wizard armour, don't trigger the achievement. +// } +// } +// // If it gets this far, then all slots must be wizard armour, so trigger the achievement. +// WizardryAdvancementTriggers.armour_set.triggerFor(player); +// } +// } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + // Armour cost reduction + if(event.getCaster() == null) return; + int armourPieces = getMatchingArmourCount(event.getCaster(), event.getSpell().getElement()); + float multiplier = 1f - armourPieces * Constants.COST_REDUCTION_PER_ARMOUR; + if(armourPieces == WizardryUtilities.ARMOUR_SLOTS.length) multiplier -= Constants.FULL_ARMOUR_SET_BONUS; + event.getModifiers().set(SpellModifiers.COST, event.getModifiers().get(SpellModifiers.COST) * multiplier, false); + } + + /** Counts the number of armour pieces the given entity is wearing that match the given element. */ + public static int getMatchingArmourCount(EntityLivingBase entity, Element element){ + return (int)Arrays.stream(WizardryUtilities.ARMOUR_SLOTS) + .map(s -> entity.getItemStackFromSlot(s).getItem()) + .filter(i -> i instanceof ItemWizardArmour && ((ItemWizardArmour)i).element == element) + .count(); } @SubscribeEvent - public static void onLivingUpdateEvent(LivingUpdateEvent event){ + public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ + // Undo the mob detection penalty for wearing armour when invisible + // Only bother doing this for players because the penalty only applies to them + if(event.getTarget() instanceof EntityPlayer && event.getEntityLiving() instanceof EntityLiving + && event.getEntityLiving().isInvisible()){ - if(event.getEntityLiving() instanceof EntityPlayer){ - - EntityPlayer player = (EntityPlayer)event.getEntityLiving(); + int armourPieces = (int)Streams.stream(event.getTarget().getArmorInventoryList()) + .filter(s -> !s.isEmpty() && !(s.getItem() instanceof ItemWizardArmour)) + .count(); - for(ItemStack stack : player.getArmorInventoryList()){ - if(!(stack.getItem() instanceof ItemWizardArmour)){ - return; // If any of the armour slots doesn't contain wizard armour, don't trigger the achievement. - } - } - // If it gets this far, then all slots must be wizard armour, so trigger the achievement. - WizardryAdvancementTriggers.armour_set.triggerFor(player); + if(armourPieces == 0) return; + + // Repeat the calculation from EntityAIFindNearestPlayer, but ignoring wizard armour + IAttributeInstance attribute = event.getEntityLiving().getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE); + double followRange = attribute == null ? 16 : attribute.getAttributeValue(); + if(event.getTarget().isSneaking()) followRange *= 0.8; + float f = armourPieces / ((EntityPlayer)event.getTarget()).inventory.armorInventory.size(); + if(f < 0.1F) f = 0.1F; + followRange *= (double)(0.7F * f); + // Don't need to worry about the isSuitableTarget check since it must already have been checked to get this far + if(event.getTarget().getDistance(event.getEntity()) > followRange) ((EntityLiving)event.getEntityLiving()).setAttackTarget(null); } } diff --git a/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java b/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java index 67a210c5..e2c6a39b 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java +++ b/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java @@ -1,13 +1,8 @@ package electroblob.wizardry.item; -import java.util.List; - -import javax.annotation.Nullable; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.WizardryGuiHandler; import electroblob.wizardry.registry.WizardryTabs; -import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -16,6 +11,9 @@ import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumHand; import net.minecraft.world.World; +import javax.annotation.Nullable; +import java.util.List; + public class ItemWizardHandbook extends Item { // Yep, I hardcoded my own name into the mod. Don't want people changing it now, do I? @@ -28,7 +26,7 @@ public class ItemWizardHandbook extends Item { } @Override - public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, ITooltipFlag flagIn) { + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, net.minecraft.client.util.ITooltipFlag flag) { tooltip.add( "\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_handbook.desc", AUTHOR)); } diff --git a/src/main/java/electroblob/wizardry/loot/RandomSpell.java b/src/main/java/electroblob/wizardry/loot/RandomSpell.java index 9fe703e6..7b9848c0 100644 --- a/src/main/java/electroblob/wizardry/loot/RandomSpell.java +++ b/src/main/java/electroblob/wizardry/loot/RandomSpell.java @@ -1,57 +1,51 @@ package electroblob.wizardry.loot; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import org.apache.commons.lang3.ArrayUtils; - -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSyntaxException; - +import com.google.gson.*; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ItemScroll; import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.SpellProperties; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.JsonUtils; import net.minecraft.util.ResourceLocation; import net.minecraft.world.storage.loot.LootContext; import net.minecraft.world.storage.loot.conditions.LootCondition; import net.minecraft.world.storage.loot.functions.LootFunction; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; /** * Loot function that allows spell books and scrolls to select a random spell based on the standard weighting. - * Automatically discounts continuous spells when the item in question is a scroll. Can be used as-is with no - * parameters, but several optional parameters are available for those wishing to customise further: - *

    + * Can be used as-is with no parameters, but several optional parameters are available for those wishing to customise further: + *

    * - spells: A list of spells to choose from. Defaults to all enabled spells.
    * - ignore_weighting: true to ignore the standard weighting and just pick a completely random spell. Defaults to * false.
    + * - undiscovered_bias: A number between 0 and 1 representing the bias towards undiscovered spells, with 0 + * being no bias and 1 meaning spells are guaranteed to be undiscovered.
    * - tiers: A list of tiers to choose from. Defaults to all tiers.
    * - elements: A list of elements to choose from. Defaults to all elements. - *

    + *

    * This class is effectively a loot table-friendly replacement for the standard weighting method in WizardryUtilities * which was the basis of all the old loot systems (not counting wizard trades, which were - and still are - completely * separate). - *

    + *

    * Since spells are stored as metadata, this could be done by having an entry for each tier and letting it pick a * random spell from that tier by setting the metadata to a random value in the range of values that correspond to that * tier. However, this creates a two-fold problem: firstly, not all spells are in tier order, and secondly, if spells * are added via addon mods the entries would have to be updated manually with the new numbers. - *

    + *

    * (This reasoning is similar to that for the enchant_randomly function in vanilla Minecraft, since you can specify NBT * data in loot tables, but using NBT in this way would be incredibly verbose and inflexible, hence the loot function.) - * - * @see WizardryUtilities#getStandardWeightedRandomSpellId(Random, boolean) + * * @author Electroblob * @since Wizardry 1.2 */ @@ -62,14 +56,16 @@ public class RandomSpell extends LootFunction { private final List spells; private final boolean ignoreWeighting; + private final float undiscoveredBias; private final List tiers; private final List elements; - protected RandomSpell(LootCondition[] conditions, List spells, boolean ignoreWeighting, List tiers, - List elements){ + protected RandomSpell(LootCondition[] conditions, List spells, boolean ignoreWeighting, + float undiscoveredBias, List tiers, List elements){ super(conditions); this.spells = spells; this.ignoreWeighting = ignoreWeighting; + this.undiscoveredBias = undiscoveredBias; this.tiers = tiers; this.elements = elements; } @@ -99,8 +95,8 @@ public class RandomSpell extends LootFunction { // Elements aren't weighted if(elements == null || elements.isEmpty()){ - // Element can only be MAGIC if tier is BASIC - if(tier == Tier.BASIC){ + // Element can only be MAGIC if tier is NOVICE + if(tier == Tier.NOVICE){ element = Element.values()[random.nextInt(Element.values().length)]; }else{ Element[] elements = ArrayUtils.removeElement(Element.values(), Element.MAGIC); @@ -114,15 +110,17 @@ public class RandomSpell extends LootFunction { // Here's a thought: does randomly selecting the element beforehand (as opposed to leaving it null and letting // the spell randomiser use any element) change the overall outcome at all? - List spellsList = Spell.getSpells(new Spell.TierElementFilter(tier, element)); - if(stack.getItem() instanceof ItemScroll) spellsList.retainAll(Spell.getSpells(Spell.nonContinuousSpells)); + List spellsList = Spell.getSpells(new Spell.TierElementFilter(tier, element, SpellProperties.Context.TREASURE)); - // Ensures the tier chosen actually has spells in it, and if not uses BASIC instead. BASIC always has at least + if(stack.getItem() instanceof ItemScroll) spellsList.removeIf(s -> !s.isEnabled(SpellProperties.Context.SCROLL)); + if(stack.getItem() instanceof ItemSpellBook) spellsList.removeIf(s -> !s.isEnabled(SpellProperties.Context.BOOK)); + + // Ensures the tier chosen actually has spells in it, and if not uses NOVICE instead. NOVICE always has at least // the NONE spell since this spell cannot be disabled. - // NOTE: Commented out for now because it will interfere with the ability to specify tiers and elements. + // Commented out for now because it will interfere with the ability to specify tiers and elements. // To be honest, I may as well just say that if you disable enough spells to make this important, you deserve // less loot! - /* if(spells.isEmpty()){ spellsList = Spell.getSpells(new Spell.TierElementFilter(EnumTier.BASIC, null)); + /* if(spells.isEmpty()){ spellsList = Spell.getSpells(new Spell.TierElementFilter(EnumTier.NOVICE, null)); * if(stack.getItem() instanceof ItemScroll) spellsList.retainAll(Spell.getSpells(Spell.nonContinuousSpells)); * } */ @@ -130,12 +128,30 @@ public class RandomSpell extends LootFunction { spellsList.retainAll(spells); } + // This method is badly-named, loot chests pass a player through too, not just mobs + // (And WHY does it only return an entity?! The underlying field is always a player so I'm casting it anyway) + EntityPlayer player = (EntityPlayer)context.getKillerPlayer(); + + // Remove either the undiscovered spells or the discovered ones, depending on the bias + if(undiscoveredBias > 0 && player != null){ + + WizardData data = WizardData.get(player); + + int discoveredCount = (int)spellsList.stream().filter(data::hasSpellBeenDiscovered).count(); + // If none have been discovered or they've all been discovered, don't bother! + if(discoveredCount > 0 && discoveredCount < spellsList.size()){ + // Kinda unintuitive but it's very neat! + boolean keepDiscovered = random.nextFloat() < 0.5f + 0.5f * undiscoveredBias; + spellsList.removeIf(s -> keepDiscovered != data.hasSpellBeenDiscovered(s)); + } + } + if(spellsList.isEmpty()){ Wizardry.logger.warn("Tried to apply the random_spell loot function to an item, but no enabled spells" + "matched the criteria specified. Substituting placeholder (metadata 0) item."); stack.setItemDamage(0); }else{ - stack.setItemDamage(spellsList.get(random.nextInt(spellsList.size())).id()); + stack.setItemDamage(spellsList.get(random.nextInt(spellsList.size())).metadata()); } return stack; @@ -162,6 +178,8 @@ public class RandomSpell extends LootFunction { object.addProperty("ignore_weighting", function.ignoreWeighting); + object.addProperty("undiscovered_bias", function.undiscoveredBias); + if(function.tiers != null && !function.tiers.isEmpty()){ JsonArray jsonarray = new JsonArray(); @@ -178,7 +196,7 @@ public class RandomSpell extends LootFunction { JsonArray jsonarray = new JsonArray(); for(Element element : function.elements){ - jsonarray.add(new JsonPrimitive(element.getUnlocalisedName())); + jsonarray.add(new JsonPrimitive(element.getName())); } object.add("elements", jsonarray); @@ -194,8 +212,7 @@ public class RandomSpell extends LootFunction { if(object.has("spells")){ - // Vanilla Minecraft calls Lists.newArrayList() here, which is completely pointless. - spells = new ArrayList(); + spells = new ArrayList<>(); // Importantly, it is necessary to specify a default (the new JsonArray) here because otherwise the // parameter will be mandatory, and the game will crash if it isn't present. @@ -215,50 +232,43 @@ public class RandomSpell extends LootFunction { boolean ignoreWeighting = JsonUtils.getBoolean(object, "ignore_weighting", false); + float undiscoveredBias = JsonUtils.getFloat(object, "undiscovered_bias", 0); + if(object.has("tiers")){ - // Vanilla Minecraft calls Lists.newArrayList() here, which is completely pointless. - tiers = new ArrayList(); + tiers = new ArrayList<>(); - jsonarray: for(JsonElement element : JsonUtils.getJsonArray(object, "tiers", new JsonArray())){ + for(JsonElement element : JsonUtils.getJsonArray(object, "tiers", new JsonArray())){ String string = JsonUtils.getString(element, "tier"); - // IDEA: If it is necessary to get tiers by name elsewhere in the future, move this to EnumTier. - for(Tier tier : Tier.values()){ - if(tier.getUnlocalisedName().equals(string)){ - tiers.add(tier); - continue jsonarray; - } + try { + tiers.add(Tier.fromName(string)); + }catch(IllegalArgumentException e){ + // If the string does not match any of the tiers, throws an exception. + throw new JsonSyntaxException("Unknown tier \'" + string + "\'"); } - // If the string does not match any of the tiers, throws an exception. - throw new JsonSyntaxException("Unknown tier \'" + string + "\'"); } } if(object.has("elements")){ - // Vanilla Minecraft calls Lists.newArrayList() here, which is completely pointless. - elements = new ArrayList(); + elements = new ArrayList<>(); - jsonarray: for(JsonElement jelement : JsonUtils.getJsonArray(object, "elements", new JsonArray())){ + for(JsonElement jelement : JsonUtils.getJsonArray(object, "elements", new JsonArray())){ String string = JsonUtils.getString(jelement, "element"); - // IDEA: If it is necessary to get elements by name elsewhere in the future, move this to - // EnumElement. - for(Element element : Element.values()){ - if(element.getUnlocalisedName().equals(string)){ - elements.add(element); - continue jsonarray; - } + try { + elements.add(Element.fromName(string)); + }catch(IllegalArgumentException e){ + // If the string does not match any of the elements, throws an exception. + throw new JsonSyntaxException("Unknown element \'" + string + "\'"); } - // If the string does not match any of the elements, throws an exception. - throw new JsonSyntaxException("Unknown element \'" + string + "\'"); } } - return new RandomSpell(conditions, spells, ignoreWeighting, tiers, elements); + return new RandomSpell(conditions, spells, ignoreWeighting, undiscoveredBias, tiers, elements); } } diff --git a/src/main/java/electroblob/wizardry/loot/WizardSpell.java b/src/main/java/electroblob/wizardry/loot/WizardSpell.java index 3609926f..72c6d35a 100644 --- a/src/main/java/electroblob/wizardry/loot/WizardSpell.java +++ b/src/main/java/electroblob/wizardry/loot/WizardSpell.java @@ -1,12 +1,8 @@ package electroblob.wizardry.loot; -import java.util.List; -import java.util.Random; - import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.item.ItemSpellBook; @@ -18,6 +14,9 @@ import net.minecraft.world.storage.loot.LootContext; import net.minecraft.world.storage.loot.conditions.LootCondition; import net.minecraft.world.storage.loot.functions.LootFunction; +import java.util.List; +import java.util.Random; + /** * Loot function that allows spell books to select a random spell from the spells used by the ISpellCaster that dropped * them. @@ -40,7 +39,7 @@ public class WizardSpell extends LootFunction { if(context.getLootedEntity() instanceof ISpellCaster){ List spells = ((ISpellCaster)context.getLootedEntity()).getSpells(); spells.remove(Spells.magic_missile); // Can't drop magic missile - stack.setItemDamage(spells.get(random.nextInt(spells.size())).id()); + stack.setItemDamage(spells.get(random.nextInt(spells.size())).metadata()); }else{ Wizardry.logger.warn("Applying the wizard_spell loot function to an entity that isn't a spell caster."); } diff --git a/src/main/java/electroblob/wizardry/misc/BehaviourSpellDispense.java b/src/main/java/electroblob/wizardry/misc/BehaviourSpellDispense.java new file mode 100644 index 00000000..cf75f72b --- /dev/null +++ b/src/main/java/electroblob/wizardry/misc/BehaviourSpellDispense.java @@ -0,0 +1,105 @@ +package electroblob.wizardry.misc; + +import electroblob.wizardry.data.DispenserCastingData; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.event.SpellCastEvent.Source; +import electroblob.wizardry.item.ItemScroll; +import electroblob.wizardry.packet.PacketDispenserCastSpell; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.block.BlockDispenser; +import net.minecraft.dispenser.IBlockSource; +import net.minecraft.dispenser.IPosition; +import net.minecraft.init.Bootstrap.BehaviorDispenseOptional; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +/** + * Dispenser behaviour for casting spells from dispensers based on the metadata of the dispensed item. This class, along + * with the capability {@link DispenserCastingData DispenserCastingData}, forms the dispenser + * equivalent of {@link electroblob.wizardry.entity.living.EntityAIAttackSpell EntityAIAttackSpell}, which handles spell + * casting for NPCs. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +/* + * The dispenser and NPC casting systems are somewhat comparable in structure: + * + | Dispensers | NPCs | + | -------------------------|---------------------------| + | BehaviourSpellDispense | EntityAIAttackSpell | Handles the actual spell casting. These are slightly different + | | | | | in that EntityAIAttackSpell handles ticking for NPC spells, + | | | | | whereas for dispensers this is handled in DispenserCastingData. + | V | V | + | DispenserCastingData | ISpellCaster | Deals with data storage for spell casting. Of course, being an + | | | interface, ISpellCaster doesn't actually store the data itself. + * + */ +public class BehaviourSpellDispense extends BehaviorDispenseOptional { + + public BehaviourSpellDispense(){} + + @Override + protected ItemStack dispenseStack(IBlockSource source, ItemStack stack){ + + // This is only ever called server-side. + + successful = false; + + World world = source.getWorld(); + // This returns a position that is 0.2 blocks away from the middle of the front face of the dispenser + IPosition position = BlockDispenser.getDispensePosition(source); + EnumFacing direction = source.getBlockState().getValue(BlockDispenser.FACING); + + Spell spell = Spell.byMetadata(stack.getMetadata()); + + // If there's a block in the way, nothing happens + if(world.isSideSolid(source.getBlockPos().offset(direction), direction.getOpposite())) return stack; + + // If the scroll can never be cast by a dispenser, it should be dispensed as an item. + if(!spell.canBeCastByDispensers()) return super.dispenseStack(source, stack); + + SpellModifiers modifiers = new SpellModifiers(); + + double x = position.getX(); + double y = position.getY(); + double z = position.getZ(); + + // For horizontal dispensers, the position is lowered by 0.125 so it actually lines up with the hole. + if(direction.getAxis().isHorizontal()) y -= 0.125; + + // If the scroll can be cast by a dispenser, it should be cast. If this fails, then the scroll should stay in + // the dispenser. + if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.DISPENSER, spell, world, x, y, z, direction, modifiers))) + return stack; + + successful = spell.cast(world, x, y, z, direction, 0, -1, modifiers); + + if(successful){ + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.DISPENSER, spell, world, x, y, z, direction, modifiers)); + + stack.shrink(1); + + if(spell.isContinuous || spell.requiresPacket()){ + // Sends a packet to all players in dimension to tell them to spawn particles. + IMessage msg = new PacketDispenserCastSpell.Message(x, y, z, direction, source.getBlockPos(), spell, + spell.isContinuous ? ItemScroll.CASTING_TIME : 0, modifiers); // Non-continuous spells ignore duration + WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); + } + + if(spell.isContinuous){ + DispenserCastingData data = DispenserCastingData.get(source.getBlockTileEntity()); + data.startCasting(spell, x, y, z, ItemScroll.CASTING_TIME, modifiers); + } + } + + return stack; + } + +} diff --git a/src/main/java/electroblob/wizardry/misc/Forfeit.java b/src/main/java/electroblob/wizardry/misc/Forfeit.java new file mode 100644 index 00000000..e5782a66 --- /dev/null +++ b/src/main/java/electroblob/wizardry/misc/Forfeit.java @@ -0,0 +1,487 @@ +package electroblob.wizardry.misc; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.entity.EntityMeteor; +import electroblob.wizardry.entity.construct.*; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.entity.projectile.EntityFirebomb; +import electroblob.wizardry.entity.projectile.EntityMagicFireball; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.*; +import electroblob.wizardry.spell.Banish; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.effect.EntityLightningBolt; +import net.minecraft.entity.item.EntityFallingBlock; +import net.minecraft.entity.passive.EntitySquid; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.init.MobEffects; +import net.minecraft.item.ItemStack; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; +import net.minecraftforge.common.IPlantable; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.function.BiConsumer; + +/** + * A {@code Forfeit} object represents a negative effect that may happen when a player attempts to cast an + * undiscovered spell. The nature and severity of the forfeit depends on the element and tier of the spell that was + * attempted.
    + *
    + * Adding a new forfeit is as simple as calling {@link Forfeit#add(Tier, Element, Forfeit)} and supplying the + * {@code Forfeit} instance along with a tier and element to associate it with. To create a forfeit, you may extend + * this class and instantiate it, or use {@link Forfeit#create(ResourceLocation, BiConsumer)} to concisely define the + * behaviour (this method is provided since most forfeits' behaviour code is fairly brief and would otherwise result in + * a large number of trivial 'stub' classes - in fact, many of wizardry's forfeits are defined in a single line).
    + *
    + * This class also handles the (event-driven) selection of forfeits and determines when one should be applied. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +// With the exception of sound events, everything forfeit-related is done right here. How about that for modularity? :P +@Mod.EventBusSubscriber +public abstract class Forfeit { + + private static final ListMultimap, Forfeit> forfeits = ArrayListMultimap.create(); + + private static final float TIER_CHANGE_CHANCE = 0.2f; + + private final ResourceLocation name; + + protected final SoundEvent sound; + + public Forfeit(ResourceLocation name){ + this.name = name; + this.sound = WizardrySounds.createSound("forfeit." + name.getPath()); + } + + public abstract void apply(World world, EntityPlayer player); + + /** + * Returns an {@link ITextComponent} for the message displayed when this forfeit is activated. + * @param implementName An {@code ITextComponent} for the name of the implement being used. This is usually + * something generic like 'wand' or 'scroll'. + * @return An {@code ITextComponent} representing this forfeit's message, for use in chat messages. + * @see Forfeit#getMessageForWand() + * @see Forfeit#getMessageForScroll() + */ + public ITextComponent getMessage(ITextComponent implementName){ + return new TextComponentTranslation("forfeit." + name.toString(), implementName); + } + + /** Wrapper for {@link Forfeit#getMessage(ITextComponent)} with {@code implementName} set to the lang file key + * {@code item.ebwizardry:wand.generic} */ + public ITextComponent getMessageForWand(){ + return getMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":wand.generic")); + } + + /** Wrapper for {@link Forfeit#getMessage(ITextComponent)} with {@code implementName} set to the lang file key + * {@code item.ebwizardry:scroll.generic} */ + public ITextComponent getMessageForScroll(){ + return getMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":scroll.generic")); + } + + /** Returns the {@link SoundEvent} played when this forfeit is activated. */ + public SoundEvent getSound(){ + return sound; + } + + public static void add(Tier tier, Element element, Forfeit forfeit){ + forfeits.put(Pair.of(tier, element), forfeit); + } + + public static Forfeit getRandomForfeit(Random random, Tier tier, Element element){ + float f = random.nextFloat(); + if(f < TIER_CHANGE_CHANCE && tier.ordinal() > 0) tier = Tier.values()[tier.ordinal() - 1]; + else if(f > 1 - TIER_CHANGE_CHANCE && tier.ordinal() < Tier.values().length-1) tier = Tier.values()[tier.ordinal() + 1]; + List matches = forfeits.get(Pair.of(tier, element)); + if(matches.isEmpty()){ + Wizardry.logger.warn("No forfeits with tier {} and element {}!", tier, element); + return null; + } + return matches.get(random.nextInt(matches.size())); + } + + public static Collection getForfeits(){ + return Collections.unmodifiableCollection(forfeits.values()); + } + + /** Static helper method that creates a {@code Forfeit} with the given name and an effect specified by the given + * consumer. This allows code to use a neater lambda expression rather than an anonymous class. */ + public static Forfeit create(ResourceLocation name, BiConsumer effect){ + return new Forfeit(name){ + @Override + public void apply(World world, EntityPlayer player){ + effect.accept(world, player); + } + }; + } + + /** Internal wrapper for {@link Forfeit#create(ResourceLocation, BiConsumer)} so I don't have to put wizardry's + * mod ID in every time. */ + private static Forfeit create(String name, BiConsumer effect){ + return create(new ResourceLocation(Wizardry.MODID, name), effect); + } + + @SubscribeEvent(priority = EventPriority.NORMAL) // Forfeits come after spell disabling but before modifiers + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + + if(!Wizardry.settings.discoveryMode) return; + + if(event.getCaster() instanceof EntityPlayer && !((EntityPlayer)event.getCaster()).isCreative() + && (event.getSource() == SpellCastEvent.Source.WAND || event.getSource() == SpellCastEvent.Source.SCROLL)){ + + EntityPlayer player = (EntityPlayer)event.getCaster(); + WizardData data = WizardData.get(player); + + float chance = (float)Wizardry.settings.forfeitChance; + if(ItemArtefact.isArtefactActive(player, WizardryItems.amulet_wisdom)) chance *= 0.5; + + // Use the synchronised random to ensure the same outcome on client- and server-side + if(data.synchronisedRandom.nextFloat() < chance && !data.hasSpellBeenDiscovered(event.getSpell())){ + + event.setCanceled(true); + + Forfeit forfeit = getRandomForfeit(event.getWorld().rand, event.getSpell().getTier(), event.getSpell().getElement()); + + if(forfeit == null){ // Should never happen, but just in case... + if(!event.getWorld().isRemote) player.sendMessage(new TextComponentTranslation("forfeit.ebwizardry:do_nothing")); + return; + } + + forfeit.apply(event.getWorld(), player); + + WizardryAdvancementTriggers.spell_failure.triggerFor(player); + + WizardryUtilities.playSoundAtPlayer(player, forfeit.getSound(), WizardrySounds.SPELLS, 1, 1); + + if(!event.getWorld().isRemote) player.sendMessage( + event.getSource() == SpellCastEvent.Source.WAND ? forfeit.getMessageForWand() : forfeit.getMessageForScroll()); + } + } + } + + /** Called from the preInit method in the main mod class to set up all the forfeits. */ + public static void register(){ + + add(Tier.NOVICE, Element.FIRE, create("burn_self", (w, p) -> p.setFire(5))); + + add(Tier.APPRENTICE, Element.FIRE, create("fireball", (w, p) -> { + if(!w.isRemote){ + EntityMagicFireball fireball = new EntityMagicFireball(w); + Vec3d vec = p.getPositionEyes(0).add(p.getLookVec().scale(6)); + fireball.setPosition(vec.x, vec.y, vec.z); + fireball.shoot(p.posX, p.posY + p.getEyeHeight(), p.posZ, 1.5f, 1); + w.spawnEntity(fireball); + } + })); + + add(Tier.APPRENTICE, Element.FIRE, create("firebomb", (w, p) -> { + if(!w.isRemote){ + EntityFirebomb firebomb = new EntityFirebomb(w); + firebomb.setPosition(p.posX, p.posY + 5, p.posZ); + w.spawnEntity(firebomb); + } + })); + + add(Tier.ADVANCED, Element.FIRE, create("explode", (w, p) -> w.createExplosion(null, p.posX, p.posY, p.posZ, 1, false))); + + add(Tier.ADVANCED, Element.FIRE, create("blazes", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 3; i++){ + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2); + if(pos == null) break; + EntityBlazeMinion blaze = new EntityBlazeMinion(w); + blaze.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(blaze); + } + } + })); + + add(Tier.MASTER, Element.FIRE, create("burn_surroundings", (w, p) -> { + if(!w.isRemote){ + List sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 6); + for(BlockPos pos : sphere){ + if(w.rand.nextBoolean() && w.isAirBlock(pos)) w.setBlockState(pos, Blocks.FIRE.getDefaultState()); + } + } + })); + + add(Tier.MASTER, Element.FIRE, create("meteors", (w, p) -> { + if(!w.isRemote) for(int i=0; i<5; i++) w.spawnEntity(new EntityMeteor(w, p.posX + w.rand.nextDouble() * 16 - 8, + p.posY + 40 + w.rand.nextDouble() * 30, p.posZ + w.rand.nextDouble() * 16 - 8, + 1, WizardryUtilities.canDamageBlocks(p, w))); + })); + + add(Tier.NOVICE, Element.ICE, create("freeze_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200)))); + + add(Tier.APPRENTICE, Element.ICE, create("freeze_self_2", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 1)))); + + add(Tier.APPRENTICE, Element.ICE, create("ice_spikes", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 5; i++){ + EntityIceSpike iceSpike = new EntityIceSpike(w); + double x = p.posX + 2 - w.rand.nextFloat() * 4; + double z = p.posZ + 2 - w.rand.nextFloat() * 4; + Integer y = WizardryUtilities.getNearestSurface(w, new BlockPos(x, p.posY, z), EnumFacing.UP, 2, true, + WizardryUtilities.SurfaceCriteria.basedOn(World::isBlockFullCube)); + if(y == null) break; + iceSpike.setFacing(EnumFacing.UP); + iceSpike.setPosition(x, y, z); + w.spawnEntity(iceSpike); + } + } + })); + + add(Tier.ADVANCED, Element.ICE, create("blizzard", (w, p) -> { + if(!w.isRemote){ + EntityBlizzard blizzard = new EntityBlizzard(w); + blizzard.setPosition(p.posX, p.posY, p.posZ); + w.spawnEntity(blizzard); + } + })); + + add(Tier.ADVANCED, Element.ICE, create("ice_wraiths", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 3; i++){ + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2); + if(pos == null) break; + EntityIceWraith iceWraith = new EntityIceWraith(w); + iceWraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(iceWraith); + } + } + })); + + add(Tier.MASTER, Element.ICE, create("hailstorm", (w, p) -> { + if(!w.isRemote){ + EntityHailstorm hailstorm = new EntityHailstorm(w); + hailstorm.setPosition(p.posX, p.posY + 5, p.posZ - 3); // Subtract 3 from z because it's facing south (yaw 0) + w.spawnEntity(hailstorm); + } + })); + + add(Tier.MASTER, Element.ICE, create("ice_giant", (w, p) -> { + if(!w.isRemote){ + EntityIceGiant iceGiant = new EntityIceGiant(w); + iceGiant.setPosition(p.posX + p.getLookVec().x * 4, p.posY, p.posZ + p.getLookVec().z * 4); + w.spawnEntity(iceGiant); + } + })); + + add(Tier.NOVICE, Element.LIGHTNING, create("thunder", (w, p) -> { + p.addVelocity(-p.getLookVec().x, 0, -p.getLookVec().z); + if(w.isRemote) w.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, p.posX, p.posY, p.posZ, 0, 0, 0); + })); + + add(Tier.APPRENTICE, Element.LIGHTNING, create("storm", (w, p) -> { + int standardWeatherTime = (300 + (new Random()).nextInt(600)) * 20; + w.getWorldInfo().setRaining(true); + w.getWorldInfo().setRainTime(standardWeatherTime); + w.getWorldInfo().setThundering(true); + w.getWorldInfo().setThunderTime(standardWeatherTime); + })); + + add(Tier.APPRENTICE, Element.LIGHTNING, create("lightning_sigils", (w, p) -> { + if(!w.isRemote){ + for(EnumFacing direction : EnumFacing.HORIZONTALS){ + BlockPos pos = p.getPosition().offset(direction, 2); + Integer y = WizardryUtilities.getNearestFloor(w, pos, 2); + if(y == null) continue; + EntityLightningSigil sigil = new EntityLightningSigil(w); + sigil.setPosition(pos.getX() + 0.5, y, pos.getZ() + 0.5); + w.spawnEntity(sigil); + } + } + })); + + add(Tier.ADVANCED, Element.LIGHTNING, create("lightning", (w, p) -> w.addWeatherEffect(new EntityLightningBolt(w, p.posX, p.posY, p.posZ, false)))); + + add(Tier.ADVANCED, Element.LIGHTNING, create("paralyse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.paralysis, 200)))); + + add(Tier.ADVANCED, Element.LIGHTNING, create("lightning_wraiths", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 3; i++){ + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2); + if(pos == null) break; + EntityLightningWraith lightningWraith = new EntityLightningWraith(w); + lightningWraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(lightningWraith); + } + } + })); + + add(Tier.MASTER, Element.LIGHTNING, create("storm_elementals", (w, p) -> { + if(!w.isRemote){ + for(EnumFacing direction : EnumFacing.HORIZONTALS){ + BlockPos pos = p.getPosition().offset(direction, 3); + EntityStormElemental stormElemental = new EntityStormElemental(w); + stormElemental.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(stormElemental); + } + } + })); + + add(Tier.NOVICE, Element.NECROMANCY, create("nausea", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 400)))); + + add(Tier.APPRENTICE, Element.NECROMANCY, create("zombie_horde", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 3; i++){ + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2); + if(pos == null) break; + EntityZombieMinion zombie = new EntityZombieMinion(w); + zombie.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(zombie); + } + } + })); + + add(Tier.ADVANCED, Element.NECROMANCY, create("wither_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WITHER, 400)))); + + add(Tier.MASTER, Element.NECROMANCY, create("cripple_self", (w, p) -> p.attackEntityFrom(DamageSource.MAGIC, p.getHealth() - 1))); + + add(Tier.MASTER, Element.NECROMANCY, create("shadow_wraiths", (w, p) -> { + if(!w.isRemote){ + for(EnumFacing direction : EnumFacing.HORIZONTALS){ + BlockPos pos = p.getPosition().offset(direction, 3); + EntityShadowWraith wraith = new EntityShadowWraith(w); + wraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + w.spawnEntity(wraith); + } + } + })); + + add(Tier.NOVICE, Element.EARTH, create("snares", (w, p) -> { + if(!w.isRemote){ + for(EnumFacing direction : EnumFacing.HORIZONTALS){ + BlockPos pos = p.getPosition().offset(direction); + w.setBlockState(pos, WizardryBlocks.snare.getDefaultState()); + } + } + })); + + add(Tier.NOVICE, Element.EARTH, create("squid", (w, p) -> { + if(!w.isRemote){ + EntitySquid squid = new EntitySquid(w); + squid.setPosition(p.posX, p.posY + 3, p.posZ); + w.spawnEntity(squid); + } + })); + + add(Tier.APPRENTICE, Element.EARTH, create("uproot_plants", (w, p) -> { + if(!w.isRemote){ + List sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 5); + sphere.removeIf(pos -> !(w.getBlockState(pos).getBlock() instanceof IPlantable)); + sphere.forEach(pos -> w.destroyBlock(pos, true)); + } + })); + + add(Tier.APPRENTICE, Element.EARTH, create("poison_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.POISON, 400, 1)))); + + add(Tier.ADVANCED, Element.EARTH, create("flood", (w, p) -> { + if(!w.isRemote){ + List sphere = WizardryUtilities.getBlockSphere(p.getPosition().up(), 2); + sphere.removeIf(pos -> !WizardryUtilities.canBlockBeReplaced(w, pos, true)); + sphere.forEach(pos -> w.setBlockState(pos, Blocks.WATER.getDefaultState())); + } + })); + + add(Tier.MASTER, Element.EARTH, create("bury_self", (w, p) -> { + if(!w.isRemote){ + List sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 4); + sphere.removeIf(pos -> !w.getBlockState(pos).isFullCube()); + sphere.forEach(pos -> { + EntityFallingBlock block = new EntityFallingBlock(w, pos.getX() + 0.5, pos.getY() + 0.5, + pos.getZ() + 0.5, w.getBlockState(pos)); + block.motionY = 0.3 * (4 - (p.getPosition().getY() - pos.getY())); + w.spawnEntity(block); + }); + } + })); + + add(Tier.NOVICE, Element.SORCERY, create("spill_inventory", (w, p) -> { + for(int i = 0; i < p.inventory.mainInventory.size(); i++){ + ItemStack stack = p.inventory.mainInventory.get(i); + if(!stack.isEmpty()){ + p.dropItem(stack, true, false); + p.inventory.mainInventory.set(i, ItemStack.EMPTY); + } + } + })); + + add(Tier.APPRENTICE, Element.SORCERY, create("teleport_self", (w, p) -> ((Banish)Spells.banish).teleport(p, w, 8 + w.rand.nextDouble() * 8))); + + add(Tier.ADVANCED, Element.SORCERY, create("levitate_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.LEVITATION, 200)))); + + add(Tier.ADVANCED, Element.SORCERY, create("vex_horde", (w, p) -> { + if(!w.isRemote){ + for(int i = 0; i < 4; i++){ + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2); + if(pos == null) break; + EntityVexMinion vex = new EntityVexMinion(w); + vex.setPosition(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5); + w.spawnEntity(vex); + } + } + })); + + add(Tier.MASTER, Element.SORCERY, create("black_hole", (w, p) -> { + EntityBlackHole blackHole = new EntityBlackHole(w); + Vec3d vec = p.getPositionEyes(1).add(p.getLookVec().scale(4)); + blackHole.setPosition(vec.x, vec.y, vec.z); + w.spawnEntity(blackHole); + })); + + add(Tier.MASTER, Element.SORCERY, create("arrow_rain", (w, p) -> { + if(!w.isRemote){ + EntityArrowRain arrowRain = new EntityArrowRain(w); + arrowRain.setPosition(p.posX, p.posY + 5, p.posZ - 3); // Subtract 3 from z because it's facing south (yaw 0) + w.spawnEntity(arrowRain); + } + })); + + add(Tier.NOVICE, Element.HEALING, create("damage_self", (w, p) -> p.attackEntityFrom(DamageSource.MAGIC, 4))); + + add(Tier.NOVICE, Element.HEALING, create("spill_armour", (w, p) -> { + for(int i = 0; i < p.inventory.armorInventory.size(); i++){ + ItemStack stack = p.inventory.armorInventory.get(i); + if(!stack.isEmpty()){ + p.dropItem(stack, true, false); + p.inventory.armorInventory.set(i, ItemStack.EMPTY); + } + } + })); + + add(Tier.APPRENTICE, Element.HEALING, create("hunger", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.HUNGER, 400, 4)))); + + add(Tier.APPRENTICE, Element.HEALING, create("blind_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 200)))); + + add(Tier.ADVANCED, Element.HEALING, create("weaken_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 600, 3)))); + + add(Tier.ADVANCED, Element.HEALING, create("jam_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, 300)))); + + add(Tier.MASTER, Element.HEALING, create("curse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE)))); + + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/util/WildcardTradeList.java b/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java similarity index 98% rename from src/main/java/electroblob/wizardry/util/WildcardTradeList.java rename to src/main/java/electroblob/wizardry/misc/WildcardTradeList.java index d7a60a2a..0917bcfa 100644 --- a/src/main/java/electroblob/wizardry/util/WildcardTradeList.java +++ b/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.util; +package electroblob.wizardry.misc; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; diff --git a/src/main/java/electroblob/wizardry/util/WizardryPathFinder.java b/src/main/java/electroblob/wizardry/misc/WizardryPathFinder.java similarity index 99% rename from src/main/java/electroblob/wizardry/util/WizardryPathFinder.java rename to src/main/java/electroblob/wizardry/misc/WizardryPathFinder.java index 898f9b4c..edd4156b 100644 --- a/src/main/java/electroblob/wizardry/util/WizardryPathFinder.java +++ b/src/main/java/electroblob/wizardry/misc/WizardryPathFinder.java @@ -1,11 +1,6 @@ -package electroblob.wizardry.util; - -import java.util.Set; - -import javax.annotation.Nullable; +package electroblob.wizardry.misc; import com.google.common.collect.Sets; - import electroblob.wizardry.spell.Clairvoyance; import net.minecraft.entity.EntityLiving; import net.minecraft.pathfinding.NodeProcessor; @@ -15,6 +10,9 @@ import net.minecraft.pathfinding.PathPoint; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; +import javax.annotation.Nullable; +import java.util.Set; + /** * Minecraft's pathfinder refused to play nicely, so I 'borrowed' its code and fiddled with it. Currently this is only * used for the {@link Clairvoyance} spell. diff --git a/src/main/java/electroblob/wizardry/packet/PacketCastContinuousSpell.java b/src/main/java/electroblob/wizardry/packet/PacketCastContinuousSpell.java index 35516b63..a4c30b65 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketCastContinuousSpell.java +++ b/src/main/java/electroblob/wizardry/packet/PacketCastContinuousSpell.java @@ -1,16 +1,19 @@ package electroblob.wizardry.packet; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.packet.PacketCastContinuousSpell.Message; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; /** * [Server -> Client] This packet is sent when the /cast command is used with a continuous spell, in order to - * sync the relevant variables in {@link electroblob.wizardry.WizardData WizardData}. + * sync the relevant variables in {@link WizardData WizardData}. */ public class PacketCastContinuousSpell implements IMessageHandler { @@ -21,12 +24,7 @@ public class PacketCastContinuousSpell implements IMessageHandler Wizardry.proxy.handleCastContinuousSpellPacket(message)); } return null; @@ -34,24 +32,23 @@ public class PacketCastContinuousSpell implements IMessageHandler { if(ctx.side.isClient()){ // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy // methods any more than necessary. - net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(new Runnable(){ - @Override - public void run(){ - Wizardry.proxy.handleCastSpellPacket(message); - } - }); + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleCastSpellPacket(message)); } return null; @@ -52,10 +48,10 @@ public class PacketCastSpell implements IMessageHandler { public Message(){ } - public Message(int casterID, EnumHand hand, int spellID, SpellModifiers modifiers){ + public Message(int casterID, EnumHand hand, Spell spell, SpellModifiers modifiers){ this.casterID = casterID; - this.spellID = spellID; + this.spellID = spell.networkID(); this.modifiers = modifiers; this.hand = hand == null ? EnumHand.MAIN_HAND : hand; } diff --git a/src/main/java/electroblob/wizardry/packet/PacketCastSpellAtPos.java b/src/main/java/electroblob/wizardry/packet/PacketCastSpellAtPos.java new file mode 100644 index 00000000..bb9047b7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketCastSpellAtPos.java @@ -0,0 +1,85 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import io.netty.buffer.ByteBuf; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** + * [Server -> Client] This packet is sent when a spell is cast at a position by commands and returns true, and is + * sent to clients so they can spawn the particles themselves. + */ +// Soooo many spell casting packets... +public class PacketCastSpellAtPos implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleCastSpellAtPosPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + /** Position for the spell */ + public Vec3d position; + /** Direction for the spell */ + public EnumFacing direction; + /** ID of the spell being cast */ + public int spellID; + /** SpellModifiers for the spell */ + public SpellModifiers modifiers; + /** Number of ticks to cast the spell for, or -1 for non-continuous spells */ + public int duration; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(Vec3d position, EnumFacing direction, Spell spell, SpellModifiers modifiers){ + this(position, direction, spell, modifiers, -1); + } + + public Message(Vec3d position, EnumFacing direction, Spell spell, SpellModifiers modifiers, int duration){ + this.spellID = spell.networkID(); + this.modifiers = modifiers; + this.position = position; + this.direction = direction; + this.duration = duration; + } + + @Override + public void fromBytes(ByteBuf buf){ + + // The order is important + position = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + direction = EnumFacing.byIndex(buf.readInt()); + this.spellID = buf.readInt(); + this.modifiers = new SpellModifiers(); + this.modifiers.read(buf); + this.duration = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + + buf.writeDouble(position.x); + buf.writeDouble(position.y); + buf.writeDouble(position.z); + buf.writeInt(direction.getIndex()); + buf.writeInt(spellID); + this.modifiers.write(buf); + buf.writeInt(duration); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketClairvoyance.java b/src/main/java/electroblob/wizardry/packet/PacketClairvoyance.java index cc3b919b..62a4c7ab 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketClairvoyance.java +++ b/src/main/java/electroblob/wizardry/packet/PacketClairvoyance.java @@ -1,8 +1,5 @@ package electroblob.wizardry.packet; -import java.util.ArrayList; -import java.util.List; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.packet.PacketClairvoyance.Message; import io.netty.buffer.ByteBuf; @@ -12,6 +9,9 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import java.util.ArrayList; +import java.util.List; + /** * [Server -> Client] This packet is sent when a player casts the clairvoyance spell to allow pathing to chunks * outside the render distance. diff --git a/src/main/java/electroblob/wizardry/packet/PacketConquerShrine.java b/src/main/java/electroblob/wizardry/packet/PacketConquerShrine.java new file mode 100644 index 00000000..7b04362e --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketConquerShrine.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import io.netty.buffer.ByteBuf; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** + * [Server -> Client] This packet is sent when a shrine is conquered to update nearby clients and spawn particles. + */ +public class PacketConquerShrine implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleConquerShrinePacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public int x; + public int y; + public int z; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(BlockPos pos){ + this.x = pos.getX(); + this.y = pos.getY(); + this.z = pos.getZ(); + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + this.x = buf.readInt(); + this.y = buf.readInt(); + this.z = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(x); + buf.writeInt(y); + buf.writeInt(z); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketControlInput.java b/src/main/java/electroblob/wizardry/packet/PacketControlInput.java index c0f3132d..f2a43d39 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketControlInput.java +++ b/src/main/java/electroblob/wizardry/packet/PacketControlInput.java @@ -1,12 +1,18 @@ package electroblob.wizardry.packet; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.packet.PacketControlInput.Message; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Possession; +import electroblob.wizardry.spell.Resurrection; import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; -import electroblob.wizardry.util.WandHelper; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; @@ -22,45 +28,100 @@ public class PacketControlInput implements IMessageHandler { final EntityPlayerMP player = ctx.getServerHandler().player; - player.getServerWorld().addScheduledTask(new Runnable(){ + player.getServerWorld().addScheduledTask(() -> { - public void run(){ + ItemStack wand = player.getHeldItemMainhand(); - ItemStack wand = player.getHeldItemMainhand(); + if(!(wand.getItem() instanceof ISpellCastingItem)){ + wand = player.getHeldItemOffhand(); + } - if(!(wand.getItem() instanceof ItemWand)){ - wand = player.getHeldItemOffhand(); - } + switch(message.controlType){ - switch(message.controlType){ - - case APPLY_BUTTON: + case APPLY_BUTTON: + if(!(player.openContainer instanceof ContainerArcaneWorkbench)){ + Wizardry.logger.warn("Received a PacketControlInput, but the player that sent it was not " + + "currently using an arcane workbench. This should not happen!"); + }else{ ((ContainerArcaneWorkbench)player.openContainer).onApplyButtonPressed(player); - break; - - case NEXT_SPELL_KEY: - - if(wand.getItem() instanceof ItemWand){ - - WandHelper.selectNextSpell(wand); - // This line fixes the bug with continuous spells casting when they shouldn't be - player.stopActiveHand(); - } - - break; - - case PREVIOUS_SPELL_KEY: - - if(wand.getItem() instanceof ItemWand){ - - WandHelper.selectPreviousSpell(wand); - // This line fixes the bug with continuous spells casting when they shouldn't be - player.stopActiveHand(); - } - - break; } + + break; + + case NEXT_SPELL_KEY: + + if(wand.getItem() instanceof ISpellCastingItem){ + + ((ISpellCastingItem)wand.getItem()).selectNextSpell(wand); + // This line fixes the bug with continuous spells casting when they shouldn't be + player.stopActiveHand(); + } + + break; + + case PREVIOUS_SPELL_KEY: + + if(wand.getItem() instanceof ISpellCastingItem){ + + ((ISpellCastingItem)wand.getItem()).selectPreviousSpell(wand); + // This line fixes the bug with continuous spells casting when they shouldn't be + player.stopActiveHand(); + } + + break; + + case RESURRECT_BUTTON: + + if(player.isDead && Resurrection.getRemainingWaitTime(player.deathTime) == 0){ + + ItemStack stack = WizardryUtilities.getHotbar(player).stream() + .filter(s -> Resurrection.canStackResurrect(s, player)).findFirst().orElse(null); + + if(stack != null){ + // This should suffice, since this is the only way a player can cast resurrection when dead! + ((ISpellCastingItem)stack.getItem()).cast(stack, Spells.resurrection, player, EnumHand.MAIN_HAND, 0, new SpellModifiers()); + break; + } + } + + Wizardry.logger.warn("Received a resurrect button packet, but the player that sent it was not" + + " currently able to resurrect. This should not happen!"); + + break; + + case CANCEL_RESURRECT: + + if(player.world.getGameRules().getBoolean("keepInventory")) break; // Shouldn't even receive this + + if(player.isDead){ + + ItemStack stack = WizardryUtilities.getHotbar(player).stream() + .filter(s -> Resurrection.canStackResurrect(s, player)).findFirst().orElse(null); + + if(stack != null){ + player.dropItem(stack, true, false); + player.inventory.deleteStack(stack); // Might as well + break; + } + + Wizardry.logger.warn("Received a cancel resurrect packet, but the player that sent it was not" + + " holding a wand with the resurrection spell. This should not happen!"); + } + + Wizardry.logger.warn("Received a cancel resurrect packet, but the player that sent it was not" + + " currently dead. This should not happen!"); + + break; + + case POSSESSION_PROJECTILE: + + if(!Possession.isPossessing(player)) Wizardry.logger.warn("Received a possession projectile packet, " + + "but the player that sent it is not currently possessing anything!"); + + Possession.shootProjectile(player); + + break; } }); } @@ -68,8 +129,8 @@ public class PacketControlInput implements IMessageHandler { return null; } - public static enum ControlType { - APPLY_BUTTON, NEXT_SPELL_KEY, PREVIOUS_SPELL_KEY; + public enum ControlType { + APPLY_BUTTON, NEXT_SPELL_KEY, PREVIOUS_SPELL_KEY, RESURRECT_BUTTON, CANCEL_RESURRECT, POSSESSION_PROJECTILE } public static class Message implements IMessage { diff --git a/src/main/java/electroblob/wizardry/packet/PacketDispenserCastSpell.java b/src/main/java/electroblob/wizardry/packet/PacketDispenserCastSpell.java new file mode 100644 index 00000000..3975bd14 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketDispenserCastSpell.java @@ -0,0 +1,94 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.packet.PacketDispenserCastSpell.Message; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellModifiers; +import io.netty.buffer.ByteBuf; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** + * [Server -> Client] This packet is sent when a spell is cast by a dispenser and returns true, and is sent to + * clients so they can spawn the particles. Unlike the player packets, this is for both continuous and + * non-continuous spells. + */ +public class PacketDispenserCastSpell implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleDispenserCastSpellPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + /** ID of the spell being cast */ + public int spellID; + /** Coordinates of the spell origin */ + public double x, y, z; + /** Spell casting direction */ + public EnumFacing direction; + /** BlockPos of the block that cast this spell. Not necessarily the same as the (x, y, z) coordinates. */ + public BlockPos pos; + /** The number of ticks to cast the spell for, or -1 if the spell should be cast until stopped. */ + public int duration; + /** SpellModifiers for the spell */ + public SpellModifiers modifiers; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){ + } + + public Message(double x, double y, double z, EnumFacing direction, BlockPos pos, Spell spell, int duration, SpellModifiers modifiers){ + + this.x = x; + this.y = y; + this.z = z; + this.direction = direction; + this.pos = pos; + this.spellID = spell.networkID(); + this.duration = duration; + this.modifiers = modifiers; + + } + + @Override + public void fromBytes(ByteBuf buf){ + + // The order is important + this.x = buf.readDouble(); + this.y = buf.readDouble(); + this.z = buf.readDouble(); + this.direction = EnumFacing.values()[buf.readInt()]; + this.pos = BlockPos.fromLong(buf.readLong()); + this.spellID = buf.readInt(); + this.duration = buf.readInt(); + this.modifiers = new SpellModifiers(); + this.modifiers.read(buf); + } + + @Override + public void toBytes(ByteBuf buf){ + + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeInt(direction.ordinal()); + buf.writeLong(pos.toLong()); + buf.writeInt(spellID); + buf.writeInt(duration); + this.modifiers.write(buf); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketEmitterData.java b/src/main/java/electroblob/wizardry/packet/PacketEmitterData.java new file mode 100644 index 00000000..4fe8c704 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketEmitterData.java @@ -0,0 +1,55 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.command.SpellEmitter; +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * [Server -> Client] This packet is sent each time a player joins a world to synchronise the client-side spell + * emitters with the server-side ones. + */ +public class PacketEmitterData implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleEmitterDataPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public List emitters; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(List emitters){ + this.emitters = emitters; + } + + @Override + public void fromBytes(ByteBuf buf){ + emitters = new ArrayList<>(); + while(buf.isReadable()){ + emitters.add(SpellEmitter.read(buf)); + } + } + + @Override + public void toBytes(ByteBuf buf){ + emitters.forEach(s -> s.write(buf)); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketEndSlowTime.java b/src/main/java/electroblob/wizardry/packet/PacketEndSlowTime.java new file mode 100644 index 00000000..e4d276f8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketEndSlowTime.java @@ -0,0 +1,47 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.Entity; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** [Server -> Client] This packet is sent when the slow time potion effect expires or is removed from an + * entity to unblock all nearby entities' updates. */ +public class PacketEndSlowTime implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleEndSlowTimePacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public int hostID; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){ + } + + public Message(Entity host){ + this.hostID = host.getEntityId(); + } + + @Override + public void fromBytes(ByteBuf buf){ + this.hostID = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(hostID); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketGlyphData.java b/src/main/java/electroblob/wizardry/packet/PacketGlyphData.java index 883794d1..23e4b7c3 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketGlyphData.java +++ b/src/main/java/electroblob/wizardry/packet/PacketGlyphData.java @@ -1,8 +1,5 @@ package electroblob.wizardry.packet; -import java.util.ArrayList; -import java.util.List; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.packet.PacketGlyphData.Message; import io.netty.buffer.ByteBuf; @@ -11,6 +8,9 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import java.util.ArrayList; +import java.util.List; + /** * [Server -> Client] This packet is sent each time a player joins a world to synchronise the client-side spell * glyph names with the server-side ones. @@ -23,12 +23,7 @@ public class PacketGlyphData implements IMessageHandler { if(ctx.side.isClient()){ // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy // methods any more than necessary. - net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(new Runnable(){ - @Override - public void run(){ - Wizardry.proxy.handleGlyphDataPacket(message); - } - }); + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleGlyphDataPacket(message)); } return null; @@ -52,8 +47,8 @@ public class PacketGlyphData implements IMessageHandler { @Override public void fromBytes(ByteBuf buf){ - names = new ArrayList(); - descriptions = new ArrayList(); + names = new ArrayList<>(); + descriptions = new ArrayList<>(); while(buf.isReadable()){ names.add(ByteBufUtils.readUTF8String(buf)); descriptions.add(ByteBufUtils.readUTF8String(buf)); diff --git a/src/main/java/electroblob/wizardry/packet/PacketNPCCastSpell.java b/src/main/java/electroblob/wizardry/packet/PacketNPCCastSpell.java index 4151d9a3..a25b308d 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketNPCCastSpell.java +++ b/src/main/java/electroblob/wizardry/packet/PacketNPCCastSpell.java @@ -3,6 +3,7 @@ package electroblob.wizardry.packet; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.packet.PacketNPCCastSpell.Message; +import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; import io.netty.buffer.ByteBuf; import net.minecraft.util.EnumHand; @@ -52,10 +53,10 @@ public class PacketNPCCastSpell implements IMessageHandler { public Message(){ } - public Message(int casterID, int targetID, EnumHand hand, int spellID, SpellModifiers modifiers){ + public Message(int casterID, int targetID, EnumHand hand, Spell spell, SpellModifiers modifiers){ this.casterID = casterID; this.targetID = targetID; - this.spellID = spellID; + this.spellID = spell.networkID(); this.modifiers = modifiers; this.hand = hand == null ? EnumHand.MAIN_HAND : hand; } diff --git a/src/main/java/electroblob/wizardry/packet/PacketPlayerSync.java b/src/main/java/electroblob/wizardry/packet/PacketPlayerSync.java index 879db01e..6fca18bc 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketPlayerSync.java +++ b/src/main/java/electroblob/wizardry/packet/PacketPlayerSync.java @@ -1,9 +1,8 @@ package electroblob.wizardry.packet; -import java.util.HashSet; -import java.util.Set; - import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.IVariable; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.packet.PacketPlayerSync.Message; import electroblob.wizardry.spell.Spell; import io.netty.buffer.ByteBuf; @@ -11,9 +10,11 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import java.util.*; + /** * [Server -> Client] This packet is sent to synchronise any fields that need synchronising in - * {@link electroblob.wizardry.WizardData WizardData}. This packet is not sent often enough and is too small to warrant + * {@link WizardData WizardData}. This packet is not sent often enough and is too small to warrant * having separate packets for each field that needs synchronising. */ public class PacketPlayerSync implements IMessageHandler { @@ -24,12 +25,7 @@ public class PacketPlayerSync implements IMessageHandler { if(ctx.side.isClient()){ // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy // methods any more than necessary. - net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(new Runnable(){ - @Override - public void run(){ - Wizardry.proxy.handlePlayerSyncPacket(message); - } - }); + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handlePlayerSyncPacket(message)); } return null; @@ -37,36 +33,51 @@ public class PacketPlayerSync implements IMessageHandler { public static class Message implements IMessage { + public long seed; public Set spellsDiscovered; public int selectedMinionID; + public Map spellData; // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) public Message(){ } - public Message(Set spellsDiscovered2, int selectedMinionID){ - this.spellsDiscovered = spellsDiscovered2; + public Message(long seed, Set spellsDiscovered, int selectedMinionID, Map spellData){ + this.seed = seed; + this.spellsDiscovered = spellsDiscovered; this.selectedMinionID = selectedMinionID; + this.spellData = spellData; } @Override public void fromBytes(ByteBuf buf){ + + this.seed = buf.readLong(); this.selectedMinionID = buf.readInt(); - this.spellsDiscovered = new HashSet(); + + this.spellData = new HashMap<>(); + WizardData.getSyncedVariables().forEach(v -> spellData.put(v, v.read(buf))); + // Have to send empty tags to guarantee correct ByteBuf size/order, but no point keeping the resulting nulls + spellData.values().removeIf(Objects::isNull); + + this.spellsDiscovered = new HashSet<>(); while(buf.isReadable()){ - this.spellsDiscovered.add(Spell.get(buf.readInt())); + this.spellsDiscovered.add(Spell.byNetworkID(buf.readInt())); } } @Override + @SuppressWarnings("unchecked") // We know it's ok public void toBytes(ByteBuf buf){ + buf.writeLong(seed); buf.writeInt(selectedMinionID); - if(this.spellsDiscovered == null) return; + WizardData.getSyncedVariables().forEach(v -> v.write(buf, spellData.get(v))); + if(this.spellsDiscovered == null) return; for(Spell spell : this.spellsDiscovered){ - buf.writeInt(spell.id()); + buf.writeInt(spell.networkID()); } } } diff --git a/src/main/java/electroblob/wizardry/packet/PacketPossession.java b/src/main/java/electroblob/wizardry/packet/PacketPossession.java new file mode 100644 index 00000000..34c5e3e3 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketPossession.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import javax.annotation.Nullable; + +/** [Server -> Client] This packet is sent when a player possesses an entity or when a player stops possessing + * to update all clients. */ +public class PacketPossession implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handlePossessionPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public int playerID; + public int targetID; + public int duration; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){ + } + + public Message(EntityPlayer host, @Nullable EntityLiving target, int duration){ + this.playerID = host.getEntityId(); + this.targetID = target == null ? -1 : target.getEntityId(); + this.duration = duration; + } + + @Override + public void fromBytes(ByteBuf buf){ + this.playerID = buf.readInt(); + this.targetID = buf.readInt(); + this.duration = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(playerID); + buf.writeInt(targetID); + buf.writeInt(duration); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketRequestAdvancementSync.java b/src/main/java/electroblob/wizardry/packet/PacketRequestAdvancementSync.java new file mode 100644 index 00000000..a4d7fc9c --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketRequestAdvancementSync.java @@ -0,0 +1,50 @@ +package electroblob.wizardry.packet; + +import io.netty.buffer.ByteBuf; +import net.minecraft.advancements.Advancement; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; + +/** [Client -> Server] Fired on resource reload to request that the server re-sync the player's advancements. */ +public class PacketRequestAdvancementSync implements IMessageHandler { + + @Override + public PacketSyncAdvancements.Message onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isServer()){ + + final EntityPlayerMP player = ctx.getServerHandler().player; + + ArrayList advancements = new ArrayList<>(); + + for(Advancement advancement : player.getServer().getAdvancementManager().getAdvancements()){ + if(player.getAdvancements().getProgress(advancement).isDone()) advancements.add(advancement.getId()); + } + + return new PacketSyncAdvancements.Message(false, advancements.toArray(new ResourceLocation[0])); + } + + return null; + } + + + public static class Message implements IMessage { + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + // Don't need to put anything in here! + + @Override + public void fromBytes(ByteBuf buf){} + + @Override + public void toBytes(ByteBuf buf){} + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketResurrection.java b/src/main/java/electroblob/wizardry/packet/PacketResurrection.java new file mode 100644 index 00000000..5526489e --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketResurrection.java @@ -0,0 +1,47 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** + * [Server -> Client] This packet is sent to clients in the same dimension when a player is resurrected. + */ +public class PacketResurrection implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleResurrectionPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public int playerID; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(int playerID){ + this.playerID = playerID; + } + + @Override + public void fromBytes(ByteBuf buf){ + this.playerID = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(playerID); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSpellProperties.java b/src/main/java/electroblob/wizardry/packet/PacketSpellProperties.java new file mode 100644 index 00000000..acacde6a --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketSpellProperties.java @@ -0,0 +1,64 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.SpellProperties; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; +import java.util.List; + +/** [Server -> Client] This packet is sent to sync server-side spell properties with clients on login. */ +public class PacketSpellProperties implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isServer()){ + + final EntityPlayerMP player = ctx.getServerHandler().player; + + player.getServerWorld().addScheduledTask(() -> { + for(int i=0; i propertiesList = new ArrayList<>(); + int i = 0; + + while(buf.isReadable()){ + propertiesList.add(new SpellProperties(Spell.byNetworkID(i++), buf)); + } + + propertiesArray = propertiesList.toArray(new SpellProperties[0]); + } + + @Override + public void toBytes(ByteBuf buf){ + for(SpellProperties properties : propertiesArray) properties.write(buf); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncAdvancements.java b/src/main/java/electroblob/wizardry/packet/PacketSyncAdvancements.java new file mode 100644 index 00000000..f5fc1d78 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketSyncAdvancements.java @@ -0,0 +1,61 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import io.netty.buffer.ByteBuf; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.network.ByteBufUtils; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; + +/** [Server -> Client] This packet is fired on login and on advancement gain to update the handbook progress. */ +public class PacketSyncAdvancements implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleAdvancementSyncPacket(message)); + } + + return null; + } + + public static class Message implements IMessage { + + public boolean showToasts; + public ResourceLocation[] completedAdvancements; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){ + } + + public Message(boolean showToasts, ResourceLocation... completed){ + this.showToasts = showToasts; + this.completedAdvancements = completed; + } + + @Override + public void fromBytes(ByteBuf buf){ + showToasts = buf.readBoolean(); + ArrayList advancements = new ArrayList<>(); + while(buf.isReadable()){ + advancements.add(new ResourceLocation(ByteBufUtils.readUTF8String(buf))); + } + this.completedAdvancements = advancements.toArray(new ResourceLocation[0]); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeBoolean(showToasts); + for(ResourceLocation advancement : completedAdvancements){ + ByteBufUtils.writeUTF8String(buf, advancement.toString()); + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java index c8afb156..9fc16ac5 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java +++ b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java @@ -21,13 +21,7 @@ public class PacketSyncSettings implements IMessageHandler { if(ctx.side.isClient()){ // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy // any more than necessary. - net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(new Runnable(){ - @Override - public void run(){ - copySettings(message); - } - }); - + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> copySettings(message)); } return null; @@ -35,16 +29,14 @@ public class PacketSyncSettings implements IMessageHandler { private static void copySettings(Message message){ Wizardry.settings.discoveryMode = message.settings.discoveryMode; - // Wizardry.settings.maxSpellCommandMultiplier = message.settings.maxSpellCommandMultiplier; - // Wizardry.settings.castCommandName = message.settings.castCommandName; - // Wizardry.settings.discoverspellCommandName = message.settings.discoverspellCommandName; - // Wizardry.settings.allyCommandName = message.settings.allyCommandName; - // Wizardry.settings.alliesCommandName = message.settings.alliesCommandName; + Wizardry.settings.creativeBypassesArcaneLock = message.settings.creativeBypassesArcaneLock; + Wizardry.settings.slowTimeAffectsPlayers = message.settings.slowTimeAffectsPlayers; + Wizardry.settings.forfeitChance = message.settings.forfeitChance; } public static class Message implements IMessage { - /** EntityID of the caster */ + /** Instance of wizardry's settings object */ public Settings settings; // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) @@ -62,21 +54,17 @@ public class PacketSyncSettings implements IMessageHandler { settings = new Settings(); // The order is important settings.discoveryMode = buf.readBoolean(); - // settings.maxSpellCommandMultiplier = buf.readDouble(); - // settings.castCommandName = ByteBufUtils.readUTF8String(buf); - // settings.discoverspellCommandName = ByteBufUtils.readUTF8String(buf); - // settings.allyCommandName = ByteBufUtils.readUTF8String(buf); - // settings.alliesCommandName = ByteBufUtils.readUTF8String(buf); + settings.creativeBypassesArcaneLock = buf.readBoolean(); + settings.slowTimeAffectsPlayers = buf.readBoolean(); + settings.forfeitChance = buf.readFloat(); } @Override public void toBytes(ByteBuf buf){ buf.writeBoolean(settings.discoveryMode); - // buf.writeDouble(settings.maxSpellCommandMultiplier); - // ByteBufUtils.writeUTF8String(buf, settings.castCommandName); - // ByteBufUtils.writeUTF8String(buf, settings.discoverspellCommandName); - // ByteBufUtils.writeUTF8String(buf, settings.allyCommandName); - // ByteBufUtils.writeUTF8String(buf, settings.alliesCommandName); + buf.writeBoolean(settings.creativeBypassesArcaneLock); + buf.writeBoolean(settings.slowTimeAffectsPlayers); + buf.writeFloat((float)settings.forfeitChance); // Configs don't have floats but this can only be 0-1 anyway } } } diff --git a/src/main/java/electroblob/wizardry/packet/PacketTransportation.java b/src/main/java/electroblob/wizardry/packet/PacketTransportation.java index 8f6e5a0e..afb07f7d 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketTransportation.java +++ b/src/main/java/electroblob/wizardry/packet/PacketTransportation.java @@ -19,12 +19,7 @@ public class PacketTransportation implements IMessageHandler if(ctx.side.isClient()){ // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy // methods any more than necessary. - net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(new Runnable(){ - @Override - public void run(){ - Wizardry.proxy.handleTransportationPacket(message); - } - }); + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleTransportationPacket(message)); } return null; diff --git a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java index ed40e136..41d12d0d 100644 --- a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java +++ b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java @@ -22,6 +22,16 @@ public class WizardryPacketHandler { registerMessage(PacketClairvoyance.class, PacketClairvoyance.Message.class); registerMessage(PacketSyncSettings.class, PacketSyncSettings.Message.class); registerMessage(PacketNPCCastSpell.class, PacketNPCCastSpell.Message.class); + registerMessage(PacketDispenserCastSpell.class, PacketDispenserCastSpell.Message.class); + registerMessage(PacketSpellProperties.class, PacketSpellProperties.Message.class); + registerMessage(PacketSyncAdvancements.class, PacketSyncAdvancements.Message.class); + registerMessage(PacketRequestAdvancementSync.class, PacketRequestAdvancementSync.Message.class); + registerMessage(PacketEndSlowTime.class, PacketEndSlowTime.Message.class); + registerMessage(PacketResurrection.class, PacketResurrection.Message.class); + registerMessage(PacketCastSpellAtPos.class, PacketCastSpellAtPos.Message.class); + registerMessage(PacketEmitterData.class, PacketEmitterData.Message.class); + registerMessage(PacketPossession.class, PacketPossession.Message.class); + registerMessage(PacketConquerShrine.class, PacketConquerShrine.Message.class); } private static int nextPacketId = 0; diff --git a/src/main/java/electroblob/wizardry/potion/Curse.java b/src/main/java/electroblob/wizardry/potion/Curse.java new file mode 100644 index 00000000..09b9e8ab --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/Curse.java @@ -0,0 +1,70 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import net.minecraft.item.ItemStack; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.ArrayList; +import java.util.List; + +/** A curse is a permanent potion effect, which is displayed in the inventory with a special background and + * no timer. It also allows for longer potion effect names by wrapping them onto two lines. */ +public class Curse extends PotionMagicEffect { + + private static final ResourceLocation BACKGROUND = new ResourceLocation(Wizardry.MODID, "textures/gui/curse_background.png"); + + public Curse(boolean isBadEffect, int liquidColour, ResourceLocation texture){ + super(isBadEffect, liquidColour, texture); + } + + @Override + public boolean shouldRenderInvText(PotionEffect effect){ + return false; + } + + @Override + public List getCurativeItems(){ + return new ArrayList<>(); // Cannot be cured! + } + + @Override + @SideOnly(Side.CLIENT) + public void renderInventoryEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc){ + + mc.renderEngine.bindTexture(BACKGROUND); + electroblob.wizardry.client.DrawingUtils.drawTexturedRect(x, y, 0, 0, 140, 32, 256, 256); + + super.renderInventoryEffect(x, y, effect, mc); + + String name = net.minecraft.client.resources.I18n.format(this.getName()); + + // Amplifier 0 (which would be I) is not rendered and the tooltips only go up to X (amplifier 9) + // The vanilla implementation uses elseifs and only goes up to 4... how lazy. + if(effect.getAmplifier() > 0 && effect.getAmplifier() < 10){ + name = name + " " + net.minecraft.client.resources.I18n.format("enchantment.level." + (effect.getAmplifier() + 1)); + } + + List lines = mc.fontRenderer.listFormattedStringToWidth(name, 100); + + int i=0; + for(String line : lines){ + int h = lines.size() == 1 ? 5 : i * (mc.fontRenderer.FONT_HEIGHT + 1); + mc.fontRenderer.drawStringWithShadow(line, (float)(x + 10 + 18), (float)(y + 6 + h), 0xbf00ee); + i++; + } + } + + @Override + @SideOnly(Side.CLIENT) + public void renderHUDEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc, float alpha){ + + net.minecraft.client.renderer.GlStateManager.color(1, 1, 1, 1); + mc.renderEngine.bindTexture(BACKGROUND); + electroblob.wizardry.client.DrawingUtils.drawTexturedRect(x, y, 141, 0, 24, 24, 256, 256); + super.renderHUDEffect(x, y, effect, mc, alpha); + } + +} diff --git a/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java b/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java new file mode 100644 index 00000000..69daa132 --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java @@ -0,0 +1,49 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryPotions; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.util.FoodStats; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import java.lang.reflect.Field; + +@Mod.EventBusSubscriber +public class CurseEnfeeblement extends Curse { + + // Yay more reflection + private static final Field foodTimer; + + static { + foodTimer = ObfuscationReflectionHelper.findField(FoodStats.class, "field_75123_d"); + foodTimer.setAccessible(true); + } + + public CurseEnfeeblement(boolean isBadEffect, int liquiidColour){ + super(isBadEffect, liquiidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_curse_of_enfeeblement.png")); + // This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet. + this.setPotionName("potion." + Wizardry.MODID + ":curse_of_enfeeblement"); + this.registerPotionAttributeModifier(SharedMonsterAttributes.MAX_HEALTH, + "2e8c378e-3d51-4ba1-b02c-591b5d968a05", -0.2, 1); + } + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + // Players are the only entities with natural regeneration + // This can't be done in performEffect as that method only gets called every 20 ticks or so + // Don't bother trying to prevent it unless the player is full enough + if(event.player.isPotionActive(WizardryPotions.curse_of_enfeeblement) && event.player.getFoodStats().getFoodLevel() > 17){ + try{ + // Constantly setting this to zero prevents natural regeneration + foodTimer.set(event.player.getFoodStats(), 0); + }catch(IllegalAccessException e){ + Wizardry.logger.error("Error setting player food timer: ", e); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/potion/CurseUndeath.java b/src/main/java/electroblob/wizardry/potion/CurseUndeath.java new file mode 100644 index 00000000..6f2d0a9f --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/CurseUndeath.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; + +public class CurseUndeath extends Curse { + + public CurseUndeath(boolean isBadEffect, int liquiidColour){ + super(isBadEffect, liquiidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_curse_of_undeath.png")); + // This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet. + this.setPotionName("potion." + Wizardry.MODID + ":curse_of_undeath"); + } + + @Override + public boolean isReady(int duration, int amplifier){ + return true; + } + + @Override + public void performEffect(EntityLivingBase entitylivingbase, int strength){ + + // Adapted from EntityZombie + if(entitylivingbase.world.isDaytime() && !entitylivingbase.world.isRemote){ + + float f = entitylivingbase.getBrightness(); + + if(f > 0.5F && entitylivingbase.world.rand.nextFloat() * 30.0F < (f - 0.4F) * 2.0F + && entitylivingbase.world.canSeeSky(new BlockPos(entitylivingbase.posX, + entitylivingbase.posY + (double)entitylivingbase.getEyeHeight(), entitylivingbase.posZ))){ + + boolean flag = true; + ItemStack itemstack = entitylivingbase.getItemStackFromSlot(EntityEquipmentSlot.HEAD); + + if(!itemstack.isEmpty()){ + if(itemstack.isItemStackDamageable()){ + + itemstack.setItemDamage(itemstack.getItemDamage() + entitylivingbase.world.rand.nextInt(2)); + + if(itemstack.getItemDamage() >= itemstack.getMaxDamage()){ + entitylivingbase.renderBrokenItemStack(itemstack); + entitylivingbase.setItemStackToSlot(EntityEquipmentSlot.HEAD, ItemStack.EMPTY); + } + } + + flag = false; + } + + if(flag){ + entitylivingbase.setFire(8); + } + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java b/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java index 8bc4a30c..ab0ec6e0 100644 --- a/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java +++ b/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java @@ -1,20 +1,31 @@ package electroblob.wizardry.potion; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; +import net.minecraft.potion.PotionUtils; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.event.entity.living.PotionColorCalculationEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.stream.Collectors; + /** - * Interface for potion effects that spawn custom particles instead of (or as well as) the vanilla 'swirly' particles. + * Interface for potion effects that spawn custom particles instead of (or as well as) the vanilla 'swirly' particles.
    + *
    + * To hide the vanilla 'swirly' particles, set the potion's liquid colour to 0 (black). By default, potions that + * implement this interface do not mix their colour with other potions.
    + *
    + * Potions that implement this interface also implement {@link ISyncedPotion} since any custom particles require syncing + * to disappear correctly when the effect ends; if syncing is not required, override + * {@link ISyncedPotion#shouldSync(EntityLivingBase)} to return false. * * @author Electroblob * @since Wizardry 1.2 */ -// TODO: Backport. @Mod.EventBusSubscriber -public interface ICustomPotionParticles { +public interface ICustomPotionParticles extends ISyncedPotion { /** * Called from the event handler to spawn a single custom potion particle. To get an instance of @@ -26,6 +37,11 @@ public interface ICustomPotionParticles { * @param z The z coordinate of the particle, already set to a random value within the entity's bounding box. */ void spawnCustomParticle(World world, double x, double y, double z); + + /** Returns true if this potion should mix its colour with others, false if not. Defaults to false. */ + default boolean shouldMixColour(){ + return false; + } @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ @@ -42,10 +58,18 @@ public interface ICustomPotionParticles { double z = event.getEntityLiving().posZ + (event.getEntityLiving().world.rand.nextDouble() - 0.5) * event.getEntityLiving().width; - ((ICustomPotionParticles)effect.getPotion()).spawnCustomParticle(event.getEntityLiving().world, x, - y, z); + ((ICustomPotionParticles)effect.getPotion()).spawnCustomParticle(event.getEntityLiving().world, x, y, z); } } } } + + @SubscribeEvent + // Prevents instances of this interface for which shouldMixColour() returns false from affecting mixed potion colours + public static void onPotionColourCalculationEvent(PotionColorCalculationEvent event){ + event.setColor(PotionUtils.getPotionColorFromEffectList(event.getEffects().stream().filter( + p -> !(p instanceof ICustomPotionParticles && !((ICustomPotionParticles)p).shouldMixColour())) + .collect(Collectors.toList()))); + } + } diff --git a/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java b/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java new file mode 100644 index 00000000..76741001 --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java @@ -0,0 +1,81 @@ +package electroblob.wizardry.potion; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.SPacketEntityEffect; +import net.minecraft.network.play.server.SPacketRemoveEntityEffect; +import net.minecraftforge.event.entity.living.PotionEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * Interface for potion effects that need syncing to ensure client and server side are consistent. Simply implement + * this interface and the potion will be synced automatically. + * + * @author Electroblob + * @since Wizardry 1.2 + */ +@Mod.EventBusSubscriber +public interface ISyncedPotion { + + /** The distance from an entity with this effect within which players will receive potion update packets. */ + double SYNC_RADIUS = 64; + + /** Returns true if this potion should sync with nearby clients when added to / removed from an entity and on + * expiry, false if not. The host entity is provided in case syncing is entity-dependent. Defaults to true. */ + default boolean shouldSync(EntityLivingBase host){ + return true; + } + + // The following event handlers fix the inconsistencies caused by clients not syncing correctly + // These packets are only sent for players with potion effects in vanilla, and only to that player's client + + // This one is only actually necessary if the effect gets added via a server-side method e.g. commands + // Unfortunately there's no way of checking that, so we'll just have to live with the extra packets + @SubscribeEvent + public static void onPotionAddedEvent(PotionEvent.PotionAddedEvent event){ + + if(event.getPotionEffect().getPotion() instanceof ISyncedPotion + && ((ISyncedPotion)event.getPotionEffect().getPotion()).shouldSync(event.getEntityLiving())){ + + if(!event.getEntityLiving().world.isRemote){ + event.getEntityLiving().world.playerEntities.stream() + .filter(p -> p.getDistanceSq(event.getEntityLiving()) < SYNC_RADIUS * SYNC_RADIUS) + // Apparently unchecked casting in a lambda expression doesn't generate a warning. Who knew? + // (We know this cast is safe though) + .forEach(p -> ((EntityPlayerMP)p).connection.sendPacket(new SPacketEntityEffect( + event.getEntity().getEntityId(), event.getPotionEffect()))); + } + } + } + + @SubscribeEvent + public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ + + if(event.getPotionEffect().getPotion() instanceof ISyncedPotion + && ((ISyncedPotion)event.getPotionEffect().getPotion()).shouldSync(event.getEntityLiving())){ + + if(!event.getEntityLiving().world.isRemote){ + event.getEntityLiving().world.playerEntities.stream() + .filter(p -> p.getDistanceSq(event.getEntityLiving()) < SYNC_RADIUS * SYNC_RADIUS) + .forEach(p -> ((EntityPlayerMP)p).connection.sendPacket(new SPacketRemoveEntityEffect( + event.getEntity().getEntityId(), event.getPotionEffect().getPotion()))); + } + } + } + + @SubscribeEvent + public static void onPotionRemoveEvent(PotionEvent.PotionRemoveEvent event){ + + if(event.getPotionEffect().getPotion() instanceof ISyncedPotion + && ((ISyncedPotion)event.getPotionEffect().getPotion()).shouldSync(event.getEntityLiving())){ + + if(!event.getEntityLiving().world.isRemote){ + event.getEntityLiving().world.playerEntities.stream() + .filter(p -> p.getDistanceSq(event.getEntityLiving()) < SYNC_RADIUS * SYNC_RADIUS) + .forEach(p -> ((EntityPlayerMP)p).connection.sendPacket(new SPacketRemoveEntityEffect( + event.getEntity().getEntityId(), event.getPotionEffect().getPotion()))); + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/potion/PotionContainment.java b/src/main/java/electroblob/wizardry/potion/PotionContainment.java new file mode 100644 index 00000000..672d6ba6 --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/PotionContainment.java @@ -0,0 +1,121 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.network.play.server.SPacketEntityVelocity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@Mod.EventBusSubscriber +public class PotionContainment extends PotionMagicEffect { + + public static final String ENTITY_TAG = "containmentPos"; + + public PotionContainment(boolean isBadEffect, int liquidColour){ + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_containment.png")); + this.setPotionName("potion." + Wizardry.MODID + ":containment"); + } + + @Override + public boolean isReady(int duration, int amplifier){ + return true; // Execute the effect every tick + } + + public static float getContainmentDistance(int effectStrength){ + return 15 - effectStrength * 4; + } + + @Override + public void performEffect(EntityLivingBase target, int strength){ + + float maxDistance = getContainmentDistance(strength); + + // Initialise the containment position to the entity's position if it wasn't set already + if(!target.getEntityData().hasKey(ENTITY_TAG)){ + target.getEntityData().setTag(ENTITY_TAG, NBTUtil.createPosTag(new BlockPos(target.getPositionVector().subtract(0.5, 0.5, 0.5)))); + } + + Vec3d origin = WizardryUtilities.getCentre(NBTUtil.getPosFromTag(target.getEntityData().getCompoundTag(ENTITY_TAG))); + + double x = target.posX, y = target.posY, z = target.posZ; + + // Containment fields are cubes so we're dealing with each axis separately + if(target.getEntityBoundingBox().maxX > origin.x + maxDistance) x = origin.x + maxDistance - target.width/2; + if(target.getEntityBoundingBox().minX < origin.x - maxDistance) x = origin.x - maxDistance + target.width/2; + + if(target.getEntityBoundingBox().maxY > origin.y + maxDistance) y = origin.y + maxDistance - target.height; + if(target.getEntityBoundingBox().minY < origin.y - maxDistance) y = origin.y - maxDistance; + + if(target.getEntityBoundingBox().maxZ > origin.z + maxDistance) z = origin.z + maxDistance - target.width/2; + if(target.getEntityBoundingBox().minZ < origin.z - maxDistance) z = origin.z - maxDistance + target.width/2; + + if(x != target.posX || y != target.posY || z != target.posZ){ + +// if(target.world.isRemote){ +// +// if(x != target.posX){ +// for(int i = 0; i < 20; i++){ +// ParticleBuilder.create(ParticleBuilder.Type.DUST).pos( +// x, +// target.getEntityBoundingBox().minY + target.height * target.world.rand.nextFloat(), +// target.posZ + target.width * (target.world.rand.nextFloat() - 0.5f)) +// .face(EnumFacing.EAST).clr(0.8f, 0.9f, 1).spawn(target.world); +// } +// } +// +// if(y != target.posY){ +// for(int i = 0; i < 20; i++){ +// ParticleBuilder.create(ParticleBuilder.Type.DUST).pos( +// target.posX + target.width * (target.world.rand.nextFloat() - 0.5f), +// y, +// target.posZ + target.width * (target.world.rand.nextFloat() - 0.5f)) +// .face(EnumFacing.UP).clr(0.8f, 0.9f, 1).spawn(target.world); +// } +// } +// +// if(z != target.posZ){ +// for(int i = 0; i < 20; i++){ +// ParticleBuilder.create(ParticleBuilder.Type.DUST).pos( +// target.posX + target.width * (target.world.rand.nextFloat() - 0.5f), +// target.getEntityBoundingBox().minY + target.height * target.world.rand.nextFloat(), +// z) +// .face(EnumFacing.SOUTH).clr(0.8f, 0.9f, 1).spawn(target.world); +// } +// } +// } + + WizardryUtilities.undoGravity(target); + target.addVelocity(0.35 * Math.signum(x - target.posX), 0.35 * Math.signum(y - target.posY), 0.35 * Math.signum(z - target.posZ)); + target.setPositionAndUpdate(x, y, z); + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + } +// +// target.world.playSound(target.posX, target.posY, target.posZ, WizardrySounds.ENTITY_FORCEFIELD_DEFLECT, +// WizardrySounds.SPELLS, 0.3f, 1f, false); + + } + + // Need to do this here because it's the only way to hook into potion ending both client- and server-side + if(target.getActivePotionEffect(this).getDuration() <= 1) target.getEntityData().removeTag(ENTITY_TAG); + + } + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + if(event.getEntityLiving().getEntityData().hasKey(ENTITY_TAG) + && !event.getEntityLiving().isPotionActive(WizardryPotions.containment)){ + event.getEntityLiving().getEntityData().removeTag(ENTITY_TAG); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/potion/PotionDecay.java b/src/main/java/electroblob/wizardry/potion/PotionDecay.java index c16cb8db..c4562d3c 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionDecay.java +++ b/src/main/java/electroblob/wizardry/potion/PotionDecay.java @@ -1,32 +1,25 @@ package electroblob.wizardry.potion; -import java.util.List; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.entity.construct.EntityDecay; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.potion.Potion; -import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.util.ResourceLocation; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.List; @Mod.EventBusSubscriber -public class PotionDecay extends Potion { - - private static final ResourceLocation ICON = new ResourceLocation(Wizardry.MODID, "textures/gui/decay_icon.png"); +public class PotionDecay extends PotionMagicEffect { public PotionDecay(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_decay.png")); // This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet. this.setPotionName("potion." + Wizardry.MODID + ":decay"); this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED, @@ -34,32 +27,18 @@ public class PotionDecay extends Potion { } @Override - public boolean isReady(int p_76397_1_, int p_76397_2_){ + public boolean isReady(int duration, int amplifier){ // Copied from the vanilla wither effect. It does the timing stuff. 25 is the number of ticks between hits at // amplifier 0 - int k = 25 >> p_76397_2_; - return k > 0 ? p_76397_1_ % k == 0 : true; + int k = 25 >> amplifier; + return k > 0 ? duration % k == 0 : true; } @Override - public void performEffect(EntityLivingBase target, int strength){ - target.attackEntityFrom(DamageSource.WITHER, 1); + public void performEffect(EntityLivingBase host, int strength){ + host.attackEntityFrom(DamageSource.WITHER, 1); } - @Override - @SideOnly(Side.CLIENT) - public void renderInventoryEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc){ - mc.renderEngine.bindTexture(ICON); - WizardryUtilities.drawTexturedRect(x + 6, y + 7, 0, 0, 18, 18, 18, 18); - } - - @Override - @SideOnly(Side.CLIENT) - public void renderHUDEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc, float alpha){ - mc.renderEngine.bindTexture(ICON); - WizardryUtilities.drawTexturedRect(x + 3, y + 3, 0, 0, 18, 18, 18, 18); - } - @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ @@ -68,8 +47,8 @@ public class PotionDecay extends Potion { EntityLivingBase target = event.getEntityLiving(); - if(target.isPotionActive(WizardryPotions.decay) && target.ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0 - && target.onGround){ + if(!target.world.isRemote && target.isPotionActive(WizardryPotions.decay) && target.onGround + && target.ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0){ List entities = target.world.getEntitiesWithinAABBExcludingEntity(target, target.getEntityBoundingBox()); @@ -80,7 +59,10 @@ public class PotionDecay extends Potion { // The victim spreading the decay is the 'caster' here, so that it can actually wear off, otherwise it // just gets infected with its own decay and the effect lasts forever. - target.world.spawnEntity(new EntityDecay(target.world, target.posX, target.posY, target.posZ, target)); + EntityDecay decay = new EntityDecay(target.world); + decay.setCaster(target); + decay.setPosition(target.posX, target.posY, target.posZ); + target.world.spawnEntity(decay); } } diff --git a/src/main/java/electroblob/wizardry/potion/PotionFrost.java b/src/main/java/electroblob/wizardry/potion/PotionFrost.java index 6f267755..28b89553 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionFrost.java +++ b/src/main/java/electroblob/wizardry/potion/PotionFrost.java @@ -3,27 +3,20 @@ package electroblob.wizardry.potion; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLivingBase; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.potion.Potion; -import net.minecraft.potion.PotionEffect; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; @Mod.EventBusSubscriber -public class PotionFrost extends Potion implements ICustomPotionParticles { - - private static final ResourceLocation ICON = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_icon.png"); +public class PotionFrost extends PotionMagicEffect implements ICustomPotionParticles { public PotionFrost(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_frost.png")); // This needs to be here because registerPotionAttributeModifier doesn't like it if the potion has no name yet. this.setPotionName("potion." + Wizardry.MODID + ":frost"); // With -0.5 as the 'amount', frost 1 slows the entity down by a half and frost 2 roots it to the spot @@ -32,29 +25,9 @@ public class PotionFrost extends Potion implements ICustomPotionParticles { // More UUIDs: 85602e0b-4801-4a87-94f3-bf617c97014e } - @Override - public void performEffect(EntityLivingBase entitylivingbase, int strength){ - // Nothing here because this potion works on attribute modifiers. - } - @Override public void spawnCustomParticle(World world, double x, double y, double z){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, x, y, z, 0, -0.02, 0, - 15 + world.rand.nextInt(5)); - } - - @Override - @SideOnly(Side.CLIENT) - public void renderInventoryEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc){ - mc.renderEngine.bindTexture(ICON); - WizardryUtilities.drawTexturedRect(x + 6, y + 7, 0, 0, 18, 18, 18, 18); - } - - @Override - @SideOnly(Side.CLIENT) - public void renderHUDEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc, float alpha){ - mc.renderEngine.bindTexture(ICON); - WizardryUtilities.drawTexturedRect(x + 3, y + 3, 0, 0, 18, 18, 18, 18); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).time(15 + world.rand.nextInt(5)).spawn(world); } @SubscribeEvent diff --git a/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java b/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java new file mode 100644 index 00000000..4c369526 --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java @@ -0,0 +1,116 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.block.BlockLiquid; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.enchantment.EnchantmentFrostWalker; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.lang.reflect.Field; + +@Mod.EventBusSubscriber +public class PotionFrostStep extends PotionMagicEffect implements ICustomPotionParticles { + + private static final Field prevBlockPos = ObfuscationReflectionHelper.findField(EntityLivingBase.class, "field_184620_bC"); + + public PotionFrostStep(boolean isBadEffect, int liquidColour){ + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_frost_step.png")); + this.setPotionName("potion." + Wizardry.MODID + ":frost_step"); + } + +// @Override +// public boolean isReady(int duration, int amplifier){ +// return true; // Execute the effect every tick +// } + + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.SNOW).pos(x, y, z).time(15 + world.rand.nextInt(5)).spawn(world); + } + + // Use LivingUpdateEvent instead of performEffect because it gets called before the actual frost walker processing + // performEffect is called afterwards, at which point prevBlockPos has already been set to the current position + // regardless of whether the player is wearing frost walker boots or not + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + + EntityLivingBase host = event.getEntityLiving(); + + if(host.isPotionActive(WizardryPotions.frost_step)){ + // Mimics the behaviour of the frost walker enchantment itself + if(!host.world.isRemote){ + + BlockPos currentPos = new BlockPos(host); + + try{ + + if(!currentPos.equals(prevBlockPos.get(host))){ + + prevBlockPos.set(host, currentPos); + + int strength = host.getActivePotionEffect(WizardryPotions.frost_step).getAmplifier(); + + EnchantmentFrostWalker.freezeNearby(host, host.world, currentPos, strength); + + if(host instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)host, WizardryItems.charm_lava_walking)){ + freezeNearbyLava(host, host.world, currentPos, strength); + } + } + + }catch(IllegalAccessException e){ + Wizardry.logger.error("Error accessing living entity previous block pos:", e); + } + } + } + } + + /** Copied from {@link EnchantmentFrostWalker#freezeNearby(EntityLivingBase, World, BlockPos, int)} and modified + * to turn lava to obsidian crust blocks instead. */ + private static void freezeNearbyLava(EntityLivingBase living, World world, BlockPos pos, int level){ + + if(living.onGround){ + + float f = (float)Math.min(16, 2 + level); + BlockPos.MutableBlockPos pos1 = new BlockPos.MutableBlockPos(0, 0, 0); + + for(BlockPos.MutableBlockPos pos2 : BlockPos.getAllInBoxMutable(pos.add((double)(-f), -1.0D, (double)(-f)), pos.add((double)f, -1.0D, (double)f))){ + + if(pos2.distanceSqToCenter(living.posX, living.posY, living.posZ) <= (double)(f * f)){ + + pos1.setPos(pos2.getX(), pos2.getY() + 1, pos2.getZ()); + IBlockState state1 = world.getBlockState(pos1); + + if(state1.getMaterial() == Material.AIR){ + + IBlockState state2 = world.getBlockState(pos2); + + if(state2.getMaterial() == Material.LAVA && (state2.getBlock() == Blocks.LAVA || state2.getBlock() == Blocks.FLOWING_LAVA) && state2.getValue(BlockLiquid.LEVEL) == 0 && world.mayPlace(WizardryBlocks.obsidian_crust, pos2, false, EnumFacing.DOWN, null)){ + world.setBlockState(pos2, WizardryBlocks.obsidian_crust.getDefaultState()); + world.scheduleUpdate(pos2.toImmutable(), WizardryBlocks.obsidian_crust, MathHelper.getInt(living.getRNG(), 60, 120)); + } + } + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/potion/PotionMagicEffect.java b/src/main/java/electroblob/wizardry/potion/PotionMagicEffect.java index 02e6bc7a..72b2f747 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionMagicEffect.java +++ b/src/main/java/electroblob/wizardry/potion/PotionMagicEffect.java @@ -1,7 +1,5 @@ package electroblob.wizardry.potion; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionEffect; @@ -9,15 +7,17 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -/** Class for all potions that work on events only. */ +/** + * As of Wizardry 4.2, this class is used by all of wizardry's potions. Potions that work solely on events + * instantiate this class directly, all other potions extend it. + */ public class PotionMagicEffect extends Potion { - private static final ResourceLocation ICONS = new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons.png"); - private final int textureIndex; + private final ResourceLocation texture; - public PotionMagicEffect(boolean isBadEffect, int liquidColour, int textureIndex){ + public PotionMagicEffect(boolean isBadEffect, int liquidColour, ResourceLocation texture){ super(isBadEffect, liquidColour); - this.textureIndex = textureIndex; + this.texture = texture; } @Override @@ -28,17 +28,20 @@ public class PotionMagicEffect extends Potion { @Override @SideOnly(Side.CLIENT) public void renderInventoryEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc){ - mc.renderEngine.bindTexture(ICONS); - WizardryUtilities.drawTexturedRect(x + 6, y + 7, 18 * (textureIndex % 4), 18 * (textureIndex / 4), 18, 18, 72, - 72); + drawIcon(x + 6, y + 7, effect, mc); } @Override @SideOnly(Side.CLIENT) public void renderHUDEffect(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc, float alpha){ - mc.renderEngine.bindTexture(ICONS); - WizardryUtilities.drawTexturedRect(x + 3, y + 3, 18 * (textureIndex % 4), 18 * (textureIndex / 4), 18, 18, 72, - 72); + net.minecraft.client.renderer.GlStateManager.color(1, 1, 1, alpha); + drawIcon(x + 3, y + 3, effect, mc); + } + + @SideOnly(Side.CLIENT) + protected void drawIcon(int x, int y, PotionEffect effect, net.minecraft.client.Minecraft mc){ + mc.renderEngine.bindTexture(texture); + electroblob.wizardry.client.DrawingUtils.drawTexturedRect(x, y, 0, 0, 18, 18, 18, 18); } } diff --git a/src/main/java/electroblob/wizardry/potion/PotionMagicEffectParticles.java b/src/main/java/electroblob/wizardry/potion/PotionMagicEffectParticles.java index 8112a43b..eea1b759 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionMagicEffectParticles.java +++ b/src/main/java/electroblob/wizardry/potion/PotionMagicEffectParticles.java @@ -1,13 +1,16 @@ package electroblob.wizardry.potion; +import net.minecraft.util.ResourceLocation; + /** * Same as {@link PotionMagicEffect}, but also implements {@link ICustomPotionParticles} to allow anonymous classes to - * extend it and add their own particles. + * extend it and add their own particles. It is advised that all other (named) classes extend and implement the + * underlying class and interface rather than extending this class. */ public abstract class PotionMagicEffectParticles extends PotionMagicEffect implements ICustomPotionParticles { - public PotionMagicEffectParticles(boolean isBadEffect, int liquidColour, int textureIndex){ - super(isBadEffect, liquidColour, textureIndex); + public PotionMagicEffectParticles(boolean isBadEffect, int liquidColour, ResourceLocation texture){ + super(isBadEffect, liquidColour, texture); } } diff --git a/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java b/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java new file mode 100644 index 00000000..b6e1f51d --- /dev/null +++ b/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java @@ -0,0 +1,172 @@ +package electroblob.wizardry.potion; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.packet.PacketEndSlowTime; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.SlowTime; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.IProjectile; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.event.entity.living.PotionEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.List; + +@Mod.EventBusSubscriber +public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { + + // FIXME: Minecarts with entities in them (and, I suspect, any other ridden entities) go crazy when time-slowed + + public PotionSlowTime(boolean isBadEffect, int liquidColour){ + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_slow_time.png")); + this.setPotionName("potion." + Wizardry.MODID + ":slow_time"); + } + + private static double getEffectRadius(){ + return Spells.slow_time.getProperty(Spell.EFFECT_RADIUS).doubleValue(); + } + + public static void unblockNearbyEntities(EntityLivingBase host){ + List targetsBeyondRange = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius() + 3, host.posX, host.posY, host.posZ, host.world, Entity.class); + targetsBeyondRange.forEach(e -> e.updateBlocked = false); + } + + // Not done in performEffect because it's client-inconsistent; it only fires on the client of the player with the + // potion effect, and doesn't fire on the client at all for non-players + private static void performEffectConsistent(EntityLivingBase host, int strength){ + + boolean stopTime = host instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)host, WizardryItems.charm_stop_time); + + int interval = strength * 4 + 6; + + // Mark all entities within range + List targetsInRange = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius(), host.posX, host.posY, host.posZ, host.world, Entity.class); + targetsInRange.remove(host); + // Other entities with the slow time effect are unaffected + targetsInRange.removeIf(t -> t instanceof EntityLivingBase && ((EntityLivingBase)t).isPotionActive(WizardryPotions.slow_time)); + if(!Wizardry.settings.slowTimeAffectsPlayers) targetsInRange.removeIf(t -> t instanceof EntityPlayer); + targetsInRange.removeIf(t -> t instanceof EntityArrow && t.isEntityInsideOpaqueBlock()); + + for(Entity entity : targetsInRange){ + + // If time is stopped, block all updates; otherwise block all updates except every [interval] ticks + entity.updateBlocked = stopTime || host.ticksExisted % interval != 0; + + if(!stopTime && entity.world.isRemote){ + + // Client-side movement interpolation (smoothing) + + if(entity.onGround) entity.motionY = 0; // Don't ask. It just works. + +// if(entity instanceof EntityLivingBase){ +// ((EntityLivingBase)entity).prevLimbSwingAmount = ((EntityLivingBase)entity).limbSwingAmount; +// ((EntityLivingBase)entity).swingProgress = ((EntityLivingBase)entity).prevSwingProgress; +// ((EntityLivingBase)entity).renderYawOffset = ((EntityLivingBase)entity).prevRenderYawOffset; +// ((EntityLivingBase)entity).rotationYawHead = ((EntityLivingBase)entity).prevRotationYawHead; +// } + + if(entity.updateBlocked){ + // When the update is blocked, the entity is moved 1/interval times the distance it would have moved + double x = entity.posX + entity.motionX * 1d / (double)interval; + double y = entity.posY + entity.motionY * 1d / (double)interval; + double z = entity.posZ + entity.motionZ * 1d / (double)interval; + + entity.prevPosX = entity.posX; + entity.prevPosY = entity.posY; + entity.prevPosZ = entity.posZ; + + entity.posX = x; + entity.posY = y; + entity.posZ = z; + + }else{ + // When the update is not blocked, the entity is moved BACK 1-1/interval times the distance it moved + // This is because the entity already covered most of that distance when its update was blocked + entity.posX += entity.motionX * 1d / (double)interval; + entity.posY += entity.motionY * 1d / (double)interval; + entity.posZ += entity.motionZ * 1d / (double)interval; + + double x = entity.posX - entity.motionX * 1d / (double)interval; + double y = entity.posY - entity.motionY * 1d / (double)interval; + double z = entity.posZ - entity.motionZ * 1d / (double)interval; + + entity.prevPosX = x; + entity.prevPosY = y; + entity.prevPosZ = z; + } + } + + if(entity.world.isRemote && host.ticksExisted % 2 == 0){ + int lifetime = 15; + double dx = (entity.world.rand.nextDouble() - 0.5D) * 2 * (double)entity.width; + double dy = (entity.world.rand.nextDouble() - 0.5D) * 2 * (double)entity.width; + double dz = (entity.world.rand.nextDouble() - 0.5D) * 2 * (double)entity.width; + double x = entity.posX + dx; + double y = entity instanceof IProjectile ? entity.posY + dy : entity.posY + entity.height/2 + dy; + double z = entity.posZ + dz; + ParticleBuilder.create(ParticleBuilder.Type.DUST) + .pos(x, y, z) + .vel(-dx/lifetime, -dy/lifetime, -dz/lifetime) + .clr(0x5be3bb).time(15).spawn(entity.world); + } + } + + // Un-mark all entities that have just left range + List targetsBeyondRange = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius() + 3, host.posX, host.posY, host.posZ, host.world, Entity.class); + targetsBeyondRange.removeAll(targetsInRange); + targetsBeyondRange.forEach(e -> e.updateBlocked = false); + + } + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + + EntityLivingBase entity = event.getEntityLiving(); + + if(entity.isPotionActive(WizardryPotions.slow_time)){ + performEffectConsistent(entity, entity.getActivePotionEffect(WizardryPotions.slow_time).getAmplifier()); + } + } + + @SubscribeEvent + public static void onPotionAddedEvent(PotionEvent.PotionAddedEvent event){ + if(event.getEntity().world.isRemote && event.getPotionEffect().getPotion() == WizardryPotions.slow_time + && event.getEntity() == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SlowTime.SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); + } + } + + @SubscribeEvent + public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ + if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){ + unblockNearbyEntities(event.getEntityLiving()); + if(!event.getEntity().world.isRemote){ + WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension); + } + } + } + + @SubscribeEvent + public static void onPotionRemoveEvent(PotionEvent.PotionRemoveEvent event){ + if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){ + unblockNearbyEntities(event.getEntityLiving()); + if(!event.getEntity().world.isRemote){ + WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/registry/Spells.java b/src/main/java/electroblob/wizardry/registry/Spells.java index b573df79..5398da75 100644 --- a/src/main/java/electroblob/wizardry/registry/Spells.java +++ b/src/main/java/electroblob/wizardry/registry/Spells.java @@ -1,7 +1,12 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.construct.*; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.entity.projectile.*; import electroblob.wizardry.spell.*; +import net.minecraft.init.MobEffects; +import net.minecraft.item.EnumAction; import net.minecraft.util.ResourceLocation; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; @@ -10,29 +15,26 @@ import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder; import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.RegistryBuilder; +import javax.annotation.Nonnull; + /** - * Class responsible for defining, storing and registering all of wizardry's spells. + * Class responsible for defining, storing and registering all of wizardry's spells. Use this to access individual + * spell instances, similar to the {@code Blocks} and {@code Items} classes. * * @author Electroblob * @since Wizardry 2.1 */ -// In case anyone was wondering, the reason @ObjectHolder is useful within one mod is that it allows you to initialise -// stuff during the registry events (or whenever), whilst still having a final field (which is important, not only -// because it makes the text go bold, but also because it stops anyone fiddling with your fields). "Why would I want to -// initialise things within the registry events?", I hear you ask - well, for one, custom registries don't like it if -// you haven't created the registry before you start calling constructors of classes extending IForgeRegistryEntry.Impl, -// and secondly, you might want to initialise objects based on certain conditions - perhaps a config option, or whether -// another mod is installed. This, presumably, is why everyone at forge is encouraging us to use @ObjectHolder. @ObjectHolder(Wizardry.MODID) @Mod.EventBusSubscriber public final class Spells { + private Spells(){} // No instances! + // This is here because this class is already an event handler. @SubscribeEvent public static void createRegistry(RegistryEvent.NewRegistry event){ - // Beats me why we need both of these. Surely the type parameter means it already knows? - RegistryBuilder builder = new RegistryBuilder(); + RegistryBuilder builder = new RegistryBuilder<>(); builder.setType(Spell.class); builder.setName(new ResourceLocation(Wizardry.MODID, "spells")); builder.setIDRange(0, 5000); // Is there any penalty for using a larger number? @@ -40,194 +42,235 @@ public final class Spells { Spell.registry = builder.create(); } + @Nonnull + @SuppressWarnings("ConstantConditions") + private static T placeholder(){ return null; } + // Wizardry 1.0 spells - public static final Spell none = null; - public static final Spell magic_missile = null; - public static final Spell ignite = null; - public static final Spell freeze = null; - public static final Spell snowball = null; - public static final Spell arc = null; - public static final Spell thunderbolt = null; - public static final Spell summon_zombie = null; - public static final Spell snare = null; - public static final Spell dart = null; - public static final Spell light = null; - public static final Spell telekinesis = null; - public static final Spell heal = null; + public static final Spell none = placeholder(); + public static final Spell magic_missile = placeholder(); + public static final Spell ignite = placeholder(); + public static final Spell freeze = placeholder(); + public static final Spell snowball = placeholder(); + public static final Spell arc = placeholder(); + public static final Spell thunderbolt = placeholder(); + public static final Spell summon_zombie = placeholder(); + public static final Spell snare = placeholder(); + public static final Spell dart = placeholder(); + public static final Spell light = placeholder(); + public static final Spell telekinesis = placeholder(); + public static final Spell heal = placeholder(); - public static final Spell fireball = null; - public static final Spell flame_ray = null; - public static final Spell firebomb = null; - public static final Spell fire_sigil = null; - public static final Spell firebolt = null; - public static final Spell frost_ray = null; - public static final Spell summon_snow_golem = null; - public static final Spell ice_shard = null; - public static final Spell ice_statue = null; - public static final Spell frost_sigil = null; - public static final Spell lightning_ray = null; - public static final Spell spark_bomb = null; - public static final Spell homing_spark = null; - public static final Spell lightning_sigil = null; - public static final Spell lightning_arrow = null; - public static final Spell life_drain = null; - public static final Spell summon_skeleton = null; - public static final Spell metamorphosis = null; - public static final Spell wither = null; - public static final Spell poison = null; - public static final Spell growth_aura = null; - public static final Spell bubble = null; - public static final Spell whirlwind = null; - public static final Spell poison_bomb = null; - public static final Spell summon_spirit_wolf = null; - public static final Spell blink = null; - public static final Spell agility = null; - public static final Spell conjure_sword = null; - public static final Spell conjure_pickaxe = null; - public static final Spell conjure_bow = null; - public static final Spell force_arrow = null; - public static final Spell shield = null; - public static final Spell replenish_hunger = null; - public static final Spell cure_effects = null; - public static final Spell heal_ally = null; + public static final Spell fireball = placeholder(); + public static final Spell flame_ray = placeholder(); + public static final Spell firebomb = placeholder(); + public static final Spell fire_sigil = placeholder(); + public static final Spell firebolt = placeholder(); + public static final Spell frost_ray = placeholder(); + public static final Spell summon_snow_golem = placeholder(); + public static final Spell ice_shard = placeholder(); + public static final Spell ice_statue = placeholder(); + public static final Spell frost_sigil = placeholder(); + public static final Spell lightning_ray = placeholder(); + public static final Spell spark_bomb = placeholder(); + public static final Spell homing_spark = placeholder(); + public static final Spell lightning_sigil = placeholder(); + public static final Spell lightning_arrow = placeholder(); + public static final Spell life_drain = placeholder(); + public static final Spell summon_skeleton = placeholder(); + public static final Spell metamorphosis = placeholder(); + public static final Spell wither = placeholder(); + public static final Spell poison = placeholder(); + public static final Spell growth_aura = placeholder(); + public static final Spell bubble = placeholder(); + public static final Spell whirlwind = placeholder(); + public static final Spell poison_bomb = placeholder(); + public static final Spell summon_spirit_wolf = placeholder(); + public static final Spell blink = placeholder(); + public static final Spell agility = placeholder(); + public static final Spell conjure_sword = placeholder(); + public static final Spell conjure_pickaxe = placeholder(); + public static final Spell conjure_bow = placeholder(); + public static final Spell force_arrow = placeholder(); + public static final Spell shield = placeholder(); + public static final Spell replenish_hunger = placeholder(); + public static final Spell cure_effects = placeholder(); + public static final Spell heal_ally = placeholder(); - public static final Spell summon_blaze = null; - public static final Spell ring_of_fire = null; - public static final Spell detonate = null; - public static final Spell fire_resistance = null; - public static final Spell fireskin = null; - public static final Spell flaming_axe = null; - public static final Spell blizzard = null; - public static final Spell summon_ice_wraith = null; - public static final Spell ice_shroud = null; - public static final Spell ice_charge = null; - public static final Spell frost_axe = null; - public static final Spell invoke_weather = null; - public static final Spell chain_lightning = null; - public static final Spell lightning_bolt = null; - public static final Spell summon_lightning_wraith = null; - public static final Spell static_aura = null; - public static final Spell lightning_disc = null; - public static final Spell mind_control = null; - public static final Spell summon_wither_skeleton = null; - public static final Spell entrapment = null; - public static final Spell wither_skull = null; - public static final Spell darkness_orb = null; - public static final Spell shadow_ward = null; - public static final Spell decay = null; - public static final Spell water_breathing = null; - public static final Spell tornado = null; - public static final Spell glide = null; - public static final Spell summon_spirit_horse = null; - public static final Spell spider_swarm = null; - public static final Spell slime = null; - public static final Spell petrify = null; - public static final Spell invisibility = null; - public static final Spell levitation = null; - public static final Spell force_orb = null; - public static final Spell transportation = null; - public static final Spell spectral_pathway = null; - public static final Spell phase_step = null; - public static final Spell vanishing_box = null; - public static final Spell greater_heal = null; - public static final Spell healing_aura = null; - public static final Spell forcefield = null; - public static final Spell ironflesh = null; - public static final Spell transience = null; + public static final Spell summon_blaze = placeholder(); + public static final Spell ring_of_fire = placeholder(); + public static final Spell detonate = placeholder(); + public static final Spell fire_resistance = placeholder(); + public static final Spell fireskin = placeholder(); + public static final Spell flaming_axe = placeholder(); + public static final Spell blizzard = placeholder(); + public static final Spell summon_ice_wraith = placeholder(); + public static final Spell ice_shroud = placeholder(); + public static final Spell ice_charge = placeholder(); + public static final Spell frost_axe = placeholder(); + public static final Spell invoke_weather = placeholder(); + public static final Spell chain_lightning = placeholder(); + public static final Spell lightning_bolt = placeholder(); + public static final Spell summon_lightning_wraith = placeholder(); + public static final Spell static_aura = placeholder(); + public static final Spell lightning_disc = placeholder(); + public static final Spell mind_control = placeholder(); + public static final Spell summon_wither_skeleton = placeholder(); + public static final Spell entrapment = placeholder(); + public static final Spell wither_skull = placeholder(); + public static final Spell darkness_orb = placeholder(); + public static final Spell shadow_ward = placeholder(); + public static final Spell decay = placeholder(); + public static final Spell water_breathing = placeholder(); + public static final Spell tornado = placeholder(); + public static final Spell glide = placeholder(); + public static final Spell summon_spirit_horse = placeholder(); + public static final Spell spider_swarm = placeholder(); + public static final Spell slime = placeholder(); + public static final Spell petrify = placeholder(); + public static final Spell invisibility = placeholder(); + public static final Spell levitation = placeholder(); + public static final Spell force_orb = placeholder(); + public static final Spell transportation = placeholder(); + public static final Spell spectral_pathway = placeholder(); + public static final Spell phase_step = placeholder(); + public static final Spell vanishing_box = placeholder(); + public static final Spell greater_heal = placeholder(); + public static final Spell healing_aura = placeholder(); + public static final Spell forcefield = placeholder(); + public static final Spell ironflesh = placeholder(); + public static final Spell transience = placeholder(); - public static final Spell meteor = null; - public static final Spell firestorm = null; - public static final Spell summon_phoenix = null; - public static final Spell ice_age = null; - public static final Spell wall_of_frost = null; - public static final Spell summon_ice_giant = null; - public static final Spell thunderstorm = null; - public static final Spell lightning_hammer = null; - public static final Spell plague_of_darkness = null; - public static final Spell summon_skeleton_legion = null; - public static final Spell summon_shadow_wraith = null; - public static final Spell forests_curse = null; - public static final Spell flight = null; - public static final Spell silverfish_swarm = null; - public static final Spell black_hole = null; - public static final Spell shockwave = null; - public static final Spell summon_iron_golem = null; - public static final Spell arrow_rain = null; - public static final Spell diamondflesh = null; - public static final Spell font_of_vitality = null; + public static final Spell meteor = placeholder(); + public static final Spell fire_breath = placeholder(); + public static final Spell summon_phoenix = placeholder(); + public static final Spell ice_age = placeholder(); + public static final Spell wall_of_frost = placeholder(); + public static final Spell summon_ice_giant = placeholder(); + public static final Spell thunderstorm = placeholder(); + public static final Spell lightning_hammer = placeholder(); + public static final Spell plague_of_darkness = placeholder(); + public static final Spell summon_skeleton_legion = placeholder(); + public static final Spell summon_shadow_wraith = placeholder(); + public static final Spell forests_curse = placeholder(); + public static final Spell flight = placeholder(); + public static final Spell silverfish_swarm = placeholder(); + public static final Spell black_hole = placeholder(); + public static final Spell shockwave = placeholder(); + public static final Spell summon_iron_golem = placeholder(); + public static final Spell arrow_rain = placeholder(); + public static final Spell diamondflesh = placeholder(); + public static final Spell font_of_vitality = placeholder(); // Wizardry 1.1 spells - public static final Spell smoke_bomb = null; - public static final Spell mind_trick = null; - public static final Spell leap = null; + public static final Spell smoke_bomb = placeholder(); + public static final Spell mind_trick = placeholder(); + public static final Spell leap = placeholder(); - public static final Spell pocket_furnace = null; - public static final Spell intimidate = null; - public static final Spell banish = null; - public static final Spell sixth_sense = null; - public static final Spell darkvision = null; - public static final Spell clairvoyance = null; - public static final Spell pocket_workbench = null; - public static final Spell imbue_weapon = null; - public static final Spell invigorating_presence = null; - public static final Spell oakflesh = null; + public static final Spell pocket_furnace = placeholder(); + public static final Spell intimidate = placeholder(); + public static final Spell banish = placeholder(); + public static final Spell sixth_sense = placeholder(); + public static final Spell darkvision = placeholder(); + public static final Spell clairvoyance = placeholder(); + public static final Spell pocket_workbench = placeholder(); + public static final Spell imbue_weapon = placeholder(); + public static final Spell invigorating_presence = placeholder(); + public static final Spell oakflesh = placeholder(); - public static final Spell greater_fireball = null; - public static final Spell flaming_weapon = null; - public static final Spell ice_lance = null; - public static final Spell freezing_weapon = null; - public static final Spell ice_spikes = null; - public static final Spell lightning_pulse = null; - public static final Spell curse_of_soulbinding = null; - public static final Spell cobwebs = null; - public static final Spell decoy = null; - public static final Spell arcane_jammer = null; - public static final Spell conjure_armour = null; - public static final Spell group_heal = null; + public static final Spell greater_fireball = placeholder(); + public static final Spell flaming_weapon = placeholder(); + public static final Spell ice_lance = placeholder(); + public static final Spell freezing_weapon = placeholder(); + public static final Spell ice_spikes = placeholder(); + public static final Spell lightning_pulse = placeholder(); + public static final Spell curse_of_soulbinding = placeholder(); + public static final Spell cobwebs = placeholder(); + public static final Spell decoy = placeholder(); + public static final Spell arcane_jammer = placeholder(); + public static final Spell conjure_armour = placeholder(); + public static final Spell group_heal = placeholder(); - public static final Spell hailstorm = null; - public static final Spell lightning_web = null; - public static final Spell summon_storm_elemental = null; - public static final Spell earthquake = null; - public static final Spell font_of_mana = null; + public static final Spell hailstorm = placeholder(); + public static final Spell lightning_web = placeholder(); + public static final Spell summon_storm_elemental = placeholder(); + public static final Spell earthquake = placeholder(); + public static final Spell font_of_mana = placeholder(); + + // Wizardry 4.2 spells + + public static final Spell mine = placeholder(); + public static final Spell conjure_block = placeholder(); + public static final Spell muffle = placeholder(); + public static final Spell ward = placeholder(); + public static final Spell evade = placeholder(); + + public static final Spell iceball = placeholder(); + public static final Spell charge = placeholder(); + public static final Spell reversal = placeholder(); + public static final Spell grapple = placeholder(); + public static final Spell divination = placeholder(); + public static final Spell empowering_presence = placeholder(); + + public static final Spell disintegration = placeholder(); + public static final Spell combustion_rune = placeholder(); + public static final Spell frost_step = placeholder(); + public static final Spell paralysis = placeholder(); + public static final Spell shulker_bullet = placeholder(); + public static final Spell curse_of_undeath = placeholder(); + public static final Spell dragon_fireball = placeholder(); + public static final Spell greater_telekinesis = placeholder(); + public static final Spell vex_swarm = placeholder(); + public static final Spell arcane_lock = placeholder(); + public static final Spell containment = placeholder(); + public static final Spell satiety = placeholder(); + public static final Spell greater_ward = placeholder(); + public static final Spell ray_of_purification = placeholder(); + public static final Spell remove_curse = placeholder(); + + public static final Spell possession = placeholder(); + public static final Spell curse_of_enfeeblement = placeholder(); + public static final Spell forest_of_thorns = placeholder(); + public static final Spell speed_time = placeholder(); + public static final Spell slow_time = placeholder(); + public static final Spell resurrection = placeholder(); @SubscribeEvent public static void register(RegistryEvent.Register event){ IForgeRegistry registry = event.getRegistry(); - // event.getRegistry should always equal Spell.registry. registry.register(new None()); - registry.register(new MagicMissile()); + registry.register(new SpellArrow<>("magic_missile", EntityMagicMissile::new).addProperties(Spell.DAMAGE).soundValues(1, 1.4f, 0.4f)); registry.register(new Ignite()); registry.register(new Freeze()); registry.register(new Snowball()); registry.register(new Arc()); - registry.register(new Thunderbolt()); + registry.register(new SpellProjectile<>("thunderbolt", EntityThunderbolt::new).addProperties(Spell.DAMAGE, EntityThunderbolt.KNOCKBACK_STRENGTH).soundValues(0.8f, 0.9f, 0.2f)); registry.register(new SummonZombie()); registry.register(new Snare()); - registry.register(new Dart()); + registry.register(new SpellArrow<>("dart", EntityDart::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new Light()); registry.register(new Telekinesis()); registry.register(new Heal()); - registry.register(new Fireball()); + registry.register(new SpellProjectile<>("fireball", EntityMagicFireball::new).addProperties(Spell.DAMAGE, Spell.BURN_DURATION));//new Fireball()); registry.register(new FlameRay()); - registry.register(new Firebomb()); - registry.register(new FireSigil()); - registry.register(new Firebolt()); + registry.register(new SpellProjectile<>("firebomb", EntityFirebomb::new).addProperties(Spell.DIRECT_DAMAGE, Spell.SPLASH_DAMAGE, Spell.BLAST_RADIUS, Spell.BURN_DURATION).soundValues(0.5f, 0.4f, 0.2f)); + registry.register(new SpellConstructRanged<>("fire_sigil", EntityFireSigil::new, true).floor(true).addProperties(Spell.DAMAGE, Spell.BURN_DURATION)); + registry.register(new SpellProjectile<>("firebolt", EntityFirebolt::new).addProperties(Spell.DAMAGE, Spell.BURN_DURATION)); registry.register(new FrostRay()); registry.register(new SummonSnowGolem()); - registry.register(new IceShard()); + registry.register(new SpellArrow<>("ice_shard", EntityIceShard::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(1, 1.6f, 0.4f)); registry.register(new IceStatue()); - registry.register(new FrostSigil()); + registry.register(new SpellConstructRanged<>("frost_sigil", EntityFrostSigil::new, true).floor(true).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH)); registry.register(new LightningRay()); - registry.register(new SparkBomb()); - registry.register(new HomingSpark()); - registry.register(new LightningSigil()); - registry.register(new LightningArrow()); + registry.register(new SpellProjectile<>("spark_bomb", EntitySparkBomb::new).addProperties(Spell.DIRECT_DAMAGE, Spell.EFFECT_RADIUS, EntitySparkBomb.SECONDARY_MAX_TARGETS, Spell.SPLASH_DAMAGE).soundValues(0.5f, 0.4f, 0.2f)); + registry.register(new SpellProjectile<>("homing_spark", EntitySpark::new).addProperties(Spell.DAMAGE, Spell.SEEKING_STRENGTH).soundValues(1.0f, 0.4f, 0.2f)); + registry.register(new SpellConstructRanged<>("lightning_sigil", EntityLightningSigil::new, true).floor(true).addProperties(Spell.DIRECT_DAMAGE, Spell.EFFECT_RADIUS, EntityLightningSigil.SECONDARY_MAX_TARGETS, Spell.SPLASH_DAMAGE)); + registry.register(new SpellArrow<>("lightning_arrow", EntityLightningArrow::new).addProperties(Spell.DAMAGE).soundValues(1, 1.45f, 0.3f)); registry.register(new LifeDrain()); registry.register(new SummonSkeleton()); registry.register(new Metamorphosis()); @@ -236,69 +279,69 @@ public final class Spells { registry.register(new GrowthAura()); registry.register(new Bubble()); registry.register(new Whirlwind()); - registry.register(new PoisonBomb()); + registry.register(new SpellProjectile<>("poison_bomb", EntityPoisonBomb::new).addProperties(Spell.DIRECT_DAMAGE, Spell.EFFECT_RADIUS, Spell.DIRECT_EFFECT_DURATION, Spell.DIRECT_EFFECT_STRENGTH, Spell.SPLASH_DAMAGE, Spell.SPLASH_EFFECT_DURATION, Spell.SPLASH_EFFECT_STRENGTH).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new SummonSpiritWolf()); registry.register(new Blink()); - registry.register(new Agility()); - registry.register(new ConjureSword()); - registry.register(new ConjurePickaxe()); - registry.register(new ConjureBow()); + registry.register(new SpellBuff("agility", 0.4f, 1.0f, 0.8f, () -> MobEffects.SPEED, () -> MobEffects.JUMP_BOOST).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new SpellConjuration("conjure_sword", WizardryItems.spectral_sword)); + registry.register(new SpellConjuration("conjure_pickaxe", WizardryItems.spectral_pickaxe)); + registry.register(new SpellConjuration("conjure_bow", WizardryItems.spectral_bow)); registry.register(new ForceArrow()); registry.register(new Shield()); registry.register(new ReplenishHunger()); registry.register(new CureEffects()); registry.register(new HealAlly()); - registry.register(new SummonBlaze()); - registry.register(new RingOfFire()); + registry.register(new SpellMinion<>("summon_blaze", EntityBlazeMinion::new).soundValues(1, 1.1f, 0.2f)); + registry.register(new SpellConstruct<>("ring_of_fire", EnumAction.BOW, EntityFireRing::new, false).floor(true).addProperties(Spell.DAMAGE, Spell.BURN_DURATION)); registry.register(new Detonate()); - registry.register(new FireResistance()); - registry.register(new Fireskin()); + registry.register(new SpellBuff("fire_resistance", 1, 0.5f, 0, () -> MobEffects.FIRE_RESISTANCE).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new SpellBuff("fireskin", 1, 0.5f, 0, () -> WizardryPotions.fireskin).addProperties(Spell.BURN_DURATION)); registry.register(new FlamingAxe()); - registry.register(new Blizzard()); - registry.register(new SummonIceWraith()); - registry.register(new IceShroud()); - registry.register(new IceCharge()); + registry.register(new SpellConstructRanged<>("blizzard", EntityBlizzard::new, false).addProperties(Spell.EFFECT_RADIUS)); + registry.register(new SpellMinion<>("summon_ice_wraith", EntityIceWraith::new).soundValues(1, 1.1f, 0.2f)); + registry.register(new SpellBuff("ice_shroud", 0.3f, 0.5f, 1, () -> WizardryPotions.ice_shroud).addProperties(Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(1, 1.6f, 0.4f)); + registry.register(new SpellProjectile<>("ice_charge", EntityIceCharge::new).addProperties(Spell.DAMAGE, Spell.EFFECT_RADIUS, Spell.DIRECT_EFFECT_DURATION, Spell.DIRECT_EFFECT_STRENGTH, Spell.SPLASH_EFFECT_DURATION, Spell.SPLASH_EFFECT_STRENGTH, EntityIceCharge.ICE_SHARDS).soundValues(1, 1.6f, 0.4f)); registry.register(new FrostAxe()); registry.register(new InvokeWeather()); registry.register(new ChainLightning()); registry.register(new LightningBolt()); - registry.register(new SummonLightningWraith()); - registry.register(new StaticAura()); - registry.register(new LightningDisc()); + registry.register(new SpellMinion<>("summon_lightning_wraith", EntityLightningWraith::new).soundValues(1, 1.1f, 0.2f)); + registry.register(new SpellBuff("static_aura", 0, 0.5f, 0.7f, () -> WizardryPotions.static_aura).addProperties(Spell.DAMAGE).soundValues(1, 1.6f, 0.4f)); + registry.register(new SpellProjectile<>("lightning_disc", EntityLightningDisc::new).addProperties(Spell.DAMAGE, Spell.SEEKING_STRENGTH).soundValues(1, 0.95f, 0.3f)); registry.register(new MindControl()); registry.register(new SummonWitherSkeleton()); registry.register(new Entrapment()); registry.register(new WitherSkull()); - registry.register(new DarknessOrb()); + registry.register(new SpellProjectile<>("darkness_orb", EntityDarknessOrb::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new ShadowWard()); registry.register(new Decay()); - registry.register(new WaterBreathing()); + registry.register(new SpellBuff("water_breathing", 0.3f, 0.3f, 1, () -> MobEffects.WATER_BREATHING){ @Override public boolean canBeCastByNPCs(){ return false; } }.soundValues(0.7f, 1.2f, 0.4f)); registry.register(new Tornado()); registry.register(new Glide()); registry.register(new SummonSpiritHorse()); - registry.register(new SpiderSwarm()); + registry.register(new SpellMinion<>("spider_swarm", EntitySpiderMinion::new).soundValues(1, 1.1f, 0.1f)); registry.register(new Slime()); registry.register(new Petrify()); - registry.register(new Invisibility()); + registry.register(new SpellBuff("invisibility", 0.7f, 1, 1, () -> MobEffects.INVISIBILITY).soundValues(0.7f, 1.2f, 0.4f)); registry.register(new Levitation()); - registry.register(new ForceOrb()); + registry.register(new SpellProjectile<>("force_orb", EntityForceOrb::new).addProperties(Spell.DAMAGE, Spell.BLAST_RADIUS).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new Transportation()); registry.register(new SpectralPathway()); registry.register(new PhaseStep()); registry.register(new VanishingBox()); registry.register(new GreaterHeal()); - registry.register(new HealingAura()); + registry.register(new SpellConstruct<>("healing_aura", EnumAction.BOW, EntityHealAura::new, false).addProperties(Spell.DAMAGE, Spell.HEALTH)); registry.register(new Forcefield()); - registry.register(new Ironflesh()); + registry.register(new SpellBuff("ironflesh", 0.4f, 0.5f, 0.6f, () -> MobEffects.RESISTANCE).soundValues(0.7f, 1.2f, 0.4f)); registry.register(new Transience()); registry.register(new Meteor()); - registry.register(new Firestorm()); - registry.register(new SummonPhoenix()); + registry.register(new FireBreath()); + registry.register(new SpellMinion<>("summon_phoenix", EntityPhoenix::new).flying(true).soundValues(1, 1.1f, 0.1f)); registry.register(new IceAge()); registry.register(new WallOfFrost()); - registry.register(new SummonIceGiant()); + registry.register(new SpellMinion<>("summon_ice_giant", EntityIceGiant::new).soundValues(1, 0.15f, 0.1f)); registry.register(new Thunderstorm()); registry.register(new LightningHammer()); registry.register(new PlagueOfDarkness()); @@ -306,17 +349,17 @@ public final class Spells { registry.register(new SummonShadowWraith()); registry.register(new ForestsCurse()); registry.register(new Flight()); - registry.register(new SilverfishSwarm()); - registry.register(new BlackHole()); + registry.register(new SpellMinion<>("silverfish_swarm", EntitySilverfishMinion::new).soundValues(1, 1.1f, 0.1f)); + registry.register(new SpellConstructRanged<>("black_hole", EntityBlackHole::new, false).soundValues(2, 0.7f, 0)); registry.register(new Shockwave()); registry.register(new SummonIronGolem()); registry.register(new ArrowRain()); - registry.register(new Diamondflesh()); - registry.register(new FontOfVitality()); + registry.register(new SpellBuff("diamondflesh", 0.1f, 0.7f, 1, () -> MobEffects.RESISTANCE).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new SpellBuff("font_of_vitality", 1, 0.8f, 0.3f, () -> MobEffects.ABSORPTION, () -> MobEffects.REGENERATION).soundValues(0.7f, 1.2f, 0.4f)); // Wizardry 1.1 spells - registry.register(new SmokeBomb()); + registry.register(new SpellProjectile<>("smoke_bomb", EntitySmokeBomb::new).addProperties(Spell.BLAST_RADIUS, Spell.EFFECT_DURATION).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new MindTrick()); registry.register(new Leap()); @@ -324,16 +367,16 @@ public final class Spells { registry.register(new Intimidate()); registry.register(new Banish()); registry.register(new SixthSense()); - registry.register(new Darkvision()); + registry.register(new SpellBuff("darkvision", 0, 0.4f, 0.7f, () -> MobEffects.NIGHT_VISION){ @Override public boolean canBeCastByNPCs(){ return false; } }.soundValues(0.7f, 1.2f, 0.4f)); registry.register(new Clairvoyance()); registry.register(new PocketWorkbench()); registry.register(new ImbueWeapon()); registry.register(new InvigoratingPresence()); - registry.register(new Oakflesh()); + registry.register(new SpellBuff("oakflesh", 0.6f, 0.5f, 0.4f, () -> MobEffects.RESISTANCE).soundValues(0.7f, 1.2f, 0.4f)); - registry.register(new GreaterFireball()); + registry.register(new SpellProjectile<>("greater_fireball", EntityLargeMagicFireball::new).addProperties(Spell.DAMAGE, EntityLargeMagicFireball.EXPLOSION_POWER));//new GreaterFireball()); registry.register(new FlamingWeapon()); - registry.register(new IceLance()); + registry.register(new SpellArrow<>("ice_lance", EntityIceLance::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(1, 1, 0.4f)); registry.register(new FreezingWeapon()); registry.register(new IceSpikes()); registry.register(new LightningPulse()); @@ -346,9 +389,47 @@ public final class Spells { registry.register(new Hailstorm()); registry.register(new LightningWeb()); - registry.register(new SummonStormElemental()); + registry.register(new SpellMinion<>("summon_storm_elemental", EntityStormElemental::new).soundValues(1, 1.1f, 0.1f)); registry.register(new Earthquake()); registry.register(new FontOfMana()); + + // Wizardry 4.2 spells + + registry.register(new Mine()); + registry.register(new ConjureBlock()); + registry.register(new SpellBuff("muffle", 0.3f, 0.4f, 0.8f, () -> WizardryPotions.muffle).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new SpellBuff("ward", 0.75f, 0.6f, 0.8f, () -> WizardryPotions.ward).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new Evade()); + + registry.register(new SpellProjectile<>("iceball", EntityIceball::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH)); + registry.register(new Charge()); + registry.register(new Reversal()); + registry.register(new Grapple()); + registry.register(new Divination()); + registry.register(new EmpoweringPresence()); + + registry.register(new Disintegration()); + registry.register(new SpellConstructRanged<>("combustion_rune", EntityCombustionRune::new, true).floor(true).addProperties(Spell.BLAST_RADIUS)); + registry.register(new SpellBuff("frost_step", 0.3f, 0.4f, 0.8f, () -> WizardryPotions.frost_step).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new Paralysis()); + registry.register(new ShulkerBullet()); + registry.register(new CurseOfUndeath()); + registry.register(new DragonFireball()); + registry.register(new GreaterTelekinesis()); + registry.register(new SpellMinion<>("vex_swarm", EntityVexMinion::new).flying(true).soundValues(1, 1.1f, 0.1f)); + registry.register(new ArcaneLock()); + registry.register(new Containment()); + registry.register(new Satiety()); + registry.register(new SpellBuff("greater_ward", 0.75f, 0.6f, 0.8f, () -> WizardryPotions.ward).soundValues(0.7f, 1.2f, 0.4f)); + registry.register(new RayOfPurification()); + registry.register(new RemoveCurse()); + + registry.register(new Possession()); + registry.register(new CurseOfEnfeeblement()); + registry.register(new ForestOfThorns()); + registry.register(new SpeedTime()); + registry.register(new SlowTime()); + registry.register(new Resurrection()); } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java b/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java index 5d50f2ba..f5cc4aef 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java @@ -1,59 +1,48 @@ package electroblob.wizardry.registry; -import electroblob.wizardry.util.CustomAdvancementTrigger; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.advancement.*; import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.util.ResourceLocation; /** - * This class stores a collection of custom advancement triggers, for advancements that cannot be triggered - * from plain vanilla JSON definitions. It replaces the old WizardryAchievements class. + * Class responsible for defining, storing and registering all of wizardry's advancement triggers. As of wizardry 4.2, + * the 'dummy' advancement triggers are being phased out in favour of proper custom triggers with JSON parameters, or + * where possible, existing triggers in Minecraft. * - * @author 12foo + * @author 12foo, Electroblob * @since Wizardry 4.1.0 */ public final class WizardryAdvancementTriggers { - - public static final CustomAdvancementTrigger armour_set = new CustomAdvancementTrigger("trigger_armour_set"); - public static final CustomAdvancementTrigger jam_wizard = new CustomAdvancementTrigger("trigger_jam_wizard"); - public static final CustomAdvancementTrigger self_destruct = new CustomAdvancementTrigger("trigger_self_destruct"); - public static final CustomAdvancementTrigger all_spells = new CustomAdvancementTrigger("trigger_all_spells"); - public static final CustomAdvancementTrigger element_master = new CustomAdvancementTrigger("trigger_element_master"); - public static final CustomAdvancementTrigger identify_spell = new CustomAdvancementTrigger("trigger_identify_spell"); - public static final CustomAdvancementTrigger elemental = new CustomAdvancementTrigger("trigger_elemental"); + + private WizardryAdvancementTriggers(){} // No instances! + public static final CustomAdvancementTrigger legendary = new CustomAdvancementTrigger("trigger_legendary"); public static final CustomAdvancementTrigger max_out_wand = new CustomAdvancementTrigger("trigger_max_out_wand"); public static final CustomAdvancementTrigger special_upgrade = new CustomAdvancementTrigger("trigger_special_upgrade"); - public static final CustomAdvancementTrigger pig_tornado = new CustomAdvancementTrigger("trigger_pig_tornado"); - public static final CustomAdvancementTrigger master = new CustomAdvancementTrigger("trigger_master"); - public static final CustomAdvancementTrigger apprentice = new CustomAdvancementTrigger("trigger_apprentice"); public static final CustomAdvancementTrigger anger_wizard = new CustomAdvancementTrigger("trigger_anger_wizard"); public static final CustomAdvancementTrigger buy_master_spell = new CustomAdvancementTrigger("trigger_buy_master_spell"); - public static final CustomAdvancementTrigger wizard_trade = new CustomAdvancementTrigger("trigger_wizard_trade"); - public static final CustomAdvancementTrigger slime_skeleton = new CustomAdvancementTrigger("trigger_slime_skeleton"); - public static final CustomAdvancementTrigger freeze_blaze = new CustomAdvancementTrigger("trigger_freeze_blaze"); - public static final CustomAdvancementTrigger frankenstein = new CustomAdvancementTrigger("trigger_frankenstein"); - public static final CustomAdvancementTrigger charge_creeper = new CustomAdvancementTrigger("trigger_charge_creeper"); + public static final CustomAdvancementTrigger wizard_trade = new CustomAdvancementTrigger("trigger_wizard_trade"); + public static final CustomAdvancementTrigger spell_failure = new CustomAdvancementTrigger("trigger_spell_failure"); + + public static final StructureTrigger visit_structure = new StructureTrigger(new ResourceLocation(Wizardry.MODID, "visit_structure")); + public static final ArcaneWorkbenchTrigger arcane_workbench = new ArcaneWorkbenchTrigger(new ResourceLocation(Wizardry.MODID, "arcane_workbench")); + public static final SpellCastTrigger cast_spell = new SpellCastTrigger(new ResourceLocation(Wizardry.MODID, "cast_spell")); + public static final SpellDiscoveryTrigger discover_spell = new SpellDiscoveryTrigger(new ResourceLocation(Wizardry.MODID, "discover_spell")); public static void register(){ - - CriteriaTriggers.register(armour_set); - CriteriaTriggers.register(jam_wizard); - CriteriaTriggers.register(self_destruct); - CriteriaTriggers.register(all_spells); - CriteriaTriggers.register(element_master); - CriteriaTriggers.register(identify_spell); - CriteriaTriggers.register(elemental); + CriteriaTriggers.register(legendary); CriteriaTriggers.register(max_out_wand); CriteriaTriggers.register(special_upgrade); - CriteriaTriggers.register(pig_tornado); - CriteriaTriggers.register(master); - CriteriaTriggers.register(apprentice); CriteriaTriggers.register(anger_wizard); CriteriaTriggers.register(buy_master_spell); CriteriaTriggers.register(wizard_trade); - CriteriaTriggers.register(slime_skeleton); - CriteriaTriggers.register(freeze_blaze); - CriteriaTriggers.register(frankenstein); - CriteriaTriggers.register(charge_creeper); + CriteriaTriggers.register(spell_failure); + + CriteriaTriggers.register(visit_structure); + CriteriaTriggers.register(arcane_workbench); + CriteriaTriggers.register(cast_spell); + CriteriaTriggers.register(discover_spell); } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java b/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java index bb2a0db0..c2311c92 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java @@ -1,37 +1,32 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.block.BlockArcaneWorkbench; -import electroblob.wizardry.block.BlockCrystalFlower; -import electroblob.wizardry.block.BlockCrystalOre; -import electroblob.wizardry.block.BlockMagicLight; -import electroblob.wizardry.block.BlockSnare; -import electroblob.wizardry.block.BlockSpectral; -import electroblob.wizardry.block.BlockStatue; -import electroblob.wizardry.block.BlockTransportationStone; -import electroblob.wizardry.block.BlockVanishingCobweb; +import electroblob.wizardry.block.*; +import electroblob.wizardry.tileentity.*; import net.minecraft.block.Block; import net.minecraft.block.material.Material; +import net.minecraft.util.ResourceLocation; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder; import net.minecraftforge.registries.IForgeRegistry; +import javax.annotation.Nonnull; + /** - * Class responsible for defining, storing and registering all of wizardry's blocks. + * Class responsible for defining, storing and registering all of wizardry's blocks. Also handles registry of the + * tile entities. * * @author Electroblob * @since Wizardry 2.1 */ +@ObjectHolder(Wizardry.MODID) @Mod.EventBusSubscriber public final class WizardryBlocks { - // I get registry events, they make sense - even I doubted myself with when to do various things in the load - // process, - // so I can see why the folks at Forge wanted to save us having to think about it. - // What I do not understand is why you would use @ObjectHolder for your own blocks and items. What's the point in - // making your code longer and more complicated, when you can just define the blocks as constants and register them - // later? + private WizardryBlocks(){} // No instances! // Found a very nice way of registering things using arrays, which might make @ObjectHolder actually useful. // http://www.minecraftforge.net/forum/topic/49497-1112-is-using-registryevent-this-way-ok/ @@ -40,23 +35,27 @@ public final class WizardryBlocks { // setSoundType should be public, but in this particular version it isn't... which is a bit of a pain. - public static final Block arcane_workbench = new BlockArcaneWorkbench().setHardness(1.0F).setCreativeTab(WizardryTabs.WIZARDRY); - public static final Block crystal_ore = new BlockCrystalOre(Material.ROCK).setHardness(3.0F).setCreativeTab(WizardryTabs.WIZARDRY); - public static final Block petrified_stone = new BlockStatue(Material.ROCK).setHardness(1.5F).setResistance(10.0F); - public static final Block ice_statue = new BlockStatue(Material.ICE).setHardness(0.5F).setLightOpacity(3); - public static final Block magic_light = new BlockMagicLight(Material.CIRCUITS); - public static final Block crystal_flower = new BlockCrystalFlower(Material.PLANTS).setHardness(0.0F).setCreativeTab(WizardryTabs.WIZARDRY); - public static final Block snare = new BlockSnare(Material.PLANTS).setHardness(0.0F); - public static final Block transportation_stone = new BlockTransportationStone(Material.ROCK).setHardness(0.3F).setLightLevel(0.5f).setLightOpacity(0).setCreativeTab(WizardryTabs.WIZARDRY); - public static final Block spectral_block = new BlockSpectral(Material.GLASS).setLightLevel(0.7f).setLightOpacity(0).setBlockUnbreakable().setResistance(6000000.0F); - public static final Block crystal_block = new Block(Material.IRON).setHardness(5.0F).setResistance(10.0F).setCreativeTab(WizardryTabs.WIZARDRY); - public static final Block meteor = new Block(Material.ROCK).setLightLevel(1); - public static final Block vanishing_cobweb = new BlockVanishingCobweb(Material.WEB).setLightOpacity(1).setHardness(4.0F); + @Nonnull + @SuppressWarnings("ConstantConditions") + private static T placeholder(){ return null; } - static{ - // This is here because Block#setHarvestLevel isn't chainable. - crystal_block.setHarvestLevel("pickaxe", 2); - } + public static final Block arcane_workbench = placeholder(); + public static final Block crystal_ore = placeholder(); + public static final Block petrified_stone = placeholder(); + public static final Block ice_statue = placeholder(); + public static final Block magic_light = placeholder(); + public static final Block crystal_flower = placeholder(); + public static final Block snare = placeholder(); + public static final Block transportation_stone = placeholder(); + public static final Block spectral_block = placeholder(); + public static final Block crystal_block = placeholder(); + public static final Block meteor = placeholder(); + public static final Block vanishing_cobweb = placeholder(); + public static final Block runestone = placeholder(); + public static final Block runestone_pedestal = placeholder(); + public static final Block thorns = placeholder(); + public static final Block obsidian_crust = placeholder(); + public static final Block dry_frosted_ice = placeholder(); /** * Sets both the registry and unlocalised names of the given block, then registers it with the given registry. Use @@ -64,11 +63,11 @@ public final class WizardryBlocks { * construction, for convenience and consistency. * * @param registry The registry to register the given block to. - * @param block The block to register. * @param name The name of the block, without the mod ID or the .name stuff. The registry name will be * {@code ebwizardry:[name]}. The unlocalised name will be {@code tile.ebwizardry:[name].name}. + * @param block The block to register. */ - public static void registerBlock(IForgeRegistry registry, Block block, String name){ + public static void registerBlock(IForgeRegistry registry, String name, Block block){ block.setRegistryName(Wizardry.MODID, name); block.setTranslationKey(block.getRegistryName().toString()); registry.register(block); @@ -79,17 +78,35 @@ public final class WizardryBlocks { IForgeRegistry registry = event.getRegistry(); - registerBlock(registry, arcane_workbench, "arcane_workbench"); - registerBlock(registry, crystal_ore, "crystal_ore"); - registerBlock(registry, petrified_stone, "petrified_stone"); - registerBlock(registry, ice_statue, "ice_statue"); - registerBlock(registry, magic_light, "magic_light"); - registerBlock(registry, crystal_flower, "crystal_flower"); - registerBlock(registry, snare, "snare"); - registerBlock(registry, transportation_stone, "transportation_stone"); - registerBlock(registry, spectral_block, "spectral_block"); - registerBlock(registry, crystal_block, "crystal_block"); - registerBlock(registry, meteor, "meteor"); - registerBlock(registry, vanishing_cobweb, "vanishing_cobweb"); + registerBlock(registry, "arcane_workbench", new BlockArcaneWorkbench().setHardness(1.0F).setCreativeTab(WizardryTabs.WIZARDRY)); + registerBlock(registry, "crystal_ore", new BlockCrystalOre(Material.ROCK).setHardness(3.0F).setCreativeTab(WizardryTabs.WIZARDRY)); + registerBlock(registry, "petrified_stone", new BlockStatue(Material.ROCK).setHardness(1.5F).setResistance(10.0F)); + registerBlock(registry, "ice_statue", new BlockStatue(Material.ICE).setHardness(0.5F).setLightOpacity(3)); + registerBlock(registry, "magic_light", new BlockMagicLight(Material.CIRCUITS)); + registerBlock(registry, "crystal_flower", new BlockCrystalFlower(Material.PLANTS).setHardness(0.0F).setCreativeTab(WizardryTabs.WIZARDRY)); + registerBlock(registry, "snare", new BlockSnare(Material.PLANTS).setHardness(0.0F)); + registerBlock(registry, "transportation_stone", new BlockTransportationStone(Material.ROCK).setHardness(0.3F).setLightLevel(0.5f).setLightOpacity(0).setCreativeTab(WizardryTabs.WIZARDRY)); + registerBlock(registry, "spectral_block", new BlockSpectral(Material.GLASS).setLightLevel(0.7f).setLightOpacity(0).setBlockUnbreakable().setResistance(6000000.0F)); + registerBlock(registry, "crystal_block", new BlockCrystal(Material.IRON).setHardness(5.0F).setResistance(10.0F).setCreativeTab(WizardryTabs.WIZARDRY)); + registerBlock(registry, "meteor", new Block(Material.ROCK).setLightLevel(1)); + registerBlock(registry, "vanishing_cobweb", new BlockVanishingCobweb(Material.WEB).setLightOpacity(1).setHardness(4.0F)); + registerBlock(registry, "runestone", new BlockRunestone(Material.ROCK)); + registerBlock(registry, "runestone_pedestal", new BlockPedestal(Material.ROCK)); + registerBlock(registry, "thorns", new BlockThorns()); + registerBlock(registry, "obsidian_crust", new BlockObsidianCrust()); + registerBlock(registry, "dry_frosted_ice", new BlockDryFrostedIce()); + + } + + /** Called from the preInit method in the main mod class to register all the tile entities. */ + public static void registerTileEntities(){ + // Nope, these still don't have their own registry... + GameRegistry.registerTileEntity(TileEntityArcaneWorkbench.class, new ResourceLocation(Wizardry.MODID, "arcane_workbench")); + GameRegistry.registerTileEntity(TileEntityStatue.class, new ResourceLocation(Wizardry.MODID, "petrified_stone")); + GameRegistry.registerTileEntity(TileEntityMagicLight.class, new ResourceLocation(Wizardry.MODID, "magic_light")); + GameRegistry.registerTileEntity(TileEntityTimer.class, new ResourceLocation(Wizardry.MODID, "timer")); + GameRegistry.registerTileEntity(TileEntityPlayerSave.class, new ResourceLocation(Wizardry.MODID, "player_save")); + GameRegistry.registerTileEntity(TileEntityPlayerSaveTimed.class, new ResourceLocation(Wizardry.MODID, "player_save_timed")); + GameRegistry.registerTileEntity(TileEntityShrineCore.class, new ResourceLocation(Wizardry.MODID, "shrine_core")); } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java b/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java index 09288bb2..b8a767f2 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java @@ -1,13 +1,17 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.enchantment.EnchantmentMagicProtection; import electroblob.wizardry.enchantment.EnchantmentMagicSword; import electroblob.wizardry.enchantment.EnchantmentTimed; +import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.enchantment.Enchantment; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import javax.annotation.Nonnull; + /** * Class responsible for defining, storing and registering all of wizardry's enchantments. * @@ -17,30 +21,42 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber public final class WizardryEnchantments { - // At the moment these enchantments generate on books in dungeon chests due to a bad bit of code - // (EnchantRandomly:50). + private WizardryEnchantments(){} // No instances! + + // At the moment these enchantments generate on books in dungeon chests due to a bad bit of code (EnchantRandomly:49). // No idea how to fix this because I have no way of hooking into that code... removing the enchantments from the // registry works, but breaks everything else! - // TODO: For the time being, a dynamic solution will have to do, i.e. intercept the book when it is generated and + // For the time being, a dynamic solution will have to do, i.e. intercept the book when it is generated and // reassign its enchantment. // All of these have custom classes, so the unlocalised name (referred to simply as 'name' for enchantments) is // dealt with inside those classes. - public static final Enchantment magic_sword = new EnchantmentMagicSword().setRegistryName(Wizardry.MODID, - "magic_sword"); - public static final Enchantment magic_bow = new EnchantmentTimed().setRegistryName(Wizardry.MODID, "magic_bow"); - public static final Enchantment flaming_weapon = new EnchantmentTimed().setRegistryName(Wizardry.MODID, - "flaming_weapon"); - public static final Enchantment freezing_weapon = new EnchantmentTimed().setRegistryName(Wizardry.MODID, - "freezing_weapon"); + + @Nonnull + @SuppressWarnings("ConstantConditions") + private static T placeholder(){ return null; } + + public static final Enchantment magic_sword = placeholder(); + public static final Enchantment magic_bow = placeholder(); + public static final Enchantment flaming_weapon = placeholder(); + public static final Enchantment freezing_weapon = placeholder(); + + public static final Enchantment magic_protection = placeholder(); + public static final Enchantment frost_protection = placeholder(); + public static final Enchantment shock_protection = placeholder(); @SubscribeEvent public static void register(RegistryEvent.Register event){ - event.getRegistry().register(magic_sword); - event.getRegistry().register(magic_bow); - event.getRegistry().register(flaming_weapon); - event.getRegistry().register(freezing_weapon); + + event.getRegistry().register(new EnchantmentMagicSword().setRegistryName(Wizardry.MODID, "magic_sword")); + event.getRegistry().register(new EnchantmentTimed().setRegistryName(Wizardry.MODID, "magic_bow")); + event.getRegistry().register(new EnchantmentTimed().setRegistryName(Wizardry.MODID, "flaming_weapon")); + event.getRegistry().register(new EnchantmentTimed().setRegistryName(Wizardry.MODID, "freezing_weapon")); + + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.UNCOMMON, EnchantmentMagicProtection.Type.MAGIC, WizardryUtilities.ARMOUR_SLOTS).setRegistryName(Wizardry.MODID, "magic_protection")); + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.RARE, EnchantmentMagicProtection.Type.FROST, WizardryUtilities.ARMOUR_SLOTS).setRegistryName(Wizardry.MODID, "frost_protection")); + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.RARE, EnchantmentMagicProtection.Type.SHOCK, WizardryUtilities.ARMOUR_SLOTS).setRegistryName(Wizardry.MODID, "shock_protection")); } } diff --git a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java new file mode 100644 index 00000000..b7bf9839 --- /dev/null +++ b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java @@ -0,0 +1,190 @@ +package electroblob.wizardry.registry; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.EntityLevitatingBlock; +import electroblob.wizardry.entity.EntityMeteor; +import electroblob.wizardry.entity.EntityShield; +import electroblob.wizardry.entity.construct.*; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.entity.projectile.*; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.BiomeDictionary; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.EntityEntry; +import net.minecraftforge.fml.common.registry.EntityEntryBuilder; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Class responsible for registering all of wizardry's entities and their spawning conditions. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +@Mod.EventBusSubscriber +public class WizardryEntities { + + private WizardryEntities(){} // No instances! + + /** Most entity trackers fall into one of a few categories, so they are defined here for convenience. This + * generally follows the values used in vanilla for each entity type. */ + enum TrackingType { + + LIVING(80, 3, true), + PROJECTILE(64, 10, true), + CONSTRUCT(160, 10, false); + + int range; + int interval; + boolean trackVelocity; + + TrackingType(int range, int interval, boolean trackVelocity){ + this.range = range; + this.interval = interval; + this.trackVelocity = trackVelocity; + } + } + + /** Incrementing index for the mod-specific entity network ID. */ + private static int id = 0; + + @SubscribeEvent + public static void register(RegistryEvent.Register event){ + + IForgeRegistry registry = event.getRegistry(); + + // Vanilla summoned creatures + registry.register(createEntry(EntityZombieMinion.class, "zombie_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntityHuskMinion.class, "husk_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntitySkeletonMinion.class, "skeleton_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntityStrayMinion.class, "stray_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntitySpiderMinion.class, "spider_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntityBlazeMinion.class, "blaze_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntityWitherSkeletonMinion.class, "wither_skeleton_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntitySilverfishMinion.class, "silverfish_minion", TrackingType.LIVING).build()); + registry.register(createEntry(EntityVexMinion.class, "vex_minion", TrackingType.LIVING).build()); + + // Custom summoned creatures + registry.register(createEntry(EntityIceWraith.class, "ice_wraith", TrackingType.LIVING).egg(0xaafaff, 0x001ce1) + .spawn(EnumCreatureType.MONSTER, Wizardry.settings.iceWraithSpawnRate, 1, 1, ForgeRegistries.BIOMES.getValuesCollection().stream() + .filter(b -> !Arrays.asList(Wizardry.settings.mobSpawnBiomeBlacklist).contains(b.getRegistryName()) + && BiomeDictionary.hasType(b, BiomeDictionary.Type.SNOWY) + && !BiomeDictionary.hasType(b, BiomeDictionary.Type.FOREST)) + .collect(Collectors.toSet())).build()); + + registry.register(createEntry(EntityLightningWraith.class, "lightning_wraith", TrackingType.LIVING).egg(0x35424b, 0x27b9d9) + .spawn(EnumCreatureType.MONSTER, Wizardry.settings.lightningWraithSpawnRate, 1, 1, ForgeRegistries.BIOMES.getValuesCollection().stream() + .filter(b -> !Arrays.asList(Wizardry.settings.mobSpawnBiomeBlacklist).contains(b.getRegistryName())) + .collect(Collectors.toSet())).build()); + + registry.register(createEntry(EntitySpiritWolf.class, "spirit_wolf", TrackingType.LIVING).egg(0xbcc2e8, 0x5464c6).build()); + registry.register(createEntry(EntitySpiritHorse.class, "spirit_horse", TrackingType.LIVING).egg(0x5464c6, 0xbcc2e8).build()); + registry.register(createEntry(EntityPhoenix.class, "phoenix", TrackingType.LIVING).egg(0xff4900, 0xfde535).build()); + registry.register(createEntry(EntityIceGiant.class, "ice_giant", TrackingType.LIVING).egg(0x5bacd9, 0xeffaff).build()); + + registry.register(createEntry(EntityMagicSlime.class, "magic_slime", TrackingType.LIVING).build()); + registry.register(createEntry(EntityDecoy.class, "decoy", TrackingType.LIVING).build()); + + // These two are only made of particles, so we can afford a lower update frequency + registry.register(createEntry(EntityShadowWraith.class, "shadow_wraith") .tracker(80, 10, true).egg(0x11071c, 0x421384).build()); + registry.register(createEntry(EntityStormElemental.class, "storm_elemental") .tracker(80, 10, true).egg(0x162128, 0x135279).build()); + + // Other living entities + registry.register(createEntry(EntityWizard.class, "wizard", TrackingType.LIVING).egg(0x19295e, 0xee9312).build()); + registry.register(createEntry(EntityEvilWizard.class, "evil_wizard", TrackingType.LIVING).egg(0x290404, 0xee9312) + // For reference: 5, 1, 1 are the parameters for the witch in vanilla + .spawn(EnumCreatureType.MONSTER, Wizardry.settings.evilWizardSpawnRate, 1, 1, ForgeRegistries.BIOMES.getValuesCollection().stream() + .filter(b -> !Arrays.asList(Wizardry.settings.mobSpawnBiomeBlacklist).contains(b.getRegistryName())) + .collect(Collectors.toSet())).build()); + + // Directed projectiles + registry.register(createEntry(EntityMagicMissile.class, "magic_missile", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityIceShard.class, "ice_shard", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityLightningArrow.class, "lightning_arrow", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityForceArrow.class, "force_arrow", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityDart.class, "dart", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityIceLance.class, "ice_lance", TrackingType.PROJECTILE).build()); + + // Directionless projectiles + registry.register(createEntry(EntityFirebomb.class, "firebomb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityPoisonBomb.class, "poison_bomb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntitySparkBomb.class, "spark_bomb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntitySmokeBomb.class, "smoke_bomb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityIceCharge.class, "ice_charge", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityForceOrb.class, "force_orb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntitySpark.class, "spark", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityDarknessOrb.class, "darkness_orb", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityFirebolt.class, "firebolt", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityThunderbolt.class, "thunderbolt", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityLightningDisc.class, "lightning_disc", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityEmber.class, "ember", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityMagicFireball.class, "magic_fireball", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityLargeMagicFireball.class, "large_magic_fireball", TrackingType.PROJECTILE).build()); + registry.register(createEntry(EntityIceball.class, "iceball", TrackingType.PROJECTILE).build()); + + // These are effectively projectiles, but since they're bigger and start high up they need updating from further away + registry.register(createEntry(EntityMeteor.class, "meteor") .tracker(160, 3, true).build()); + registry.register(createEntry(EntityHammer.class, "lightning_hammer") .tracker(160, 3, true).build()); + registry.register(createEntry(EntityLevitatingBlock.class, "levitating_block") .tracker(160, 3, true).build()); + + // Constructs + registry.register(createEntry(EntityBlackHole.class, "black_hole", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityBlizzard.class, "blizzard", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityForcefield.class, "forcefield", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityFireSigil.class, "fire_sigil", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityFrostSigil.class, "frost_sigil", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityLightningSigil.class, "lightning_sigil", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityCombustionRune.class, "combustion_rune", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityFireRing.class, "ring_of_fire", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityHealAura.class, "healing_aura", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityDecay.class, "decay", TrackingType.CONSTRUCT).build()); + + // These ones don't render, currently that makes no difference here but we might as well separate them + registry.register(createEntry(EntityArrowRain.class, "arrow_rain", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityEarthquake.class, "earthquake", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityHailstorm.class, "hailstorm", TrackingType.CONSTRUCT).build()); + + // These ones move, velocity updates are sent if that's not at constant velocity + registry.register(createEntry(EntityShield.class, "shield") .tracker(160, 10, true).build()); + registry.register(createEntry(EntityBubble.class, "bubble") .tracker(160, 3, false).build()); + registry.register(createEntry(EntityTornado.class, "tornado") .tracker(160, 3, false).build()); + registry.register(createEntry(EntityIceSpike.class, "ice_spike") .tracker(160, 1, true).build()); + + } + + /** + * Private helper method that simplifies the parts of an {@link EntityEntry} that are common to all entities. + * This automatically assigns a network id, and accepts a {@link TrackingType} for automatic tracker assignment. + * @param entityClass The entity class to use. + * @param name The name of the entity. This will form the path of a {@code ResourceLocation} with domain + * {@code ebwizardry}, which in turn will be used as both the registry name and the 'command' name. + * @param tracking The {@link TrackingType} to use for this entity. + * @param The type of entity. + * @return The (part-built) builder instance, allowing other builder methods to be added as necessary. + */ + private static EntityEntryBuilder createEntry(Class entityClass, String name, TrackingType tracking){ + return createEntry(entityClass, name).tracker(tracking.range, tracking.interval, tracking.trackVelocity); + } + + /** + * Private helper method that simplifies the parts of an {@link EntityEntry} that are common to all entities. + * This automatically assigns a network id. + * @param entityClass The entity class to use. + * @param name The name of the entity. This will form the path of a {@code ResourceLocation} with domain + * {@code ebwizardry}, which in turn will be used as both the registry name and the 'command' name. + * @param The type of entity. + * @return The (part-built) builder instance, allowing other builder methods to be added as necessary. + */ + private static EntityEntryBuilder createEntry(Class entityClass, String name){ + ResourceLocation registryName = new ResourceLocation(Wizardry.MODID, name); + return EntityEntryBuilder.create().entity(entityClass).id(registryName, id++).name(registryName.toString()); + } + +} diff --git a/src/main/java/electroblob/wizardry/registry/WizardryItems.java b/src/main/java/electroblob/wizardry/registry/WizardryItems.java index 01b55a44..6ae881c3 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryItems.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryItems.java @@ -1,45 +1,42 @@ package electroblob.wizardry.registry; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.collect.ImmutableMap; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.ItemArcaneTome; -import electroblob.wizardry.item.ItemArmourUpgrade; -import electroblob.wizardry.item.ItemFirebomb; -import electroblob.wizardry.item.ItemFlamingAxe; -import electroblob.wizardry.item.ItemFrostAxe; -import electroblob.wizardry.item.ItemIdentificationScroll; -import electroblob.wizardry.item.ItemPoisonBomb; -import electroblob.wizardry.item.ItemScroll; -import electroblob.wizardry.item.ItemSmokeBomb; -import electroblob.wizardry.item.ItemSpectralArmour; -import electroblob.wizardry.item.ItemSpectralBow; -import electroblob.wizardry.item.ItemSpectralPickaxe; -import electroblob.wizardry.item.ItemSpectralSword; -import electroblob.wizardry.item.ItemSpellBook; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.item.ItemWizardArmour; -import electroblob.wizardry.item.ItemWizardHandbook; +import electroblob.wizardry.entity.projectile.EntityFirebomb; +import electroblob.wizardry.entity.projectile.EntityPoisonBomb; +import electroblob.wizardry.entity.projectile.EntitySmokeBomb; +import electroblob.wizardry.entity.projectile.EntitySparkBomb; +import electroblob.wizardry.item.*; +import electroblob.wizardry.misc.BehaviourSpellDispense; +import electroblob.wizardry.registry.WizardryTabs.CreativeTabListed; +import electroblob.wizardry.registry.WizardryTabs.CreativeTabSorted; import net.minecraft.block.Block; -import net.minecraft.init.SoundEvents; +import net.minecraft.block.BlockDispenser; +import net.minecraft.dispenser.BehaviorProjectileDispense; +import net.minecraft.dispenser.IPosition; +import net.minecraft.entity.IProjectile; import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.inventory.EntityEquipmentSlot.Type; +import net.minecraft.item.EnumRarity; import net.minecraft.item.Item; import net.minecraft.item.Item.ToolMaterial; import net.minecraft.item.ItemArmor.ArmorMaterial; import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; import net.minecraftforge.common.util.EnumHelper; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder; import net.minecraftforge.registries.IForgeRegistry; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; /** * Class responsible for defining, storing and registering all of wizardry's items. Also registers the ItemBlocks for @@ -48,158 +45,616 @@ import net.minecraftforge.registries.IForgeRegistry; * @author Electroblob * @since Wizardry 2.1 */ +@ObjectHolder(Wizardry.MODID) @Mod.EventBusSubscriber public final class WizardryItems { - public static final Item magic_crystal = new Item().setCreativeTab(WizardryTabs.WIZARDRY); + private WizardryItems(){} // No instances! - public static final Item magic_wand = new ItemWand(Tier.BASIC, null); - public static final Item apprentice_wand = new ItemWand(Tier.APPRENTICE, null); - public static final Item advanced_wand = new ItemWand(Tier.ADVANCED, null); - public static final Item master_wand = new ItemWand(Tier.MASTER, null); + /** Keeping the material fields in here means {@code @ObjectHolder} ignores them. In actual fact, I could have just + * made them private since wizardry only uses them within this class, but in case someone needs them elsewhere I've + * used this trick instead to keep them public. */ + public static final class Materials { - public static final Item arcane_tome = new ItemArcaneTome(); - public static final Item wizard_handbook = new ItemWizardHandbook(); - public static final Item spell_book = new ItemSpellBook(); + public static final ToolMaterial MAGICAL = EnumHelper.addToolMaterial("MAGICAL", 3, 1000, 8.0f, 4.0f, 0); - public static final Item basic_fire_wand = new ItemWand(Tier.BASIC, Element.FIRE); - public static final Item basic_ice_wand = new ItemWand(Tier.BASIC, Element.ICE); - public static final Item basic_lightning_wand = new ItemWand(Tier.BASIC, Element.LIGHTNING); - public static final Item basic_necromancy_wand = new ItemWand(Tier.BASIC, Element.NECROMANCY); - public static final Item basic_earth_wand = new ItemWand(Tier.BASIC, Element.EARTH); - public static final Item basic_sorcery_wand = new ItemWand(Tier.BASIC, Element.SORCERY); - public static final Item basic_healing_wand = new ItemWand(Tier.BASIC, Element.HEALING); + public static final ArmorMaterial SILK = EnumHelper.addArmorMaterial("SILK", "wizardry/textures/armour/wizard_armour", + 15, new int[]{0, 0, 0, 0}, 15, WizardrySounds.ITEM_ARMOUR_EQUIP_SILK, 0.0F); - public static final Item apprentice_fire_wand = new ItemWand(Tier.APPRENTICE, Element.FIRE); - public static final Item apprentice_ice_wand = new ItemWand(Tier.APPRENTICE, Element.ICE); - public static final Item apprentice_lightning_wand = new ItemWand(Tier.APPRENTICE, Element.LIGHTNING); - public static final Item apprentice_necromancy_wand = new ItemWand(Tier.APPRENTICE, Element.NECROMANCY); - public static final Item apprentice_earth_wand = new ItemWand(Tier.APPRENTICE, Element.EARTH); - public static final Item apprentice_sorcery_wand = new ItemWand(Tier.APPRENTICE, Element.SORCERY); - public static final Item apprentice_healing_wand = new ItemWand(Tier.APPRENTICE, Element.HEALING); + } - public static final Item advanced_fire_wand = new ItemWand(Tier.ADVANCED, Element.FIRE); - public static final Item advanced_ice_wand = new ItemWand(Tier.ADVANCED, Element.ICE); - public static final Item advanced_lightning_wand = new ItemWand(Tier.ADVANCED, Element.LIGHTNING); - public static final Item advanced_necromancy_wand = new ItemWand(Tier.ADVANCED, Element.NECROMANCY); - public static final Item advanced_earth_wand = new ItemWand(Tier.ADVANCED, Element.EARTH); - public static final Item advanced_sorcery_wand = new ItemWand(Tier.ADVANCED, Element.SORCERY); - public static final Item advanced_healing_wand = new ItemWand(Tier.ADVANCED, Element.HEALING); + @Nonnull + @SuppressWarnings("ConstantConditions") + private static T placeholder(){ return null; } - public static final Item master_fire_wand = new ItemWand(Tier.MASTER, Element.FIRE); - public static final Item master_ice_wand = new ItemWand(Tier.MASTER, Element.ICE); - public static final Item master_lightning_wand = new ItemWand(Tier.MASTER, Element.LIGHTNING); - public static final Item master_necromancy_wand = new ItemWand(Tier.MASTER, Element.NECROMANCY); - public static final Item master_earth_wand = new ItemWand(Tier.MASTER, Element.EARTH); - public static final Item master_sorcery_wand = new ItemWand(Tier.MASTER, Element.SORCERY); - public static final Item master_healing_wand = new ItemWand(Tier.MASTER, Element.HEALING); + // This is the most concise way I can think of to register the items. Really, I'd prefer it if there was only one + // point where all the items were listed, but that's not possible within the current system unless you use an array, + // which means you lose the individual fields... - public static final Item spectral_sword = new ItemSpectralSword(ToolMaterial.IRON); - public static final Item spectral_pickaxe = new ItemSpectralPickaxe(ToolMaterial.IRON); - public static final Item spectral_bow = new ItemSpectralBow(); + public static final Item magic_crystal = placeholder(); - public static final Item mana_flask = new Item().setCreativeTab(WizardryTabs.WIZARDRY); + public static final Item grand_crystal = placeholder(); + public static final Item crystal_shard = placeholder(); - public static final Item storage_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item siphon_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item condenser_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item range_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item duration_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item cooldown_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item blast_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item attunement_upgrade = new Item().setCreativeTab(WizardryTabs.WIZARDRY); + public static final Item wizard_handbook = placeholder(); + public static final Item arcane_tome = placeholder(); + public static final Item spell_book = placeholder(); + public static final Item scroll = placeholder(); - public static final Item magic_silk = new Item().setCreativeTab(WizardryTabs.WIZARDRY); + public static final Item magic_wand = placeholder(); + public static final Item apprentice_wand = placeholder(); + public static final Item advanced_wand = placeholder(); + public static final Item master_wand = placeholder(); - public static final Item.ToolMaterial MAGICAL = EnumHelper.addToolMaterial("MAGICAL", 3, 1000, 8.0f, 4.0f, 0); + public static final Item novice_fire_wand = placeholder(); + public static final Item novice_ice_wand = placeholder(); + public static final Item novice_lightning_wand = placeholder(); + public static final Item novice_necromancy_wand = placeholder(); + public static final Item novice_earth_wand = placeholder(); + public static final Item novice_sorcery_wand = placeholder(); + public static final Item novice_healing_wand = placeholder(); - public static final Item flaming_axe = new ItemFlamingAxe(MAGICAL); - public static final Item frost_axe = new ItemFrostAxe(MAGICAL); + public static final Item apprentice_fire_wand = placeholder(); + public static final Item apprentice_ice_wand = placeholder(); + public static final Item apprentice_lightning_wand = placeholder(); + public static final Item apprentice_necromancy_wand = placeholder(); + public static final Item apprentice_earth_wand = placeholder(); + public static final Item apprentice_sorcery_wand = placeholder(); + public static final Item apprentice_healing_wand = placeholder(); - public static final Item firebomb = new ItemFirebomb(); - public static final Item poison_bomb = new ItemPoisonBomb(); + public static final Item advanced_fire_wand = placeholder(); + public static final Item advanced_ice_wand = placeholder(); + public static final Item advanced_lightning_wand = placeholder(); + public static final Item advanced_necromancy_wand = placeholder(); + public static final Item advanced_earth_wand = placeholder(); + public static final Item advanced_sorcery_wand = placeholder(); + public static final Item advanced_healing_wand = placeholder(); - public static final Item blank_scroll = new Item().setCreativeTab(WizardryTabs.WIZARDRY); - public static final Item scroll = new ItemScroll().setCreativeTab(WizardryTabs.SPELLS); + public static final Item master_fire_wand = placeholder(); + public static final Item master_ice_wand = placeholder(); + public static final Item master_lightning_wand = placeholder(); + public static final Item master_necromancy_wand = placeholder(); + public static final Item master_earth_wand = placeholder(); + public static final Item master_sorcery_wand = placeholder(); + public static final Item master_healing_wand = placeholder(); - // The only way to get these is in dungeon chests (They are legendary, after all. Wizards don't just have them. - // Also if they were sold you could buy four from the same wizard - and that's no fun at all!) - public static final Item armour_upgrade = new ItemArmourUpgrade(); + public static final Item spectral_sword = placeholder(); + public static final Item spectral_pickaxe = placeholder(); + public static final Item spectral_bow = placeholder(); - public static final ArmorMaterial SILK = EnumHelper.addArmorMaterial("SILK", - "wizardry/textures/armour/wizard_armour", 15, new int[]{0, 0, 0, 0}, 0, - SoundEvents.ITEM_ARMOR_EQUIP_LEATHER, 0.0F); - - // Saw a post somewhere that said you have to put these in the init methods rather than defining them as constants. - // I *think* that's because the post was for a newer Minecraft version, where custom armour has its own renderer or - // something, meaning it would be done a lot like the entity rendering registry in ClientProxy. This is all working - // fine it seems, so I'm not going to fiddle with it, but it might be useful to know if I update versions again. - public static final Item wizard_hat = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, null); - public static final Item wizard_robe = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, null); - public static final Item wizard_leggings = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, null); - public static final Item wizard_boots = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, null); + public static final Item blank_scroll = placeholder(); + public static final Item magic_silk = placeholder(); - public static final Item wizard_hat_fire = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.FIRE); - public static final Item wizard_robe_fire = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.FIRE); - public static final Item wizard_leggings_fire = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.FIRE); - public static final Item wizard_boots_fire = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.FIRE); + public static final Item small_mana_flask = placeholder(); + public static final Item medium_mana_flask = placeholder(); + public static final Item large_mana_flask = placeholder(); - public static final Item wizard_hat_ice = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.ICE); - public static final Item wizard_robe_ice = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.ICE); - public static final Item wizard_leggings_ice = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.ICE); - public static final Item wizard_boots_ice = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.ICE); + public static final Item storage_upgrade = placeholder(); + public static final Item siphon_upgrade = placeholder(); + public static final Item condenser_upgrade = placeholder(); + public static final Item range_upgrade = placeholder(); + public static final Item duration_upgrade = placeholder(); + public static final Item cooldown_upgrade = placeholder(); + public static final Item blast_upgrade = placeholder(); + public static final Item attunement_upgrade = placeholder(); + public static final Item melee_upgrade = placeholder(); - public static final Item wizard_hat_lightning = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.LIGHTNING); - public static final Item wizard_robe_lightning = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.LIGHTNING); - public static final Item wizard_leggings_lightning = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.LIGHTNING); - public static final Item wizard_boots_lightning = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.LIGHTNING); + public static final Item flaming_axe = placeholder(); + public static final Item frost_axe = placeholder(); - public static final Item wizard_hat_necromancy = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.NECROMANCY); - public static final Item wizard_robe_necromancy = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.NECROMANCY); - public static final Item wizard_leggings_necromancy = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.NECROMANCY); - public static final Item wizard_boots_necromancy = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.NECROMANCY); + public static final Item identification_scroll = placeholder(); + public static final Item armour_upgrade = placeholder(); + public static final Item astral_diamond = placeholder(); + public static final Item purifying_elixir = placeholder(); - public static final Item wizard_hat_earth = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.EARTH); - public static final Item wizard_robe_earth = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.EARTH); - public static final Item wizard_leggings_earth = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.EARTH); - public static final Item wizard_boots_earth = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.EARTH); + public static final Item firebomb = placeholder(); + public static final Item poison_bomb = placeholder(); + public static final Item smoke_bomb = placeholder(); + public static final Item spark_bomb = placeholder(); - public static final Item wizard_hat_sorcery = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.SORCERY); - public static final Item wizard_robe_sorcery = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.SORCERY); - public static final Item wizard_leggings_sorcery = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.SORCERY); - public static final Item wizard_boots_sorcery = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.SORCERY); + public static final Item wizard_hat = placeholder(); + public static final Item wizard_robe = placeholder(); + public static final Item wizard_leggings = placeholder(); + public static final Item wizard_boots = placeholder(); - public static final Item wizard_hat_healing = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.HEAD, Element.HEALING); - public static final Item wizard_robe_healing = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.CHEST, Element.HEALING); - public static final Item wizard_leggings_healing = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.LEGS, Element.HEALING); - public static final Item wizard_boots_healing = new ItemWizardArmour(SILK, 1, EntityEquipmentSlot.FEET, Element.HEALING); + public static final Item wizard_hat_fire = placeholder(); + public static final Item wizard_robe_fire = placeholder(); + public static final Item wizard_leggings_fire = placeholder(); + public static final Item wizard_boots_fire = placeholder(); - public static final Item spectral_helmet = new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.HEAD); - public static final Item spectral_chestplate = new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.CHEST); - public static final Item spectral_leggings = new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.LEGS); - public static final Item spectral_boots = new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.FEET); + public static final Item wizard_hat_ice = placeholder(); + public static final Item wizard_robe_ice = placeholder(); + public static final Item wizard_leggings_ice = placeholder(); + public static final Item wizard_boots_ice = placeholder(); - public static final Map SPECTRAL_ARMOUR_MAP = ImmutableMap.of( - EntityEquipmentSlot.HEAD, spectral_helmet, EntityEquipmentSlot.CHEST, spectral_chestplate, - EntityEquipmentSlot.LEGS, spectral_leggings, EntityEquipmentSlot.FEET, spectral_boots); - - public static final Item smoke_bomb = new ItemSmokeBomb(); + public static final Item wizard_hat_lightning = placeholder(); + public static final Item wizard_robe_lightning = placeholder(); + public static final Item wizard_leggings_lightning = placeholder(); + public static final Item wizard_boots_lightning = placeholder(); - public static final Item identification_scroll = new ItemIdentificationScroll(); + public static final Item wizard_hat_necromancy = placeholder(); + public static final Item wizard_robe_necromancy = placeholder(); + public static final Item wizard_leggings_necromancy = placeholder(); + public static final Item wizard_boots_necromancy = placeholder(); - public static final Map, Item> WAND_MAP = new HashMap<>(); - public static final Map, Item> ARMOUR_MAP = new HashMap<>(); + public static final Item wizard_hat_earth = placeholder(); + public static final Item wizard_robe_earth = placeholder(); + public static final Item wizard_leggings_earth = placeholder(); + public static final Item wizard_boots_earth = placeholder(); - static { + public static final Item wizard_hat_sorcery = placeholder(); + public static final Item wizard_robe_sorcery = placeholder(); + public static final Item wizard_leggings_sorcery = placeholder(); + public static final Item wizard_boots_sorcery = placeholder(); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.MAGIC), magic_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.FIRE), basic_fire_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.ICE), basic_ice_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.LIGHTNING), basic_lightning_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.NECROMANCY), basic_necromancy_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.EARTH), basic_earth_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.SORCERY), basic_sorcery_wand); - WAND_MAP.put(ImmutablePair.of(Tier.BASIC, Element.HEALING), basic_healing_wand); + public static final Item wizard_hat_healing = placeholder(); + public static final Item wizard_robe_healing = placeholder(); + public static final Item wizard_leggings_healing = placeholder(); + public static final Item wizard_boots_healing = placeholder(); + + public static final Item spectral_helmet = placeholder(); + public static final Item spectral_chestplate = placeholder(); + public static final Item spectral_leggings = placeholder(); + public static final Item spectral_boots = placeholder(); + + public static final Item lightning_hammer = placeholder(); + + public static final Item ring_condensing = placeholder(); + public static final Item ring_siphoning = placeholder(); + public static final Item ring_battlemage = placeholder(); + public static final Item ring_combustion = placeholder(); + public static final Item ring_fire_melee = placeholder(); + public static final Item ring_fire_biome = placeholder(); + public static final Item ring_disintegration = placeholder(); + public static final Item ring_ice_melee = placeholder(); + public static final Item ring_ice_biome = placeholder(); + public static final Item ring_arcane_frost = placeholder(); + public static final Item ring_shattering = placeholder(); + public static final Item ring_lightning_melee = placeholder(); + public static final Item ring_storm = placeholder(); + public static final Item ring_seeking = placeholder(); + public static final Item ring_hammer = placeholder(); + public static final Item ring_soulbinding = placeholder(); + public static final Item ring_leeching = placeholder(); + public static final Item ring_necromancy_melee = placeholder(); + public static final Item ring_mind_control = placeholder(); + public static final Item ring_poison = placeholder(); + public static final Item ring_earth_melee = placeholder(); + public static final Item ring_earth_biome = placeholder(); + public static final Item ring_full_moon = placeholder(); + public static final Item ring_extraction = placeholder(); + public static final Item ring_mana_return = placeholder(); + public static final Item ring_blockwrangler = placeholder(); + public static final Item ring_conjurer = placeholder(); + public static final Item ring_defender = placeholder(); + public static final Item ring_paladin = placeholder(); + public static final Item ring_interdiction = placeholder(); + + public static final Item amulet_arcane_defence = placeholder(); + public static final Item amulet_warding = placeholder(); + public static final Item amulet_wisdom = placeholder(); + public static final Item amulet_fire_protection = placeholder(); + public static final Item amulet_fire_cloaking = placeholder(); + public static final Item amulet_ice_immunity = placeholder(); + public static final Item amulet_ice_protection = placeholder(); + public static final Item amulet_potential = placeholder(); + public static final Item amulet_channeling = placeholder(); + public static final Item amulet_lich = placeholder(); + public static final Item amulet_wither_immunity = placeholder(); + public static final Item amulet_glide = placeholder(); + public static final Item amulet_banishing = placeholder(); + public static final Item amulet_anchoring = placeholder(); + public static final Item amulet_recovery = placeholder(); + public static final Item amulet_transience = placeholder(); + public static final Item amulet_resurrection = placeholder(); + public static final Item amulet_auto_shield = placeholder(); + + public static final Item charm_haggler = placeholder(); + public static final Item charm_experience_tome = placeholder(); + public static final Item charm_auto_smelt = placeholder(); + public static final Item charm_lava_walking = placeholder(); + public static final Item charm_storm = placeholder(); + public static final Item charm_minion_health = placeholder(); + public static final Item charm_minion_variants = placeholder(); + public static final Item charm_flight = placeholder(); + public static final Item charm_growth = placeholder(); + public static final Item charm_abseiling = placeholder(); + public static final Item charm_silk_touch = placeholder(); + public static final Item charm_stop_time = placeholder(); + public static final Item charm_light = placeholder(); + public static final Item charm_transportation = placeholder(); + public static final Item charm_feeding = placeholder(); + + private static final Map, Item> WAND_MAP = new HashMap<>(); + private static final Map, Item> ARMOUR_MAP = new HashMap<>(); + + /** + * Helper method to return the appropriate wand based on tier and element. As of Wizardry 2.1, this uses the + * immutable map stored in {@link WizardryItems#WAND_MAP}. Currently used for upgrading wands, for chest generation + * and to iterate through wands for charging recipes. + * + * @param tier The tier of the wand required. + * @param element The element of the wand required. Null will be converted to {@link Element#MAGIC}. + * @return The wand item which corresponds to the given tier and element, or null if no such item exists. + * @throws NullPointerException if the given tier is null. + * @deprecated This is being phased out; it is now only used for wizard gear and trades. To add an item to + * charging recipes, use {@link WizardryRecipes#addToManaFlaskCharging(Item)}. + */ + @Deprecated + public static Item getWand(Tier tier, Element element){ + if(tier == null) throw new NullPointerException("The given tier cannot be null."); + if(element == null) element = Element.MAGIC; + return WAND_MAP.get(ImmutablePair.of(tier, element)); + } + + /** + * Helper method to return the appropriate armour item based on element and slot. As of Wizardry 2.1, this uses the + * immutable map stored in {@link WizardryItems#ARMOUR_MAP}. Currently used to iterate through armour for + * registering charging recipes and for chest generation. + * + * @param element The EnumElement of the armour required. Null will be converted to {@link Element#MAGIC}. + * @param slot EntityEquipmentSlot of the armour piece required + * @return The armour item which corresponds to the given element and slot, or null if no such item exists. + * @throws IllegalArgumentException if the given slot is not an armour slot. + * @deprecated This is being phased out; it is now only used for wizard gear and trades. To add an item to + * charging recipes, use {@link WizardryRecipes#addToManaFlaskCharging(Item)}. + */ + @Deprecated + public static Item getArmour(Element element, EntityEquipmentSlot slot){ + if(slot == null || slot.getSlotType() != Type.ARMOR) + throw new IllegalArgumentException("Must be a valid armour slot"); + if(element == null) element = Element.MAGIC; + return ARMOUR_MAP.get(ImmutablePair.of(slot, element)); + } + + /** + * Sets both the registry and unlocalised names of the given item, then registers it with the given registry. Use + * this instead of {@link Item#setRegistryName(String)} and {@link Item#setTranslationKey(String)} during + * construction, for convenience and consistency. As of wizardry 4.2, this also automatically adds it to the order + * list for its creative tab if that tab is a {@link CreativeTabListed}, meaning the order can be defined simply + * by the order in which the items are registered in this class. + * + * @param registry The registry to register the given item to. + * @param name The name of the item, without the mod ID or the .name stuff. The registry name will be + * {@code ebwizardry:[name]}. The unlocalised name will be {@code item.ebwizardry:[name].name}. + * @param item The item to register. + */ + // It now makes sense to have the name first, since it's shorter than an entire item declaration. + public static void registerItem(IForgeRegistry registry, String name, Item item){ + registerItem(registry, name, item, false); + } + + /** + * Sets both the registry and unlocalised names of the given item, then registers it with the given registry. Use + * this instead of {@link Item#setRegistryName(String)} and {@link Item#setTranslationKey(String)} during + * construction, for convenience and consistency. As of wizardry 4.2, this also automatically adds it to the order + * list for its creative tab if that tab is a {@link CreativeTabListed}, meaning the order can be defined simply + * by the order in which the items are registered in this class. + * + * @param registry The registry to register the given item to. + * @param name The name of the item, without the mod ID or the .name stuff. The registry name will be + * {@code ebwizardry:[name]}. The unlocalised name will be {@code item.ebwizardry:[name].name}. + * @param item The item to register. + * @param setTabIcon True to set this item as the icon for its creative tab. + */ + // It now makes sense to have the name first, since it's shorter than an entire item declaration. + public static void registerItem(IForgeRegistry registry, String name, Item item, boolean setTabIcon){ + + item.setRegistryName(Wizardry.MODID, name); + item.setTranslationKey(item.getRegistryName().toString()); + registry.register(item); + + if(setTabIcon && item.getCreativeTab() instanceof CreativeTabSorted){ + ((CreativeTabSorted)item.getCreativeTab()).setIconItem(new ItemStack(item)); + } + + if(item.getCreativeTab() instanceof CreativeTabListed){ + ((CreativeTabListed)item.getCreativeTab()).order.add(item); + } + } + + /** Registers an ItemBlock for the given block, with the same registry name as that block. As of wizardry 4.2, this + * also automatically adds it to the order list for its creative tab if that tab is a {@link CreativeTabListed}, + * meaning the order can be defined simply by the order in which the items are registered in this class. */ + private static void registerItemBlock(IForgeRegistry registry, Block block){ + // We don't need to keep a reference to the ItemBlock + Item itemblock = new ItemBlock(block).setRegistryName(block.getRegistryName()); + registry.register(itemblock); + + if(block.getCreativeTab() instanceof CreativeTabListed){ + ((CreativeTabListed)block.getCreativeTab()).order.add(itemblock); + } + } + + private static void registerMultiTexturedItemBlock(IForgeRegistry registry, Block block, boolean separateNames){ + // We don't need to keep a reference to the ItemBlock + Item itemblock = new ItemBlockMultiTexturedElemental(block, separateNames).setRegistryName(block.getRegistryName()); + registry.register(itemblock); + + if(block.getCreativeTab() instanceof CreativeTabListed){ + ((CreativeTabListed)block.getCreativeTab()).order.add(itemblock); + } + } + + + @SubscribeEvent + public static void register(RegistryEvent.Register event){ + + IForgeRegistry registry = event.getRegistry(); + + // ItemBlocks + + // Not all blocks need an ItemBlock + registerItemBlock(registry, WizardryBlocks.arcane_workbench); + registerItemBlock(registry, WizardryBlocks.crystal_ore); + registerItemBlock(registry, WizardryBlocks.crystal_flower); + registerItemBlock(registry, WizardryBlocks.transportation_stone); + registerMultiTexturedItemBlock(registry, WizardryBlocks.crystal_block, true); + registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone, false); + registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone_pedestal, false); + + // Items + + registerItem(registry, "magic_crystal", new ItemCrystal()); + + registerItem(registry, "crystal_shard", new Item().setCreativeTab(WizardryTabs.WIZARDRY)); + registerItem(registry, "grand_crystal", new Item().setCreativeTab(WizardryTabs.WIZARDRY)); + + registerItem(registry, "wizard_handbook", new ItemWizardHandbook(), true); + registerItem(registry, "arcane_tome", new ItemArcaneTome()); + registerItem(registry, "spell_book", new ItemSpellBook(), true); + registerItem(registry, "scroll", new ItemScroll()); + + registerItem(registry, "magic_wand", new ItemWand(Tier.NOVICE, null)); + registerItem(registry, "apprentice_wand", new ItemWand(Tier.APPRENTICE, null)); + registerItem(registry, "advanced_wand", new ItemWand(Tier.ADVANCED, null)); + registerItem(registry, "master_wand", new ItemWand(Tier.MASTER, null)); + + registerItem(registry, "novice_fire_wand", new ItemWand(Tier.NOVICE, Element.FIRE)); + registerItem(registry, "apprentice_fire_wand", new ItemWand(Tier.APPRENTICE, Element.FIRE)); + registerItem(registry, "advanced_fire_wand", new ItemWand(Tier.ADVANCED, Element.FIRE)); + registerItem(registry, "master_fire_wand", new ItemWand(Tier.MASTER, Element.FIRE)); + + registerItem(registry, "novice_ice_wand", new ItemWand(Tier.NOVICE, Element.ICE)); + registerItem(registry, "apprentice_ice_wand", new ItemWand(Tier.APPRENTICE, Element.ICE)); + registerItem(registry, "advanced_ice_wand", new ItemWand(Tier.ADVANCED, Element.ICE)); + registerItem(registry, "master_ice_wand", new ItemWand(Tier.MASTER, Element.ICE)); + + registerItem(registry, "novice_lightning_wand", new ItemWand(Tier.NOVICE, Element.LIGHTNING)); + registerItem(registry, "apprentice_lightning_wand", new ItemWand(Tier.APPRENTICE, Element.LIGHTNING)); + registerItem(registry, "advanced_lightning_wand", new ItemWand(Tier.ADVANCED, Element.LIGHTNING)); + registerItem(registry, "master_lightning_wand", new ItemWand(Tier.MASTER, Element.LIGHTNING)); + + registerItem(registry, "novice_necromancy_wand", new ItemWand(Tier.NOVICE, Element.NECROMANCY)); + registerItem(registry, "apprentice_necromancy_wand", new ItemWand(Tier.APPRENTICE, Element.NECROMANCY)); + registerItem(registry, "advanced_necromancy_wand", new ItemWand(Tier.ADVANCED, Element.NECROMANCY)); + registerItem(registry, "master_necromancy_wand", new ItemWand(Tier.MASTER, Element.NECROMANCY)); + + registerItem(registry, "novice_earth_wand", new ItemWand(Tier.NOVICE, Element.EARTH)); + registerItem(registry, "apprentice_earth_wand", new ItemWand(Tier.APPRENTICE, Element.EARTH)); + registerItem(registry, "advanced_earth_wand", new ItemWand(Tier.ADVANCED, Element.EARTH)); + registerItem(registry, "master_earth_wand", new ItemWand(Tier.MASTER, Element.EARTH)); + + registerItem(registry, "novice_sorcery_wand", new ItemWand(Tier.NOVICE, Element.SORCERY)); + registerItem(registry, "apprentice_sorcery_wand", new ItemWand(Tier.APPRENTICE, Element.SORCERY)); + registerItem(registry, "advanced_sorcery_wand", new ItemWand(Tier.ADVANCED, Element.SORCERY)); + registerItem(registry, "master_sorcery_wand", new ItemWand(Tier.MASTER, Element.SORCERY)); + + registerItem(registry, "novice_healing_wand", new ItemWand(Tier.NOVICE, Element.HEALING)); + registerItem(registry, "apprentice_healing_wand", new ItemWand(Tier.APPRENTICE, Element.HEALING)); + registerItem(registry, "advanced_healing_wand", new ItemWand(Tier.ADVANCED, Element.HEALING)); + registerItem(registry, "master_healing_wand", new ItemWand(Tier.MASTER, Element.HEALING)); + + registerItem(registry, "spectral_sword", new ItemSpectralSword(ToolMaterial.IRON)); + registerItem(registry, "spectral_pickaxe", new ItemSpectralPickaxe(ToolMaterial.IRON)); + registerItem(registry, "spectral_bow", new ItemSpectralBow()); + + registerItem(registry, "blank_scroll", new ItemBlankScroll()); + registerItem(registry, "magic_silk", new Item().setCreativeTab(WizardryTabs.WIZARDRY)); + + registerItem(registry, "small_mana_flask", new ItemManaFlask(ItemManaFlask.Size.SMALL)); + registerItem(registry, "medium_mana_flask", new ItemManaFlask(ItemManaFlask.Size.MEDIUM)); + registerItem(registry, "large_mana_flask", new ItemManaFlask(ItemManaFlask.Size.LARGE)); + + registerItem(registry, "storage_upgrade", new ItemWandUpgrade()); + registerItem(registry, "siphon_upgrade", new ItemWandUpgrade()); + registerItem(registry, "condenser_upgrade", new ItemWandUpgrade()); + registerItem(registry, "range_upgrade", new ItemWandUpgrade()); + registerItem(registry, "duration_upgrade", new ItemWandUpgrade()); + registerItem(registry, "cooldown_upgrade", new ItemWandUpgrade()); + registerItem(registry, "blast_upgrade", new ItemWandUpgrade()); + registerItem(registry, "attunement_upgrade", new ItemWandUpgrade()); + registerItem(registry, "melee_upgrade", new ItemWandUpgrade()); + + registerItem(registry, "flaming_axe", new ItemFlamingAxe(Materials.MAGICAL)); + registerItem(registry, "frost_axe", new ItemFrostAxe(Materials.MAGICAL)); + + registerItem(registry, "identification_scroll", new ItemIdentificationScroll()); + registerItem(registry, "armour_upgrade", new ItemArmourUpgrade()); + registerItem(registry, "astral_diamond", new Item(){ @Override public EnumRarity getRarity(ItemStack stack){ return EnumRarity.RARE; }}.setCreativeTab(WizardryTabs.WIZARDRY)); + registerItem(registry, "purifying_elixir", new ItemPurifyingElixir()); + + registerItem(registry, "firebomb", new ItemFirebomb()); + registerItem(registry, "poison_bomb", new ItemPoisonBomb()); + registerItem(registry, "smoke_bomb", new ItemSmokeBomb()); + registerItem(registry, "spark_bomb", new ItemSparkBomb()); + + registerItem(registry, "wizard_hat", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, null), true); + registerItem(registry, "wizard_robe", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, null)); + registerItem(registry, "wizard_leggings", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, null)); + registerItem(registry, "wizard_boots", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, null)); + + registerItem(registry, "wizard_hat_fire", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.FIRE)); + registerItem(registry, "wizard_robe_fire", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.FIRE)); + registerItem(registry, "wizard_leggings_fire", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.FIRE)); + registerItem(registry, "wizard_boots_fire", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.FIRE)); + + registerItem(registry, "wizard_hat_ice", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.ICE)); + registerItem(registry, "wizard_robe_ice", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.ICE)); + registerItem(registry, "wizard_leggings_ice", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.ICE)); + registerItem(registry, "wizard_boots_ice", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.ICE)); + + registerItem(registry, "wizard_hat_lightning", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.LIGHTNING)); + registerItem(registry, "wizard_robe_lightning", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.LIGHTNING)); + registerItem(registry, "wizard_leggings_lightning", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.LIGHTNING)); + registerItem(registry, "wizard_boots_lightning", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.LIGHTNING)); + + registerItem(registry, "wizard_hat_necromancy", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.NECROMANCY)); + registerItem(registry, "wizard_robe_necromancy", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.NECROMANCY)); + registerItem(registry, "wizard_leggings_necromancy", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.NECROMANCY)); + registerItem(registry, "wizard_boots_necromancy", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.NECROMANCY)); + + registerItem(registry, "wizard_hat_earth", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.EARTH)); + registerItem(registry, "wizard_robe_earth", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.EARTH)); + registerItem(registry, "wizard_leggings_earth", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.EARTH)); + registerItem(registry, "wizard_boots_earth", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.EARTH)); + + registerItem(registry, "wizard_hat_sorcery", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.SORCERY)); + registerItem(registry, "wizard_robe_sorcery", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.SORCERY)); + registerItem(registry, "wizard_leggings_sorcery", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.SORCERY)); + registerItem(registry, "wizard_boots_sorcery", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.SORCERY)); + + registerItem(registry, "wizard_hat_healing", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.HEAD, Element.HEALING)); + registerItem(registry, "wizard_robe_healing", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.CHEST, Element.HEALING)); + registerItem(registry, "wizard_leggings_healing", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.LEGS, Element.HEALING)); + registerItem(registry, "wizard_boots_healing", new ItemWizardArmour(Materials.SILK, 1, EntityEquipmentSlot.FEET, Element.HEALING)); + + registerItem(registry, "spectral_helmet", new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.HEAD)); + registerItem(registry, "spectral_chestplate", new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.CHEST)); + registerItem(registry, "spectral_leggings", new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.LEGS)); + registerItem(registry, "spectral_boots", new ItemSpectralArmour(ArmorMaterial.IRON, 1, EntityEquipmentSlot.FEET)); + + registerItem(registry, "lightning_hammer", new ItemLightningHammer()); + + registerItem(registry, "ring_condensing", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_siphoning", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_battlemage", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_combustion", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_fire_melee", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_fire_biome", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_disintegration", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_ice_melee", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_ice_biome", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_arcane_frost", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_shattering", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_lightning_melee", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_storm", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_seeking", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_hammer", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_soulbinding", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_leeching", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_necromancy_melee", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_mind_control", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_poison", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_earth_melee", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_earth_biome", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_full_moon", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_extraction", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_mana_return", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + registerItem(registry, "ring_blockwrangler", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_conjurer", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_defender", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.RING)); + registerItem(registry, "ring_paladin", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.RING)); + registerItem(registry, "ring_interdiction", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.RING)); + + registerItem(registry, "amulet_arcane_defence", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_warding", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_wisdom", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_fire_protection", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_fire_cloaking", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_ice_immunity", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_ice_protection", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_potential", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_channeling", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_lich", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_wither_immunity", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_glide", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_banishing", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_anchoring", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_recovery", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_transience", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_resurrection", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.AMULET)); + registerItem(registry, "amulet_auto_shield", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.AMULET)); + + registerItem(registry, "charm_haggler", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_experience_tome", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_auto_smelt", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_lava_walking", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_storm", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_minion_health", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_minion_variants", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_flight", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_growth", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_abseiling", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_silk_touch", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_stop_time", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_light", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_transportation", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_feeding", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.CHARM)); + + } + + /** Called from init() in the main mod class to register wizardry's dispenser behaviours. */ + public static void registerDispenseBehaviours(){ + + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(firebomb, new BehaviorProjectileDispense(){ + + @Override + protected IProjectile getProjectileEntity(World world, IPosition position, ItemStack stack){ + EntityFirebomb entity = new EntityFirebomb(world); + entity.setPosition(position.getX(), position.getY(), position.getZ()); + return entity; + } + + }); + + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(poison_bomb, new BehaviorProjectileDispense(){ + + @Override + protected IProjectile getProjectileEntity(World world, IPosition position, ItemStack stack){ + EntityPoisonBomb entity = new EntityPoisonBomb(world); + entity.setPosition(position.getX(), position.getY(), position.getZ()); + return entity; + } + + }); + + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(smoke_bomb, new BehaviorProjectileDispense(){ + + @Override + protected IProjectile getProjectileEntity(World world, IPosition position, ItemStack stack){ + EntitySmokeBomb entity = new EntitySmokeBomb(world); + entity.setPosition(position.getX(), position.getY(), position.getZ()); + return entity; + } + + }); + + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(spark_bomb, new BehaviorProjectileDispense(){ + + @Override + protected IProjectile getProjectileEntity(World world, IPosition position, ItemStack stack){ + EntitySparkBomb entity = new EntitySparkBomb(world); + entity.setPosition(position.getX(), position.getY(), position.getZ()); + return entity; + } + + }); + + BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(scroll, new BehaviourSpellDispense()); + + } + + public static void populateWandMap(){ + + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.MAGIC), magic_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.FIRE), novice_fire_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.ICE), novice_ice_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.LIGHTNING), novice_lightning_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.NECROMANCY), novice_necromancy_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.EARTH), novice_earth_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.SORCERY), novice_sorcery_wand); + WAND_MAP.put(ImmutablePair.of(Tier.NOVICE, Element.HEALING), novice_healing_wand); WAND_MAP.put(ImmutablePair.of(Tier.APPRENTICE, Element.MAGIC), apprentice_wand); WAND_MAP.put(ImmutablePair.of(Tier.APPRENTICE, Element.FIRE), apprentice_fire_wand); WAND_MAP.put(ImmutablePair.of(Tier.APPRENTICE, Element.ICE), apprentice_ice_wand); @@ -224,6 +679,9 @@ public final class WizardryItems { WAND_MAP.put(ImmutablePair.of(Tier.MASTER, Element.EARTH), master_earth_wand); WAND_MAP.put(ImmutablePair.of(Tier.MASTER, Element.SORCERY), master_sorcery_wand); WAND_MAP.put(ImmutablePair.of(Tier.MASTER, Element.HEALING), master_healing_wand); + } + + public static void populateArmourMap(){ ARMOUR_MAP.put(ImmutablePair.of(EntityEquipmentSlot.HEAD, Element.MAGIC), wizard_hat); ARMOUR_MAP.put(ImmutablePair.of(EntityEquipmentSlot.HEAD, Element.FIRE), wizard_hat_fire); @@ -259,162 +717,4 @@ public final class WizardryItems { ARMOUR_MAP.put(ImmutablePair.of(EntityEquipmentSlot.FEET, Element.HEALING), wizard_boots_healing); } - /** - * Sets both the registry and unlocalised names of the given item, then registers it with the given registry. Use - * this instead of {@link Item#setRegistryName(String)} and {@link Item#setTranslationKey(String)} during - * construction, for convenience and consistency. - * - * @param registry The registry to register the given item to. - * @param item The item to register. - * @param name The name of the item, without the mod ID or the .name stuff. The registry name will be - * {@code ebwizardry:[name]}. The unlocalised name will be {@code item.ebwizardry:[name].name}. - */ - public static void registerItem(IForgeRegistry registry, Item item, String name){ - item.setRegistryName(Wizardry.MODID, name); - item.setTranslationKey(item.getRegistryName().toString()); - registry.register(item); - } - - /** Registers an ItemBlock afor the given block, with the same registry name as that block. */ - private static void registerItemBlock(IForgeRegistry registry, Block block){ - // We don't need to keep a reference to the ItemBlock, so this can all be done in one line. - registry.register(new ItemBlock(block).setRegistryName(block.getRegistryName())); - } - - @SubscribeEvent - public static void register(RegistryEvent.Register event){ - IForgeRegistry registry = event.getRegistry(); - - // ItemBlocks - - // Not all blocks need an ItemBlock - registerItemBlock(registry, WizardryBlocks.arcane_workbench); - registerItemBlock(registry, WizardryBlocks.crystal_ore); - registerItemBlock(registry, WizardryBlocks.crystal_flower); - registerItemBlock(registry, WizardryBlocks.transportation_stone); - registerItemBlock(registry, WizardryBlocks.crystal_block); - - // Items - - registerItem(registry, magic_crystal, "magic_crystal"); - - registerItem(registry, magic_wand, "magic_wand"); - registerItem(registry, apprentice_wand, "apprentice_wand"); - registerItem(registry, advanced_wand, "advanced_wand"); - registerItem(registry, master_wand, "master_wand"); - - registerItem(registry, spell_book, "spell_book"); - registerItem(registry, arcane_tome, "arcane_tome"); - registerItem(registry, wizard_handbook, "wizard_handbook"); - - registerItem(registry, basic_fire_wand, "basic_fire_wand"); - registerItem(registry, basic_ice_wand, "basic_ice_wand"); - registerItem(registry, basic_lightning_wand, "basic_lightning_wand"); - registerItem(registry, basic_necromancy_wand, "basic_necromancy_wand"); - registerItem(registry, basic_earth_wand, "basic_earth_wand"); - registerItem(registry, basic_sorcery_wand, "basic_sorcery_wand"); - registerItem(registry, basic_healing_wand, "basic_healing_wand"); - - registerItem(registry, apprentice_fire_wand, "apprentice_fire_wand"); - registerItem(registry, apprentice_ice_wand, "apprentice_ice_wand"); - registerItem(registry, apprentice_lightning_wand, "apprentice_lightning_wand"); - registerItem(registry, apprentice_necromancy_wand, "apprentice_necromancy_wand"); - registerItem(registry, apprentice_earth_wand, "apprentice_earth_wand"); - registerItem(registry, apprentice_sorcery_wand, "apprentice_sorcery_wand"); - registerItem(registry, apprentice_healing_wand, "apprentice_healing_wand"); - - registerItem(registry, advanced_fire_wand, "advanced_fire_wand"); - registerItem(registry, advanced_ice_wand, "advanced_ice_wand"); - registerItem(registry, advanced_lightning_wand, "advanced_lightning_wand"); - registerItem(registry, advanced_necromancy_wand, "advanced_necromancy_wand"); - registerItem(registry, advanced_earth_wand, "advanced_earth_wand"); - registerItem(registry, advanced_sorcery_wand, "advanced_sorcery_wand"); - registerItem(registry, advanced_healing_wand, "advanced_healing_wand"); - - registerItem(registry, master_fire_wand, "master_fire_wand"); - registerItem(registry, master_ice_wand, "master_ice_wand"); - registerItem(registry, master_lightning_wand, "master_lightning_wand"); - registerItem(registry, master_necromancy_wand, "master_necromancy_wand"); - registerItem(registry, master_earth_wand, "master_earth_wand"); - registerItem(registry, master_sorcery_wand, "master_sorcery_wand"); - registerItem(registry, master_healing_wand, "master_healing_wand"); - - registerItem(registry, spectral_sword, "spectral_sword"); - registerItem(registry, spectral_pickaxe, "spectral_pickaxe"); - registerItem(registry, spectral_bow, "spectral_bow"); - - registerItem(registry, mana_flask, "mana_flask"); - - registerItem(registry, storage_upgrade, "storage_upgrade"); - registerItem(registry, siphon_upgrade, "siphon_upgrade"); - registerItem(registry, condenser_upgrade, "condenser_upgrade"); - registerItem(registry, range_upgrade, "range_upgrade"); - registerItem(registry, duration_upgrade, "duration_upgrade"); - registerItem(registry, cooldown_upgrade, "cooldown_upgrade"); - registerItem(registry, blast_upgrade, "blast_upgrade"); - registerItem(registry, attunement_upgrade, "attunement_upgrade"); - - registerItem(registry, flaming_axe, "flaming_axe"); - registerItem(registry, frost_axe, "frost_axe"); - - registerItem(registry, firebomb, "firebomb"); - registerItem(registry, poison_bomb, "poison_bomb"); - - registerItem(registry, blank_scroll, "blank_scroll"); - registerItem(registry, scroll, "scroll"); - - registerItem(registry, armour_upgrade, "armour_upgrade"); - - registerItem(registry, magic_silk, "magic_silk"); - - registerItem(registry, wizard_hat, "wizard_hat"); - registerItem(registry, wizard_robe, "wizard_robe"); - registerItem(registry, wizard_leggings, "wizard_leggings"); - registerItem(registry, wizard_boots, "wizard_boots"); - - registerItem(registry, wizard_hat_fire, "wizard_hat_fire"); - registerItem(registry, wizard_robe_fire, "wizard_robe_fire"); - registerItem(registry, wizard_leggings_fire, "wizard_leggings_fire"); - registerItem(registry, wizard_boots_fire, "wizard_boots_fire"); - - registerItem(registry, wizard_hat_ice, "wizard_hat_ice"); - registerItem(registry, wizard_robe_ice, "wizard_robe_ice"); - registerItem(registry, wizard_leggings_ice, "wizard_leggings_ice"); - registerItem(registry, wizard_boots_ice, "wizard_boots_ice"); - - registerItem(registry, wizard_hat_lightning, "wizard_hat_lightning"); - registerItem(registry, wizard_robe_lightning, "wizard_robe_lightning"); - registerItem(registry, wizard_leggings_lightning, "wizard_leggings_lightning"); - registerItem(registry, wizard_boots_lightning, "wizard_boots_lightning"); - - registerItem(registry, wizard_hat_necromancy, "wizard_hat_necromancy"); - registerItem(registry, wizard_robe_necromancy, "wizard_robe_necromancy"); - registerItem(registry, wizard_leggings_necromancy, "wizard_leggings_necromancy"); - registerItem(registry, wizard_boots_necromancy, "wizard_boots_necromancy"); - - registerItem(registry, wizard_hat_earth, "wizard_hat_earth"); - registerItem(registry, wizard_robe_earth, "wizard_robe_earth"); - registerItem(registry, wizard_leggings_earth, "wizard_leggings_earth"); - registerItem(registry, wizard_boots_earth, "wizard_boots_earth"); - - registerItem(registry, wizard_hat_sorcery, "wizard_hat_sorcery"); - registerItem(registry, wizard_robe_sorcery, "wizard_robe_sorcery"); - registerItem(registry, wizard_leggings_sorcery, "wizard_leggings_sorcery"); - registerItem(registry, wizard_boots_sorcery, "wizard_boots_sorcery"); - - registerItem(registry, wizard_hat_healing, "wizard_hat_healing"); - registerItem(registry, wizard_robe_healing, "wizard_robe_healing"); - registerItem(registry, wizard_leggings_healing, "wizard_leggings_healing"); - registerItem(registry, wizard_boots_healing, "wizard_boots_healing"); - - registerItem(registry, spectral_helmet, "spectral_helmet"); - registerItem(registry, spectral_chestplate, "spectral_chestplate"); - registerItem(registry, spectral_leggings, "spectral_leggings"); - registerItem(registry, spectral_boots, "spectral_boots"); - - registerItem(registry, smoke_bomb, "smoke_bomb"); - - registerItem(registry, identification_scroll, "identification_scroll"); - } - } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryLoot.java b/src/main/java/electroblob/wizardry/registry/WizardryLoot.java new file mode 100644 index 00000000..551e11e0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/registry/WizardryLoot.java @@ -0,0 +1,156 @@ +package electroblob.wizardry.registry; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.loot.RandomSpell; +import electroblob.wizardry.loot.WizardSpell; +import electroblob.wizardry.spell.Spell; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.storage.loot.*; +import net.minecraft.world.storage.loot.conditions.LootCondition; +import net.minecraft.world.storage.loot.functions.LootFunctionManager; +import net.minecraftforge.event.LootTableLoadEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.EntityEntry; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Predicate; + +/** + * Class responsible for registering wizardry's loot functions and loot tables. Also handles loot injection and the + * standard weighting. + * + * @author Electroblob + * @since Wizardry 1.0 + */ +@Mod.EventBusSubscriber +public final class WizardryLoot { + + //public static final String FROM_SPAWNER_NBT_FLAG = "fromSpawner"; + + private WizardryLoot(){} // No instances! + + /** Called from the preInit method in the main mod class to register the custom dungeon loot. */ + public static void register(){ + + /* Loot tables work as follows: Minecraft goes through each pool in turn. For each pool, it does a certain + * number of rolls, which can either be set to always be one number or a random number from a range. Each roll, + * it generates one stack of a single random entry in that pool, weighted according to the weights of the + * entries. Functions allow properties of that stack (stack size, damage, nbt) to be set, and even allow it to + * be replaced dynamically with a completely different item (though there's very little point in doing that as + * it could be achieved just as easily with more entries, which makes me think it would be bad practice). You + * can also use conditions to control whether an entry or pool is used at all, which is mostly for mob drops + * under specific conditions, but one of them is simply a random chance, meaning you could use it to make a pool + * that only gets rolled sometimes. All in all, this can get rather confusing, because stackable items can have + * 5 stages of randomness applied to them at once: a random chance for the pool, a random number of rolls for + * the pool, the weighted random chance of choosing that particular entry, a random chance for that entry, and a + * random stack size, and that's before you take functions into account. + * + * ...oh, and entries can be entire loot tables in themselves, allowing for potentially infinite levels of + * randomness. Yeah. */ + + // Always registers the loot tables, but only injects the additions into vanilla if the appropriate option is + // enabled in the config (see WizardryEventHandler). + LootFunctionManager.registerFunction(new RandomSpell.Serializer()); + LootFunctionManager.registerFunction(new WizardSpell.Serializer()); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/wizard_tower")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/obelisk")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/shrine")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/dungeon_additions")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/jungle_dispenser_additions")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/elemental_crystals")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/wizard_armour")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/arcane_tomes")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/wand_upgrades")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/evil_wizard")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/mob_additions")); + + } + + /** + * Helper method which gets a spell id according to the standard weighting. The tier is a weighted random value; the + * actual spell within that tier is completely random. Will not return the id of a spell which has been disabled in + * the config. This is for simple stuff like chests and drops; more complex generators like wizard trades don't use + * this method. + *

    + * For reference, the standard weighting is as follows: Novice: 60%, Apprentice: 25%, Advanced: 10%, Master: 5% + * + * @param random An instance of {@link Random} to use for RNG + * @param filter A {@link Predicate} specifying any requirements the chosen spell must fulfil + * @return A random spell id number, or -1 if no spell exists that satisfies the given filter + * @deprecated Everything uses loot tables now, I may remove this as some point + */ + @Deprecated + public static int getStandardWeightedRandomSpellId(Random random, Predicate filter){ + + Tier tier = Tier.getWeightedRandomTier(random); + + List spells = Spell.getSpells(new Spell.TierElementFilter(tier, null)); + spells.removeIf(filter.negate()); + + // Ensures the tier chosen actually has spells in it, and if not uses NOVICE instead. + if(spells.isEmpty()){ + spells = Spell.getSpells(new Spell.TierElementFilter(Tier.NOVICE, null)); + spells.removeIf(filter.negate()); + } + + if(spells.isEmpty()) return -1; + + // Finds a random spell in the list and returns its id. + return spells.get(random.nextInt(spells.size())).metadata(); + } + + @SubscribeEvent + public static void onLootTableLoadEvent(LootTableLoadEvent event){ + // General dungeon loot + if(Arrays.asList(Wizardry.settings.lootInjectionLocations).contains(event.getName())){ + event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/dungeon_additions", Wizardry.MODID + "_additional_dungeon_loot")); + } + // Jungle temple dispensers + if(event.getName().toString().matches("minecraft:chests/jungle_temple_dispenser")){ + event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/jungle_dispenser_additions", Wizardry.MODID + "_additional_dispenser_loot")); + } + // Mob drops + // Let's hope mods will play nice and store their entity loot tables under 'entities' or 'entity' + // If not, packmakers will have to sort it out themselves using the whitelist/blacklist + if(Arrays.asList(Wizardry.settings.mobLootTableWhitelist).contains(event.getName())){ + event.getTable().addPool(getAdditive(Wizardry.MODID + ":entities/mob_additions", Wizardry.MODID + "_additional_mob_drops")); + + }else if(!Arrays.asList(Wizardry.settings.mobLootTableBlacklist).contains(event.getName()) + && event.getName().getPath().contains("entities") || event.getName().getPath().contains("entity")){ + // Get the filename of the loot table json, for well-behaved mods this will be the entity name + String[] split = event.getName().getPath().split("/"); + String entityName = split[split.length - 1]; + + EntityEntry entry = ForgeRegistries.ENTITIES.getValue(new ResourceLocation(entityName)); + if(entry == null) return; // If this is true it didn't work :( + Class entityClass = entry.getEntityClass(); + + if(EnumCreatureType.MONSTER.getCreatureClass().isAssignableFrom(entityClass)){ + event.getTable().addPool(getAdditive(Wizardry.MODID + ":entities/mob_additions", Wizardry.MODID + "_additional_mob_drops")); + } + } + } + + private static LootPool getAdditive(String entryName, String poolName){ + return new LootPool(new LootEntry[]{getAdditiveEntry(entryName, 1)}, new LootCondition[0], + new RandomValueRange(1), new RandomValueRange(0, 1), Wizardry.MODID + "_" + poolName); + } + + private static LootEntryTable getAdditiveEntry(String name, int weight){ + return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0], + Wizardry.MODID + "_additive_entry"); + } + +// @SubscribeEvent +// public static void onLivingSpawnEvent(LivingSpawnEvent.SpecialSpawn event){ +// if(event.getSpawner() != null) event.getEntityLiving().getEntityData().setBoolean(FROM_SPAWNER_NBT_FLAG, true); +// } + +} diff --git a/src/main/java/electroblob/wizardry/registry/WizardryPotions.java b/src/main/java/electroblob/wizardry/registry/WizardryPotions.java index 681c26f7..3f799f6a 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryPotions.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryPotions.java @@ -1,96 +1,72 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.potion.PotionDecay; -import electroblob.wizardry.potion.PotionFrost; -import electroblob.wizardry.potion.PotionMagicEffect; -import electroblob.wizardry.potion.PotionMagicEffectParticles; -import electroblob.wizardry.util.WizardryParticleType; +import electroblob.wizardry.potion.*; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.Potion; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder; import net.minecraftforge.registries.IForgeRegistry; +import javax.annotation.Nonnull; + /** * Class responsible for defining, storing and registering all of wizardry's potion effects. * * @author Electroblob * @since Wizardry 2.1 */ +@ObjectHolder(Wizardry.MODID) @Mod.EventBusSubscriber public final class WizardryPotions { - /* Interestingly, setting the colour to black stops the particles from rendering. This is great, however, the black - * colour then 'mixes' with other potions that are applied. Whilst this is a bit annoying, for the amount it is - * likely to get noticed it is certainly preferable to always making showParticles false, because now I can give the - * user the option (via commands) of having one of my potion effects without particles, and more importantly the - * potion effect HUD still gets displayed. TODO: Backport to 1.7.10, assuming it also works in that version. This - * means also changing whenever the potion effect is added such that it is NOT ambient. */ + private WizardryPotions(){} // No instances! - public static final Potion frost = new PotionFrost(true, 0); // Colour was 0x38ddec (was arbitrary anyway) + @Nonnull + @SuppressWarnings("ConstantConditions") + private static T placeholder(){ return null; } - public static final Potion transience = new PotionMagicEffectParticles(false, 0, 0){ - @Override - public void spawnCustomParticle(World world, double x, double y, double z){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DUST, world, x, y, z, 0, 0, 0, - (int)(16.0D / (Math.random() * 0.8D + 0.2D)), 0.8f, 0.8f, 1.0f); - } - }.setBeneficial(); // 0xffe89b - - public static final Potion fireskin = new PotionMagicEffectParticles(false, 0, 1){ - @Override - public void spawnCustomParticle(World world, double x, double y, double z){ - world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); - } - - @Override - public void performEffect(EntityLivingBase entitylivingbase, int strength){ - entitylivingbase.extinguish(); // Stops melee mobs that are on fire from setting the player on fire, - // without allowing the player to actually stand in fire or swim in lava without taking damage. - }; - }.setBeneficial(); // 0xff2f02 - - public static final Potion ice_shroud = new PotionMagicEffectParticles(false, 0, 2){ - @Override - public void spawnCustomParticle(World world, double x, double y, double z){ - float brightness = 0.5f + (world.rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x, y, z, 0, 0, 0, - 48 + world.rand.nextInt(12), brightness, brightness + 0.1f, 1.0f, true, 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, x, y, z, 0, -0.02, 0, - 40 + world.rand.nextInt(10)); - } - }.setBeneficial(); // 0x52f1ff - - public static final Potion static_aura = new PotionMagicEffectParticles(false, 0, 3){ - @Override - public void spawnCustomParticle(World world, double x, double y, double z){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, x, y, z, 0, 0, 0, 3); - } - }.setBeneficial(); // 0x0070ff - - public static final Potion decay = new PotionDecay(true, 0x3c006c); - public static final Potion sixth_sense = new PotionMagicEffect(false, 0xc6ff01, 4).setBeneficial(); - public static final Potion arcane_jammer = new PotionMagicEffect(false, 0xcf4aa2, 5); - public static final Potion mind_trick = new PotionMagicEffect(true, 0x601683, 6); - public static final Potion mind_control = new PotionMagicEffect(true, 0x320b44, 7); - public static final Potion font_of_mana = new PotionMagicEffect(false, 0xffe5bb, 8).setBeneficial(); - public static final Potion fear = new PotionMagicEffect(true, 0xbd0100, 9); + public static final Potion frost = placeholder(); + public static final Potion transience = placeholder(); + public static final Potion fireskin = placeholder(); + public static final Potion ice_shroud = placeholder(); + public static final Potion static_aura = placeholder(); + public static final Potion decay = placeholder(); + public static final Potion sixth_sense = placeholder(); + public static final Potion arcane_jammer = placeholder(); + public static final Potion mind_trick = placeholder(); + public static final Potion mind_control = placeholder(); + public static final Potion font_of_mana = placeholder(); + public static final Potion fear = placeholder(); + public static final Potion curse_of_soulbinding = placeholder(); + public static final Potion paralysis = placeholder(); + public static final Potion muffle = placeholder(); + public static final Potion ward = placeholder(); + public static final Potion slow_time = placeholder(); + public static final Potion empowerment = placeholder(); + public static final Potion curse_of_enfeeblement = placeholder(); + public static final Potion curse_of_undeath = placeholder(); + public static final Potion containment = placeholder(); + public static final Potion frost_step = placeholder(); /** * Sets both the registry and unlocalised names of the given potion, then registers it with the given registry. Use - * this instead of {@link Potion#setRegistryName(String)} and {@link Potion#setUnlocalizedName(String)} during + * this instead of {@link Potion#setRegistryName(String)} and {@link Potion#setPotionName(String)} during * construction, for convenience and consistency. * * @param registry The registry to register the given potion to. - * @param potion The potion to register. * @param name The name of the potion, without the mod ID or the .name stuff. The registry name will be * {@code ebwizardry:[name]}. The unlocalised name will be {@code potion.ebwizardry:[name].name}. + * @param potion The potion to register. */ - public static void registerPotion(IForgeRegistry registry, Potion potion, String name){ + public static void registerPotion(IForgeRegistry registry, String name, Potion potion){ potion.setRegistryName(Wizardry.MODID, name); // For some reason, Potion#getName() doesn't prepend "potion." itself, so it has to be done here. potion.setPotionName("potion." + potion.getRegistryName().toString()); @@ -102,18 +78,115 @@ public final class WizardryPotions { IForgeRegistry registry = event.getRegistry(); - registerPotion(registry, frost, "frost"); - registerPotion(registry, transience, "transience"); - registerPotion(registry, fireskin, "fireskin"); - registerPotion(registry, ice_shroud, "ice_shroud"); - registerPotion(registry, static_aura, "static_aura"); - registerPotion(registry, decay, "decay"); - registerPotion(registry, sixth_sense, "sixth_sense"); - registerPotion(registry, arcane_jammer, "arcane_jammer"); - registerPotion(registry, mind_trick, "mind_trick"); - registerPotion(registry, mind_control, "mind_control"); - registerPotion(registry, font_of_mana, "font_of_mana"); - registerPotion(registry, fear, "fear"); + // Interestingly, setting the colour to black stops the particles from rendering. + + registerPotion(registry, "frost", new PotionFrost(true, 0)); // Colour was 0x38ddec (was arbitrary anyway) + + registerPotion(registry, "transience", new PotionMagicEffectParticles(false, 0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_transience.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.DUST).pos(x, y, z).clr(0.8f, 0.8f, 1.0f).shaded(true).spawn(world); + } + }.setBeneficial()); // 0xffe89b + + registerPotion(registry, "fireskin", new PotionMagicEffectParticles(false, 0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_fireskin.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); + } + + @Override + public void performEffect(EntityLivingBase entitylivingbase, int strength){ + entitylivingbase.extinguish(); // Stops melee mobs that are on fire from setting the player on fire, + // without allowing the player to actually stand in fire or swim in lava without taking damage. + } + }.setBeneficial()); // 0xff2f02 + + registerPotion(registry, "ice_shroud", new PotionMagicEffectParticles(false, 0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_ice_shroud.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + float brightness = 0.5f + (world.rand.nextFloat() / 2); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(brightness, brightness + 0.1f, 1.0f).gravity(true).spawn(world); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).spawn(world); + } + }.setBeneficial()); // 0x52f1ff + + registerPotion(registry, "static_aura", new PotionMagicEffectParticles(false, 0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_static_aura.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.SPARK).pos(x, y, z).spawn(world); + } + }.setBeneficial()); // 0x0070ff + + registerPotion(registry, "decay", new PotionDecay(true, 0x3c006c)); + + registerPotion(registry, "sixth_sense", new PotionMagicEffect(false, 0xc6ff01, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_sixth_sense.png")){ + @Override + public void performEffect(EntityLivingBase target, int strength){ + // Reset the shader (a bit dirty but both the potion expiry hooks are only fired server-side, and + // there's no point sending packets unnecessarily if we can just do this instead) + if(target.getActivePotionEffect(this).getDuration() <= 1 && target.world.isRemote + && target == net.minecraft.client.Minecraft.getMinecraft().player){ + net.minecraft.client.Minecraft.getMinecraft().entityRenderer.stopUseShader(); + } + } + }.setBeneficial()); + + registerPotion(registry, "arcane_jammer", new PotionMagicEffect(true, 0xcf4aa2, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_arcane_jammer.png"))); + + registerPotion(registry, "mind_trick", new PotionMagicEffect(true, 0x601683, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_mind_trick.png"))); + + registerPotion(registry, "mind_control", new PotionMagicEffect(true, 0x320b44, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_mind_control.png"))); + + registerPotion(registry, "font_of_mana", new PotionMagicEffect(false, 0xffe5bb, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_font_of_mana.png")).setBeneficial()); + + registerPotion(registry, "fear", new PotionMagicEffect(true, 0xbd0100, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_fear.png"))); + + registerPotion(registry, "curse_of_soulbinding", new Curse(true, 0x0f000f, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_curse_of_soulbinding.png")){ + @Override // We're not removing any attributes, but it's called when we want it to be so... + public void removeAttributesModifiersFromEntity(EntityLivingBase entity, net.minecraft.entity.ai.attributes.AbstractAttributeMap attributeMapIn, int amplifier){ + // TODO: Hmmmm... + } + }); + + registerPotion(registry, "paralysis", new PotionMagicEffectParticles(true, 0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_paralysis.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.SPARK).pos(x, y, z).spawn(world); + } + }); + + registerPotion(registry, "muffle", new PotionMagicEffect(false, 0x4464d9, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_muffle.png")).setBeneficial()); + + registerPotion(registry, "ward", new PotionMagicEffect(false, 0xc991d0, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_ward.png")).setBeneficial()); + + registerPotion(registry, "slow_time", new PotionSlowTime(false, 0x5be3bb).setBeneficial()); + + registerPotion(registry, "empowerment", new PotionMagicEffect(false, 0x8367bd, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_empowerment.png")).setBeneficial()); + + registerPotion(registry, "curse_of_enfeeblement", new CurseEnfeeblement(true, 0x36000b)); + + registerPotion(registry, "curse_of_undeath", new CurseUndeath(true, 0x685c00)); + + registerPotion(registry, "containment", new PotionContainment(true, 0x7988cc)); + + registerPotion(registry, "frost_step", new PotionFrostStep(false, 0).setBeneficial()); + } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryRecipes.java b/src/main/java/electroblob/wizardry/registry/WizardryRecipes.java new file mode 100644 index 00000000..f2691884 --- /dev/null +++ b/src/main/java/electroblob/wizardry/registry/WizardryRecipes.java @@ -0,0 +1,137 @@ +package electroblob.wizardry.registry; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.IManaStoringItem; +import electroblob.wizardry.item.ItemManaFlask; +import net.minecraft.inventory.ContainerPlayer; +import net.minecraft.inventory.ContainerWorkbench; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.oredict.OreDictionary; +import net.minecraftforge.oredict.ShapelessOreRecipe; +import net.minecraftforge.registries.IForgeRegistry; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Class responsible for defining and registering wizardry's non-JSON recipes (i.e. smelting recipes and dynamic + * crafting recipes). Also handles dynamic recipe display and usage. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +@Mod.EventBusSubscriber +public final class WizardryRecipes { + + private WizardryRecipes(){} // No instances! + + private static final Queue chargingRecipeQueue = new LinkedList<>(); + + /** Adds the given item to the list of items that can be charged using mana flasks. Dynamic charging recipes + * will be added for these items during {@code RegistryEvent.Register}. The item must implement + * {@link IManaStoringItem} for the recipes to work correctly. This method should be called from the item's + * constructor. */ + public static void addToManaFlaskCharging(Item item){ + chargingRecipeQueue.offer(item); + } + + /** Now only deals with the dynamic crafting recipes and the smelting recipes. */ + @SubscribeEvent + public static void registerRecipes(RegistryEvent.Register event){ + + IForgeRegistry registry = event.getRegistry(); + + FurnaceRecipes.instance().addSmeltingRecipeForBlock(WizardryBlocks.crystal_ore, new ItemStack(WizardryItems.magic_crystal), 0.5f); + + // Mana flask recipes + + ItemStack smallFlaskStack = new ItemStack(WizardryItems.small_mana_flask); + ItemStack mediumFlaskStack = new ItemStack(WizardryItems.medium_mana_flask); + ItemStack largeFlaskStack = new ItemStack(WizardryItems.large_mana_flask); + + ItemStack chargeable; + + while(!chargingRecipeQueue.isEmpty()){ + // Use remove() and not poll() because the queue shouldn't be empty in here + chargeable = new ItemStack(chargingRecipeQueue.remove(), 1, OreDictionary.WILDCARD_VALUE); + + registry.register(new ShapelessOreRecipe(null, chargeable, chargeable, smallFlaskStack){ + @Override public boolean isDynamic(){ return true; } // Stops it appearing in the recipe book + }.setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/small_flask_" + chargeable.getItem().getRegistryName().getPath()))); + + registry.register(new ShapelessOreRecipe(null, chargeable, chargeable, mediumFlaskStack){ + @Override public boolean isDynamic(){ return true; } + }.setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/medium_flask_" + chargeable.getItem().getRegistryName().getPath()))); + + registry.register(new ShapelessOreRecipe(null, chargeable, chargeable, largeFlaskStack){ + @Override public boolean isDynamic(){ return true; } + }.setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/large_flask_" + chargeable.getItem().getRegistryName().getPath()))); + + } + } + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.phase == TickEvent.Phase.START){ + + if(event.player.openContainer instanceof ContainerWorkbench){ + + IInventory craftMatrix = ((ContainerWorkbench)event.player.openContainer).craftMatrix; + ItemStack output = ((ContainerWorkbench)event.player.openContainer).craftResult.getStackInSlot(0); + processManaFlaskCrafting(craftMatrix, output); + + }else if(event.player.openContainer instanceof ContainerPlayer){ + + IInventory craftMatrix = ((ContainerPlayer)event.player.openContainer).craftMatrix; + ItemStack output = ((ContainerPlayer)event.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); + } + } + } + + private static void processManaFlaskCrafting(IInventory craftMatrix, ItemStack output){ + + // Charges wand using mana flask + + ItemManaFlask flask = null; + ItemStack input = ItemStack.EMPTY; + + for(int i = 0; i < craftMatrix.getSizeInventory(); i++){ + + ItemStack stack = craftMatrix.getStackInSlot(i); + + if(stack.getItem() instanceof ItemManaFlask){ + flask = (ItemManaFlask)stack.getItem(); + } + + if(stack.getItem() instanceof IManaStoringItem){ + input = stack; + } + } + + if(flask == null) return; + + if(output.getItem() instanceof IManaStoringItem && !input.isEmpty()){ + + output.setTagCompound((input.getTagCompound())); + + int currentMana = ((IManaStoringItem)input.getItem()).getMana(input); + + ((IManaStoringItem)output.getItem()).setMana(output, Math.min(currentMana + flask.size.capacity, + ((IManaStoringItem)input.getItem()).getManaCapacity(input))); + } + } +} diff --git a/src/main/java/electroblob/wizardry/registry/WizardryRegistry.java b/src/main/java/electroblob/wizardry/registry/WizardryRegistry.java deleted file mode 100644 index e87a81aa..00000000 --- a/src/main/java/electroblob/wizardry/registry/WizardryRegistry.java +++ /dev/null @@ -1,254 +0,0 @@ -package electroblob.wizardry.registry; - -import java.util.List; - -import com.google.common.collect.Lists; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.entity.EntityArc; -import electroblob.wizardry.entity.EntityMeteor; -import electroblob.wizardry.entity.EntityShield; -import electroblob.wizardry.entity.construct.EntityArrowRain; -import electroblob.wizardry.entity.construct.EntityBlackHole; -import electroblob.wizardry.entity.construct.EntityBlizzard; -import electroblob.wizardry.entity.construct.EntityBubble; -import electroblob.wizardry.entity.construct.EntityDecay; -import electroblob.wizardry.entity.construct.EntityEarthquake; -import electroblob.wizardry.entity.construct.EntityFireRing; -import electroblob.wizardry.entity.construct.EntityFireSigil; -import electroblob.wizardry.entity.construct.EntityForcefield; -import electroblob.wizardry.entity.construct.EntityFrostSigil; -import electroblob.wizardry.entity.construct.EntityHailstorm; -import electroblob.wizardry.entity.construct.EntityHammer; -import electroblob.wizardry.entity.construct.EntityHealAura; -import electroblob.wizardry.entity.construct.EntityIceSpike; -import electroblob.wizardry.entity.construct.EntityLightningPulse; -import electroblob.wizardry.entity.construct.EntityLightningSigil; -import electroblob.wizardry.entity.construct.EntityTornado; -import electroblob.wizardry.entity.living.EntityBlazeMinion; -import electroblob.wizardry.entity.living.EntityDecoy; -import electroblob.wizardry.entity.living.EntityEvilWizard; -import electroblob.wizardry.entity.living.EntityIceGiant; -import electroblob.wizardry.entity.living.EntityIceWraith; -import electroblob.wizardry.entity.living.EntityLightningWraith; -import electroblob.wizardry.entity.living.EntityMagicSlime; -import electroblob.wizardry.entity.living.EntityPhoenix; -import electroblob.wizardry.entity.living.EntityShadowWraith; -import electroblob.wizardry.entity.living.EntitySilverfishMinion; -import electroblob.wizardry.entity.living.EntitySkeletonMinion; -import electroblob.wizardry.entity.living.EntitySpiderMinion; -import electroblob.wizardry.entity.living.EntitySpiritHorse; -import electroblob.wizardry.entity.living.EntitySpiritWolf; -import electroblob.wizardry.entity.living.EntityStormElemental; -import electroblob.wizardry.entity.living.EntityWitherSkeletonMinion; -import electroblob.wizardry.entity.living.EntityWizard; -import electroblob.wizardry.entity.living.EntityZombieMinion; -import electroblob.wizardry.entity.projectile.EntityDarknessOrb; -import electroblob.wizardry.entity.projectile.EntityDart; -import electroblob.wizardry.entity.projectile.EntityFirebolt; -import electroblob.wizardry.entity.projectile.EntityFirebomb; -import electroblob.wizardry.entity.projectile.EntityForceArrow; -import electroblob.wizardry.entity.projectile.EntityForceOrb; -import electroblob.wizardry.entity.projectile.EntityIceCharge; -import electroblob.wizardry.entity.projectile.EntityIceLance; -import electroblob.wizardry.entity.projectile.EntityIceShard; -import electroblob.wizardry.entity.projectile.EntityLightningArrow; -import electroblob.wizardry.entity.projectile.EntityLightningDisc; -import electroblob.wizardry.entity.projectile.EntityMagicMissile; -import electroblob.wizardry.entity.projectile.EntityPoisonBomb; -import electroblob.wizardry.entity.projectile.EntitySmokeBomb; -import electroblob.wizardry.entity.projectile.EntitySpark; -import electroblob.wizardry.entity.projectile.EntitySparkBomb; -import electroblob.wizardry.entity.projectile.EntityThunderbolt; -import electroblob.wizardry.loot.RandomSpell; -import electroblob.wizardry.loot.WizardSpell; -import electroblob.wizardry.recipe.RecipeRechargeWithFlask; -import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; -import electroblob.wizardry.tileentity.TileEntityMagicLight; -import electroblob.wizardry.tileentity.TileEntityPlayerSave; -import electroblob.wizardry.tileentity.TileEntityStatue; -import electroblob.wizardry.tileentity.TileEntityTimer; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EnumCreatureType; -import net.minecraft.init.Biomes; -import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.FurnaceRecipes; -import net.minecraft.item.crafting.IRecipe; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.biome.Biome; -import net.minecraft.world.storage.loot.LootTableList; -import net.minecraft.world.storage.loot.functions.LootFunctionManager; -import net.minecraftforge.event.RegistryEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.registry.EntityRegistry; -import net.minecraftforge.fml.common.registry.ForgeRegistries; -import net.minecraftforge.fml.common.registry.GameRegistry; -import net.minecraftforge.registries.IForgeRegistry; - -/** - * Class responsible for registering all the things that don't have (or need) instances: entities, loot tables, recipes, - * etc. - * - * @author Electroblob - * @since Wizardry 1.0 - */ -@Mod.EventBusSubscriber -public final class WizardryRegistry { - - // NOTE: In 1.12, recipes have a registry (they can still stay here though since we don't keep references to them) - - /** Called from the preInit method in the main mod class to register the custom dungeon loot. */ - public static void registerLoot(){ - - /* Loot tables work as follows: Minecraft goes through each pool in turn. For each pool, it does a certain - * number or rolls, which can either be set to always be one number or a random number from a range. Each roll, - * it generates one stack of a single random entry in that pool, weighted according to the weights of the - * entries. Functions allow properties of that stack (stack size, damage, nbt) to be set, and even allow it to - * be replaced dynamically with a completely different item (though there's very little point in doing that as - * it could be achieved just as easily with more entries, which makes me think it would be bad practice). You - * can also use conditions to control whether an entry or pool is used at all, which is mostly for mob drops - * under specific conditions, but one of them is simply a random chance, meaning you could use it to make a pool - * that only gets rolled sometimes. All in all, this can get rather confusing, because stackable items can have - * 5 stages of randomness applied to them at once: a random chance for the pool, a random number of rolls for - * the pool, the weighted random chance of choosing that particular entry, a random chance for that entry, and a - * random stack size, and that's before you take functions into account. - * - * ...oh, and entries can be entire loot tables in themselves, allowing for potentially infinite levels of - * randomness. Yeah. */ - - // Always registers the loot tables, but only injects the additions into vanilla if the appropriate option is - // enabled in the config (see WizardryEventHandler). - LootFunctionManager.registerFunction(new RandomSpell.Serializer()); - LootFunctionManager.registerFunction(new WizardSpell.Serializer()); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/wizard_tower")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "chests/dungeon_additions")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/novice_wands")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/wizard_armour")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/arcane_tomes")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/wand_upgrades")); - LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/evil_wizard")); - // TODO: At the moment this is not used anywhere because I can't find a way to add it to all mobs. - // LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/mob_additions")); - - } - - /** Called from the preInit method in the main mod class to register all the tile entities. */ - public static void registerTileEntities(){ - - GameRegistry.registerTileEntity(TileEntityArcaneWorkbench.class, Wizardry.MODID + "ArcaneWorkbenchTileEntity"); - GameRegistry.registerTileEntity(TileEntityStatue.class, Wizardry.MODID + "PetrifiedStoneTileEntity"); - GameRegistry.registerTileEntity(TileEntityMagicLight.class, Wizardry.MODID + "MagicLightTileEntity"); - GameRegistry.registerTileEntity(TileEntityTimer.class, Wizardry.MODID + "TimerTileEntity"); - GameRegistry.registerTileEntity(TileEntityPlayerSave.class, Wizardry.MODID + "TileEntityPlayerSave"); - } - - /** Not actually the frequency at all; smaller numbers are more frequent. Vanilla uses 3 I think. */ - private static final int LIVING_UPDATE_INTERVAL = 3; - /** Not actually the frequency at all; smaller numbers are more frequent. */ - private static final int PROJECTILE_UPDATE_INTERVAL = 10; - - /** Called from the preInit method in the main mod class to register all the entities. */ - public static void registerEntities(){ - - int id = 0; // Incrementable index for the mod specific entity id. - - registerEntity(EntityZombieMinion.class, "zombie_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityMagicMissile.class, "magic_missile", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - // TODO: This should be a particle - registerEntity(EntityArc.class, "arc", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntitySkeletonMinion.class, "skeleton_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntitySparkBomb.class, "spark_bomb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntitySpiritWolf.class, "spirit_wolf", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityIceShard.class, "ice_shard", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityBlazeMinion.class, "blaze_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityIceWraith.class, "ice_wraith", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityLightningWraith.class, "lightning_wraith", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityBlackHole.class, "black_hole", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityShield.class, "shield", id++, 128, 1, true); - registerEntity(EntityMeteor.class, "meteor", id++, 128, 5, true); - registerEntity(EntityBlizzard.class, "blizzard", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntityAndEgg(EntityWizard.class, "wizard", id++, 128, LIVING_UPDATE_INTERVAL, true, 0x19295e, 0xee9312); - registerEntity(EntityBubble.class, "bubble", id++, 128, 3, false); - registerEntity(EntityTornado.class, "tornado", id++, 128, 1, false); - registerEntity(EntityHammer.class, "lightning_hammer", id++, 128, 1, true); - registerEntity(EntityFirebomb.class, "firebomb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityForceOrb.class, "force_orb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityArrowRain.class, "arrow_rain", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntitySpark.class, "spark", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityShadowWraith.class, "shadow_wraith", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityDarknessOrb.class, "darkness_orb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntitySpiderMinion.class, "spider_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityHealAura.class, "healing_aura", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityFireSigil.class, "fire_sigil", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityFrostSigil.class, "frost_sigil", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityLightningSigil.class, "lightning_sigil", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityLightningArrow.class, "lightning_arrow", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityFirebolt.class, "firebolt", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityPoisonBomb.class, "poison_bomb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityIceCharge.class, "ice_charge", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityForceArrow.class, "force_arrow", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityDart.class, "dart", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityMagicSlime.class, "magic_slime", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityForcefield.class, "forcefield", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityFireRing.class, "ring_of_fire", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityLightningDisc.class, "lightning_disc", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityThunderbolt.class, "thunderbolt", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityIceGiant.class, "ice_giant", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntitySpiritHorse.class, "spirit_horse", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityPhoenix.class, "phoenix", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntitySilverfishMinion.class, "silverfish_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityDecay.class, "decay", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityStormElemental.class, "storm_elemental", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityEarthquake.class, "earthquake", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityIceLance.class, "ice_lance", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntity(EntityHailstorm.class, "hailstorm", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntitySmokeBomb.class, "smoke_bomb", id++, 128, PROJECTILE_UPDATE_INTERVAL, true); - registerEntityAndEgg(EntityEvilWizard.class, "evil_wizard", id++, 128, LIVING_UPDATE_INTERVAL, true, 0x290404, 0xee9312); - registerEntity(EntityDecoy.class, "decoy", id++, 128, LIVING_UPDATE_INTERVAL, true); - registerEntity(EntityIceSpike.class, "ice_spike", id++, 128, 1, true); - // TODO: This should be a particle. - registerEntity(EntityLightningPulse.class, "lightning_pulse", id++, 128, PROJECTILE_UPDATE_INTERVAL, false); - registerEntity(EntityWitherSkeletonMinion.class, "wither_skeleton_minion", id++, 128, LIVING_UPDATE_INTERVAL, true); - - // TODO: May need fixing - List biomes = Lists.newArrayList(); - for (Biome biome : ForgeRegistries.BIOMES.getValuesCollection()) { - biomes.add(biome); - } - biomes.remove(Biomes.MUSHROOM_ISLAND); - biomes.remove(Biomes.MUSHROOM_ISLAND_SHORE); - // For reference: 5, 1, 1 are the parameters for the witch in vanilla. - EntityRegistry.addSpawn(EntityEvilWizard.class, 3, 1, 1, EnumCreatureType.MONSTER, biomes.toArray(new Biome[biomes.size()])); - - } - - /** Private helper method for registering entities; keeps things neater. For some reason, Forge 1.11.2 wants a - * ResourceLocation and a string name... probably because it's transitioning to the registry system. */ - private static void registerEntity(Class entityClass, String name, int id, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates){ - ResourceLocation registryName = new ResourceLocation(Wizardry.MODID, name); - EntityRegistry.registerModEntity(registryName, entityClass, registryName.toString(), id, Wizardry.instance, trackingRange, updateFrequency, sendsVelocityUpdates); - } - - /** Private helper method for registering entities with eggs; keeps things neater. For some reason, Forge 1.11.2 - * wants a ResourceLocation and a string name... probably because it's transitioning to the registry system. */ - private static void registerEntityAndEgg(Class entityClass, String name, int id, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates, int eggColour, int spotColour){ - ResourceLocation registryName = new ResourceLocation(Wizardry.MODID, name); - EntityRegistry.registerModEntity(registryName, entityClass, registryName.toString(), id, Wizardry.instance, trackingRange, updateFrequency, sendsVelocityUpdates); - EntityRegistry.registerEgg(registryName, eggColour, spotColour); - } - - /** Now only deals with the dynamic crafting recipes and the smelting recipes. */ - @SubscribeEvent - public static void registerRecipes(RegistryEvent.Register event){ - - IForgeRegistry registry = event.getRegistry(); - - FurnaceRecipes.instance().addSmeltingRecipeForBlock(WizardryBlocks.crystal_ore, new ItemStack(WizardryItems.magic_crystal), 0.5f); - - // Mana flask recipe - registry.register(new RecipeRechargeWithFlask().setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/eb_rechargeable_with_flask"))); - - } - -} diff --git a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java index f9a2b90f..97f9f25e 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java +++ b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java @@ -1,15 +1,17 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.misc.Forfeit; +import electroblob.wizardry.spell.Spell; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundEvent; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; /** - * Class responsible for defining, storing and registering all of wizardry's sound events. For some reason, these worked - * in the beta versions despite not being registered... + * Class responsible for defining, storing and registering all of wizardry's sound events. * * @author Electroblob * @since Wizardry 2.1 @@ -17,26 +19,118 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber public final class WizardrySounds { - // Anything with LOOP in its name is intended to be played in a continuous loop. - public static final SoundEvent SPELL_SPARK = createSound("arc"); - public static final SoundEvent SPELL_CONJURATION = createSound("aura"); - public static final SoundEvent SPELL_SHOCKWAVE = createSound("boom"); - public static final SoundEvent SPELL_LOOP_CRACKLE = createSound("crackle"); - public static final SoundEvent SPELL_SUMMONING = createSound("darkaura"); - public static final SoundEvent SPELL_DEFLECTION = createSound("effect"); - public static final SoundEvent SPELL_LIGHTNING = createSound("electricitya"); - public static final SoundEvent SPELL_LOOP_LIGHTNING = createSound("electricityb"); - public static final SoundEvent SPELL_LOOP_FIRE = createSound("flameray"); - public static final SoundEvent SPELL_FREEZE = createSound("freeze"); - public static final SoundEvent SPELL_LOOP_ICE = createSound("frostray"); - public static final SoundEvent SPELL_HEAL = createSound("heal"); - public static final SoundEvent SPELL_ICE = createSound("ice"); - public static final SoundEvent SPELL_CONJURATION_LARGE = createSound("largeaura"); - public static final SoundEvent SPELL_MAGIC = createSound("magic"); - public static final SoundEvent SPELL_LOOP_SPARKLE = createSound("sparkle"); - public static final SoundEvent SPELL_LOOP_WIND = createSound("wind"); - public static final SoundEvent SPELL_EARTHQUAKE = createSound("rumble"); - public static final SoundEvent SPELL_FORCE = createSound("force"); + private WizardrySounds(){} // No instances! + + /** Sound category for all spell-related sounds. This includes inanimate magical entities, but not minions. + * @see electroblob.wizardry.util.CustomSoundCategory */ + public static SoundCategory SPELLS; + + public static final SoundEvent BLOCK_ARCANE_WORKBENCH_SPELLBIND = createSound("block.arcane_workbench.bind_spell"); + public static final SoundEvent BLOCK_PEDESTAL_ACTIVATE = createSound("block.pedestal.activate"); + public static final SoundEvent BLOCK_PEDESTAL_CONQUER = createSound("block.pedestal.conquer"); + + public static final SoundEvent ITEM_WAND_SWITCH_SPELL = createSound("item.wand.switch_spell"); + public static final SoundEvent ITEM_WAND_LEVELUP = createSound("item.wand.levelup"); + public static final SoundEvent ITEM_WAND_MELEE = createSound("item.wand.melee"); + public static final SoundEvent ITEM_ARMOUR_EQUIP_SILK = createSound("item.armour.equip_silk"); + public static final SoundEvent ITEM_PURIFYING_ELIXIR_DRINK = createSound("item.purifying_elixir.drink"); + + public static final SoundEvent ENTITY_BLACK_HOLE_AMBIENT = createSound("entity.black_hole.ambient"); + public static final SoundEvent ENTITY_BLACK_HOLE_VANISH = createSound("entity.black_hole.vanish"); + public static final SoundEvent ENTITY_BUBBLE_POP = createSound("entity.bubble.pop"); + public static final SoundEvent ENTITY_BLIZZARD_AMBIENT = createSound("entity.blizzard.ambient"); + public static final SoundEvent ENTITY_DECAY_AMBIENT = createSound("entity.decay.ambient"); + public static final SoundEvent ENTITY_ENTRAPMENT_AMBIENT = createSound("entity.entrapment.ambient"); + public static final SoundEvent ENTITY_ENTRAPMENT_VANISH = createSound("entity.entrapment.vanish"); + public static final SoundEvent ENTITY_FIRE_RING_AMBIENT = createSound("entity.fire_ring.ambient"); + public static final SoundEvent ENTITY_FIRE_SIGIL_TRIGGER = createSound("entity.fire_sigil.trigger"); + public static final SoundEvent ENTITY_FORCEFIELD_DEFLECT = createSound("entity.forcefield.deflect"); + public static final SoundEvent ENTITY_FROST_SIGIL_TRIGGER = createSound("entity.frost_sigil.trigger"); + public static final SoundEvent ENTITY_HAMMER_ATTACK = createSound("entity.hammer.attack"); + public static final SoundEvent ENTITY_HAMMER_EXPLODE = createSound("entity.hammer.explode"); + public static final SoundEvent ENTITY_HAMMER_THROW = createSound("entity.hammer.throw"); + public static final SoundEvent ENTITY_HAMMER_LAND = createSound("entity.hammer.land"); + public static final SoundEvent ENTITY_HEAL_AURA_AMBIENT = createSound("entity.heal_aura.ambient"); + public static final SoundEvent ENTITY_ICE_SPIKE_EXTEND = createSound("entity.ice_spike.extend"); + public static final SoundEvent ENTITY_LIGHTNING_SIGIL_TRIGGER = createSound("entity.lightning_sigil.trigger"); + public static final SoundEvent ENTITY_METEOR_FALLING = createSound("entity.meteor.falling"); + public static final SoundEvent ENTITY_SHIELD_DEFLECT = createSound("entity.shield.deflect"); + public static final SoundEvent ENTITY_TORNADO_AMBIENT = createSound("entity.tornado.ambient"); + + public static final SoundEvent ENTITY_EVIL_WIZARD_AMBIENT = createSound("entity.evil_wizard.ambient"); + public static final SoundEvent ENTITY_EVIL_WIZARD_HURT = createSound("entity.evil_wizard.hurt"); + public static final SoundEvent ENTITY_EVIL_WIZARD_DEATH = createSound("entity.evil_wizard.death"); + public static final SoundEvent ENTITY_ICE_GIANT_ATTACK = createSound("entity.ice_giant.attack"); + public static final SoundEvent ENTITY_ICE_GIANT_DESPAWN = createSound("entity.ice_giant.despawn"); + public static final SoundEvent ENTITY_ICE_WRAITH_AMBIENT = createSound("entity.ice_wraith.ambient"); + public static final SoundEvent ENTITY_MAGIC_SLIME_ATTACK = createSound("entity.magic_slime.attack"); + public static final SoundEvent ENTITY_MAGIC_SLIME_EXPLODE = createSound("entity.magic_slime.explode"); + public static final SoundEvent ENTITY_MAGIC_SLIME_SPLAT = createSound("entity.magic_slime.splat"); + public static final SoundEvent ENTITY_PHOENIX_AMBIENT = createSound("entity.phoenix.ambient"); + public static final SoundEvent ENTITY_PHOENIX_BURN = createSound("entity.phoenix.burn"); + public static final SoundEvent ENTITY_PHOENIX_FLAP = createSound("entity.phoenix.flap"); + public static final SoundEvent ENTITY_PHOENIX_HURT = createSound("entity.phoenix.hurt"); + public static final SoundEvent ENTITY_PHOENIX_DEATH = createSound("entity.phoenix.death"); + public static final SoundEvent ENTITY_SHADOW_WRAITH_AMBIENT = createSound("entity.shadow_wraith.ambient"); + public static final SoundEvent ENTITY_SHADOW_WRAITH_NOISE = createSound("entity.shadow_wraith.noise"); + public static final SoundEvent ENTITY_SHADOW_WRAITH_HURT = createSound("entity.shadow_wraith.hurt"); + public static final SoundEvent ENTITY_SHADOW_WRAITH_DEATH = createSound("entity.shadow_wraith.death"); + public static final SoundEvent ENTITY_SPIRIT_HORSE_VANISH = createSound("entity.spirit_horse.vanish"); + public static final SoundEvent ENTITY_SPIRIT_WOLF_VANISH = createSound("entity.spirit_wolf.vanish"); + public static final SoundEvent ENTITY_STORM_ELEMENTAL_AMBIENT = createSound("entity.storm_elemental.ambient"); + public static final SoundEvent ENTITY_STORM_ELEMENTAL_BURN = createSound("entity.storm_elemental.burn"); + public static final SoundEvent ENTITY_STORM_ELEMENTAL_WIND = createSound("entity.storm_elemental.wind"); + public static final SoundEvent ENTITY_STORM_ELEMENTAL_HURT = createSound("entity.storm_elemental.hurt"); + public static final SoundEvent ENTITY_STORM_ELEMENTAL_DEATH = createSound("entity.storm_elemental.death"); + public static final SoundEvent ENTITY_WIZARD_YES = createSound("entity.wizard.yes"); + public static final SoundEvent ENTITY_WIZARD_NO = createSound("entity.wizard.no"); + public static final SoundEvent ENTITY_WIZARD_AMBIENT = createSound("entity.wizard.ambient"); + public static final SoundEvent ENTITY_WIZARD_TRADING = createSound("entity.wizard.trading"); + public static final SoundEvent ENTITY_WIZARD_HURT = createSound("entity.wizard.hurt"); + public static final SoundEvent ENTITY_WIZARD_DEATH = createSound("entity.wizard.death"); + + public static final SoundEvent ENTITY_DARKNESS_ORB_HIT = createSound("entity.darkness_orb.hit"); + public static final SoundEvent ENTITY_DART_HIT = createSound("entity.dart.hit"); + public static final SoundEvent ENTITY_DART_HIT_BLOCK = createSound("entity.dart.hit_block"); + public static final SoundEvent ENTITY_FIREBOLT_HIT = createSound("entity.firebolt.hit"); + public static final SoundEvent ENTITY_FIREBOMB_THROW = createSound("entity.firebomb.throw"); + public static final SoundEvent ENTITY_FIREBOMB_SMASH = createSound("entity.firebomb.smash"); + public static final SoundEvent ENTITY_FIREBOMB_FIRE = createSound("entity.firebomb.fire"); + public static final SoundEvent ENTITY_FORCE_ARROW_HIT = createSound("entity.force_arrow.hit"); + public static final SoundEvent ENTITY_FORCE_ORB_HIT = createSound("entity.force_orb.hit"); + public static final SoundEvent ENTITY_FORCE_ORB_HIT_BLOCK = createSound("entity.force_orb.hit_block"); + public static final SoundEvent ENTITY_ICEBALL_HIT = createSound("entity.iceball.hit"); + public static final SoundEvent ENTITY_ICE_CHARGE_SMASH = createSound("entity.ice_charge.smash"); + public static final SoundEvent ENTITY_ICE_CHARGE_ICE = createSound("entity.ice_charge.ice"); + public static final SoundEvent ENTITY_ICE_LANCE_SMASH = createSound("entity.ice_lance.smash"); + public static final SoundEvent ENTITY_ICE_LANCE_HIT = createSound("entity.ice_lance.hit"); + public static final SoundEvent ENTITY_ICE_SHARD_SMASH = createSound("entity.ice_shard.smash"); + public static final SoundEvent ENTITY_ICE_SHARD_HIT = createSound("entity.ice_shard.hit"); + public static final SoundEvent ENTITY_LIGHTNING_ARROW_HIT = createSound("entity.lightning_arrow.hit"); + public static final SoundEvent ENTITY_LIGHTNING_DISC_HIT = createSound("entity.lightning_disc.hit"); +// public static final SoundEvent ENTITY_MAGIC_FIREBALL_HIT = createSound("entity.magic_fireball.hit"); + public static final SoundEvent ENTITY_MAGIC_MISSILE_HIT = createSound("entity.magic_missile.hit"); + public static final SoundEvent ENTITY_POISON_BOMB_THROW = createSound("entity.poison_bomb.throw"); + public static final SoundEvent ENTITY_POISON_BOMB_SMASH = createSound("entity.poison_bomb.smash"); + public static final SoundEvent ENTITY_POISON_BOMB_POISON = createSound("entity.poison_bomb.poison"); + public static final SoundEvent ENTITY_SMOKE_BOMB_THROW = createSound("entity.smoke_bomb.throw"); + public static final SoundEvent ENTITY_SMOKE_BOMB_SMASH = createSound("entity.smoke_bomb.smash"); + public static final SoundEvent ENTITY_SMOKE_BOMB_SMOKE = createSound("entity.smoke_bomb.smoke"); + public static final SoundEvent ENTITY_HOMING_SPARK_HIT = createSound("entity.homing_spark.hit"); + public static final SoundEvent ENTITY_SPARK_BOMB_THROW = createSound("entity.spark_bomb.throw"); + public static final SoundEvent ENTITY_SPARK_BOMB_HIT = createSound("entity.spark_bomb.hit"); + public static final SoundEvent ENTITY_SPARK_BOMB_HIT_BLOCK = createSound("entity.spark_bomb.hit_block"); + public static final SoundEvent ENTITY_SPARK_BOMB_CHAIN = createSound("entity.spark_bomb.chain"); + public static final SoundEvent ENTITY_THUNDERBOLT_HIT = createSound("entity.thunderbolt.hit"); + + public static final SoundEvent SPELL_STATIC_AURA_RETALIATE = createSound("spell.static_aura.retaliate"); + public static final SoundEvent SPELL_CURSE_OF_SOULBINDING_RETALIATE = createSound("spell.curse_of_soulbinding.retaliate"); + public static final SoundEvent SPELL_TRANSPORTATION_TRAVEL = createSound("spell.transportation.travel"); + + public static final SoundEvent MISC_DISCOVER_SPELL = createSound("misc.discover_spell"); + public static final SoundEvent MISC_BOOK_OPEN = createSound("misc.book_open"); + public static final SoundEvent MISC_PAGE_TURN = createSound("misc.page_turn"); + public static final SoundEvent MISC_FREEZE = createSound("misc.freeze"); /** Trick borrowed from the Twilight Forest, makes things neater. */ public static SoundEvent createSound(String name){ @@ -44,26 +138,124 @@ public final class WizardrySounds { return new SoundEvent(new ResourceLocation(Wizardry.MODID, name)).setRegistryName(name); } + // For some reason, sound events seem to work even when they aren't registered, without even so much as a warning. + @SubscribeEvent public static void register(RegistryEvent.Register event){ - event.getRegistry().register(SPELL_SPARK); - event.getRegistry().register(SPELL_CONJURATION); - event.getRegistry().register(SPELL_SHOCKWAVE); - event.getRegistry().register(SPELL_LOOP_CRACKLE); - event.getRegistry().register(SPELL_SUMMONING); - event.getRegistry().register(SPELL_DEFLECTION); - event.getRegistry().register(SPELL_LIGHTNING); - event.getRegistry().register(SPELL_LOOP_LIGHTNING); - event.getRegistry().register(SPELL_LOOP_FIRE); - event.getRegistry().register(SPELL_FREEZE); - event.getRegistry().register(SPELL_LOOP_ICE); - event.getRegistry().register(SPELL_HEAL); - event.getRegistry().register(SPELL_ICE); - event.getRegistry().register(SPELL_CONJURATION_LARGE); - event.getRegistry().register(SPELL_MAGIC); - event.getRegistry().register(SPELL_LOOP_SPARKLE); - event.getRegistry().register(SPELL_LOOP_WIND); - event.getRegistry().register(SPELL_EARTHQUAKE); - event.getRegistry().register(SPELL_FORCE); + + event.getRegistry().register(BLOCK_ARCANE_WORKBENCH_SPELLBIND); + event.getRegistry().register(BLOCK_PEDESTAL_ACTIVATE); + event.getRegistry().register(BLOCK_PEDESTAL_CONQUER); + + event.getRegistry().register(ITEM_WAND_SWITCH_SPELL); + event.getRegistry().register(ITEM_WAND_LEVELUP); + event.getRegistry().register(ITEM_WAND_MELEE); + event.getRegistry().register(ITEM_ARMOUR_EQUIP_SILK); + event.getRegistry().register(ITEM_PURIFYING_ELIXIR_DRINK); + + event.getRegistry().register(ENTITY_BLACK_HOLE_AMBIENT); + event.getRegistry().register(ENTITY_BLACK_HOLE_VANISH); + event.getRegistry().register(ENTITY_BUBBLE_POP); + event.getRegistry().register(ENTITY_BLIZZARD_AMBIENT); + event.getRegistry().register(ENTITY_DECAY_AMBIENT); + event.getRegistry().register(ENTITY_ENTRAPMENT_AMBIENT); + event.getRegistry().register(ENTITY_ENTRAPMENT_VANISH); + event.getRegistry().register(ENTITY_FIRE_RING_AMBIENT); + event.getRegistry().register(ENTITY_FIRE_SIGIL_TRIGGER); + event.getRegistry().register(ENTITY_FORCEFIELD_DEFLECT); + event.getRegistry().register(ENTITY_FROST_SIGIL_TRIGGER); + event.getRegistry().register(ENTITY_HAMMER_ATTACK); + event.getRegistry().register(ENTITY_HAMMER_EXPLODE); + event.getRegistry().register(ENTITY_HAMMER_THROW); + event.getRegistry().register(ENTITY_HAMMER_LAND); + event.getRegistry().register(ENTITY_HEAL_AURA_AMBIENT); + event.getRegistry().register(ENTITY_ICE_SPIKE_EXTEND); + event.getRegistry().register(ENTITY_LIGHTNING_SIGIL_TRIGGER); + event.getRegistry().register(ENTITY_METEOR_FALLING); + event.getRegistry().register(ENTITY_SHIELD_DEFLECT); + event.getRegistry().register(ENTITY_TORNADO_AMBIENT); + + event.getRegistry().register(ENTITY_EVIL_WIZARD_AMBIENT); + event.getRegistry().register(ENTITY_EVIL_WIZARD_HURT); + event.getRegistry().register(ENTITY_EVIL_WIZARD_DEATH); + event.getRegistry().register(ENTITY_ICE_GIANT_ATTACK); + event.getRegistry().register(ENTITY_ICE_GIANT_DESPAWN); + event.getRegistry().register(ENTITY_ICE_WRAITH_AMBIENT); + event.getRegistry().register(ENTITY_MAGIC_SLIME_ATTACK); + event.getRegistry().register(ENTITY_MAGIC_SLIME_EXPLODE); + event.getRegistry().register(ENTITY_MAGIC_SLIME_SPLAT); + event.getRegistry().register(ENTITY_PHOENIX_AMBIENT); + event.getRegistry().register(ENTITY_PHOENIX_BURN); + event.getRegistry().register(ENTITY_PHOENIX_FLAP); + event.getRegistry().register(ENTITY_PHOENIX_HURT); + event.getRegistry().register(ENTITY_PHOENIX_DEATH); + event.getRegistry().register(ENTITY_SHADOW_WRAITH_AMBIENT); + event.getRegistry().register(ENTITY_SHADOW_WRAITH_NOISE); + event.getRegistry().register(ENTITY_SHADOW_WRAITH_HURT); + event.getRegistry().register(ENTITY_SHADOW_WRAITH_DEATH); + event.getRegistry().register(ENTITY_SPIRIT_HORSE_VANISH); + event.getRegistry().register(ENTITY_SPIRIT_WOLF_VANISH); + event.getRegistry().register(ENTITY_STORM_ELEMENTAL_AMBIENT); + event.getRegistry().register(ENTITY_STORM_ELEMENTAL_BURN); + event.getRegistry().register(ENTITY_STORM_ELEMENTAL_WIND); + event.getRegistry().register(ENTITY_STORM_ELEMENTAL_HURT); + event.getRegistry().register(ENTITY_STORM_ELEMENTAL_DEATH); + event.getRegistry().register(ENTITY_WIZARD_YES); + event.getRegistry().register(ENTITY_WIZARD_NO); + event.getRegistry().register(ENTITY_WIZARD_AMBIENT); + event.getRegistry().register(ENTITY_WIZARD_TRADING); + event.getRegistry().register(ENTITY_WIZARD_HURT); + event.getRegistry().register(ENTITY_WIZARD_DEATH); + + event.getRegistry().register(ENTITY_DARKNESS_ORB_HIT); + event.getRegistry().register(ENTITY_DART_HIT); + event.getRegistry().register(ENTITY_DART_HIT_BLOCK); + event.getRegistry().register(ENTITY_FIREBOLT_HIT); + event.getRegistry().register(ENTITY_FIREBOMB_THROW); + event.getRegistry().register(ENTITY_FIREBOMB_SMASH); + event.getRegistry().register(ENTITY_FIREBOMB_FIRE); + event.getRegistry().register(ENTITY_FORCE_ARROW_HIT); + event.getRegistry().register(ENTITY_FORCE_ORB_HIT); + event.getRegistry().register(ENTITY_FORCE_ORB_HIT_BLOCK); + event.getRegistry().register(ENTITY_ICEBALL_HIT); + event.getRegistry().register(ENTITY_ICE_CHARGE_SMASH); + event.getRegistry().register(ENTITY_ICE_CHARGE_ICE); + event.getRegistry().register(ENTITY_ICE_LANCE_SMASH); + event.getRegistry().register(ENTITY_ICE_LANCE_HIT); + event.getRegistry().register(ENTITY_ICE_SHARD_SMASH); + event.getRegistry().register(ENTITY_ICE_SHARD_HIT); + event.getRegistry().register(ENTITY_LIGHTNING_ARROW_HIT); + event.getRegistry().register(ENTITY_LIGHTNING_DISC_HIT); +// event.getRegistry().register(ENTITY_MAGIC_FIREBALL_HIT); + event.getRegistry().register(ENTITY_MAGIC_MISSILE_HIT); + event.getRegistry().register(ENTITY_POISON_BOMB_THROW); + event.getRegistry().register(ENTITY_POISON_BOMB_SMASH); + event.getRegistry().register(ENTITY_POISON_BOMB_POISON); + event.getRegistry().register(ENTITY_SMOKE_BOMB_THROW); + event.getRegistry().register(ENTITY_SMOKE_BOMB_SMASH); + event.getRegistry().register(ENTITY_SMOKE_BOMB_SMOKE); + event.getRegistry().register(ENTITY_HOMING_SPARK_HIT); + event.getRegistry().register(ENTITY_SPARK_BOMB_THROW); + event.getRegistry().register(ENTITY_SPARK_BOMB_HIT); + event.getRegistry().register(ENTITY_SPARK_BOMB_HIT_BLOCK); + event.getRegistry().register(ENTITY_SPARK_BOMB_CHAIN); + event.getRegistry().register(ENTITY_THUNDERBOLT_HIT); + + event.getRegistry().register(SPELL_STATIC_AURA_RETALIATE); + event.getRegistry().register(SPELL_CURSE_OF_SOULBINDING_RETALIATE); + event.getRegistry().register(SPELL_TRANSPORTATION_TRAVEL); + + event.getRegistry().register(MISC_DISCOVER_SPELL); + event.getRegistry().register(MISC_BOOK_OPEN); + event.getRegistry().register(MISC_PAGE_TURN); + event.getRegistry().register(MISC_FREEZE); + + for(Spell spell : Spell.getSpells(Spell.allSpells)){ + event.getRegistry().registerAll(spell.getSounds()); + } + + for(Forfeit forfeit : Forfeit.getForfeits()){ + event.getRegistry().register(forfeit.getSound()); + } } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryTabs.java b/src/main/java/electroblob/wizardry/registry/WizardryTabs.java index 6858715f..3e830707 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryTabs.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryTabs.java @@ -1,10 +1,5 @@ package electroblob.wizardry.registry; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - import electroblob.wizardry.item.ItemScroll; import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.spell.Spell; @@ -15,6 +10,10 @@ import net.minecraft.util.NonNullList; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + /** * Class responsible for defining and storing all of wizardry's creative tabs. Also handles sorting of the items. * @@ -23,111 +22,100 @@ import net.minecraftforge.fml.relauncher.SideOnly; */ public final class WizardryTabs { - private static Comparator itemSorter; - private static Comparator spellItemSorter; + private WizardryTabs(){} // No instances! - // Creative Tabs - public static final CreativeTabs WIZARDRY = new CreativeTabs("ebwizardry"){ + public static final CreativeTabs WIZARDRY = new CreativeTabListed("ebwizardry"); + public static final CreativeTabs GEAR = new CreativeTabListed("ebwizardrygear"); + public static final CreativeTabs SPELLS = new CreativeTabSorted("ebwizardryspells", + + (stack1, stack2) -> { - @Override - @SideOnly(Side.CLIENT) - public ItemStack createIcon(){ - return new ItemStack(WizardryItems.wizard_handbook); - } + if((stack1.getItem() instanceof ItemSpellBook && stack2.getItem() instanceof ItemSpellBook) + || (stack1.getItem() instanceof ItemScroll && stack2.getItem() instanceof ItemScroll)){ - @Override - @SideOnly(Side.CLIENT) - public void displayAllRelevantItems(NonNullList items){ - super.displayAllRelevantItems(items); - Collections.sort(items, itemSorter); - } - }; + Spell spell1 = Spell.byMetadata(stack1.getItemDamage()); + Spell spell2 = Spell.byMetadata(stack2.getItemDamage()); + + return spell1.compareTo(spell2); + + }else if(stack1.getItem() instanceof ItemScroll){ + return 1; + }else if(stack2.getItem() instanceof ItemScroll){ + return -1; + } + return 0; + }, + + true); - public static final CreativeTabs SPELLS = new CreativeTabs("ebwizardryspells"){ + public static class CreativeTabSorted extends CreativeTabs { + + private ItemStack iconItem; + private final Comparator sorter; + private final boolean searchable; + + public CreativeTabSorted(String label, Comparator sorter){ + this(label, sorter, false); + } + + public CreativeTabSorted(String label, Comparator sorter, boolean searchable){ + super(label); + this.sorter = sorter; + this.searchable = searchable; + } @Override @SideOnly(Side.CLIENT) public ItemStack createIcon(){ - return new ItemStack(WizardryItems.spell_book); + return iconItem; + } + + public void setIconItem(ItemStack iconItem){ + this.iconItem = iconItem; } @Override @SideOnly(Side.CLIENT) public void displayAllRelevantItems(NonNullList items){ super.displayAllRelevantItems(items); - Collections.sort(items, spellItemSorter); + items.sort(sorter); } @Override public boolean hasSearchBar(){ - return true; + return searchable; } @Override @SideOnly(Side.CLIENT) public String getBackgroundImageName(){ - return "item_search.png"; + return searchable ? "item_search.png" : super.getBackgroundImageName(); } - }; + } + + public static class CreativeTabListed extends CreativeTabSorted { - /** Initialises the item sorters for the creative tabs. */ - public static void sort(){ + public final List order; - List orderedItemList = Arrays.asList( - - Item.getItemFromBlock(WizardryBlocks.arcane_workbench), - Item.getItemFromBlock(WizardryBlocks.crystal_ore), Item.getItemFromBlock(WizardryBlocks.crystal_block), - Item.getItemFromBlock(WizardryBlocks.crystal_flower), - Item.getItemFromBlock(WizardryBlocks.transportation_stone), WizardryItems.magic_crystal, - WizardryItems.magic_wand, WizardryItems.apprentice_wand, WizardryItems.advanced_wand, - WizardryItems.master_wand, WizardryItems.arcane_tome, WizardryItems.wizard_handbook, - WizardryItems.basic_fire_wand, WizardryItems.basic_ice_wand, WizardryItems.basic_lightning_wand, - WizardryItems.basic_necromancy_wand, WizardryItems.basic_earth_wand, WizardryItems.basic_sorcery_wand, - WizardryItems.basic_healing_wand, WizardryItems.smoke_bomb, WizardryItems.firebomb, - WizardryItems.poison_bomb, WizardryItems.blank_scroll, WizardryItems.identification_scroll, - WizardryItems.mana_flask, WizardryItems.storage_upgrade, WizardryItems.siphon_upgrade, - WizardryItems.condenser_upgrade, WizardryItems.range_upgrade, WizardryItems.duration_upgrade, - WizardryItems.cooldown_upgrade, WizardryItems.blast_upgrade, WizardryItems.attunement_upgrade, - WizardryItems.magic_silk, WizardryItems.armour_upgrade, WizardryItems.wizard_hat, - WizardryItems.wizard_robe, WizardryItems.wizard_leggings, WizardryItems.wizard_boots, - WizardryItems.wizard_hat_fire, WizardryItems.wizard_robe_fire, WizardryItems.wizard_leggings_fire, - WizardryItems.wizard_boots_fire, WizardryItems.wizard_hat_ice, WizardryItems.wizard_robe_ice, - WizardryItems.wizard_leggings_ice, WizardryItems.wizard_boots_ice, WizardryItems.wizard_hat_lightning, - WizardryItems.wizard_robe_lightning, WizardryItems.wizard_leggings_lightning, - WizardryItems.wizard_boots_lightning, WizardryItems.wizard_hat_necromancy, - WizardryItems.wizard_robe_necromancy, WizardryItems.wizard_leggings_necromancy, - WizardryItems.wizard_boots_necromancy, WizardryItems.wizard_hat_earth, WizardryItems.wizard_robe_earth, - WizardryItems.wizard_leggings_earth, WizardryItems.wizard_boots_earth, WizardryItems.wizard_hat_sorcery, - WizardryItems.wizard_robe_sorcery, WizardryItems.wizard_leggings_sorcery, - WizardryItems.wizard_boots_sorcery, WizardryItems.wizard_hat_healing, WizardryItems.wizard_robe_healing, - WizardryItems.wizard_leggings_healing, WizardryItems.wizard_boots_healing); - - itemSorter = (stack1, stack2) -> { - // Neither stack is in the creative tab - if(!orderedItemList.contains(stack1.getItem()) && !orderedItemList.contains(stack2.getItem())) return 0; - if(!orderedItemList.contains(stack1.getItem())) return 1; // Only stack 2 is in the creative tab - if(!orderedItemList.contains(stack2.getItem())) return -1; // Only stack 1 is in the creative tab - // Both stacks are in the creative tab - return orderedItemList.indexOf(stack1.getItem()) - orderedItemList.indexOf(stack2.getItem()); - }; - - spellItemSorter = (stack1, stack2) -> { - - if((stack1.getItem() instanceof ItemSpellBook && stack2.getItem() instanceof ItemSpellBook) - || (stack1.getItem() instanceof ItemScroll && stack2.getItem() instanceof ItemScroll)){ - - Spell spell1 = Spell.get(stack1.getItemDamage()); - Spell spell2 = Spell.get(stack2.getItemDamage()); - - return spell1.compareTo(spell2); - - }else if(stack1.getItem() instanceof ItemScroll){ - return 1; - }else if(stack2.getItem() instanceof ItemScroll){ - return -1; - } - return 0; - }; + public CreativeTabListed(String label){ + // Can't accomplish this in a single constructor... just a quirk of the java compiler! + this(label, new ArrayList<>()); + } + + // Hey, maybe someone might want to keep a reference to the list, or pass in a different one. + public CreativeTabListed(String label, List order){ + + super(label, (stack1, stack2) -> { + // Neither stack is in the creative tab + if(!order.contains(stack1.getItem()) && !order.contains(stack2.getItem())) return 0; + if(!order.contains(stack1.getItem())) return 1; // Only stack 2 is in the creative tab + if(!order.contains(stack2.getItem())) return -1; // Only stack 1 is in the creative tab + // Both stacks are in the creative tab + return order.indexOf(stack1.getItem()) - order.indexOf(stack2.getItem()); + }); + + this.order = order; + } } } diff --git a/src/main/java/electroblob/wizardry/spell/Agility.java b/src/main/java/electroblob/wizardry/spell/Agility.java deleted file mode 100644 index cdd36fb3..00000000 --- a/src/main/java/electroblob/wizardry/spell/Agility.java +++ /dev/null @@ -1,49 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Agility extends Spell { - - public Agility(){ - super(Tier.APPRENTICE, 20, Element.SORCERY, "agility", SpellType.UTILITY, 40, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // 1.10 allows the particles to be completely hidden. - caster.addPotionEffect(new PotionEffect(MobEffects.SPEED, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - caster.addPotionEffect(new PotionEffect(MobEffects.JUMP_BOOST, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Arc.java b/src/main/java/electroblob/wizardry/spell/Arc.java index d2065b15..adfe2670 100644 --- a/src/main/java/electroblob/wizardry/spell/Arc.java +++ b/src/main/java/electroblob/wizardry/spell/Arc.java @@ -1,119 +1,66 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.EntityArc; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -// This spell was the 'guinea pig' for damage types, so to speak, so there's a bit of commentary on them here that may -// be useful for future reference. -public class Arc extends Spell { +public class Arc extends SpellRay { public Arc(){ - super(Tier.BASIC, 5, Element.LIGHTNING, "arc", SpellType.ATTACK, 15, EnumAction.NONE, false); + super("arc", false, EnumAction.NONE); + this.aimAssist(0.6f); + this.soundValues(1, 1.7f, 0.2f); + this.addProperties(DAMAGE); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 8 * modifiers.get(WizardryItems.range_upgrade), 4.0f); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - Entity target = rayTrace.entityHit; - - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(caster.posX, caster.posY + 1, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - world.spawnEntity(arc); - }else{ - for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + if(world.isRemote){ + // Rather neatly, the entity can be set here and if it's null nothing will happen. + ParticleBuilder.create(Type.LIGHTNING).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + ParticleBuilder.spawnShockParticles(world, target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ); } - + // This is a lot neater than it was, thanks to the damage type system. if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", + target.getName(), this.getNameForTranslationFormatted()), true); }else{ target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } - - caster.swingArm(hand); - target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); + return true; } - + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(caster.posX, caster.posY + 1, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - world.spawnEntity(arc); - }else{ - for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } - } - - // What's great about the damage type system is that, because I don't need to know if the creature resisted - // the damage here, I can simply call this without having to check for immunities at all. - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); - - caster.swingArm(hand); - target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java b/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java index 102fa9c5..7f4be44a 100644 --- a/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java +++ b/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java @@ -1,114 +1,77 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityWizard; import electroblob.wizardry.event.SpellCastEvent; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.monster.EntitySpellcasterIllager; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber -public class ArcaneJammer extends Spell { +public class ArcaneJammer extends SpellRay { public ArcaneJammer(){ - super(Tier.ADVANCED, 30, Element.HEALING, "arcane_jammer", SpellType.ATTACK, 50, EnumAction.NONE, false); + super("arcane_jammer", false, EnumAction.NONE); + this.soundValues(0.7f, 1, 0.4f); + this.addProperties(EFFECT_DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase entity = (EntityLivingBase)rayTrace.entityHit; - if(entity instanceof EntityWizard) WizardryAdvancementTriggers.jam_wizard.triggerFor(caster); - + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + if(!world.isRemote){ - entity.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0)); } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.9f, 0.3f, 0.7f); - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_DEFLECTION, 0.7F, - world.rand.nextFloat() * 0.4F + 0.8F); + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(!world.isRemote){ - target.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.9f, 0.3f, 0.7f); - } - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_DEFLECTION, 0.7F, world.rand.nextFloat() * 0.4F + 0.8F); - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.9f, 0.3f, 0.7f) + .spawn(world); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) // Prevents all spells so it comes before everything else + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + // Arcane jammer prevents spell casting. + if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.arcane_jammer)) event.setCanceled(true); + } @SubscribeEvent - public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ - // Arcane jammer prevents spell casting. - if(event.getEntityLiving().isPotionActive(WizardryPotions.arcane_jammer)) event.setCanceled(true); + public static void onLivingUpdateEvent(LivingEvent.LivingUpdateEvent event){ + if(event.getEntity() instanceof EntitySpellcasterIllager + && event.getEntityLiving().isPotionActive(WizardryPotions.arcane_jammer)){ + ((EntitySpellcasterIllager)event.getEntity()).setSpellType(EntitySpellcasterIllager.SpellType.NONE); + } } } diff --git a/src/main/java/electroblob/wizardry/spell/ArcaneLock.java b/src/main/java/electroblob/wizardry/spell/ArcaneLock.java new file mode 100644 index 00000000..38930604 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ArcaneLock.java @@ -0,0 +1,133 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.NBTExtras; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.event.world.ExplosionEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@Mod.EventBusSubscriber +public class ArcaneLock extends SpellRay { + + /** The NBT tag name for storing the owner's UUID in the tile entity data. */ + public static final String NBT_KEY = "arcaneLockOwner"; + + public ArcaneLock(){ + super("arcane_lock", false, EnumAction.NONE); + } + + @Override public boolean requiresPacket(){ return true; } + + @Override public boolean canBeCastByDispensers(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return false; } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(caster instanceof EntityPlayer){ + + TileEntity tileentity = world.getTileEntity(pos); + + if(tileentity != null){ + + if(tileentity.getTileData().hasUniqueId(NBT_KEY)){ + // Unlocking + if(world.getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(NBT_KEY)) == caster){ + NBTExtras.removeUniqueId(tileentity.getTileData(), NBT_KEY); + return true; + } + }else{ + // Locking + tileentity.getTileData().setUniqueId(NBT_KEY, caster.getUniqueID()); + return true; + } + } + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @SubscribeEvent + public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){ + + if(!canBypassLocks(event.getEntityPlayer())){ + + TileEntity tileentity = event.getWorld().getTileEntity(event.getPos()); + + // Prevents arcane-locked containers from being broken + // Need to check if it has the unique id first because if it is absent getUniqueId will return the nil UUID + if(tileentity != null && tileentity.getTileData().hasUniqueId(ArcaneLock.NBT_KEY)){ + // Only the player that owns the lock may break the container + // If nobody owns it (i.e. it's part of a shrine), player will be null + // Why is getUniqueId marked @Nullable? It literally creates a UUID and returns it! + if(event.getEntityPlayer().getUniqueID() != tileentity.getTileData().getUniqueId(ArcaneLock.NBT_KEY)){ + event.setCanceled(true); + } + } + } + } + + @SubscribeEvent + public static void onRightClickBlockEvent(PlayerInteractEvent.RightClickBlock event){ + + if(!canBypassLocks(event.getEntityPlayer())){ + + TileEntity tileentity = event.getWorld().getTileEntity(event.getPos()); + + // Prevents arcane-locked containers from being opened + // Need to check if it has the unique id first because if it is absent getUniqueId will return the nil UUID + if(tileentity != null && tileentity.getTileData().hasUniqueId(ArcaneLock.NBT_KEY)){ + // Why is getUniqueId marked @Nullable? It literally creates a UUID and returns it! + EntityPlayer owner = event.getWorld().getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(ArcaneLock.NBT_KEY)); + // Only the player that owns the lock or an ally of that player may open the container + // If nobody owns it (i.e. it's part of a shrine, or the owner logged out), player will be null + // Unfortunately we can't get the owner's allies if the owner is offline + // Perhaps if this crops up again we can store inverted 'ally of' maps as well? + if(owner == null || (owner != event.getEntityPlayer() && !AllyDesignationSystem.isPlayerAlly(owner, event.getEntityPlayer()))){ + event.setCanceled(true); + } + } + } + } + + private static boolean canBypassLocks(EntityPlayer player){ + + if(!player.isCreative()) return false; + if(Wizardry.settings.creativeBypassesArcaneLock) return true; + MinecraftServer server = player.world.getMinecraftServer(); + return server != null && WizardryUtilities.isPlayerOp(player, server); + } + + @SubscribeEvent + public static void onExplosionEvent(ExplosionEvent.Detonate event){ + // Prevents arcane-locked containers from being exploded + event.getAffectedBlocks().removeIf(pos -> event.getWorld().getTileEntity(pos) != null + && event.getWorld().getTileEntity(pos).getTileData().hasUniqueId(NBT_KEY)); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/ArrowRain.java b/src/main/java/electroblob/wizardry/spell/ArrowRain.java index b767a523..11f598ae 100644 --- a/src/main/java/electroblob/wizardry/spell/ArrowRain.java +++ b/src/main/java/electroblob/wizardry/spell/ArrowRain.java @@ -1,61 +1,38 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityArrowRain; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; import net.minecraft.world.World; -public class ArrowRain extends Spell { +public class ArrowRain extends SpellConstructRanged { public ArrowRain(){ - super(Tier.MASTER, 75, Element.SORCERY, "arrow_rain", SpellType.ATTACK, 300, EnumAction.NONE, false); + super("arrow_rain", EntityArrowRain::new, false); + this.floor(true); } - + @Override - public boolean doesSpellRequirePacket(){ - return false; + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + + // Moves the entity back towards the caster a bit, so the area of effect is better centred on the position. + // 3 is the distance to move the entity back towards the caster. + double dx = caster.posX - x; + double dz = caster.posZ - z; + double distRatio = 3 / Math.sqrt(dx * dx + dz * dz); + x += dx * distRatio; + z += dz * distRatio; + // Moves the entity up 5 blocks so that it is above mobs' heads. + y += 5; + + return super.spawnConstruct(world, x, y, z, side, caster, modifiers); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(20 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - // Moves the entity back towards the caster a bit, so the area of effect is better centred on the - // position. - // 3.0d is the distance to move the entity back towards the caster. - double dx = caster.posX - x; - double dz = caster.posZ - z; - double distRatio = 3.0d / Math.sqrt(dx * dx + dz * dz); - x += dx * distRatio; - z += dz * distRatio; - - EntityArrowRain arrowrain = new EntityArrowRain(world, x, y + 5, z, caster, - (int)(120 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - arrowrain.rotationYaw = caster.rotationYawHead; - world.spawnEntity(arrowrain); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 1.0F, 1.0F); - return true; - } - return false; + protected void addConstructExtras(EntityArrowRain construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + // Makes the arrows shoot in the direction the caster was looking when they cast the spell. + construct.rotationYaw = caster.rotationYawHead; } } diff --git a/src/main/java/electroblob/wizardry/spell/Banish.java b/src/main/java/electroblob/wizardry/spell/Banish.java index 5d49397c..9777892f 100644 --- a/src/main/java/electroblob/wizardry/spell/Banish.java +++ b/src/main/java/electroblob/wizardry/spell/Banish.java @@ -1,171 +1,108 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Banish extends Spell { +public class Banish extends SpellRay { + + public static final String MINIMUM_TELEPORT_DISTANCE = "minimum_teleport_distance"; + public static final String MAXIMUM_TELEPORT_DISTANCE = "maximum_teleport_distance"; public Banish(){ - super(Tier.APPRENTICE, 15, Element.NECROMANCY, "banish", SpellType.ATTACK, 40, EnumAction.NONE, false); + super("banish", false, EnumAction.NONE); + this.addProperties(MINIMUM_TELEPORT_DISTANCE, MAXIMUM_TELEPORT_DISTANCE); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(target instanceof EntityLivingBase){ - Vec3d look = caster.getLookVec(); + EntityLivingBase entity = (EntityLivingBase)target; - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); + double minRadius = getProperty(MINIMUM_TELEPORT_DISTANCE).doubleValue(); + double maxRadius = getProperty(MAXIMUM_TELEPORT_DISTANCE).doubleValue(); + double radius = (minRadius + world.rand.nextDouble() * maxRadius-minRadius) * modifiers.get(WizardryItems.blast_upgrade); - // Left as EntityLivingBase, since it's reasonable to teleport armour stands around. - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && rayTrace.entityHit instanceof EntityLivingBase){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - double radius = (8 + world.rand.nextDouble() * 8) * modifiers.get(WizardryItems.range_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; - - int x = MathHelper.floor(target.posX + Math.sin(angle) * radius); - int z = MathHelper.floor(target.posZ - Math.cos(angle) * radius); - int y = WizardryUtilities.getNearestFloorLevel(world, - new BlockPos(x, (int)caster.getEntityBoundingBox().minY, z), (int)radius); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double dx1 = target.posX; - double dy1 = target.getEntityBoundingBox().minY + target.height * world.rand.nextFloat(); - double dz1 = target.posZ; - world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5, - world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5); - } - } - - if(y > -1){ - - // This means stuff like snow layers is ignored, meaning when on snow-covered ground the caster does - // not teleport 1 block above the ground. - if(!world.getBlockState(new BlockPos(x, y, z)).getMaterial().blocksMovement()){ - y--; - } - - if(world.getBlockState(new BlockPos(x, y + 1, z)).getMaterial().blocksMovement() - || world.getBlockState(new BlockPos(x, y + 2, z)).getMaterial().blocksMovement()){ - return false; - } - - if(!world.isRemote){ - target.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5); - } - - target.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); - } + teleport(entity, world, radius); } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.PORTAL, x, y - 0.5, z, 0, 0, 0); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.2f, 0, 0.2f).spawn(world); + } + + // Extracted as a separate method for external use + public boolean teleport(EntityLivingBase entity, World world, double radius){ + + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + + int x = MathHelper.floor(entity.posX + MathHelper.sin(angle) * radius); + int z = MathHelper.floor(entity.posZ - MathHelper.cos(angle) * radius); + Integer y = WizardryUtilities.getNearestFloor(world, + new BlockPos(x, (int)entity.getEntityBoundingBox().minY, z), (int)radius); if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - world.spawnParticle(EnumParticleTypes.PORTAL, x1, y1 - 0.5, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.2f, 0.0f, 0.2f); + for(int i=0; i<10; i++){ + double dx1 = entity.posX; + double dy1 = entity.getEntityBoundingBox().minY + entity.height * world.rand.nextFloat(); + double dz1 = entity.posZ; + world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5, + world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5); } + + // Can't be bothered to route this through the proxies! + if(entity == net.minecraft.client.Minecraft.getMinecraft().player) + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); - caster.swingArm(hand); - return true; - } + if(y != null){ - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - double radius = (8 + world.rand.nextDouble() * 8) * modifiers.get(WizardryItems.range_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; - - int x = MathHelper.floor(target.posX + Math.sin(angle) * radius); - int z = MathHelper.floor(target.posZ - Math.cos(angle) * radius); - int y = WizardryUtilities.getNearestFloorLevel(world, - new BlockPos(x, (int)caster.getEntityBoundingBox().minY, z), (int)radius); - - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < 25; i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.PORTAL, x1, y1 - 0.5, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.2f, 0.0f, 0.2f); - } - - for(int i = 0; i < 10; i++){ - double dx1 = target.posX; - double dy1 = target.getEntityBoundingBox().minY + target.height * world.rand.nextFloat(); - double dz1 = target.posZ; - world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5, - world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5); - } + // This means stuff like snow layers is ignored, meaning when on snow-covered ground the target does + // not teleport 1 block above the ground. + if(!world.getBlockState(new BlockPos(x, y, z)).getMaterial().blocksMovement()){ + y--; } - if(y > -1){ - - // This means stuff like snow layers is ignored, meaning when on snow-covered ground the caster does - // not teleport 1 block above the ground. - if(!world.getBlockState(new BlockPos(x, y, z)).getMaterial().blocksMovement()){ - y--; - } - - if(world.getBlockState(new BlockPos(x, y + 1, z)).getMaterial().blocksMovement() - || world.getBlockState(new BlockPos(x, y + 2, z)).getMaterial().blocksMovement()){ - return false; - } - - if(!world.isRemote){ - target.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5); - } - - target.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); + if(world.getBlockState(new BlockPos(x, y + 1, z)).getMaterial().blocksMovement() + || world.getBlockState(new BlockPos(x, y + 2, z)).getMaterial().blocksMovement()){ + return false; } + + if(!world.isRemote){ + entity.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5); + } + + this.playSound(world, entity, 0, -1, new SpellModifiers()); } - caster.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); - caster.swingArm(hand); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ return true; } diff --git a/src/main/java/electroblob/wizardry/spell/BlackHole.java b/src/main/java/electroblob/wizardry/spell/BlackHole.java deleted file mode 100644 index f8c15034..00000000 --- a/src/main/java/electroblob/wizardry/spell/BlackHole.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityBlackHole; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; - -public class BlackHole extends Spell { - - public BlackHole(){ - super(Tier.MASTER, 150, Element.SORCERY, "black_hole", SpellType.ATTACK, 400, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - // This demonstrates beautifully the elegance of BlockPos. In 1.7.10 this required a 50-line long switch - // statement and a flag variable; now it only needs a few lines. - BlockPos pos = new BlockPos(rayTrace.hitVec).offset(rayTrace.sideHit); - - if(world.isAirBlock(pos)){ - - if(!world.isRemote){ - world.spawnEntity(new EntityBlackHole(world, pos.getX() + 0.5, pos.getY() - 1 + 0.5, - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE))); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 2.0f, 0.7f); - return true; - } - - }else{ - int x = (int)(Math.floor(caster.posX) + caster.getLookVec().x * 8); - int y = (int)(Math.floor(caster.posY) + caster.eyeHeight + caster.getLookVec().y * 8); - int z = (int)(Math.floor(caster.posZ) + caster.getLookVec().z * 8); - if(!world.isRemote){ - world.spawnEntity(new EntityBlackHole(world, x, y, z, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE))); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 2.0f, 0.7f); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Blink.java b/src/main/java/electroblob/wizardry/spell/Blink.java index 85fee81c..f6adf784 100644 --- a/src/main/java/electroblob/wizardry/spell/Blink.java +++ b/src/main/java/electroblob/wizardry/spell/Blink.java @@ -1,15 +1,12 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.RayTracer; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -22,14 +19,15 @@ import net.minecraft.world.World; public class Blink extends Spell { public Blink(){ - super(Tier.APPRENTICE, 15, Element.SORCERY, "blink", SpellType.UTILITY, 25, EnumAction.NONE, false); + super("blink", EnumAction.NONE, false); + addProperties(RANGE); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - RayTraceResult rayTrace = WizardryUtilities.rayTrace(25 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, + getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade), false); // It's worth noting that on the client side, the cast() method only gets called if the server side // cast method succeeded, so you need not check any conditions for spawning particles. @@ -43,14 +41,17 @@ public class Blink extends Spell { world.spawnParticle(EnumParticleTypes.PORTAL, dx, dy, dz, world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5); } + + // Can't be bothered to route this through the proxies! + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); } if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ BlockPos pos = rayTrace.getBlockPos(); - // Can't teleport onto the ceiling. - if(rayTrace.sideHit == EnumFacing.DOWN) return false; + // Leave space for the player's head + if(rayTrace.sideHit == EnumFacing.DOWN) pos = pos.down(); // This means stuff like snow layers is ignored, meaning when on snow-covered ground the player does // not teleport 1 block above the ground. @@ -58,7 +59,7 @@ public class Blink extends Spell { pos = pos.down(); } - pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); + pos = pos.offset(rayTrace.sideHit); // Prevents the player from teleporting into blocks and suffocating. if(world.getBlockState(pos).getMaterial().blocksMovement() @@ -67,11 +68,11 @@ public class Blink extends Spell { } // Plays before and after so it is heard from both positions - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); if(!world.isRemote) caster.setPositionAndUpdate(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); caster.swingArm(hand); return true; } @@ -83,14 +84,14 @@ public class Blink extends Spell { public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ - double angle = Math.atan2(target.posZ - caster.posZ, target.posX - caster.posX) - + world.rand.nextDouble() * Math.PI; + float angle = (float)(Math.atan2(target.posZ - caster.posZ, target.posX - caster.posX) + + world.rand.nextDouble() * Math.PI); double radius = caster.getDistance(target.posX, target.getEntityBoundingBox().minY, target.posZ) + world.rand.nextDouble() * 3.0d; - int x = MathHelper.floor(target.posX + Math.sin(angle) * radius); - int z = MathHelper.floor(target.posZ - Math.cos(angle) * radius); - int y = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(caster), (int)radius); + int x = MathHelper.floor(target.posX + MathHelper.sin(angle) * radius); + int z = MathHelper.floor(target.posZ - MathHelper.cos(angle) * radius); + Integer y = WizardryUtilities.getNearestFloor(world, new BlockPos(caster), (int)radius); // It's worth noting that on the client side, the cast() method only gets called if the server side // cast method succeeded, so you need not check any conditions for spawning particles. @@ -106,7 +107,7 @@ public class Blink extends Spell { } } - if(y > -1){ + if(y != null){ // This means stuff like snow layers is ignored, meaning when on snow-covered ground the caster does // not teleport 1 block above the ground. @@ -120,13 +121,13 @@ public class Blink extends Spell { } // Plays before and after so it is heard from both positions - caster.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); if(!world.isRemote){ caster.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5); } - caster.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); caster.swingArm(hand); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Blizzard.java b/src/main/java/electroblob/wizardry/spell/Blizzard.java deleted file mode 100644 index 38a1b4b8..00000000 --- a/src/main/java/electroblob/wizardry/spell/Blizzard.java +++ /dev/null @@ -1,81 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityBlizzard; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; - -public class Blizzard extends Spell { - - public Blizzard(){ - super(Tier.ADVANCED, 40, Element.ICE, "blizzard", SpellType.ATTACK, 100, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(20 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - EntityBlizzard blizzard = new EntityBlizzard(world, x, y + 0.5, z, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(blizzard); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - double x = target.posX; - double y = target.posY; - double z = target.posZ; - EntityBlizzard blizzard = new EntityBlizzard(world, x, y + 0.5, z, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(blizzard); - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Bubble.java b/src/main/java/electroblob/wizardry/spell/Bubble.java index 3886ee36..2ac47406 100644 --- a/src/main/java/electroblob/wizardry/spell/Bubble.java +++ b/src/main/java/electroblob/wizardry/spell/Bubble.java @@ -1,123 +1,74 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityBubble; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Bubble extends Spell { +public class Bubble extends SpellRay { public Bubble(){ - super(Tier.APPRENTICE, 15, Element.EARTH, "bubble", SpellType.ATTACK, 20, EnumAction.NONE, false); + super("bubble", false, EnumAction.NONE); + this.soundValues(0.5f, 1.1f, 0.2f); + addProperties(DURATION); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - EntityLivingBase entity = (EntityLivingBase)rayTrace.entityHit; + protected SoundEvent[] createSounds(){ + return this.createSoundsWithSuffixes("shoot", "splash"); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + if(!world.isRemote){ - entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - // Deprecated in favour of entity riding method - // entity.addPotionEffect(new PotionEffect(Wizardry.bubblePotion, 200, 0)); - EntityBubble entitybubble = new EntityBubble(world, entity.posX, entity.posY, entity.posZ, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), false, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(entitybubble); - entity.startRiding(entitybubble); + // Deals a small amount damage so the target counts as being hit by the caster + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), 1); + + EntityBubble bubble = new EntityBubble(world); + bubble.setPosition(target.posX, target.posY, target.posZ); + bubble.setCaster(caster); + bubble.lifetime = ((int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + bubble.isDarkOrb = false; + bubble.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + + world.spawnEntity(bubble); + target.startRiding(bubble); } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.WATER_SPLASH, x1, y1, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_BUBBLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0); - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_GENERIC_SWIM, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.5F, - world.rand.nextFloat() * 0.2F + 1.0F); + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - // Deprecated in favour of entity riding method - // entity.addPotionEffect(new PotionEffect(Wizardry.bubblePotion, 200, 0)); - EntityBubble entitybubble = new EntityBubble(world, target.posX, target.posY, target.posZ, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), false, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(entitybubble); - target.startRiding(entitybubble); - - } - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.WATER_SPLASH, x1, y1, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_BUBBLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0); - } - } - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_GENERIC_SWIM, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - caster.playSound(WizardrySounds.SPELL_ICE, 0.5F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.WATER_SPLASH, x, y, z, 0, 0, 0); + ParticleBuilder.create(Type.MAGIC_BUBBLE).pos(x, y, z).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/ChainLightning.java b/src/main/java/electroblob/wizardry/spell/ChainLightning.java index fcaea29f..83b3df2f 100644 --- a/src/main/java/electroblob/wizardry/spell/ChainLightning.java +++ b/src/main/java/electroblob/wizardry/spell/ChainLightning.java @@ -1,182 +1,119 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.EntityArc; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.item.EntityArmorStand; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class ChainLightning extends Spell { +import java.util.List; + +public class ChainLightning extends SpellRay { + + public static final String PRIMARY_DAMAGE = "primary_damage"; + public static final String SECONDARY_DAMAGE = "secondary_damage"; + public static final String TERTIARY_DAMAGE = "tertiary_damage"; + + public static final String SECONDARY_RANGE = "secondary_range"; + public static final String TERTIARY_RANGE = "tertiary_range"; + + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; + public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target public ChainLightning(){ - super(Tier.ADVANCED, 25, Element.LIGHTNING, "chain_lightning", SpellType.ATTACK, 50, EnumAction.NONE, false); + super("chain_lightning", false, EnumAction.NONE); + this.aimAssist(0.6f); + this.soundValues(1, 1.7f, 0.2f); + addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE, + SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // First shot has range 10 (this is the only range affected by upgrades) and does 5 hearts of damage. - // Chains to up to 5 secondary targets within a range of 5 of the primary target, and then to up to 2 - // tertiary targets per secondary target within a range of 5 of that. Secondary targets are dealt 4 hearts - // of damage; tertiary targets are dealt 3 hearts of damage. - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade), 8.0f); + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ // Anything can be attacked with the initial arc, because the player has control over where it goes. If they // hit a minion or an ally, it's their problem! - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ + if(WizardryUtilities.isLiving(target)){ - Entity target = rayTrace.entityHit; - - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(caster.posX, caster.posY + caster.height / 2, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - world.spawnEntity(arc); - }else{ - for(int i = 0; i < 8; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } - } - - target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); - - if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - }else{ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 10.0f * modifiers.get(SpellModifiers.DAMAGE)); - } + electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue() + * modifiers.get(SpellModifiers.POTENCY)); // Secondary chaining effect - double seekerRange = 5.0d; + List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(SECONDARY_RANGE).doubleValue(), target.posX, target.posY + target.height / 2, target.posZ, world); - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, - target.posX, target.posY + target.height / 2, target.posZ, world); - - secondaryTargets.removeIf(e -> e instanceof EntityArmorStand); + secondaryTargets.remove(target); + secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + secondaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e)); + if(secondaryTargets.size() > getProperty(SECONDARY_MAX_TARGETS).intValue()) + secondaryTargets = secondaryTargets.subList(0, getProperty(SECONDARY_MAX_TARGETS).intValue()); - for(int i = 0; i < Math.min(secondaryTargets.size(), 5); i++){ + for(EntityLivingBase secondaryTarget : secondaryTargets){ - EntityLivingBase secondaryTarget = secondaryTargets.get(i); + electrocute(world, caster, target.getPositionVector().add(0, target.height/2, 0), secondaryTarget, + getProperty(SECONDARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - if(secondaryTarget != target && WizardryUtilities.isValidTarget(caster, secondaryTarget)){ + // Tertiary chaining effect - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(target.posX, target.posY + target.height / 2, target.posZ, - secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, - secondaryTarget.posZ); - world.spawnEntity(arc); - }else{ - for(int j = 0; j < 8; j++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } - } + List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(TERTIARY_RANGE).doubleValue(), secondaryTarget.posX, + secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world); - secondaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F); + tertiaryTargets.remove(target); + tertiaryTargets.removeAll(secondaryTargets); + tertiaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + tertiaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e)); + if(tertiaryTargets.size() > getProperty(TERTIARY_MAX_TARGETS).intValue()) + tertiaryTargets = tertiaryTargets.subList(0, getProperty(TERTIARY_MAX_TARGETS).intValue()); - if(MagicDamage.isEntityImmune(DamageType.SHOCK, secondaryTarget)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", - secondaryTarget.getName(), this.getNameForTranslationFormatted())); - }else{ - secondaryTarget.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 8.0f * modifiers.get(SpellModifiers.DAMAGE)); - } - - // Tertiary chaining effect - - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, - secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, - secondaryTarget.posZ, world); - - tertiaryTargets.removeIf(e -> e instanceof EntityArmorStand); - - for(int j = 0; j < Math.min(tertiaryTargets.size(), 2); j++){ - - EntityLivingBase tertiaryTarget = (EntityLivingBase)tertiaryTargets.get(j); - - if(tertiaryTarget != target && !secondaryTargets.contains(tertiaryTarget) - && WizardryUtilities.isValidTarget(caster, tertiaryTarget)){ - - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(secondaryTarget.posX, - secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, - tertiaryTarget.posX, tertiaryTarget.posY + tertiaryTarget.height / 2, - tertiaryTarget.posZ); - world.spawnEntity(arc); - }else{ - for(int k = 0; k < 8; k++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - tertiaryTarget.posX + world.rand.nextFloat() - 0.5, - tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - tertiaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, - tertiaryTarget.posX + world.rand.nextFloat() - 0.5, - tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - tertiaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); - } - } - - tertiaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, - world.rand.nextFloat() * 0.4F + 1.5F); - - if(MagicDamage.isEntityImmune(DamageType.SHOCK, tertiaryTarget)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", - tertiaryTarget.getName(), this.getNameForTranslationFormatted())); - }else{ - tertiaryTarget.attackEntityFrom( - MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 6.0f * modifiers.get(SpellModifiers.DAMAGE)); - } - } - } + for(EntityLivingBase tertiaryTarget : tertiaryTargets){ + electrocute(world, caster, secondaryTarget.getPositionVector().add(0, secondaryTarget.height/2, 0), + tertiaryTarget, getProperty(TERTIARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } } - caster.swingArm(hand); return true; } + return false; } + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + private void electrocute(World world, Entity caster, Vec3d origin, Entity target, float damage){ + + if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), + true); + }else{ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), damage); + } + + if(world.isRemote){ + + ParticleBuilder.create(Type.LIGHTNING).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + + ParticleBuilder.spawnShockParticles(world, target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ); + } + + //target.playSound(WizardrySounds.SPELL_SPARK, 1, 1.5f + 0.4f * world.rand.nextFloat()); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Charge.java b/src/main/java/electroblob/wizardry/spell/Charge.java new file mode 100644 index 00000000..d6e9a4df --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Charge.java @@ -0,0 +1,125 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.data.IVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.SoundEvents; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.List; + +@Mod.EventBusSubscriber +public class Charge extends Spell { + + public static final IVariable CHARGE_TIME = new IVariable.Variable(Persistence.NEVER).withTicker(Charge::update); + public static final IVariable CHARGE_MODIFIERS = new IVariable.Variable<>(Persistence.NEVER); + + public static final String CHARGE_SPEED = "charge_speed"; + public static final String KNOCKBACK_STRENGTH = "knockback_strength"; + + private static final double EXTRA_HIT_MARGIN = 1; + + public Charge(){ + super("charge", EnumAction.NONE, false); + addProperties(CHARGE_SPEED, DURATION, DAMAGE, KNOCKBACK_STRENGTH); + this.soundValues(0.6f, 1, 0); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + WizardData.get(caster).setVariable(CHARGE_TIME, (int)(getProperty(DURATION).floatValue() + * modifiers.get(WizardryItems.duration_upgrade))); + + WizardData.get(caster).setVariable(CHARGE_MODIFIERS, modifiers); + + if(world.isRemote) world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX, caster.posY + caster.height/2, caster.posZ, 0, 0, 0); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + + return true; + } + + private static int update(EntityPlayer player, Integer chargeTime){ + + if(chargeTime == null) chargeTime = 0; + + if(chargeTime > 0){ + + SpellModifiers modifiers = WizardData.get(player).getVariable(CHARGE_MODIFIERS); + if(modifiers == null) modifiers = new SpellModifiers(); + + Vec3d look = player.getLookVec(); + + float speed = Spells.charge.getProperty(Charge.CHARGE_SPEED).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + player.motionX = look.x * speed; + player.motionZ = look.z * speed; + + if(player.world.isRemote){ + for(int i = 0; i < 5; i++){ + ParticleBuilder.create(Type.SPARK, player).spawn(player.world); + } + } + + List collided = player.world.getEntitiesWithinAABB(EntityLivingBase.class, player.getEntityBoundingBox().grow(EXTRA_HIT_MARGIN)); + + collided.remove(player); + + float damage = Spells.charge.getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); + float knockback = Spells.charge.getProperty(KNOCKBACK_STRENGTH).floatValue(); + + collided.forEach(e -> e.attackEntityFrom(MagicDamage.causeDirectMagicDamage(player, MagicDamage.DamageType.SHOCK), damage)); + collided.forEach(e -> e.addVelocity(player.motionX * knockback, player.motionY * knockback + 0.3f, player.motionZ * knockback)); + + if(player.world.isRemote) player.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, + player.posX + player.motionX, player.posY + player.height/2, player.posZ + player.motionZ, 0, 0, 0); + + if(collided.isEmpty()) chargeTime--; + else{ + WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_GENERIC_HURT, 1, 1); + chargeTime = 0; + } + } + + return chargeTime; + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void onLivingAttackEvent(LivingAttackEvent event){ + // Players are immune to melee damage while charging + if(event.getEntity() instanceof EntityPlayer && event.getSource().getTrueSource() instanceof EntityLivingBase){ + + EntityPlayer player = (EntityPlayer)event.getEntity(); + EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource(); + + if(WizardData.get(player) != null){ + + Integer chargeTime = WizardData.get(player).getVariable(CHARGE_TIME); + + if(chargeTime != null && chargeTime > 0 + && player.getEntityBoundingBox().grow(EXTRA_HIT_MARGIN).intersects(attacker.getEntityBoundingBox())){ + event.setCanceled(true); + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Clairvoyance.java b/src/main/java/electroblob/wizardry/spell/Clairvoyance.java index 8b6fa959..0e638ceb 100644 --- a/src/main/java/electroblob/wizardry/spell/Clairvoyance.java +++ b/src/main/java/electroblob/wizardry/spell/Clairvoyance.java @@ -1,21 +1,17 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.data.IStoredVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.misc.WizardryPathFinder; import electroblob.wizardry.packet.PacketClairvoyance; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryPathFinder; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.monster.EntityZombie; import net.minecraft.entity.player.EntityPlayer; @@ -39,25 +35,32 @@ public class Clairvoyance extends Spell { /** The number of ticks it takes each path particle to move from one path point to the next. */ public static final int PARTICLE_MOVEMENT_INTERVAL = 45; + public static final IStoredVariable LOCATION_KEY = IStoredVariable.StoredVariable.ofBlockPos("clairvoyancePos", Persistence.ALWAYS); + public static final IStoredVariable DIMENSION_KEY = IStoredVariable.StoredVariable.ofInt("clairvoyanceDimension", Persistence.ALWAYS); + public Clairvoyance(){ - super(Tier.APPRENTICE, 20, Element.SORCERY, "clairvoyance", SpellType.UTILITY, 100, EnumAction.BOW, false); + super("clairvoyance", EnumAction.BOW, false); + addProperties(RANGE, DURATION); + WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY); } - @Override - public boolean doesSpellRequirePacket(){ - return false; - } + @Override public boolean canBeCastByNPCs() { return false; } + @Override public boolean canBeCastByDispensers() { return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - WizardData properties = WizardData.get(caster); + WizardData data = WizardData.get(caster); - if(properties != null && !caster.isSneaking()){ - if(caster.dimension == properties.getClairvoyanceDimension()){ - if(properties.getClairvoyanceLocation() != null){ + if(data != null && !caster.isSneaking()){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".searching")); + Integer dimension = data.getVariable(DIMENSION_KEY); + BlockPos location = data.getVariable(LOCATION_KEY); + + if(dimension != null && caster.dimension == dimension){ + if(location != null){ + + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".searching"), true); EntityZombie arbitraryZombie = new EntityZombie(world){ @Override @@ -66,16 +69,15 @@ public class Clairvoyance extends Spell { } }; arbitraryZombie.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE) - .setBaseValue(256 * modifiers.get(WizardryItems.range_upgrade)); + .setBaseValue(getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade)); arbitraryZombie.setPosition(caster.posX, caster.posY, caster.posZ); arbitraryZombie.setPathPriority(PathNodeType.WATER, 0.0F); arbitraryZombie.onGround = true; - BlockPos destination = properties.getClairvoyanceLocation(); - WizardryPathFinder pathfinder = new WizardryPathFinder(arbitraryZombie.getNavigator().getNodeProcessor()); - Path path = pathfinder.findPath(world, arbitraryZombie, destination, 256 * modifiers.get(WizardryItems.range_upgrade)); + Path path = pathfinder.findPath(world, arbitraryZombie, location, + getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade)); if(path != null && path.getFinalPathPoint() != null){ @@ -83,9 +85,9 @@ public class Clairvoyance extends Spell { int y = path.getFinalPathPoint().y; int z = path.getFinalPathPoint().z; - if(x == destination.getX() && y == destination.getY() && z == destination.getZ()){ + if(x == location.getX() && y == location.getY() && z == location.getZ()){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); if(!world.isRemote && caster instanceof EntityPlayerMP){ WizardryPacketHandler.net.sendTo(new PacketClairvoyance.Message(path, modifiers.get(WizardryItems.duration_upgrade)), @@ -96,24 +98,27 @@ public class Clairvoyance extends Spell { } } - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".outofrange")); + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".outofrange"), true); }else{ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".undefined")); + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".undefined"), true); } }else{ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension")); + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension"), true); } } // Fixes the problem with the sound not playing for the client of the caster. - if(world.isRemote) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + if(world.isRemote) this.playSound(world, caster, ticksInUse, -1, modifiers); return false; } public static void spawnPathPaticles(World world, Path path, float durationMultiplier){ + // A bit annoying that we have to use the reference here but there's no easy way around it + float duration = Spells.clairvoyance.getProperty(DURATION).floatValue(); + PathPoint point, nextPoint; while(!path.isFinished()){ @@ -123,9 +128,11 @@ public class Clairvoyance extends Spell { nextPoint = path.getCurrentPathLength() - path.getCurrentPathIndex() <= 2 ? path.getFinalPathPoint() : path.getPathPointFromIndex(path.getCurrentPathIndex() + 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.PATH, world, point.x + 0.5, point.y + 0.5, point.z + 0.5, - (nextPoint.x - point.x) / (float)PARTICLE_MOVEMENT_INTERVAL, (nextPoint.y - point.y) / (float)PARTICLE_MOVEMENT_INTERVAL, - (nextPoint.z - point.z) / (float)PARTICLE_MOVEMENT_INTERVAL, (int)(1800 * durationMultiplier), 0, 1, 0.3f); + ParticleBuilder.create(Type.PATH).pos(point.x + 0.5, point.y + 0.5, point.z + 0.5).vel( + (nextPoint.x - point.x) / (float)PARTICLE_MOVEMENT_INTERVAL, + (nextPoint.y - point.y) / (float)PARTICLE_MOVEMENT_INTERVAL, + (nextPoint.z - point.z) / (float)PARTICLE_MOVEMENT_INTERVAL) + .time((int)(duration * durationMultiplier)).clr(0, 1, 0.3f).spawn(world); path.incrementPathIndex(); path.incrementPathIndex(); @@ -133,8 +140,8 @@ public class Clairvoyance extends Spell { point = path.getFinalPathPoint(); - Wizardry.proxy.spawnParticle(WizardryParticleType.PATH, world, point.x + 0.5, point.y + 0.5, point.z + 0.5, 0, 0, 0, - (int)(1800 * durationMultiplier), 1, 1, 1); + ParticleBuilder.create(Type.PATH).pos(point.x + 0.5, point.y + 0.5, point.z + 0.5) + .time((int)(duration * durationMultiplier)).clr(1f, 1f, 1f).spawn(world); } @SubscribeEvent @@ -143,21 +150,23 @@ public class Clairvoyance extends Spell { if(event.getEntityPlayer().isSneaking()){ // The event now has an ItemStack, which greatly simplifies hand-related stuff. - ItemStack wand = event.getItemStack(); + ItemStack stack = event.getItemStack(); - if(wand.getItem() instanceof ItemWand && WandHelper.getCurrentSpell(wand) instanceof Clairvoyance){ + if(stack.getItem() instanceof ISpellCastingItem + && ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) instanceof Clairvoyance){ - WizardData properties = WizardData.get(event.getEntityPlayer()); + WizardData data = WizardData.get(event.getEntityPlayer()); + + if(data != null){ - if(properties != null){ - // THIS is why BlockPos is a thing - in 1.7.10 this requires a clumsy switch statement. BlockPos pos = event.getPos().offset(event.getFace()); - properties.setClairvoyancePoint(pos, event.getWorld().provider.getDimension()); + data.setVariable(LOCATION_KEY, pos); + data.setVariable(DIMENSION_KEY, event.getWorld().provider.getDimension()); if(!event.getWorld().isRemote){ - event.getEntityPlayer().sendMessage( - new TextComponentTranslation("spell." + Spells.clairvoyance.getUnlocalisedName() + ".confirm", Spells.clairvoyance.getNameForTranslationFormatted())); + event.getEntityPlayer().sendStatusMessage( + new TextComponentTranslation("spell." + Spells.clairvoyance.getUnlocalisedName() + ".confirm", Spells.clairvoyance.getNameForTranslationFormatted()), true); } event.setCanceled(true); diff --git a/src/main/java/electroblob/wizardry/spell/Cobwebs.java b/src/main/java/electroblob/wizardry/spell/Cobwebs.java index 460576ee..9d2367a1 100644 --- a/src/main/java/electroblob/wizardry/spell/Cobwebs.java +++ b/src/main/java/electroblob/wizardry/spell/Cobwebs.java @@ -1,139 +1,70 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.constants.Constants; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityTimer; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Cobwebs extends Spell { +import java.util.List; - private static final int baseDuration = 400; +public class Cobwebs extends SpellRay { public Cobwebs(){ - super(Tier.ADVANCED, 30, Element.EARTH, "cobwebs", SpellType.ATTACK, 70, EnumAction.NONE, false); + super("cobwebs", false, EnumAction.NONE); + this.ignoreLivingEntities(true); + addProperties(EFFECT_RADIUS, DURATION); } + @Override public boolean requiresPacket(){ return false; } + @Override - public boolean doesSpellRequirePacket(){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + boolean flag = false; + + pos = pos.offset(side); - RayTraceResult rayTrace = WizardryUtilities.rayTrace(12 * modifiers.get(WizardryItems.range_upgrade), world, - caster, true); + int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f); - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ + float radius = getProperty(EFFECT_RADIUS).floatValue() + 0.73f * blastUpgradeCount; - boolean flag = false; + List sphere = WizardryUtilities.getBlockSphere(pos, radius * modifiers.get(WizardryItems.blast_upgrade)); - BlockPos pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); + for(BlockPos pos1 : sphere){ - if(world.isAirBlock(pos)){ + if(world.isAirBlock(pos1)){ if(!world.isRemote){ - world.setBlockState(pos, WizardryBlocks.vanishing_cobweb.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); + world.setBlockState(pos1, WizardryBlocks.vanishing_cobweb.getDefaultState()); + if(world.getTileEntity(pos1) instanceof TileEntityTimer){ + ((TileEntityTimer)world.getTileEntity(pos1)) + .setLifetime((int)(getProperty(DURATION).doubleValue() + * modifiers.get(WizardryItems.duration_upgrade))); } } flag = true; } - - for(EnumFacing side : EnumFacing.values()){ - - BlockPos pos1 = pos.offset(side); - - if(world.isAirBlock(pos1)){ - if(!world.isRemote){ - world.setBlockState(pos1, WizardryBlocks.vanishing_cobweb.getDefaultState()); - if(world.getTileEntity(pos1) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos1)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - } - flag = true; - } - } - - if(flag){ - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_LAVA_EXTINGUISH, 1.0f, 1.0f); - return true; - } } - return false; + + return flag; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - int x = MathHelper.floor(target.posX); - int y = (int)target.getEntityBoundingBox().minY; - int z = MathHelper.floor(target.posZ); - - boolean flag = false; - - BlockPos pos = new BlockPos(x, y, z); - - if(world.isAirBlock(pos)){ - if(!world.isRemote){ - world.setBlockState(pos, WizardryBlocks.vanishing_cobweb.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - } - flag = true; - } - - for(EnumFacing side : EnumFacing.values()){ - - BlockPos pos1 = pos.offset(side); - - if(world.isAirBlock(pos1)){ - if(!world.isRemote){ - world.setBlockState(pos1, WizardryBlocks.vanishing_cobweb.getDefaultState()); - if(world.getTileEntity(pos1) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos1)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - } - flag = true; - } - } - - if(flag){ - caster.swingArm(hand); - caster.playSound(SoundEvents.BLOCK_LAVA_EXTINGUISH, 1.0f, 1.0f); - return true; - } - } + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } - @Override - public boolean canBeCastByNPCs(){ - return true; - } - } diff --git a/src/main/java/electroblob/wizardry/spell/ConjureArmour.java b/src/main/java/electroblob/wizardry/spell/ConjureArmour.java index 74f5e238..ff2294a2 100644 --- a/src/main/java/electroblob/wizardry/spell/ConjureArmour.java +++ b/src/main/java/electroblob/wizardry/spell/ConjureArmour.java @@ -1,67 +1,51 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import com.google.common.collect.ImmutableMap; import electroblob.wizardry.item.IConjuredItem; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; -import net.minecraft.item.EnumAction; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagList; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; -public class ConjureArmour extends Spell { +import java.util.Map; + +public class ConjureArmour extends SpellConjuration { + + private static final Map SPECTRAL_ARMOUR_MAP = ImmutableMap.of( + EntityEquipmentSlot.HEAD, WizardryItems.spectral_helmet, + EntityEquipmentSlot.CHEST, WizardryItems.spectral_chestplate, + EntityEquipmentSlot.LEGS, WizardryItems.spectral_leggings, + EntityEquipmentSlot.FEET, WizardryItems.spectral_boots); public ConjureArmour(){ - super(Tier.ADVANCED, 45, Element.HEALING, "conjure_armour", SpellType.DEFENCE, 50, EnumAction.BOW, false); + super("conjure_armour", null); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - + protected boolean conjureItem(EntityPlayer caster, SpellModifiers modifiers){ + ItemStack armour; boolean flag = false; - // A blank "ench" tag is set to trick the renderer into showing the enchantment effect on the actual armour model. - - // Used this rather than getArmorInventoryList because I need to access the slot itself. + // Used this rather than getArmorInventoryList because I need to access the slot itself for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ if(caster.getItemStackFromSlot(slot).isEmpty() && - !WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.SPECTRAL_ARMOUR_MAP.get(slot))){ + !WizardryUtilities.doesPlayerHaveItem(caster, SPECTRAL_ARMOUR_MAP.get(slot))){ - armour = new ItemStack(WizardryItems.SPECTRAL_ARMOUR_MAP.get(slot)); + armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot)); IConjuredItem.setDurationMultiplier(armour, modifiers.get(WizardryItems.duration_upgrade)); + // Sets a blank "ench" tag to trick the renderer into showing the enchantment effect on the armour model armour.getTagCompound().setTag("ench", new NBTTagList()); caster.setItemStackToSlot(slot, armour); flag = true; } } - - if(flag){ - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 0.9f, 1.0f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); - } - + return flag; } diff --git a/src/main/java/electroblob/wizardry/spell/ConjureBlock.java b/src/main/java/electroblob/wizardry/spell/ConjureBlock.java new file mode 100644 index 00000000..23250b49 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ConjureBlock.java @@ -0,0 +1,79 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.tileentity.TileEntityTimer; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class ConjureBlock extends SpellRay { + + private static final String BLOCK_LIFETIME = "block_lifetime"; + + public ConjureBlock(){ + super("conjure_block", false, EnumAction.NONE); + this.ignoreLivingEntities(true); + addProperties(BLOCK_LIFETIME); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(world.getBlockState(pos).getBlock() == WizardryBlocks.spectral_block){ + + if(!world.isRemote){ + // Dispelling of blocks + world.setBlockToAir(pos); + }else{ + ParticleBuilder.create(Type.FLASH).pos(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5).scale(3) + .clr(0.75f, 1, 0.85f).spawn(world); + } + + return true; + } + + pos = pos.offset(side); + + if(world.isRemote){ + ParticleBuilder.create(Type.FLASH).pos(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5).scale(3) + .clr(0.75f, 1, 0.85f).spawn(world); + } + + if(WizardryUtilities.canBlockBeReplaced(world, pos)){ + + if(!world.isRemote){ + + world.setBlockState(pos, WizardryBlocks.spectral_block.getDefaultState()); + + if(world.getTileEntity(pos) instanceof TileEntityTimer){ + ((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(getProperty(BLOCK_LIFETIME).floatValue() + * modifiers.get(WizardryItems.duration_upgrade))); + } + } + + return true; + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/ConjureBow.java b/src/main/java/electroblob/wizardry/spell/ConjureBow.java deleted file mode 100644 index c03f2d2a..00000000 --- a/src/main/java/electroblob/wizardry/spell/ConjureBow.java +++ /dev/null @@ -1,60 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.IConjuredItem; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class ConjureBow extends Spell { - - public ConjureBow(){ - super(Tier.APPRENTICE, 40, Element.SORCERY, "conjure_bow", SpellType.UTILITY, 50, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - ItemStack bow = new ItemStack(WizardryItems.spectral_bow); - - IConjuredItem.setDurationMultiplier(bow, modifiers.get(WizardryItems.duration_upgrade)); - - if(!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.spectral_bow) - && conjureItemInInventory(caster, bow)){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 0.9f, 1.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); - return true; - } - return false; - } - - // TODO: When spells get superclassed, this method needs to be in the conjuration superclass. - /** Adds the given item to the given player's inventory, placing it in the main hand if the main hand is empty. */ - public static boolean conjureItemInInventory(EntityPlayer caster, ItemStack item){ - if(caster.getHeldItemMainhand().isEmpty()){ - caster.setHeldItem(EnumHand.MAIN_HAND, item); - return true; - }else{ - return caster.inventory.addItemStackToInventory(item); - } - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/ConjurePickaxe.java b/src/main/java/electroblob/wizardry/spell/ConjurePickaxe.java deleted file mode 100644 index ae4a192c..00000000 --- a/src/main/java/electroblob/wizardry/spell/ConjurePickaxe.java +++ /dev/null @@ -1,50 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.IConjuredItem; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class ConjurePickaxe extends Spell { - - public ConjurePickaxe(){ - super(Tier.APPRENTICE, 25, Element.SORCERY, "conjure_pickaxe", SpellType.UTILITY, 50, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - ItemStack pickaxe = new ItemStack(WizardryItems.spectral_pickaxe); - - IConjuredItem.setDurationMultiplier(pickaxe, modifiers.get(WizardryItems.duration_upgrade)); - - if(!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.spectral_pickaxe) - && ConjureBow.conjureItemInInventory(caster, pickaxe)){ - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 0.9f, 1.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/ConjureSword.java b/src/main/java/electroblob/wizardry/spell/ConjureSword.java deleted file mode 100644 index cefd59c9..00000000 --- a/src/main/java/electroblob/wizardry/spell/ConjureSword.java +++ /dev/null @@ -1,49 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.IConjuredItem; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class ConjureSword extends Spell { - - public ConjureSword(){ - super(Tier.APPRENTICE, 25, Element.SORCERY, "conjure_sword", SpellType.UTILITY, 50, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - ItemStack sword = new ItemStack(WizardryItems.spectral_sword); - - IConjuredItem.setDurationMultiplier(sword, modifiers.get(WizardryItems.duration_upgrade)); - - if(!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.spectral_sword) - && ConjureBow.conjureItemInInventory(caster, sword)){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 0.9f, 1.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Containment.java b/src/main/java/electroblob/wizardry/spell/Containment.java new file mode 100644 index 00000000..cc9c0b71 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Containment.java @@ -0,0 +1,54 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class Containment extends SpellRay { + + public Containment(){ + super("containment", false, EnumAction.NONE); + this.soundValues(1, 1, 0.2f); + addProperties(EFFECT_DURATION, EFFECT_STRENGTH); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.containment, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.PATH).pos(x, y, z).clr(0x7988cc).time(20 + world.rand.nextInt(8)).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(1f, 1f, 1f).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/CureEffects.java b/src/main/java/electroblob/wizardry/spell/CureEffects.java index 82051fb3..6be6e69a 100644 --- a/src/main/java/electroblob/wizardry/spell/CureEffects.java +++ b/src/main/java/electroblob/wizardry/spell/CureEffects.java @@ -1,77 +1,39 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraft.potion.PotionEffect; -public class CureEffects extends Spell { +public class CureEffects extends SpellBuff { public CureEffects(){ - super(Tier.APPRENTICE, 25, Element.HEALING, "cure_effects", SpellType.DEFENCE, 40, EnumAction.BOW, false); + super("cure_effects", 0.8f, 0.8f, 1); + this.soundValues(0.7f, 1.2f, 0.4f); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); - } - } + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ if(!caster.getActivePotionEffects().isEmpty()){ - caster.clearActivePotions(); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - // Fixes the sound not playing in first person. - if(world.isRemote) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return false; - } + ItemStack milk = new ItemStack(Items.MILK_BUCKET); - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ + boolean flag = false; - if(!caster.getActivePotionEffects().isEmpty()){ - caster.clearActivePotions(); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); + for(PotionEffect effect : caster.getActivePotionEffects()){ + // The PotionEffect version (as opposed to Potion) does not call cleanup callbacks + if(effect.isCurativeItem(milk)){ + caster.removePotionEffect(effect.getPotion()); + flag = true; } } - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; + + return flag; } - + return false; } - @Override - public boolean canBeCastByNPCs(){ - return true; - } - } diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java b/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java new file mode 100644 index 00000000..a6a5f2e1 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java @@ -0,0 +1,60 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class CurseOfEnfeeblement extends SpellRay { + + public CurseOfEnfeeblement(){ + super("curse_of_enfeeblement", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + addProperties(EFFECT_STRENGTH); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + // This will actually run out in the end, but only if you leave Minecraft running for 3.4 years + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_enfeeblement, + Integer.MAX_VALUE, getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); + // Reduce the target's health to its new max health if necessary + if(((EntityLivingBase)target).getHealth() > ((EntityLivingBase)target).getMaxHealth()){ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, MagicDamage.DamageType.WITHER), + ((EntityLivingBase)target).getHealth() - ((EntityLivingBase)target).getMaxHealth()); + } + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.2f, 0, 0.3f).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.4f, 0, 0).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java b/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java index e2494bce..c7721ba5 100644 --- a/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java +++ b/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java @@ -1,71 +1,89 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.IElementalDamage; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.data.IStoredVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; + @Mod.EventBusSubscriber -public class CurseOfSoulbinding extends Spell { +public class CurseOfSoulbinding extends SpellRay { + + public static final IStoredVariable> TARGETS_KEY = new IStoredVariable.StoredVariable<>("soulboundCreatures", + s -> NBTExtras.listToNBT(s, NBTUtil::createUUIDTag), + // For some reason gradle screams at me unless I explicitly declare the type of t here, despite IntelliJ being fine without it + (NBTTagList t) -> new HashSet<>(NBTExtras.NBTToList(t, NBTUtil::getUUIDFromTag)), + // Curse of soulbinding is lifted when the caster dies, but not when they switch dimensions. + Persistence.DIMENSION_CHANGE); public CurseOfSoulbinding(){ - super(Tier.ADVANCED, 35, Element.NECROMANCY, "curse_of_soulbinding", SpellType.ATTACK, 100, EnumAction.NONE, - false); + super("curse_of_soulbinding", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + WizardData.registerStoredVariables(TARGETS_KEY); + } + + @Override public boolean canBeCastByNPCs() { return false; } + // You can't damage a dispenser so this would be nonsense! + @Override public boolean canBeCastByDispensers() { return false; } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target) && caster instanceof EntityPlayer){ + WizardData data = WizardData.get((EntityPlayer)caster); + if(data != null){ + // Return false if soulbinding failed (e.g. if the target is already soulbound) + if(getSoulboundCreatures(data).add(target.getUniqueID())){ + // This will actually run out in the end, but only if you leave Minecraft running for 3.4 years + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_soulbinding, Integer.MAX_VALUE)); + }else{ + return false; + } + } + } + + return true; } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY - && WizardryUtilities.isLiving(rayTrace.entityHit) && WizardData.get(caster) != null){ - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - if(!WizardData.get(caster).soulbind(target)) return false; - } - - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.4f, 0.0f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.1f, 0.0f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 1.0f, 0.8f, 1.0f); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.4f, 0, 0).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(1, 0.8f, 1).spawn(world); + } @SubscribeEvent public static void onLivingHurtEvent(LivingHurtEvent event){ @@ -73,11 +91,41 @@ public class CurseOfSoulbinding extends Spell { if(!event.getEntity().world.isRemote && event.getEntityLiving() instanceof EntityPlayer && !event.getSource().isUnblockable() && !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){ - WizardData data = WizardData.get((EntityPlayer)event.getEntityLiving()); + + EntityPlayer player = (EntityPlayer)event.getEntityLiving(); + WizardData data = WizardData.get(player); + if(data != null){ - data.damageAllSoulboundCreatures(event.getAmount()); + + for(Iterator iterator = getSoulboundCreatures(data).iterator(); iterator.hasNext();){ + + Entity entity = WizardryUtilities.getEntityByUUID(player.world, iterator.next()); + + if(entity == null) iterator.remove(); + + if(entity instanceof EntityLivingBase){ + // Retaliatory effect + if(DamageSafetyChecker.attackEntitySafely(entity, MagicDamage.causeDirectMagicDamage(player, + MagicDamage.DamageType.MAGIC, true), event.getAmount(), event.getSource().getDamageType(), + DamageSource.MAGIC, false)){ + // Sound only plays if the damage succeeds + entity.playSound(WizardrySounds.SPELL_CURSE_OF_SOULBINDING_RETALIATE, 1.0F, player.world.rand.nextFloat() * 0.2F + 1.0F); + } + } + } + } } } + public static Set getSoulboundCreatures(WizardData data){ + + if(data.getVariable(TARGETS_KEY) == null){ + Set result = new HashSet<>(); + data.setVariable(TARGETS_KEY, result); + return result; + + }else return data.getVariable(TARGETS_KEY); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java b/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java new file mode 100644 index 00000000..f97e1148 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java @@ -0,0 +1,55 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class CurseOfUndeath extends SpellRay { + + public CurseOfUndeath(){ + super("curse_of_undeath", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + addProperties(EFFECT_STRENGTH); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + // This will actually run out in the end, but only if you leave Minecraft running for 3.4 years + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE, + getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x686c00).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x251609).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0xe6e592).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/DarknessOrb.java b/src/main/java/electroblob/wizardry/spell/DarknessOrb.java deleted file mode 100644 index df098a99..00000000 --- a/src/main/java/electroblob/wizardry/spell/DarknessOrb.java +++ /dev/null @@ -1,68 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityDarknessOrb; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class DarknessOrb extends Spell { - - public DarknessOrb(){ - super(Tier.ADVANCED, 20, Element.NECROMANCY, "darkness_orb", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityDarknessOrb darknessorb = new EntityDarknessOrb(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(darknessorb); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityDarknessOrb darknessorb = new EntityDarknessOrb(world, caster, - modifiers.get(SpellModifiers.DAMAGE)); - darknessorb.directTowards(target, 0.5f); - world.spawnEntity(darknessorb); - } - - caster.playSound(SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Darkvision.java b/src/main/java/electroblob/wizardry/spell/Darkvision.java deleted file mode 100644 index d9e8d7c1..00000000 --- a/src/main/java/electroblob/wizardry/spell/Darkvision.java +++ /dev/null @@ -1,45 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Darkvision extends Spell { - - public Darkvision(){ - super(Tier.APPRENTICE, 20, Element.EARTH, "darkvision", SpellType.UTILITY, 40, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.NIGHT_VISION, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.0f, 0.4f, 0.7f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Dart.java b/src/main/java/electroblob/wizardry/spell/Dart.java deleted file mode 100644 index fd74e549..00000000 --- a/src/main/java/electroblob/wizardry/spell/Dart.java +++ /dev/null @@ -1,67 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityDart; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Dart extends Spell { - - public Dart(){ - super(Tier.BASIC, 5, Element.EARTH, "dart", SpellType.ATTACK, 10, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityDart dart = new EntityDart(world, caster, 2 * modifiers.get(WizardryItems.range_upgrade), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(dart); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ARROW_SHOOT, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityDart dart = new EntityDart(world, caster, target, 2 * modifiers.get(WizardryItems.range_upgrade), - 2, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(dart); - } - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_ARROW_SHOOT, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Decay.java b/src/main/java/electroblob/wizardry/spell/Decay.java index eca8d586..dc6e9f41 100644 --- a/src/main/java/electroblob/wizardry/spell/Decay.java +++ b/src/main/java/electroblob/wizardry/spell/Decay.java @@ -1,100 +1,45 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityDecay; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; -public class Decay extends Spell { +public class Decay extends SpellConstructRanged { + + public static final String DECAY_PATCHES_SPAWNED = "decay_patches_spawned"; public Decay(){ - super(Tier.ADVANCED, 50, Element.NECROMANCY, "decay", SpellType.ATTACK, 200, EnumAction.NONE, false); + super("decay", EntityDecay::new, false); + this.soundValues(1, 1.1f, 0.1f); + this.floor(true); + this.overlap(true); + addProperties(DECAY_PATCHES_SPAWNED, EFFECT_DURATION); } @Override - public boolean doesSpellRequirePacket(){ - return false; - } + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + + if(world.getBlockState(new BlockPos(x, y, z)).isNormalCube()) return false; + + super.spawnConstruct(world, x, y, z, side, caster, modifiers); - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + float decayCount = getProperty(DECAY_PATCHES_SPAWNED).floatValue(); + int quantity = (int)(decayCount * modifiers.get(WizardryItems.blast_upgrade)); + // If there are more decay patches, they need more space to spawn in + int horizontalRange = (int)(0.4 * decayCount * modifiers.get(WizardryItems.blast_upgrade)); + int verticalRange = (int)(6 * modifiers.get(WizardryItems.blast_upgrade)); - RayTraceResult rayTrace = WizardryUtilities.rayTrace(12 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - if(world.getBlockState(pos.up()).isNormalCube()) return false; - - if(!world.isRemote){ - - world.spawnEntity(new EntityDecay(world, pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, caster)); - - for(int i = 0; i < 5; i++){ - BlockPos pos1 = WizardryUtilities.findNearbyFloorSpace(caster, 2, 6); - if(pos1 == null) break; - world.spawnEntity( - new EntityDecay(world, pos1.getX() + 0.5, pos1.getY(), pos1.getZ() + 0.5, caster)); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - caster.swingArm(hand); - return true; + for(int i=0; i targets = WizardryUtilities.getEntitiesWithinRadius( - 3.0d * modifiers.get(WizardryItems.blast_upgrade), (rayTrace.hitVec.x + 0.5), - (rayTrace.hitVec.y + 0.5), (rayTrace.hitVec.z + 0.5), world); - for(int i = 0; i < targets.size(); i++){ - targets.get(i).attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), - // Damage decreases with distance but cannot be less than 0, naturally. - Math.max(12.0f - (float)((EntityLivingBase)targets.get(i)).getDistance( - (rayTrace.hitVec.x + 0.5), (rayTrace.hitVec.y + 0.5), - (rayTrace.hitVec.z + 0.5)) * 4, 0) * modifiers.get(SpellModifiers.DAMAGE)); - - } - } - if(world.isRemote){ - double dx = (rayTrace.hitVec.x + 0.5) - caster.posX; - double dy = (rayTrace.hitVec.y + 0.5) - WizardryUtilities.getPlayerEyesPos(caster); - double dz = (rayTrace.hitVec.z + 0.5) - caster.posZ; - world.spawnParticle(EnumParticleTypes.EXPLOSION_HUGE, (rayTrace.hitVec.x + 0.5), - (rayTrace.hitVec.y + 0.5), (rayTrace.hitVec.z + 0.5), 0, 0, 0); - for(int i = 1; i < 5; i++){ - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - } - } - world.playSound(caster, (rayTrace.hitVec.x + 0.5), (rayTrace.hitVec.y + 0.5), - (rayTrace.hitVec.z + 0.5), SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.BLOCKS, 4.0F, - (1.0F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.2F) * 0.7F); - caster.swingArm(hand); - return true; - } + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(!world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(3.0d, target.posX, - target.posY, target.posZ, world); - for(int i = 0; i < targets.size(); i++){ - targets.get(i).attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), - // Damage decreases with distance but cannot be less than 0, naturally. - Math.max(12.0f - (float)((EntityLivingBase)targets.get(i)).getDistance(target.posX, - target.posY, target.posZ) * 4, 0) * modifiers.get(SpellModifiers.DAMAGE)); - - } + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(!world.isRemote){ + + List targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(BLAST_RADIUS).doubleValue() + * modifiers.get(WizardryItems.blast_upgrade), pos.getX(), pos.getY(), pos.getZ(), world); + + for(EntityLivingBase target : targets){ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), + // Damage decreases with distance but cannot be less than 0, naturally. + Math.max(getProperty(MAX_DAMAGE).floatValue() - (float)target.getDistance(pos.getX() + 0.5, + pos.getY() + 0.5, pos.getZ() + 0.5) * 4, 0) * modifiers.get(SpellModifiers.POTENCY)); } - if(world.isRemote){ - double dx = target.posX - caster.posX; - double dy = target.posY - (caster.posY + caster.getEyeHeight()); - double dz = target.posZ - caster.posZ; - world.spawnParticle(EnumParticleTypes.EXPLOSION_HUGE, target.posX, target.posY, target.posZ, 0, 0, 0); - for(int i = 1; i < 5; i++){ - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - } - } - // Player is null here because the sound was not caused by a player. - world.playSound(null, target.posX, target.posY, target.posZ, SoundEvents.ENTITY_GENERIC_EXPLODE, - SoundCategory.BLOCKS, 4.0F, - (1.0F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.2F) * 0.7F); - caster.swingArm(hand); - return true; + + }else{ + world.spawnParticle(EnumParticleTypes.EXPLOSION_HUGE, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 0, 0, 0); } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ + return true; } + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Diamondflesh.java b/src/main/java/electroblob/wizardry/spell/Diamondflesh.java deleted file mode 100644 index 32175e6a..00000000 --- a/src/main/java/electroblob/wizardry/spell/Diamondflesh.java +++ /dev/null @@ -1,53 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Diamondflesh extends Spell { - - public Diamondflesh(){ - super(Tier.MASTER, 100, Element.HEALING, "diamondflesh", SpellType.DEFENCE, 300, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 4, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.0f, 0.5f, 1.0f); - - x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.7f, 0.9f); - - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Disintegration.java b/src/main/java/electroblob/wizardry/spell/Disintegration.java new file mode 100644 index 00000000..0dda0a58 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Disintegration.java @@ -0,0 +1,97 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.projectile.EntityEmber; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; + +public class Disintegration extends SpellRay { + + public static final String EMBER_COUNT = "ember_count"; + public static final String EMBER_LIFETIME = "ember_lifetime"; + + public Disintegration(){ + super("disintegration", false, EnumAction.NONE); + addProperties(DAMAGE, BURN_DURATION, EMBER_LIFETIME, EMBER_COUNT); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){ + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); + }else{ + + target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + WizardryUtilities.attackEntityWithoutKnockback(target, caster == null ? DamageSource.MAGIC : + MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + + if(!world.isRemote && target instanceof EntityLivingBase && ((EntityLivingBase)target).getHealth() <= 0){ + spawnEmbers(world, caster, target, getProperty(EMBER_COUNT).intValue()); + } + } + + return true; + } + + public static void spawnEmbers(World world, EntityLivingBase caster, Entity target, int count){ + + for(int i = 0; i < count; i++){ + EntityEmber ember = new EntityEmber(world, caster); + double x = (world.rand.nextDouble() - 0.5) * target.width; + double y = world.rand.nextDouble() * target.height; + double z = (world.rand.nextDouble() - 0.5) * target.width; + ember.setPosition(target.posX + x, target.posY + y, target.posZ + z); + float speed = 0.2f; + ember.setVelocity(x * speed, y * 0.5f * speed, z * speed); + world.spawnEntity(ember); + } + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(world.isRemote){ + + for(int i = 0; i < 8; i++){ + world.spawnParticle(EnumParticleTypes.LAVA, hit.x, hit.y, hit.z, 0, 0, 0); + } + + if(world.getBlockState(pos).getMaterial().isSolid()){ + Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); + ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(1, 0.2f, 0).spawn(world); + } + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, EntityLivingBase caster, double distance){ + Vec3d endpoint = origin.add(direction.scale(distance)); + ParticleBuilder.create(Type.BEAM).clr(1, 0.4f, 0).fade(1, 0.1f, 0).time(4).pos(origin).target(endpoint).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Divination.java b/src/main/java/electroblob/wizardry/spell/Divination.java new file mode 100644 index 00000000..f4ed42b6 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Divination.java @@ -0,0 +1,149 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockCrystalOre; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.RelativeFacing; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.block.Block; +import net.minecraft.block.BlockOre; +import net.minecraft.block.BlockRedstoneOre; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class Divination extends Spell { + + private static final float NUDGE_SPEED = 0.2f; + + public Divination(){ + super("divination", EnumAction.NONE, false); + addProperties(RANGE); + } + + /** A set of constants representing the different 'signal strengths' for the divination spell. In practical + * terms, this means different chat readouts and effects. */ + protected enum Strength { + + NOTHING("nothing", -1), + WEAK("weak", 0), + MODERATE("moderate", 0.25f), + STRONG("strong", 0.5f), + VERY_STRONG("very_strong", 0.75f); + + String key; + float minWeight; + + Strength(String key, float minWeight){ + this.key = key; + this.minWeight = minWeight; + } + + protected static Strength forWeight(float weight){ + return Arrays.stream(values()).filter(s -> s.minWeight < weight).max(Comparator.naturalOrder()).orElse(NOTHING); + } + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), range); + + sphere.removeIf(b -> !(world.getBlockState(b).getBlock() instanceof BlockOre + || world.getBlockState(b).getBlock() instanceof BlockRedstoneOre + || world.getBlockState(b).getBlock() instanceof BlockCrystalOre + || Arrays.asList(Wizardry.settings.divinationOreWhitelist) + .contains(world.getBlockState(b).getBlock().getRegistryName()))); + + Strength strength = Strength.NOTHING; + + EnumFacing direction = EnumFacing.DOWN; // Doesn't matter what this is + + if(!sphere.isEmpty()){ + + // Sorts the positions based on weight (see below), in ascending order + sphere.sort(Comparator.comparingDouble(b -> calculateWeight(world, caster, b, range, modifiers))); + + // The weights are sorted in ascending order, so this must be the largest + BlockPos target = sphere.get(sphere.size() - 1); + + direction = EnumFacing.getFacingFromVector((float)(target.getX() + 0.5 - caster.posX), + (float)(target.getY() + 0.5 - (caster.getEntityBoundingBox().minY + caster.getEyeHeight())), + (float)(target.getZ() + 0.5 - caster.posZ)); + + if(world.isRemote) ParticleBuilder.create(ParticleBuilder.Type.SPARKLE).pos(target.getX() + 0.5, + target.getY() + 1.5, target.getZ() + 0.5).spawn(world); + + strength = Strength.forWeight(calculateWeight(world, caster, target, range, modifiers)); + } + + if(!world.isRemote){ + caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + "." + + strength.key, new TextComponentTranslation("spell." + this.getUnlocalisedName() + "." + + RelativeFacing.relativise(direction, caster).name)), false); + }else{ + switch(strength){ + case NOTHING: break; + case WEAK: break; + case MODERATE: + spawnHintParticles(world, caster, 3, direction); + break; + case STRONG: + spawnHintParticles(world, caster, 8, direction); + break; + case VERY_STRONG: + spawnHintParticles(world, caster, 12, direction); + caster.addVelocity(direction.getXOffset() * NUDGE_SPEED, direction.getYOffset() * NUDGE_SPEED, + direction.getZOffset() * NUDGE_SPEED); + break; + } + } + + return true; + } + + private static void spawnHintParticles(World world, Entity caster, int count, EnumFacing direction){ + + Vec3d vec = new Vec3d(caster.getPosition().offset(EnumFacing.UP).offset(direction, 2)).add(0.5, 0.5, 0.5); + + for(int i=0; i { + + public static final String SPREAD_SPEED = "spread_speed"; public Earthquake(){ - super(Tier.MASTER, 75, Element.EARTH, "earthquake", SpellType.ATTACK, 250, EnumAction.NONE, false); + super("earthquake", EnumAction.NONE, EntityEarthquake::new, true); + this.soundValues(2, 1, 0); + this.overlap(true); + this.floor(true); + addProperties(EFFECT_RADIUS, SPREAD_SPEED); } - + + // This one spawns particles + @Override public boolean requiresPacket(){ return true; } + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected void addConstructExtras(EntityEarthquake construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + // Calculates the lifetime based on the base radius and spread speed + // Also overwrites the -1 lifetime set due to permanent being true + construct.lifetime = (int)(getProperty(EFFECT_RADIUS).floatValue()/getProperty(SPREAD_SPEED).floatValue() + * modifiers.get(WizardryItems.blast_upgrade)); + } + + @Override + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + + if(world.isRemote){ - if(caster.onGround){ + world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX, + caster.getEntityBoundingBox().minY + 0.1, caster.posZ, 0, 0, 0); - if(!world.isRemote){ - world.spawnEntity(new EntityEarthquake(world, caster.posX, caster.getEntityBoundingBox().minY, - caster.posZ, caster, (int)(20 * modifiers.get(WizardryItems.blast_upgrade)), - modifiers.get(SpellModifiers.DAMAGE))); - }else{ + double particleX, particleZ; - world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX, - caster.getEntityBoundingBox().minY + 0.1, caster.posZ, 0, 0, 0); + for(int i=0; i<40; i++){ - double particleX, particleZ; + particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); + particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - for(int i = 0; i < 40; i++){ - - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - - IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); - if(block != null){ - world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY, - particleZ, particleX - caster.posX, 0, particleZ - caster.posZ, - Block.getStateId(block)); - } - } + IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY, + particleZ, particleX - caster.posX, 0, particleZ - caster.posZ, + Block.getStateId(block)); } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_EARTHQUAKE, 2, 1); - caster.swingArm(hand); - - return true; } - return false; + + return super.spawnConstruct(world, x, y, z, side, caster, modifiers); } } diff --git a/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java b/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java new file mode 100644 index 00000000..ad42d9c3 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java @@ -0,0 +1,81 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.List; + +@Mod.EventBusSubscriber +public class EmpoweringPresence extends Spell { + + public EmpoweringPresence(){ + super("empowering_presence", EnumAction.BOW, false); + addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + List targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).doubleValue() + * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class); + + for(EntityPlayer target : targets){ + if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){ + + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); + + target.addPotionEffect(new PotionEffect(WizardryPotions.empowerment, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier)); + } + } + + if(world.isRemote){ + + for(int i = 0; i < 50 * modifiers.get(WizardryItems.blast_upgrade); i++){ + + double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + + double x = caster.posX + radius * MathHelper.cos(angle); + double y = caster.getEntityBoundingBox().minY; + double z = caster.posZ + radius * MathHelper.sin(angle); + + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(0.5f, 0.4f, 0.75f).spawn(world); + + } + } + + //WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1 + 0.2f * world.rand.nextFloat()); + return true; + } + + @SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + // Empowerment stacks extra potency on top of the existing potency. + if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.empowerment)){ + + float potency = 1 + Constants.EMPOWERMENT_POTENCY_PER_LEVEL + * (event.getCaster().getActivePotionEffect(WizardryPotions.empowerment).getAmplifier() + 1); + + event.getModifiers().set(SpellModifiers.POTENCY, + event.getModifiers().get(SpellModifiers.POTENCY) * potency, true); + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/spell/Entrapment.java b/src/main/java/electroblob/wizardry/spell/Entrapment.java index 44436b4b..7f20fbd8 100644 --- a/src/main/java/electroblob/wizardry/spell/Entrapment.java +++ b/src/main/java/electroblob/wizardry/spell/Entrapment.java @@ -1,119 +1,70 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityBubble; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Entrapment extends Spell { +public class Entrapment extends SpellRay { + + public static final String DAMAGE_INTERVAL = "damage_interval"; public Entrapment(){ - super(Tier.ADVANCED, 35, Element.NECROMANCY, "entrapment", SpellType.ATTACK, 75, EnumAction.NONE, false); + super("entrapment", false, EnumAction.NONE); + this.soundValues(1, 0.85f, 0.3f); + addProperties(EFFECT_DURATION, DAMAGE_INTERVAL); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - EntityLivingBase entity = (EntityLivingBase)rayTrace.entityHit; + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + if(!world.isRemote){ - entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - - EntityBubble entitybubble = new EntityBubble(world, entity.posX, entity.posY, entity.posZ, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), true, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(entitybubble); - entity.startRiding(entitybubble); + // Deals a small amount damage so the target counts as being hit by the caster + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), 1); + + EntityBubble bubble = new EntityBubble(world); + bubble.setPosition(target.posX, target.posY, target.posZ); + bubble.setCaster(caster); + bubble.lifetime = ((int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + bubble.isDarkOrb = true; + bubble.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + + world.spawnEntity(bubble); + target.startRiding(bubble); } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.PORTAL, x1, y1 - 0.5, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.1f, 0.0f, 0.0f); - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, - world.rand.nextFloat() * 0.3F + 0.7F); + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - // Deprecated in favour of entity riding method - // entity.addPotionEffect(new PotionEffect(Wizardry.bubblePotion, 200, 0)); - EntityBubble entitybubble = new EntityBubble(world, target.posX, target.posY, target.posZ, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), true, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(entitybubble); - target.startRiding(entitybubble); - - } - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < 25; i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.PORTAL, x1, y1 - 0.5, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.1f, 0.0f, 0.0f); - } - } - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, world.rand.nextFloat() * 0.3F + 0.7F); - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.PORTAL, x, y - 0.5, z, 0, 0, 0); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/Evade.java b/src/main/java/electroblob/wizardry/spell/Evade.java new file mode 100644 index 00000000..9df46093 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Evade.java @@ -0,0 +1,48 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class Evade extends Spell { + + private static final String EVADE_VELOCITY = "evade_velocity"; + + private static final float UPWARD_VELOCITY = 0.25f; + + public Evade(){ + super("evade", EnumAction.NONE, false); + addProperties(EVADE_VELOCITY); + } + + @Override + public boolean requiresPacket(){ + return false; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + Vec3d look = caster.getLookVec(); + // We want a horizontal only vector + look = look.subtract(0, look.y, 0).normalize(); + + Vec3d evadeDirection; + if(caster.moveStrafing == 0){ + // If the caster isn't strafing, pick a random direction + evadeDirection = look.rotateYaw(world.rand.nextBoolean() ? (float)Math.PI/2f : (float)-Math.PI/2f); + }else{ + // Otherwise, evade always moves whichever direction the caster was already strafing + evadeDirection = look.rotateYaw(Math.signum(caster.moveStrafing) * (float)Math.PI/2f); + } + + evadeDirection = evadeDirection.scale(getProperty(EVADE_VELOCITY).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + caster.addVelocity(evadeDirection.x, UPWARD_VELOCITY, evadeDirection.z); + + return true; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FireBreath.java b/src/main/java/electroblob/wizardry/spell/FireBreath.java new file mode 100644 index 00000000..338f375d --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/FireBreath.java @@ -0,0 +1,95 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; + +public class FireBreath extends SpellRay { + + public FireBreath(){ + super("fire_breath", true, EnumAction.NONE); + this.particleVelocity(1); + this.particleJitter(0.3); + this.particleSpacing(0.25); + addProperties(DAMAGE, BURN_DURATION); + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + // Fire can damage armour stands + if(target instanceof EntityLivingBase){ + + if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){ + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster) + .sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + // This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle + // with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry. + }else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){ + target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + WizardryUtilities.attackEntityWithoutKnockback(target, + MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + } + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(!WizardryUtilities.canDamageBlocks(caster, world)) return false; + + pos = pos.offset(side); + + if(world.isAirBlock(pos)){ + if(!world.isRemote) world.setBlockState(pos, Blocks.FIRE.getDefaultState()); + return true; + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(2 + world.rand.nextFloat()).collide(true).spawn(world); + ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(2 + world.rand.nextFloat()).collide(true).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FireResistance.java b/src/main/java/electroblob/wizardry/spell/FireResistance.java deleted file mode 100644 index d4c85af3..00000000 --- a/src/main/java/electroblob/wizardry/spell/FireResistance.java +++ /dev/null @@ -1,78 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class FireResistance extends Spell { - - public FireResistance(){ - super(Tier.ADVANCED, 20, Element.FIRE, "fire_resistance", SpellType.DEFENCE, 80, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.FIRE_RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 0.5f, 0.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - // Like witches, wizards who have this spell will only cast it if they are on fire. - if(caster.isBurning() && !caster.isPotionActive(MobEffects.FIRE_RESISTANCE)){ - - caster.addPotionEffect(new PotionEffect(MobEffects.FIRE_RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 0.5f, 0.0f); - } - } - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/FireSigil.java b/src/main/java/electroblob/wizardry/spell/FireSigil.java deleted file mode 100644 index 7d4d0b0f..00000000 --- a/src/main/java/electroblob/wizardry/spell/FireSigil.java +++ /dev/null @@ -1,51 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityFireSigil; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; - -public class FireSigil extends Spell { - - public FireSigil(){ - super(Tier.APPRENTICE, 10, Element.FIRE, "fire_sigil", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && rayTrace.sideHit == EnumFacing.UP){ - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - EntityFireSigil firesigil = new EntityFireSigil(world, x, y, z, caster, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(firesigil); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ITEM_FLINTANDSTEEL_USE, 1.0F, 1.0F); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Fireball.java b/src/main/java/electroblob/wizardry/spell/Fireball.java deleted file mode 100644 index 83e680b2..00000000 --- a/src/main/java/electroblob/wizardry/spell/Fireball.java +++ /dev/null @@ -1,86 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.projectile.EntitySmallFireball; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -public class Fireball extends Spell { - - public Fireball(){ - super(Tier.APPRENTICE, 10, Element.FIRE, "fireball", SpellType.ATTACK, 15, EnumAction.NONE, false); - // Does 2.5 hearts of damage and 5 seconds of fire, for reference. - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - if(!world.isRemote){ - EntitySmallFireball fireball = new EntitySmallFireball(world, caster, 1, 1, 1); - fireball.setPosition(caster.posX + look.x, caster.posY + look.y + 1.3, caster.posZ + look.z); - fireball.accelerationX = look.x * 0.1; - fireball.accelerationY = look.y * 0.1; - fireball.accelerationZ = look.z * 0.1; - world.spawnEntity(fireball); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - - EntitySmallFireball fireball = new EntitySmallFireball(world, caster, 1, 1, 1); - - double dx = target.posX - caster.posX; - double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) - - (caster.posY + (double)(caster.height / 2.0F)); - double dz = target.posZ - caster.posZ; - - fireball.accelerationX = dx / caster.getDistance(target) * 0.1; - fireball.accelerationY = dy / caster.getDistance(target) * 0.1; - fireball.accelerationZ = dz / caster.getDistance(target) * 0.1; - - fireball.setPosition(caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ); - - world.spawnEntity(fireball); - } - - caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Firebolt.java b/src/main/java/electroblob/wizardry/spell/Firebolt.java deleted file mode 100644 index f81804b1..00000000 --- a/src/main/java/electroblob/wizardry/spell/Firebolt.java +++ /dev/null @@ -1,69 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityFirebolt; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Firebolt extends Spell { - - public Firebolt(){ - super(Tier.APPRENTICE, 10, Element.FIRE, "firebolt", SpellType.ATTACK, 10, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityFirebolt firebolt = new EntityFirebolt(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - firebolt.motionX *= 2.5; - firebolt.motionY *= 2.5; - firebolt.motionZ *= 2.5; - world.spawnEntity(firebolt); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityFirebolt firebolt = new EntityFirebolt(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - firebolt.directTowards(target, 2.5f); - world.spawnEntity(firebolt); - } - - caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Firebomb.java b/src/main/java/electroblob/wizardry/spell/Firebomb.java deleted file mode 100644 index 6a95a583..00000000 --- a/src/main/java/electroblob/wizardry/spell/Firebomb.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityFirebomb; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Firebomb extends Spell { - - public Firebomb(){ - super(Tier.APPRENTICE, 15, Element.FIRE, "firebomb", SpellType.ATTACK, 25, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityFirebomb firebomb = new EntityFirebomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(firebomb); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityFirebomb firebomb = new EntityFirebomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - firebomb.directTowards(target, 1.5f); - world.spawnEntity(firebomb); - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Fireskin.java b/src/main/java/electroblob/wizardry/spell/Fireskin.java deleted file mode 100644 index 20f4497f..00000000 --- a/src/main/java/electroblob/wizardry/spell/Fireskin.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Fireskin extends Spell { - - public Fireskin(){ - super(Tier.ADVANCED, 40, Element.FIRE, "fireskin", SpellType.DEFENCE, 250, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.fireskin)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.fireskin, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.fireskin)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.fireskin, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - return true; - } - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Firestorm.java b/src/main/java/electroblob/wizardry/spell/Firestorm.java deleted file mode 100644 index 4717aab2..00000000 --- a/src/main/java/electroblob/wizardry/spell/Firestorm.java +++ /dev/null @@ -1,90 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; -import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.Blocks; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.text.TextComponentTranslation; -import net.minecraft.world.World; - -public class Firestorm extends Spell { - - public Firestorm(){ - super(Tier.MASTER, 15, Element.FIRE, "firestorm", SpellType.ATTACK, 0, EnumAction.NONE, true); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - // Fire can damage armour stands. - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && rayTrace.entityHit instanceof EntityLivingBase){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)){ - target.setFire(10); - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), - 6.0f * modifiers.get(SpellModifiers.DAMAGE)); - }else{ - if(!world.isRemote && ticksInUse == 1) caster.sendMessage(new TextComponentTranslation("spell.resist", - target.getName(), this.getNameForTranslationFormatted())); - } - - }else if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); - - if(world.isAirBlock(pos)){ - if(!world.isRemote){ - world.setBlockState(pos, Blocks.FIRE.getDefaultState()); - } - } - } - - if(world.isRemote){ - for(int i = 0; i < 40; i++){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() * 0.6f - 0.3f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() * 0.4f - 0.2f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() * 0.6f - 0.3f; - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 0, 3 + world.rand.nextFloat(), 0, 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 0, 3 + world.rand.nextFloat(), 0, 0); - } - } - if(ticksInUse % 16 == 0){ - if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_FIRE, 0.5F, 1.0f); - } - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/FlameRay.java b/src/main/java/electroblob/wizardry/spell/FlameRay.java index 9c2852f8..b13e2e45 100644 --- a/src/main/java/electroblob/wizardry/spell/FlameRay.java +++ b/src/main/java/electroblob/wizardry/spell/FlameRay.java @@ -1,124 +1,86 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class FlameRay extends Spell { +public class FlameRay extends SpellRay { public FlameRay(){ - super(Tier.APPRENTICE, 5, Element.FIRE, "flame_ray", SpellType.ATTACK, 0, EnumAction.NONE, true); + super("flame_ray", true, EnumAction.NONE); + this.particleVelocity(1); + this.particleSpacing(0.5); + addProperties(DAMAGE, BURN_DURATION); + this.soundValues(1.5f, 1, 0); + } + + // The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy) + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ // Fire can damage armour stands - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && rayTrace.entityHit instanceof EntityLivingBase){ + if(target instanceof EntityLivingBase){ - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)){ - target.setFire(10); + if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){ + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster) + .sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + // This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle + // with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry. + }else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){ + target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); - }else{ - if(!world.isRemote && ticksInUse == 1) caster.sendMessage(new TextComponentTranslation("spell.resist", - target.getName(), this.getNameForTranslationFormatted())); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } } - if(world.isRemote){ - for(int i = 0; i < 20; i++){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 0); - } - } - if(ticksInUse % 16 == 0){ - if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_FIRE, 0.5F, 1.0f); - } + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - Vec3d vec = new Vec3d(target.posX - caster.posX, target.posY - caster.posY, target.posZ - caster.posZ).normalize(); - - target.setFire(10); - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); - - if(world.isRemote){ - for(int i = 0; i < 20; i++){ - double x1 = caster.posX + vec.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + vec.y * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + vec.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - vec.x * modifiers.get(WizardryItems.range_upgrade), - vec.y * modifiers.get(WizardryItems.range_upgrade), - vec.z * modifiers.get(WizardryItems.range_upgrade), 0); - Wizardry.proxy.spawnParticle(WizardryParticleType.MAGIC_FIRE, world, x1, y1, z1, - vec.x * modifiers.get(WizardryItems.range_upgrade), - vec.y * modifiers.get(WizardryItems.range_upgrade), - vec.z * modifiers.get(WizardryItems.range_upgrade), 0); - } - } - - if(ticksInUse % 16 == 0){ - if(ticksInUse == 0) caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - caster.playSound(WizardrySounds.SPELL_LOOP_FIRE, 0.5F, 1.0f); - } - - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).collide(true).spawn(world); + ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).collide(true).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/FlamingAxe.java b/src/main/java/electroblob/wizardry/spell/FlamingAxe.java index b320de52..1f87a883 100644 --- a/src/main/java/electroblob/wizardry/spell/FlamingAxe.java +++ b/src/main/java/electroblob/wizardry/spell/FlamingAxe.java @@ -1,50 +1,27 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.IConjuredItem; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.EnumParticleTypes; import net.minecraft.world.World; -public class FlamingAxe extends Spell { +public class FlamingAxe extends SpellConjuration { public FlamingAxe(){ - super(Tier.ADVANCED, 45, Element.FIRE, "flaming_axe", SpellType.UTILITY, 50, EnumAction.BOW, false); + super("flaming_axe", WizardryItems.flaming_axe); + addProperties(DAMAGE, BURN_DURATION); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - ItemStack flamingaxe = new ItemStack(WizardryItems.flaming_axe); - - IConjuredItem.setDurationMultiplier(flamingaxe, modifiers.get(WizardryItems.duration_upgrade)); - - if(!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.flaming_axe) - && ConjureBow.conjureItemInInventory(caster, flamingaxe)){ - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - world.spawnParticle(EnumParticleTypes.FLAME, x1, y1, z1, 0, 0, 0); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - return true; + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); } - return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java b/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java index f347d6e5..bb1c3611 100644 --- a/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java @@ -1,30 +1,25 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemSword; import net.minecraft.util.EnumHand; import net.minecraft.world.World; public class FlamingWeapon extends Spell { public FlamingWeapon(){ - super(Tier.ADVANCED, 35, Element.FIRE, "flaming_weapon", SpellType.UTILITY, 70, EnumAction.BOW, false); + super("flaming_weapon", EnumAction.BOW, false); + addProperties(EFFECT_DURATION); } @Override @@ -36,30 +31,28 @@ public class FlamingWeapon extends Spell { for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ - if((stack.getItem() instanceof ItemSword || stack.getItem() instanceof ItemBow) + if((ImbueWeapon.isSword(stack.getItem()) || ImbueWeapon.isBow(stack.getItem())) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.flaming_weapon)){ // The enchantment level as determined by the damage multiplier. The + 0.5f is so that // weird float processing doesn't incorrectly round it down. stack.addEnchantment(WizardryEnchantments.flaming_weapon, - modifiers.get(SpellModifiers.DAMAGE) == 1.0f ? 1 - : (int)((modifiers.get(SpellModifiers.DAMAGE) - 1.0f) - / Constants.DAMAGE_INCREASE_PER_TIER + 0.5f)); + modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1 + : (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) + / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); WizardData.get(caster).setImbuementDuration(WizardryEnchantments.flaming_weapon, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade))); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.9f, 0.7f, 1.0f); + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.9f, 0.7f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Flight.java b/src/main/java/electroblob/wizardry/spell/Flight.java index 2a79a85b..9e2d0d8a 100644 --- a/src/main/java/electroblob/wizardry/spell/Flight.java +++ b/src/main/java/electroblob/wizardry/spell/Flight.java @@ -1,54 +1,62 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; public class Flight extends Spell { + public static final String SPEED = "speed"; + public static final String ACCELERATION = "acceleration"; + + private static final double Y_NUDGE_ACCELERATION = 0.075; + public Flight(){ - super(Tier.MASTER, 10, Element.EARTH, "flight", SpellType.UTILITY, 0, EnumAction.NONE, true); + super("flight", EnumAction.NONE, true); + addProperties(SPEED, ACCELERATION); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - if(!caster.isInWater() && !caster.isElytraFlying()){ + if(!caster.isInWater() && !caster.isInLava() && !caster.isElytraFlying()){ + + float speed = getProperty(SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + float acceleration = getProperty(ACCELERATION).floatValue() * modifiers.get(SpellModifiers.POTENCY); + // The division thingy checks if the look direction is the opposite way to the velocity. If this is the // case then the velocity should be added regardless of the player's current speed. - if((Math.abs(caster.motionX) < 0.6 || caster.motionX / caster.getLookVec().x < 0) - && (Math.abs(caster.motionZ) < 0.6 || caster.motionZ / caster.getLookVec().z < 0)){ - caster.addVelocity(caster.getLookVec().x / 20, 0, caster.getLookVec().z / 20); + if((Math.abs(caster.motionX) < speed || caster.motionX / caster.getLookVec().x < 0) + && (Math.abs(caster.motionZ) < speed || caster.motionZ / caster.getLookVec().z < 0)){ + caster.addVelocity(caster.getLookVec().x * acceleration, 0, caster.getLookVec().z * acceleration); } // y velocity is handled separately to stop the player from falling from the sky when they reach maximum // horizontal speed. - if(Math.abs(caster.motionY) < 0.6 || caster.motionY / caster.getLookVec().y < 0){ - caster.motionY += caster.getLookVec().y / 20 + 0.075; + if(Math.abs(caster.motionY) < speed || caster.motionY / caster.getLookVec().y < 0){ + caster.motionY += caster.getLookVec().y * acceleration + Y_NUDGE_ACCELERATION; } - caster.fallDistance = 0.0f; + + if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0.0f; } + if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX - 1 + world.rand.nextDouble() * 2, - WizardryUtilities.getPlayerEyesPos(caster) - 0.5f + world.rand.nextDouble(), - caster.posZ - 1 + world.rand.nextDouble() * 2, 0, -0.1F, 0, 15, 0.8f, 1.0f, 0.5f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX - 1 + world.rand.nextDouble() * 2, - WizardryUtilities.getPlayerEyesPos(caster) - 0.5f + world.rand.nextDouble(), - caster.posZ - 1 + world.rand.nextDouble() * 2, 0, -0.1F, 0, 15, 1.0f, 1.0f, 1.0f); - } - if(ticksInUse % 24 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_FLAP, 0.5F, 1.0f); + double x = caster.posX - 1 + world.rand.nextDouble() * 2; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ - 1 + world.rand.nextDouble() * 2; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(0.8f, 1, 0.5f).spawn(world); + x = caster.posX - 1 + world.rand.nextDouble() * 2; + y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + z = caster.posZ - 1 + world.rand.nextDouble() * 2; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1f, 1f, 1f).spawn(world); } + + if(ticksInUse % 24 == 0) playSound(world, caster, ticksInUse, -1, modifiers); + return true; } diff --git a/src/main/java/electroblob/wizardry/spell/FontOfMana.java b/src/main/java/electroblob/wizardry/spell/FontOfMana.java index cbe2ef35..4dc20f74 100644 --- a/src/main/java/electroblob/wizardry/spell/FontOfMana.java +++ b/src/main/java/electroblob/wizardry/spell/FontOfMana.java @@ -1,61 +1,78 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.List; public class FontOfMana extends Spell { public FontOfMana(){ - super(Tier.MASTER, 100, Element.HEALING, "font_of_mana", SpellType.UTILITY, 250, EnumAction.BOW, false); + super("font_of_mana", EnumAction.BOW, false); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + double maxRadius = getProperty(EFFECT_RADIUS).doubleValue(); + List targets = WizardryUtilities.getEntitiesWithinRadius( - 5 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world, - EntityPlayer.class); + maxRadius * modifiers.get(WizardryItems.blast_upgrade), + caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class); for(EntityPlayer target : targets){ - if(WizardryUtilities.isPlayerAlly(caster, target) || target == caster){ - // Damage multiplier can only ever be 1 or 1.6 for master spells, so there's little point in actually - // calculating this. + if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){ target.addPotionEffect(new PotionEffect(WizardryPotions.font_of_mana, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE) > 1 ? 1 : 0)); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + (int)(getProperty(EFFECT_STRENGTH).intValue() + (modifiers.get(SpellModifiers.POTENCY) - 1) * 2))); } } if(world.isRemote){ for(int i = 0; i < 100 * modifiers.get(WizardryItems.blast_upgrade); i++){ - double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; - float hue = world.rand.nextFloat() * 0.4f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + radius * Math.cos(angle), caster.getEntityBoundingBox().minY, - caster.posZ + radius * Math.sin(angle), 0, 0.03, 0, 50, 1, 1 - hue, 0.6f + hue); + double radius = (1 + world.rand.nextDouble() * (maxRadius - 1)) * modifiers.get(WizardryItems.blast_upgrade); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; + ; + float hue = world.rand.nextFloat() * 0.4f; + + double x = caster.posX + radius * MathHelper.cos(angle); + double y = caster.getEntityBoundingBox().minY; + double z = caster.posZ + radius * MathHelper.sin(angle); + + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50) + .clr(1, 1 - hue, 0.6f + hue).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); + playSound(world, caster, ticksInUse, -1, modifiers); + return true; } -} + @SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked + public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ + // Moved from ItemWand (quite why this wasn't done with modifiers before I don't know!) + if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.font_of_mana)){ + // Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously + event.getModifiers().set(WizardryItems.cooldown_upgrade, event.getModifiers().get(WizardryItems.cooldown_upgrade) + / (2 + event.getCaster().getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier()), false); + } + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/spell/FontOfVitality.java b/src/main/java/electroblob/wizardry/spell/FontOfVitality.java deleted file mode 100644 index e951008c..00000000 --- a/src/main/java/electroblob/wizardry/spell/FontOfVitality.java +++ /dev/null @@ -1,57 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class FontOfVitality extends Spell { - - public FontOfVitality(){ - super(Tier.MASTER, 75, Element.HEALING, "font_of_vitality", SpellType.DEFENCE, 300, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.ABSORPTION, - (int)(1200 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - caster.addPotionEffect(new PotionEffect(MobEffects.REGENERATION, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 0.6f, 0.7f); - - x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 0.8f, 0.3f); - - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/ForceArrow.java b/src/main/java/electroblob/wizardry/spell/ForceArrow.java index f052b43f..4996b3d0 100644 --- a/src/main/java/electroblob/wizardry/spell/ForceArrow.java +++ b/src/main/java/electroblob/wizardry/spell/ForceArrow.java @@ -1,69 +1,21 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.projectile.EntityForceArrow; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; -public class ForceArrow extends Spell { +import javax.annotation.Nullable; + +public class ForceArrow extends SpellArrow { public ForceArrow(){ - super(Tier.APPRENTICE, 15, Element.SORCERY, "force_arrow", SpellType.ATTACK, 20, EnumAction.NONE, false); + super("force_arrow", EntityForceArrow::new); + this.addProperties(Spell.DAMAGE); + this.soundValues(1, 1.3f, 0.2f); } @Override - public boolean doesSpellRequirePacket(){ - return false; + protected void addArrowExtras(EntityForceArrow arrow, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + arrow.setMana((int)(this.getCost() * modifiers.get(SpellModifiers.COST))); } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityForceArrow forceArrow = new EntityForceArrow(world, caster, - 1 * modifiers.get(WizardryItems.range_upgrade), modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(forceArrow); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_FORCE, 1.0f, - 1.2f + world.rand.nextFloat() * 0.2f); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityForceArrow forceArrow = new EntityForceArrow(world, caster, target, - 1 * modifiers.get(WizardryItems.range_upgrade), 2, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(forceArrow); - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_FORCE, 1.0f, 1.2f + world.rand.nextFloat() * 0.2f); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - } diff --git a/src/main/java/electroblob/wizardry/spell/ForceOrb.java b/src/main/java/electroblob/wizardry/spell/ForceOrb.java deleted file mode 100644 index 5db4e5fa..00000000 --- a/src/main/java/electroblob/wizardry/spell/ForceOrb.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityForceOrb; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class ForceOrb extends Spell { - - public ForceOrb(){ - super(Tier.ADVANCED, 20, Element.SORCERY, "force_orb", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityForceOrb forceOrb = new EntityForceOrb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(forceOrb); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityForceOrb forceOrb = new EntityForceOrb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - forceOrb.directTowards(target, 1.5f); - world.spawnEntity(forceOrb); - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Forcefield.java b/src/main/java/electroblob/wizardry/spell/Forcefield.java index eea373b9..bfc298b2 100644 --- a/src/main/java/electroblob/wizardry/spell/Forcefield.java +++ b/src/main/java/electroblob/wizardry/spell/Forcefield.java @@ -1,73 +1,21 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityForcefield; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; +import net.minecraft.util.EnumFacing; -public class Forcefield extends Spell { +public class Forcefield extends SpellConstruct { public Forcefield(){ - super(Tier.ADVANCED, 45, Element.HEALING, "forcefield", SpellType.DEFENCE, 200, EnumAction.BOW, false); + super("forcefield", EnumAction.BOW, EntityForcefield::new, false); + addProperties(Spell.EFFECT_RADIUS); } @Override - public boolean doesSpellRequirePacket(){ - return false; + protected void addConstructExtras(EntityForcefield construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + construct.setRadius(getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade)); } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(caster.onGround){ - if(!world.isRemote){ - EntityForcefield forcefield = new EntityForcefield(world, caster.posX, caster.posY, caster.posZ, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(forcefield); - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION_LARGE, 1.0f, 1.0f); - return true; - } - - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - // Wizards can no longer cast forcefield when they are inside one - if(caster.onGround - && world.getEntitiesWithinAABB(EntityForcefield.class, caster.getEntityBoundingBox()).isEmpty()){ - if(!world.isRemote){ - EntityForcefield forcefield = new EntityForcefield(world, caster.posX, caster.posY, caster.posZ, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(forcefield); - } - caster.playSound(WizardrySounds.SPELL_CONJURATION_LARGE, 1.0f, 1.0f); - return true; - } - - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - } diff --git a/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java b/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java new file mode 100644 index 00000000..93860407 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java @@ -0,0 +1,99 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.block.BlockThorns; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class ForestOfThorns extends Spell { + + public ForestOfThorns(){ + super("forest_of_thorns", EnumAction.BOW, false); + addProperties(EFFECT_RADIUS, DURATION, DAMAGE); + } + + @Override public boolean requiresPacket(){ return false; } + @Override public boolean canBeCastByNPCs(){ return true; } + @Override public boolean canBeCastByDispensers(){ return true; } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + if(!summonThorns(world, caster, caster.getPosition(), modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + if(!summonThorns(world, caster, caster.getPosition(), modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + if(!summonThorns(world, null, new BlockPos(x, y, z).offset(direction), modifiers)) return false; + this.playSound(world, x, y, z, ticksInUse, duration, modifiers); + return true; + } + + private boolean summonThorns(World world, @Nullable EntityLivingBase caster, BlockPos origin, SpellModifiers modifiers){ + + if(!world.isRemote){ + + double radius = getProperty(EFFECT_RADIUS).doubleValue() * modifiers.get(WizardryItems.blast_upgrade); + + List ring = new ArrayList<>((int)(7 * radius)); // 7 is a bit more than 2 pi + + for(int x = -(int)radius; x <= radius; x++){ + + for(int z = -(int)radius; z <= radius; z++){ + + double distance = MathHelper.sqrt(x*x + z*z); + + if(distance > radius || distance < radius - 1.5) continue; + + Integer y = WizardryUtilities.getNearestSurface(world, origin.add(x, 0, z), EnumFacing.UP, (int)radius, true, WizardryUtilities.SurfaceCriteria.BUILDABLE); + if(y != null) ring.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z)); + } + } + + if(ring.isEmpty()) return false; + + // Because we're always using EnumFacing.UP in the code above, we can be sure that pos is the block above the floor + for(BlockPos pos : ring){ + + ((BlockThorns)WizardryBlocks.thorns).placeAt(world, pos, 3); + + for(int i=0; i<2; i++){ + + TileEntity tileentity = world.getTileEntity(pos.up(i)); + + if(tileentity instanceof TileEntityPlayerSaveTimed){ + ((TileEntityPlayerSaveTimed)tileentity).setLifetime((int)(getProperty(DURATION).floatValue() + * modifiers.get(WizardryItems.duration_upgrade))); + if(caster != null) ((TileEntityPlayerSaveTimed)tileentity).setCaster(caster); + } + } + } + } + + return true; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/ForestsCurse.java b/src/main/java/electroblob/wizardry/spell/ForestsCurse.java index 33487073..edb3f8d4 100644 --- a/src/main/java/electroblob/wizardry/spell/ForestsCurse.java +++ b/src/main/java/electroblob/wizardry/spell/ForestsCurse.java @@ -1,76 +1,58 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; import net.minecraft.world.World; -public class ForestsCurse extends Spell { +public class ForestsCurse extends SpellAreaEffect { public ForestsCurse(){ - super(Tier.MASTER, 75, Element.EARTH, "forests_curse", SpellType.ATTACK, 200, EnumAction.BOW, false); + super("forests_curse", EnumAction.BOW); + this.soundValues(1, 1.1f, 0.2f); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected void affectEntity(World world, EntityLivingBase caster, EntityLivingBase target, SpellModifiers modifiers){ + + if(!MagicDamage.isEntityImmune(DamageType.POISON, target) && WizardryUtilities.isLiving(target)){ + + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - List targets = WizardryUtilities.getEntitiesWithinRadius( - 5.0d * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); + int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + int amplifier = (int)(getProperty(EFFECT_STRENGTH).floatValue() + bonusAmplifier); - for(EntityLivingBase target : targets){ - if(WizardryUtilities.isValidTarget(caster, target) - && !MagicDamage.isEntityImmune(DamageType.POISON, target)){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON), - 4.0f * modifiers.get(SpellModifiers.DAMAGE)); - target.addPotionEffect(new PotionEffect(MobEffects.POISON, - (int)(140 * modifiers.get(WizardryItems.duration_upgrade)), 2)); - target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, - (int)(140 * modifiers.get(WizardryItems.duration_upgrade)), 2)); - target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, - (int)(140 * modifiers.get(WizardryItems.duration_upgrade)), 2)); - } + target.addPotionEffect(new PotionEffect(MobEffects.POISON, duration, amplifier)); + target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, amplifier)); + target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, amplifier)); } - - if(world.isRemote){ - for(int i = 0; i < 50 * modifiers.get(WizardryItems.blast_upgrade); i++){ - double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; - float brightness = world.rand.nextFloat() / 4; - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - caster.posX + radius * Math.cos(angle), WizardryUtilities.getPlayerEyesPos(caster) + 0.5, - caster.posZ + radius * Math.sin(angle), 0, -0.2, 0, 0, 0.05f + brightness, 0.2f + brightness, - 0.0f); - brightness = world.rand.nextFloat() / 4; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + radius * Math.cos(angle), WizardryUtilities.getPlayerEyesPos(caster) + 0.5, - caster.posZ + radius * Math.sin(angle), 0, -0.05, 0, 50, 0.1f + brightness, 0.2f + brightness, - 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, world, caster.posX + radius * Math.cos(angle), - WizardryUtilities.getPlayerEyesPos(caster) + 0.5, caster.posZ + radius * Math.sin(angle), 0, - -0.01, 0, 40 + world.rand.nextInt(12)); - - } - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z){ + + y += 2; // Moves the particles up to the caster's head level + + float brightness = world.rand.nextFloat() / 4; + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).vel(0, -0.2, 0) + .clr(0.05f + brightness, 0.2f + brightness, 0).spawn(world); + + brightness = world.rand.nextFloat() / 4; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.05, 0).time(50) + .clr(0.1f + brightness, 0.2f + brightness, 0).spawn(world); + + ParticleBuilder.create(Type.LEAF).pos(x, y, z).vel(0, -0.01, 0).time(40 + world.rand.nextInt(12)).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/spell/Freeze.java b/src/main/java/electroblob/wizardry/spell/Freeze.java index 500d5c79..d0d59939 100644 --- a/src/main/java/electroblob/wizardry/spell/Freeze.java +++ b/src/main/java/electroblob/wizardry/spell/Freeze.java @@ -1,19 +1,14 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityBlazeMinion; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.monster.EntityMagmaCube; @@ -22,177 +17,78 @@ import net.minecraft.init.Blocks; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Freeze extends Spell { +public class Freeze extends SpellRay { public Freeze(){ - super(Tier.BASIC, 5, Element.ICE, "freeze", SpellType.ATTACK, 10, EnumAction.NONE, false); + super("freeze", false, EnumAction.NONE); + this.soundValues(1, 1.4f, 0.4f); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + this.hitLiquids(true); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ - // Entity ray trace is done first because block ray trace passes through entities; if it was the other - // way round, entities would only be hit when there were no blocks in range behind them. - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - if(target instanceof EntityBlaze || target instanceof EntityMagmaCube - || target instanceof EntityBlazeMinion){ + if(target instanceof EntityBlaze || target instanceof EntityMagmaCube){ target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } if(MagicDamage.isEntityImmune(DamageType.FROST, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); }else{ - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue())); } - if(target.isBurning()){ - target.extinguish(); - } + if(target.isBurning()) target.extinguish(); - if(world.isRemote){ - double dx = target.posX - caster.posX; - double dy = (target.getEntityBoundingBox().minY + target.height / 2) - - WizardryUtilities.getPlayerEyesPos(caster); - double dz = target.posZ - caster.posZ; - for(int i = 1; i < 5; i++){ - float brightness = 0.5f + (world.rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), brightness, brightness + 0.1f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, -0.02, 0, - 40 + world.rand.nextInt(10)); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.2F); return true; - - }else{ - rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, caster, true); - // Gets block the player is looking at and sets to ice or covers with snow as necessary - // Note how the block is set on the server side only (kinda obvious really) but the particles are - // spawned on the client side only. - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - if(world.getBlockState(pos).getBlock() == Blocks.WATER && !world.isRemote){ - world.setBlockState(pos, Blocks.ICE.getDefaultState()); - }else if(world.getBlockState(pos).getBlock() == Blocks.LAVA && !world.isRemote){ - world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState()); - }else if(world.getBlockState(pos).getBlock() == Blocks.FLOWING_LAVA && !world.isRemote){ - world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState()); - }else if(rayTrace.sideHit == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP) - && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ - world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState()); - } - - if(world.isRemote){ - - double dx = pos.getX() + 0.5 - caster.posX; - double dy = pos.getY() + 0.5 - WizardryUtilities.getPlayerEyesPos(caster); - double dz = pos.getZ() + 0.5 - caster.posZ; - - for(int i = 1; i < 5; i++){ - float brightness = 0.5f + (world.rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0.0d, 0.0d, 0.0d, - 20 + world.rand.nextInt(8), brightness, brightness + 0.1f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0, - 20 + world.rand.nextInt(8), 1.0f, 1.0f, 1.0f); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.2F); - return true; - } } - return false; + + return false; // If the spell hit a non-living entity } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(target != null){ + if(WizardryUtilities.canDamageBlocks(caster, world)){ - if(target instanceof EntityBlaze || target instanceof EntityMagmaCube - || target instanceof EntityBlazeMinion){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); + if(world.getBlockState(pos).getBlock() == Blocks.WATER && !world.isRemote){ + world.setBlockState(pos, Blocks.ICE.getDefaultState()); + }else if(world.getBlockState(pos).getBlock() == Blocks.LAVA && !world.isRemote){ + world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState()); + }else if(world.getBlockState(pos).getBlock() == Blocks.FLOWING_LAVA && !world.isRemote){ + world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState()); + }else if(side == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP) + && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ + world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState()); } - - if(!world.isRemote && !MagicDamage.isEntityImmune(DamageType.FROST, target)){ - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); - } - - if(target.isBurning()){ - target.extinguish(); - } - - if(world.isRemote){ - double dx = target.posX - caster.posX; - double dy = (target.getEntityBoundingBox().minY + target.height / 2) - - (caster.posY + caster.getEyeHeight()); - double dz = target.posZ - caster.posZ; - for(int i = 1; i < 5; i++){ - float brightness = 0.5f + (world.rand.nextFloat() / 2); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), brightness, brightness + 0.1f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, -0.02, 0, - 40 + world.rand.nextInt(10)); - } - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.4F + 1.2F); - return true; } - - return false; + + return true; // Always succeeds if it hits a block } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + float brightness = 0.5f + (world.rand.nextFloat() / 2); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)) + .clr(brightness, brightness + 0.1f, 1).spawn(world); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java b/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java index 3c85b63d..1c798319 100644 --- a/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java @@ -1,23 +1,17 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemBow; import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemSword; import net.minecraft.util.EnumHand; import net.minecraft.world.World; @@ -30,7 +24,8 @@ public class FreezingWeapon extends Spell { public static final String FREEZING_ARROW_NBT_KEY = "frostLevel"; public FreezingWeapon(){ - super(Tier.ADVANCED, 35, Element.ICE, "freezing_weapon", SpellType.UTILITY, 70, EnumAction.BOW, false); + super("freezing_weapon", EnumAction.BOW, false); + addProperties(EFFECT_DURATION); } @Override @@ -42,30 +37,28 @@ public class FreezingWeapon extends Spell { for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ - if((stack.getItem() instanceof ItemSword || stack.getItem() instanceof ItemBow) + if((ImbueWeapon.isSword(stack.getItem()) || ImbueWeapon.isBow(stack.getItem())) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.freezing_weapon)){ // The enchantment level as determined by the damage multiplier. The + 0.5f is so that // weird float processing doesn't incorrectly round it down. stack.addEnchantment(WizardryEnchantments.freezing_weapon, - modifiers.get(SpellModifiers.DAMAGE) == 1.0f ? 1 - : (int)((modifiers.get(SpellModifiers.DAMAGE) - 1.0f) - / Constants.DAMAGE_INCREASE_PER_TIER + 0.5f)); + modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1 + : (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) + / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); WizardData.get(caster).setImbuementDuration(WizardryEnchantments.freezing_weapon, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade))); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.9f, 0.7f, 1.0f); + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.9f, 0.7f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/FrostAxe.java b/src/main/java/electroblob/wizardry/spell/FrostAxe.java index c997425a..31c104db 100644 --- a/src/main/java/electroblob/wizardry/spell/FrostAxe.java +++ b/src/main/java/electroblob/wizardry/spell/FrostAxe.java @@ -1,52 +1,27 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.IConjuredItem; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.world.World; -public class FrostAxe extends Spell { +public class FrostAxe extends SpellConjuration { public FrostAxe(){ - super(Tier.ADVANCED, 45, Element.ICE, "frost_axe", SpellType.UTILITY, 50, EnumAction.BOW, false); + super("frost_axe", WizardryItems.frost_axe); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - ItemStack frostaxe = new ItemStack(WizardryItems.frost_axe); - - IConjuredItem.setDurationMultiplier(frostaxe, modifiers.get(WizardryItems.duration_upgrade)); - - if(!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.frost_axe) - && ConjureBow.conjureItemInInventory(caster, frostaxe)){ - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, x1, y1, z1, 0, -0.02d, 0, - 40 + world.rand.nextInt(10)); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0f, 1.0f); - return true; + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SNOW).pos(x, y, z).spawn(world); } - return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/FrostRay.java b/src/main/java/electroblob/wizardry/spell/FrostRay.java index b7b3d942..7f4dbcaa 100644 --- a/src/main/java/electroblob/wizardry/spell/FrostRay.java +++ b/src/main/java/electroblob/wizardry/spell/FrostRay.java @@ -1,162 +1,98 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.monster.EntityMagmaCube; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class FrostRay extends Spell { +public class FrostRay extends SpellRay { public FrostRay(){ - super(Tier.APPRENTICE, 5, Element.ICE, "frost_ray", SpellType.ATTACK, 0, EnumAction.NONE, true); + super("frost_ray", true, EnumAction.NONE); + this.particleVelocity(1); + this.particleSpacing(0.5); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } - Vec3d look = caster.getLookVec(); + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - if(target.isBurning()){ - target.extinguish(); - } + if(target.isBurning()) target.extinguish(); if(MagicDamage.isEntityImmune(DamageType.FROST, target)){ - if(!world.isRemote && ticksInUse == 1) caster.sendMessage(new TextComponentTranslation("spell.resist", - target.getName(), this.getNameForTranslationFormatted())); - }else{ - // For frost ray the entity can move slightly, unlike freeze. - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 0)); + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster) + .sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + // This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle + // with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry. + }else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){ + // For frost ray the entity can move slightly, unlike freeze + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue())); - float baseDamage = target instanceof EntityBlaze || target instanceof EntityMagmaCube ? 6.0f : 3.0f; - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), - baseDamage * modifiers.get(SpellModifiers.DAMAGE)); + float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); + if(target instanceof EntityBlaze || target instanceof EntityMagmaCube) damage *= 2; + + WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, + DamageType.FROST), damage); } } - - if(world.isRemote){ - for(int i = 0; i < 20; i++){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 0.4f, - 0.6f, 1.0f); - - x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 1.0f, - 1.0f, 1.0f); - } - } - - if(ticksInUse % 12 == 0){ - if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.5F, 1.0f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_ICE, 0.5F, 1.0f); - } + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - Vec3d vec = new Vec3d(target.posX - caster.posX, target.posY - caster.posY, target.posZ - caster.posZ) - .normalize(); - - if(target.isBurning()){ - target.extinguish(); - } - - if(!MagicDamage.isEntityImmune(DamageType.FROST, target)){ - // For frost ray the entity can move slightly, unlike freeze. - target.addPotionEffect(new PotionEffect(WizardryPotions.frost, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - - float baseDamage = target instanceof EntityBlaze || target instanceof EntityMagmaCube ? 6.0f : 3.0f; - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), - baseDamage * modifiers.get(SpellModifiers.DAMAGE)); - - } - - if(world.isRemote){ - for(int i = 0; i < 20; i++){ - double x1 = caster.posX + vec.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + vec.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + vec.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - vec.x * modifiers.get(WizardryItems.range_upgrade), - vec.y * modifiers.get(WizardryItems.range_upgrade), - vec.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 0.4f, - 0.6f, 1.0f); - - x1 = caster.posX + vec.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - y1 = caster.posY + caster.getEyeHeight() - 0.4f + vec.y * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - z1 = caster.posZ + vec.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - vec.x * modifiers.get(WizardryItems.range_upgrade), - vec.y * modifiers.get(WizardryItems.range_upgrade), - vec.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 1.0f, - 1.0f, 1.0f); - } - } - - if(ticksInUse % 12 == 0){ - if(ticksInUse == 0) caster.playSound(WizardrySounds.SPELL_ICE, 0.5F, 1.0f); - caster.playSound(WizardrySounds.SPELL_LOOP_ICE, 0.5F, 1.0f); - } - - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + float brightness = world.rand.nextFloat(); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)) + .clr(0.4f + 0.6f * brightness, 0.6f + 0.4f*brightness, 1).collide(true).spawn(world); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)).collide(true).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/FrostSigil.java b/src/main/java/electroblob/wizardry/spell/FrostSigil.java deleted file mode 100644 index 5e2ec592..00000000 --- a/src/main/java/electroblob/wizardry/spell/FrostSigil.java +++ /dev/null @@ -1,51 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityFrostSigil; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; - -public class FrostSigil extends Spell { - - public FrostSigil(){ - super(Tier.APPRENTICE, 10, Element.ICE, "frost_sigil", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && rayTrace.sideHit == EnumFacing.UP){ - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - EntityFrostSigil frostsigil = new EntityFrostSigil(world, x, y, z, caster, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(frostsigil); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Glide.java b/src/main/java/electroblob/wizardry/spell/Glide.java index bab250b9..c1c465ad 100644 --- a/src/main/java/electroblob/wizardry/spell/Glide.java +++ b/src/main/java/electroblob/wizardry/spell/Glide.java @@ -1,50 +1,76 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; import net.minecraft.world.World; public class Glide extends Spell { + public static final String SPEED = "speed"; + public static final String FALL_SPEED = "fall_speed"; + public static final String ACCELERATION = "acceleration"; + public Glide(){ - super(Tier.ADVANCED, 5, Element.EARTH, "glide", SpellType.UTILITY, 0, EnumAction.NONE, true); + super("glide", EnumAction.NONE, true); + addProperties(SPEED, FALL_SPEED, ACCELERATION); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + if(ticksInUse == 0 && world.isRemote) Wizardry.proxy.playSpellSoundLoop(entity, this, this.sounds[0], this.sounds[0], this.sounds[0], + WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f)); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + if(ticksInUse == 0 && world.isRemote){ + Wizardry.proxy.playSpellSoundLoop(world, x, y, z, this, this.sounds[0], this.sounds[0], this.sounds[0], + WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f), duration); + } } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ if(caster.motionY < -0.1 && !caster.isInWater()){ - caster.motionY = -0.1; - if(Math.abs(caster.motionX) < 0.4 && Math.abs(caster.motionZ) < 0.4){ - caster.addVelocity(caster.getLookVec().x / 8, 0, caster.getLookVec().z / 8); - // entityplayer.moveEntity(entityplayer.motionX*10, 0, entityplayer.motionZ*10); + + float speed = getProperty(SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + // There seems to be some sort of 'terminal velocity', presumably due to the slight slowing-down effect in + // vanilla - this means we have to apply potency modifiers to the acceleration as well as the speed or they + // appear to have no effect (a bug which had me confused for quite a while!) This also applies to flight. + float acceleration = getProperty(ACCELERATION).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + caster.motionY = -getProperty(FALL_SPEED).floatValue(); + if(Math.abs(caster.motionX) < speed && Math.abs(caster.motionZ) < speed){ + caster.addVelocity(caster.getLookVec().x * acceleration, 0, caster.getLookVec().z * acceleration); } - caster.fallDistance = 0.0f; + + if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0.0f; } if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX - 0.25d + world.rand.nextDouble() / 2, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5f + world.rand.nextDouble(), - caster.posZ - 0.25d + world.rand.nextDouble() / 2, 0, -0.1F, 0, 15, 1.0f, 1.0f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, world, - caster.posX - 0.25d + world.rand.nextDouble() / 2, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5f + world.rand.nextDouble(), - caster.posZ - 0.25d + world.rand.nextDouble() / 2, 0, -0.03, 0, 20); + double x = caster.posX - 0.25 + world.rand.nextDouble() / 2; + double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble(); + double z = caster.posZ - 0.25 + world.rand.nextDouble() / 2; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1f, 1f, 1f).spawn(world); + x = caster.posX - 0.25 + world.rand.nextDouble() / 2; + y = caster.getEntityBoundingBox().minY + world.rand.nextDouble(); + z = caster.posZ - 0.25 + world.rand.nextDouble() / 2; + ParticleBuilder.create(Type.LEAF).pos(x, y, z).time(20).spawn(world); } if(ticksInUse % 24 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ITEM_ELYTRA_FLYING, 0.5F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); } + return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Grapple.java b/src/main/java/electroblob/wizardry/spell/Grapple.java new file mode 100644 index 00000000..6ee73cab --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Grapple.java @@ -0,0 +1,419 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.IVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.RayTracer; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.EnumAction; +import net.minecraft.network.play.server.SPacketEntityVelocity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class Grapple extends Spell { + + /** The speed at which the vine extends/retracts from the caster, in blocks per tick. */ + public static final String EXTENSION_SPEED = "extension_speed"; + /** The speed at which the vine reels in the caster or target, in blocks per tick. */ + public static final String REEL_SPEED = "reel_speed"; + + public static final IVariable TARGET_KEY = new IVariable.Variable(Persistence.NEVER) + .withTicker(Grapple::update); + + /** The distance from the target position at which the spell will stop reeling in entities. */ + private static final double MINIMUM_REEL_DISTANCE = 3; + /** The acceleration with which the vine reels in the caster or target. */ + private static final double REEL_ACCELERATION = 0.3; + /** The speed at which the caster or target is lowered when the caster is sneaking. */ + private static final double PAYOUT_SPEED = 0.25; + /** Once attached, the vine can stretch beyond the maximum range by this factor before breaking. */ + private static final double STRETCH_LIMIT = 1.5; + /** The distance between spawned particles. */ + protected static final double PARTICLE_SPACING = 1.5; + /** The maximum jitter (random position offset) for spawned particles. */ + protected static final double PARTICLE_JITTER = 0.04; + + public Grapple(){ + super("grapple", EnumAction.NONE, true); + addProperties(RANGE, EXTENSION_SPEED, REEL_SPEED); + } + + @Override + public boolean canBeCastByNPCs(){ + return super.canBeCastByNPCs(); + } + + @Override + public boolean canBeCastByDispensers(){ + return true; + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createSoundsWithSuffixes("shoot", "attach", "pull", "release"); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + WizardData data = WizardData.get(caster); + + Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + + float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + RayTraceResult hit = data.getVariable(TARGET_KEY); + + // Initial targeting + if(hit == null){ + hit = findTarget(world, caster, origin, caster.getLookVec(), modifiers); + data.setVariable(TARGET_KEY, hit); + caster.swingArm(hand); + // This condition prevents the sound playing every tick after a missed shot has finished extending + if(hit.typeOfHit != RayTraceResult.Type.MISS + || ticksInUse * extensionSpeed < getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade)){ + this.playSound(world, caster, ticksInUse, -1, modifiers, "shoot"); + } + } + + Vec3d target = hit.hitVec; + + if(hit.entityHit instanceof EntityLivingBase){ + // If the target is an entity, we need to use the entity's centre rather than the original hit position + // because the entity will have moved! + target = new Vec3d(hit.entityHit.posX, hit.entityHit.getEntityBoundingBox().minY + hit.entityHit.height/2, hit.entityHit.posZ); + } + + double distance = origin.distanceTo(target); + Vec3d direction = target.subtract(origin).normalize(); + + double maxLength = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT; + + // If the vine stretched too far + if(distance > maxLength){ + if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){ + spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), direction, distance); + } + data.setVariable(TARGET_KEY, null); + return false; // The spell is finished + } + + boolean extending = ticksInUse * extensionSpeed < distance; + + if(extending){ + // Extension + if(world.isRemote){ + // world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast + ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0) + .target(origin.add(direction.scale(ticksInUse * extensionSpeed))).tvel(direction.scale(extensionSpeed)) + .seed(world.getTotalWorldTime() - ticksInUse).spawn(world); + } + + }else{ + // Retraction + Vec3d velocity = direction.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + + int retractTime = ticksInUse - (int)(distance/extensionSpeed); + + switch(hit.typeOfHit){ + + case BLOCK: + // Payout + if(caster.isSneaking() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_abseiling)) + velocity = new Vec3d(velocity.x, distance < maxLength-1 ? -PAYOUT_SPEED : distance-maxLength+1, velocity.z); + + // Reel the caster towards the block hit + double ax = (velocity.x - caster.motionX) * REEL_ACCELERATION; + double ay = (velocity.y - caster.motionY) * REEL_ACCELERATION; + double az = (velocity.z - caster.motionZ) * REEL_ACCELERATION; + caster.addVelocity(ax, ay, az); + + if(caster.motionY > 0 && !Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0; // Reset fall distance if the caster moves upwards + + if(world.isRemote){ + ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0) + .target(target).seed(world.getTotalWorldTime() - ticksInUse).spawn(world); + } + + if(retractTime == 1){ // Just hit + this.playSound(world, caster, ticksInUse, -1, modifiers, "pull"); + this.playSound(world, hit.hitVec, ticksInUse, -1, modifiers, "attach"); + } + + break; + + case ENTITY: + // Payout + if(caster.isSneaking() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_abseiling)) + velocity = new Vec3d(velocity.x, distance < maxLength-1 ? PAYOUT_SPEED : maxLength-1-distance, velocity.z); + + // Reel the entity hit towards the caster + Entity entity = hit.entityHit; + + if(distance > MINIMUM_REEL_DISTANCE){ + double ax1 = (-velocity.x - entity.motionX) * REEL_ACCELERATION; + double ay1 = (-velocity.y - entity.motionY) * REEL_ACCELERATION; + double az1 = (-velocity.z - entity.motionZ) * REEL_ACCELERATION; + entity.addVelocity(ax1, ay1, az1); + // Player motion is handled on that player's client so needs packets + if(entity instanceof EntityPlayerMP){ + ((EntityPlayerMP)entity).connection.sendPacket(new SPacketEntityVelocity(entity)); + } + } + + if(world.isRemote){ + ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0) + .target(entity).seed(world.getTotalWorldTime() - ticksInUse).spawn(world); + } + + if(retractTime == 1){ // Just hit + this.playSound(world, caster, ticksInUse, -1, modifiers, "pull"); + this.playSound(world, entity.posX, entity.posY, entity.posZ, ticksInUse, -1, modifiers, "attach"); + } + + break; + + default: + // Missed + if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){ + spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), direction, distance); + } + //caster.resetActiveHand(); + data.setVariable(TARGET_KEY, null); + return false; // The spell is finished + } + } + + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + + Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + + // If the target is an entity, we need to use the entity's centre rather than the original hit position + // because the entity will have moved! + Vec3d targetVec = new Vec3d(target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ); + + double distance = origin.distanceTo(targetVec); + + // Can't cast the spell at all if the target is too far away + if(ticksInUse <= 1 && distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade)) + return false; + + Vec3d vec = targetVec.subtract(origin).normalize(); + + float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + // If the vine stretched too far + if(distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT){ + if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){ + spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), vec, distance); + } + return false; + } + + Vec3d hookPosition; + + if(ticksInUse * extensionSpeed < distance){ + // Extension + hookPosition = origin.add(vec.scale(ticksInUse * extensionSpeed)); + + }else{ + // Retraction + Vec3d velocity = vec.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + + // Reel the entity hit towards the caster + if(distance > MINIMUM_REEL_DISTANCE){ + double ax1 = (-velocity.x - target.motionX) * REEL_ACCELERATION; + double ay1 = (-velocity.y - target.motionY) * REEL_ACCELERATION; + double az1 = (-velocity.z - target.motionZ) * REEL_ACCELERATION; + target.addVelocity(ax1, ay1, az1); + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + } + } + + hookPosition = targetVec; + } + + if(world.isRemote){ + // world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast + ParticleBuilder.create(Type.VINE).pos(origin).target(hookPosition).tvel(vec.scale(extensionSpeed)) + .seed(world.getTotalWorldTime() - ticksInUse).spawn(world); + } + + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + Vec3d origin = new Vec3d(x, y, z); + + RayTraceResult result = findTarget(world, null, origin, new Vec3d(direction.getDirectionVec()), modifiers); + + if(result.entityHit instanceof EntityLivingBase){ + + Entity entity = result.entityHit; + + // If the target is an entity, we need to use the entity's centre rather than the original hit position + // because the entity will have moved! + Vec3d target = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.height/2, entity.posZ); + + double distance = origin.distanceTo(target); + Vec3d vec = target.subtract(origin).normalize(); + + float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + // If the vine stretched too far + if(distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT){ + if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){ + spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), vec, distance); + } + return false; + } + + Vec3d hookPosition; + + if(ticksInUse * extensionSpeed < distance){ + // Extension + hookPosition = origin.add(vec.scale(ticksInUse * extensionSpeed)); + + }else{ + // Retraction + Vec3d velocity = vec.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + + // Reel the entity hit towards the caster + if(distance > MINIMUM_REEL_DISTANCE){ + double ax1 = (-velocity.x - entity.motionX) * REEL_ACCELERATION; + double ay1 = (-velocity.y - entity.motionY) * REEL_ACCELERATION; + double az1 = (-velocity.z - entity.motionZ) * REEL_ACCELERATION; + entity.addVelocity(ax1, ay1, az1); + // Player motion is handled on that player's client so needs packets + if(entity instanceof EntityPlayerMP){ + ((EntityPlayerMP)entity).connection.sendPacket(new SPacketEntityVelocity(entity)); + } + } + + hookPosition = target; + } + + if(world.isRemote){ + // world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast + ParticleBuilder.create(Type.VINE).pos(origin).target(hookPosition).seed(world.getTotalWorldTime() - ticksInUse).spawn(world); + } + + return true; + } + + return false; + } + + @Override + public void finishCasting(World world, @Nullable EntityLivingBase caster, double x, double y, double z, EnumFacing facing, int duration, SpellModifiers modifiers){ + + Vec3d origin = null; + Vec3d direction = null; + Vec3d target = null; + + if(caster != null){ + + origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + + if(caster instanceof EntityPlayer){ + WizardData data = WizardData.get((EntityPlayer)caster); + if(data != null){ + RayTraceResult hit = data.getVariable(TARGET_KEY); + if(hit != null) target = hit.hitVec; + } + }else if(caster instanceof EntityLiving){ + Entity entity = ((EntityLiving)caster).getAttackTarget(); + if(entity != null) target = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.height/2, entity.posZ); + } + + if(target != null) direction = target.subtract(origin).normalize(); + + this.playSound(world, caster, duration, duration, modifiers, "release"); + + }else if(!Double.isNaN(x) && !Double.isNaN(y) && !Double.isNaN(z)){ + + origin = new Vec3d(x, y, z); + direction = new Vec3d(facing.getDirectionVec()); + RayTraceResult result = findTarget(world, null, origin, direction, modifiers); + target = result.hitVec; + + this.playSound(world, origin, duration, duration, modifiers, "release"); + } + + if(world.isRemote && origin != null && direction != null){ + + float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + double distance = Math.min(target.subtract(origin).length(), duration * extensionSpeed); + + spawnLeafParticles(world, origin, direction, distance); + } + } + + private void spawnLeafParticles(World world, Vec3d origin, Vec3d direction, double distance){ + // Copied from SpellRay + for(double d = PARTICLE_SPACING; d <= distance; d += PARTICLE_SPACING){ + double x = origin.x + d*direction.x;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1); + double y = origin.y + d*direction.y;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1); + double z = origin.z + d*direction.z;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1); + ParticleBuilder.create(Type.LEAF, world.rand, x, y, z, PARTICLE_JITTER, true).time(25 + world.rand.nextInt(5)).spawn(world); + } + } + + private RayTraceResult findTarget(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, SpellModifiers modifiers){ + + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + Vec3d endpoint = origin.add(direction.scale(range)); + + RayTraceResult result = RayTracer.rayTrace(world, origin, endpoint, 0, false, + true, false, Entity.class, RayTracer.ignoreEntityFilter(caster)); + + // Non-solid blocks (or if the result is null) count as misses + if(result == null || result.typeOfHit == RayTraceResult.Type.BLOCK + && !world.getBlockState(result.getBlockPos()).getMaterial().isSolid()){ + return new RayTraceResult(RayTraceResult.Type.MISS, endpoint, EnumFacing.DOWN, new BlockPos(endpoint)); + // Immovable entities count as misses too, but the endpoint is the hit vector instead + }else if(result.entityHit != null && !result.entityHit.canBePushed()){ + return new RayTraceResult(RayTraceResult.Type.MISS, result.hitVec, EnumFacing.DOWN, new BlockPos(endpoint)); + } + // If the ray trace missed, result.hitVec will be the endpoint anyway - neat! + return result; + } + + private static RayTraceResult update(EntityPlayer player, RayTraceResult grapplingTarget){ + + if(grapplingTarget != null && (!WizardryUtilities.isCasting(player, Spells.grapple) + || (grapplingTarget.entityHit != null && !grapplingTarget.entityHit.isEntityAlive()))){ + return null; + } + + return grapplingTarget; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/GreaterHeal.java b/src/main/java/electroblob/wizardry/spell/GreaterHeal.java index 5664cb3b..9d7055fc 100644 --- a/src/main/java/electroblob/wizardry/spell/GreaterHeal.java +++ b/src/main/java/electroblob/wizardry/spell/GreaterHeal.java @@ -1,68 +1,25 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; -public class GreaterHeal extends Spell { +public class GreaterHeal extends SpellBuff { public GreaterHeal(){ - super(Tier.ADVANCED, 15, Element.HEALING, "greater_heal", SpellType.DEFENCE, 40, EnumAction.BOW, false); + super("greater_heal", 1, 1, 0.3f); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(HEALTH); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(caster.shouldHeal()){ - caster.heal((int)(8 * modifiers.get(SpellModifiers.DAMAGE))); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double dx = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double dy = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double dz = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, dx, dy, dz, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + + if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){ + caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); return true; } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(caster.getHealth() < caster.getMaxHealth()){ - caster.heal((int)(8 * modifiers.get(SpellModifiers.DAMAGE))); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double dx = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double dy = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double dz = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, dx, dy, dz, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - return false; + + return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java b/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java new file mode 100644 index 00000000..3929d8d2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java @@ -0,0 +1,159 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.EntityLevitatingBlock; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityTNTPrimed; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.EnumAction; +import net.minecraft.network.play.server.SPacketEntityVelocity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; + +public class GreaterTelekinesis extends SpellRay { + + public static final String HOLD_RANGE = "hold_range"; + public static final String THROW_VELOCITY = "throw_velocity"; + + /** Makes things a bit smoother-looking / 'realistic'. */ + private static final float UNDERSHOOT = 0.2f; + + public GreaterTelekinesis(){ + super("greater_telekinesis", true, EnumAction.NONE); + this.aimAssist(0.4f); + this.particleSpacing(1); + this.particleJitter(0.05); + this.particleVelocity(0.3); + addProperties(HOLD_RANGE, THROW_VELOCITY, DAMAGE); + this.soundValues(0.8f, 1, 0.2f); + } + + @Override public boolean canBeCastByNPCs() { return false; } + @Override public boolean canBeCastByDispensers() { return false; } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + // Can't be cast by dispensers so we know caster isn't null, but just in case... + if(caster != null && (target instanceof EntityLivingBase || target instanceof EntityLevitatingBlock || target instanceof EntityTNTPrimed)){ + + if(target instanceof EntityPlayer && ((caster instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther) + || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){ + + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); + return false; + } + + if(target instanceof EntityLevitatingBlock){ + ((EntityLevitatingBlock)target).suspend(); + ((EntityLevitatingBlock)target).setCaster(caster); // Yep, you can steal other players' blocks in mid-air! + } + + Vec3d targetPos = target.getPositionVector().add(0, target.height/2, 0); + + if(caster.isSneaking()){ + + Vec3d look = caster.getLookVec().scale(getProperty(THROW_VELOCITY).floatValue() * modifiers.get(WizardryItems.range_upgrade)); + target.addVelocity(look.x, look.y, look.z); + if(caster instanceof EntityPlayer) caster.swingArm(caster.getActiveHand()); + + }else{ + + WizardryUtilities.undoGravity(target); + + // The following code extrapolates the entity's current velocity to determine whether it will pass the + // target position in the next tick, and adds or subtracts velocity accordingly. + + Vec3d vec = origin.add(caster.getLookVec().scale(getProperty(HOLD_RANGE).floatValue())); + + Vec3d velocity = vec.subtract(targetPos).subtract(target.motionX, target.motionY, target.motionZ) + .scale(1 - UNDERSHOOT); + + target.addVelocity(velocity.x, velocity.y, velocity.z); + } + + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); + } + + if(world.isRemote){ + + ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f + 0.3f * world.rand.nextFloat(), 1) + .pos(origin.subtract(caster.getPositionVector())).target(target).time(0) + .scale(MathHelper.sin(ticksInUse * 0.3f) * 0.1f + 0.9f).spawn(world); + + if(ticksInUse % 18 == 1) ParticleBuilder.create(Type.FLASH).entity(target).pos(0, target.height/2, 0) + .scale(2.5f).time(30).clr(0.2f, 0.8f, 1).fade(1f, 1f, 1f).spawn(world); + + ParticleBuilder.create(Type.SPARKLE, target).vel(0, 0.05, 0).time(15).scale(0.6f).clr(0.2f, 0.6f, 1) + .fade(1f, 1f, 1f).spawn(world); + } + + return true; + } + + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.canDamageBlocks(caster, world) && !WizardryUtilities.isBlockUnbreakable(world, pos) + && world.getBlockState(pos).getMaterial().isSolid() + && (world.getTileEntity(pos) == null || !world.getTileEntity(pos).getTileData().hasUniqueId(ArcaneLock.NBT_KEY))){ + + if(!world.isRemote){ + + EntityLevitatingBlock block = new EntityLevitatingBlock(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + world.getBlockState(pos)); + + block.fallTime = 1; + block.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + block.setCaster(caster); + + world.spawnEntity(block); + world.setBlockToAir(pos); + } + + return true; + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/GroupHeal.java b/src/main/java/electroblob/wizardry/spell/GroupHeal.java index 2324a906..cc2677e0 100644 --- a/src/main/java/electroblob/wizardry/spell/GroupHeal.java +++ b/src/main/java/electroblob/wizardry/spell/GroupHeal.java @@ -1,16 +1,9 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.ISummonedCreature; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -18,10 +11,14 @@ import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; +import java.util.List; + public class GroupHeal extends Spell { public GroupHeal(){ - super(Tier.ADVANCED, 35, Element.HEALING, "group_heal", SpellType.DEFENCE, 150, EnumAction.BOW, false); + super("group_heal", EnumAction.BOW, false); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(EFFECT_RADIUS, HEALTH); } @Override @@ -29,63 +26,20 @@ public class GroupHeal extends Spell { boolean flag = false; - List targets = WizardryUtilities.getEntitiesWithinRadius( - 5 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + List targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).floatValue() + * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); for(EntityLivingBase target : targets){ - if(target instanceof EntityPlayer){ + if(target == caster || AllyDesignationSystem.isAllied(caster, target)){ - if(WizardryUtilities.isPlayerAlly(caster, (EntityPlayer)target) || target == caster){ + if(target.getHealth() < target.getMaxHealth() && target.getHealth() > 0){ - if(((EntityPlayer)target).shouldHeal()){ + target.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - target.heal((int)(6 * modifiers.get(SpellModifiers.DAMAGE))); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)target.posX + world.rand.nextFloat() * 2 - 1.0F); - double d1 = (double)((float)WizardryUtilities.getPlayerEyesPos((EntityPlayer)target) - - 0.5F + world.rand.nextFloat()); - double d2 = (double)((float)target.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, - 0, 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - flag = true; - } - } - - // Now also works on summoned creatures - }else if(target instanceof ISummonedCreature){ - - EntityLivingBase summoner = ((ISummonedCreature)target).getCaster(); - - if(summoner == caster || (summoner instanceof EntityPlayer - && WizardryUtilities.isPlayerAlly(caster, (EntityPlayer)summoner))){ - - if(target.getHealth() < target.getMaxHealth()){ - - target.heal((int)(6 * modifiers.get(SpellModifiers.DAMAGE))); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)target.posX + world.rand.nextFloat() * 2 - 1.0F); - double d1 = (double)((float)WizardryUtilities.getPlayerEyesPos((EntityPlayer)target) - - 0.5F + world.rand.nextFloat()); - double d2 = (double)((float)target.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, - 0, 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - flag = true; - } + if(world.isRemote) ParticleBuilder.spawnHealParticles(world, target); + playSound(world, target, ticksInUse, -1, modifiers); + flag = true; } } } diff --git a/src/main/java/electroblob/wizardry/spell/GrowthAura.java b/src/main/java/electroblob/wizardry/spell/GrowthAura.java index 390c6603..929c803f 100644 --- a/src/main/java/electroblob/wizardry/spell/GrowthAura.java +++ b/src/main/java/electroblob/wizardry/spell/GrowthAura.java @@ -1,9 +1,7 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.IGrowable; @@ -15,14 +13,18 @@ import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import java.util.List; + public class GrowthAura extends Spell { public GrowthAura(){ - super(Tier.APPRENTICE, 20, Element.EARTH, "growth_aura", SpellType.UTILITY, 50, EnumAction.NONE, false); + super("growth_aura", EnumAction.NONE, false); + addProperties(EFFECT_RADIUS); + soundValues(0.7f, 1.2f, 0.2f); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @@ -31,45 +33,43 @@ public class GrowthAura extends Spell { boolean flag = false; - for(int i = -2; i < 1; i++){ + List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), + getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade)); - for(int j = -1; j < 2; j++){ + for(BlockPos pos : sphere){ - int x = (int)caster.posX + i; - int y = WizardryUtilities.getNearestFloorLevelC(world, - new BlockPos(caster.posX + i, caster.posY, caster.posZ + j), 2) - 1; - int z = (int)caster.posZ + j; + IBlockState state = world.getBlockState(pos); - BlockPos pos = new BlockPos(x, y, z); + if(state.getBlock() instanceof IGrowable){ - if(y > -1 && caster.getDistance(x, y, z) <= 2){ + IGrowable plant = (IGrowable)state.getBlock(); - IBlockState state = world.getBlockState(pos); + if(plant.canGrow(world, pos, state, world.isRemote)){ - if(state.getBlock() instanceof IGrowable){ - - IGrowable igrowable = (IGrowable)state.getBlock(); - - if(igrowable.canGrow(world, pos, state, world.isRemote)){ - - if(!world.isRemote){ - if(igrowable.canUseBonemeal(world, world.rand, pos, state)){ - igrowable.grow(world, world.rand, pos, state); + if(!world.isRemote){ + if(plant.canUseBonemeal(world, world.rand, pos, state)){ + if(world.rand.nextFloat() < 0.35f && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_growth)){ + while(plant.canGrow(world, pos, state, false)){ + plant.grow(world, world.rand, pos, state); + state = world.getBlockState(pos); // Update the state with the new one + plant = (IGrowable)state.getBlock(); // Update the block with the new one } }else{ - // Yes, it's meant to be 0, and it automatically changes it to 15. - ItemDye.spawnBonemealParticles(world, pos, 0); + plant.grow(world, world.rand, pos, state); } - - flag = true; } + }else{ + // Yes, it's meant to be 0, and it automatically changes it to 15. + ItemDye.spawnBonemealParticles(world, pos, 0); } + + flag = true; } } } - if(flag) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); + if(flag) this.playSound(world, caster, ticksInUse, -1, modifiers); + return flag; } diff --git a/src/main/java/electroblob/wizardry/spell/Hailstorm.java b/src/main/java/electroblob/wizardry/spell/Hailstorm.java index 481848ca..b68d2ecd 100644 --- a/src/main/java/electroblob/wizardry/spell/Hailstorm.java +++ b/src/main/java/electroblob/wizardry/spell/Hailstorm.java @@ -1,61 +1,38 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityHailstorm; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; import net.minecraft.world.World; -public class Hailstorm extends Spell { +public class Hailstorm extends SpellConstructRanged { public Hailstorm(){ - super(Tier.MASTER, 75, Element.ICE, "hailstorm", SpellType.ATTACK, 300, EnumAction.NONE, false); + super("hailstorm", EntityHailstorm::new, false); + this.floor(true); } @Override - public boolean doesSpellRequirePacket(){ - return false; + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + + // Moves the entity back towards the caster a bit, so the area of effect is better centred on the position. + // 3 is the distance to move the entity back towards the caster. + double dx = caster.posX - x; + double dz = caster.posZ - z; + double distRatio = 3 / Math.sqrt(dx * dx + dz * dz); + x += dx * distRatio; + z += dz * distRatio; + // Moves the entity up 5 blocks so that it is above mobs' heads. + y += 5; + + return super.spawnConstruct(world, x, y, z, side, caster, modifiers); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(20 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - // Moves the entity back towards the caster a bit, so the area of effect is better centred on the - // position. - // 3.0d is the distance to move the entity back towards the caster. - double dx = caster.posX - x; - double dz = caster.posZ - z; - double distRatio = 3.0d / Math.sqrt(dx * dx + dz * dz); - x += dx * distRatio; - z += dz * distRatio; - - EntityHailstorm hailstorm = new EntityHailstorm(world, x, y + 5, z, caster, - (int)(120 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - hailstorm.rotationYaw = caster.rotationYawHead; - world.spawnEntity(hailstorm); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - return false; + protected void addConstructExtras(EntityHailstorm construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + // Makes the arrows shoot in the direction the caster was looking when they cast the spell. + if(caster != null) construct.rotationYaw = caster.rotationYawHead; } } diff --git a/src/main/java/electroblob/wizardry/spell/Heal.java b/src/main/java/electroblob/wizardry/spell/Heal.java index f3bdad94..8bc201da 100644 --- a/src/main/java/electroblob/wizardry/spell/Heal.java +++ b/src/main/java/electroblob/wizardry/spell/Heal.java @@ -1,72 +1,24 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; -public class Heal extends Spell { +public class Heal extends SpellBuff { public Heal(){ - super(Tier.BASIC, 5, Element.HEALING, "heal", SpellType.DEFENCE, 20, EnumAction.BOW, false); + super("heal", 1, 1, 0.3f); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(HEALTH); } - + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(caster.shouldHeal()){ - - caster.heal((int)(4 * modifiers.get(SpellModifiers.DAMAGE))); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - // Apparently the client side spawns the particles 1 block higher than it should... hence the - - // 0.5F. - double d1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double d2 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + + if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){ + caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); return true; } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(caster.getHealth() < caster.getMaxHealth()){ - caster.heal((int)(4 * modifiers.get(SpellModifiers.DAMAGE))); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double dx = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double dy = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double dz = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, dx, dy, dz, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - + return false; } diff --git a/src/main/java/electroblob/wizardry/spell/HealAlly.java b/src/main/java/electroblob/wizardry/spell/HealAlly.java index 9f824bfb..ffcbf274 100644 --- a/src/main/java/electroblob/wizardry/spell/HealAlly.java +++ b/src/main/java/electroblob/wizardry/spell/HealAlly.java @@ -1,56 +1,52 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class HealAlly extends Spell { +public class HealAlly extends SpellRay { public HealAlly(){ - super(Tier.APPRENTICE, 10, Element.HEALING, "heal_ally", SpellType.DEFENCE, 20, EnumAction.NONE, false); + super("heal_ally", false, EnumAction.NONE); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(HEALTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + EntityLivingBase entity = (EntityLivingBase)target; + + if(entity.getHealth() < entity.getMaxHealth() && entity.getHealth() > 0){ + + entity.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade), 8.0f); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - if(target.getHealth() < target.getMaxHealth()){ - target.heal((int)(5 * modifiers.get(SpellModifiers.DAMAGE))); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double d0 = (double)((float)target.posX + world.rand.nextFloat() * 2 - 1.0F); - // Apparently the client side spawns the particles 1 block higher than it should... hence the - - // 0.5F. - double d1 = (double)((float)target.getEntityBoundingBox().minY + target.height - 0.5f - + world.rand.nextFloat()); - double d2 = (double)((float)target.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, d0, d1, d2, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 1.0f, 0.3f); - } - } - - caster.swingArm(hand); - target.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; + if(world.isRemote) ParticleBuilder.spawnHealParticles(world, entity); + playSound(world, entity, ticksInUse, -1, modifiers); } + + return true; } + + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/HealingAura.java b/src/main/java/electroblob/wizardry/spell/HealingAura.java deleted file mode 100644 index d3a9844e..00000000 --- a/src/main/java/electroblob/wizardry/spell/HealingAura.java +++ /dev/null @@ -1,68 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityHealAura; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class HealingAura extends Spell { - - public HealingAura(){ - super(Tier.ADVANCED, 35, Element.HEALING, "healing_aura", SpellType.DEFENCE, 150, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(caster.onGround){ - if(!world.isRemote){ - EntityHealAura healaura = new EntityHealAura(world, caster.posX, caster.posY, caster.posZ, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(healaura); - } - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(caster.onGround - && world.getEntitiesWithinAABB(EntityHealAura.class, caster.getEntityBoundingBox()).isEmpty()){ - if(!world.isRemote){ - EntityHealAura healaura = new EntityHealAura(world, caster.posX, caster.posY, caster.posZ, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(healaura); - } - return true; - } - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/HomingSpark.java b/src/main/java/electroblob/wizardry/spell/HomingSpark.java deleted file mode 100644 index 074658e2..00000000 --- a/src/main/java/electroblob/wizardry/spell/HomingSpark.java +++ /dev/null @@ -1,65 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntitySpark; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class HomingSpark extends Spell { - - public HomingSpark(){ - super(Tier.APPRENTICE, 10, Element.LIGHTNING, "homing_spark", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntitySpark spark = new EntitySpark(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(spark); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - } - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntitySpark spark = new EntitySpark(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - spark.directTowards(target, 0.5f); - world.spawnEntity(spark); - caster.playSound(WizardrySounds.SPELL_CONJURATION, 1.0F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - } - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/IceAge.java b/src/main/java/electroblob/wizardry/spell/IceAge.java index bec9a12b..8c94d203 100644 --- a/src/main/java/electroblob/wizardry/spell/IceAge.java +++ b/src/main/java/electroblob/wizardry/spell/IceAge.java @@ -1,163 +1,89 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.tileentity.TileEntityStatue; +import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.monster.EntityBlaze; -import net.minecraft.entity.monster.EntityMagmaCube; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import java.util.List; + public class IceAge extends Spell { - private static final int baseDuration = 1200; - public IceAge(){ - super(Tier.MASTER, 70, Element.ICE, "ice_age", SpellType.ATTACK, 250, EnumAction.BOW, false); + super("ice_age", EnumAction.BOW, false); + this.soundValues(0.7f, 1.0f, 0); + addProperties(EFFECT_RADIUS, EFFECT_DURATION); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - List targets = WizardryUtilities.getEntitiesWithinRadius( - 7 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + float radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + List targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world); for(EntityLivingBase target : targets){ - if(WizardryUtilities.isValidTarget(caster, target)){ + if(AllyDesignationSystem.isValidTarget(caster, target)){ if(!world.isRemote){ - if(target instanceof EntityBlaze || target instanceof EntityMagmaCube){ - // These have been removed for the time being because they cause the entity to sink into the - // floor when it breaks out. - // target.attackEntityFrom(WizardryUtilities.causePlayerMagicDamage(entityplayer), 8.0f * - // modifiers.get(SpellModifiers.DAMAGE)); - }else{ - // target.attackEntityFrom(WizardryUtilities.causePlayerMagicDamage(entityplayer), 4.0f * - // modifiers.get(SpellModifiers.DAMAGE)); - } - if(target.isBurning()){ - target.extinguish(); - } - - if(target instanceof EntityBlaze) WizardryAdvancementTriggers.freeze_blaze.triggerFor(caster); if(target instanceof EntityLiving){ - - // Stops the entity looking red while frozen and the resulting z-fighting - target.hurtTime = 0; - - BlockPos pos = new BlockPos(target); - - // Short mobs such as spiders and pigs - if((target.height < 1.2 || target.isChild()) - && WizardryUtilities.canBlockBeReplaced(world, pos)){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1, - 1); - ((TileEntityStatue)world.getTileEntity(pos)).setLifetime( - (int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - } - // Normal sized mobs like zombies and skeletons - else if(target.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1, - 2); - ((TileEntityStatue)world.getTileEntity(pos)).setLifetime( - (int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())) - .setCreatureAndPart((EntityLiving)target, 2, 2); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - } - // Tall mobs like endermen - else if(WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up()) - && WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1, - 3); - ((TileEntityStatue)world.getTileEntity(pos)).setLifetime( - (int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())) - .setCreatureAndPart((EntityLiving)target, 2, 3); - } - - world.setBlockState(pos.up(2), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up(2))) - .setCreatureAndPart((EntityLiving)target, 3, 3); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); + if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ + target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } } } } } - if(!world.isRemote){ - for(int i = -7; i < 8; i++){ - for(int j = -7; j < 8; j++){ + if(!world.isRemote && WizardryUtilities.canDamageBlocks(caster, world)){ + for(int i = -(int)radius; i < (int)radius + 1; i++){ + for(int j = -(int)radius; j < (int)radius + 1; j++){ BlockPos pos = new BlockPos(caster).add(i, 0, j); - int y = WizardryUtilities.getNearestFloorLevelB(world, new BlockPos(pos), 7); + Integer y = WizardryUtilities.getNearestSurface(world, new BlockPos(pos), EnumFacing.UP, (int)radius, true, WizardryUtilities.SurfaceCriteria.BUILDABLE); - pos = new BlockPos(pos.getX(), y, pos.getZ()); + if(y != null){ - double dist = caster.getDistance((int)caster.posX + i, y, (int)caster.posZ + j); + pos = new BlockPos(pos.getX(), y, pos.getZ()); - // Randomised with weighting so that the nearer the block the more likely it is to be snowed. - if(y != -1 && world.rand.nextInt((int)dist * 2 + 1) < 7 && dist < 8){ - if(world.getBlockState(pos.down()) == Blocks.WATER.getDefaultState()){ - world.setBlockState(pos.down(), Blocks.ICE.getDefaultState()); - }else if(world.getBlockState(pos.down()) == Blocks.LAVA.getDefaultState()){ - world.setBlockState(pos.down(), Blocks.OBSIDIAN.getDefaultState()); - }else if(world.getBlockState(pos.down()) == Blocks.FLOWING_LAVA.getDefaultState()){ - world.setBlockState(pos.down(), Blocks.COBBLESTONE.getDefaultState()); - }else if(Blocks.SNOW_LAYER.canPlaceBlockAt(world, pos)){ - world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState()); + double dist = caster.getDistance((int)caster.posX + i, y, (int)caster.posZ + j); + + // Randomised with weighting so that the nearer the block the more likely it is to be snowed. + if(y != -1 && world.rand.nextInt((int)dist * 2 + 1) < radius && dist < radius){ + if(world.getBlockState(pos.down()) == Blocks.WATER.getDefaultState()){ + world.setBlockState(pos.down(), Blocks.ICE.getDefaultState()); + }else if(world.getBlockState(pos.down()) == Blocks.LAVA.getDefaultState()){ + world.setBlockState(pos.down(), Blocks.OBSIDIAN.getDefaultState()); + }else if(world.getBlockState(pos.down()) == Blocks.FLOWING_LAVA.getDefaultState()){ + world.setBlockState(pos.down(), Blocks.COBBLESTONE.getDefaultState()); + }else if(Blocks.SNOW_LAYER.canPlaceBlockAt(world, pos)){ + world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState()); + } } } } } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.7F, 1.0f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_WIND, 1.0F, 1.0f); + + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/IceCharge.java b/src/main/java/electroblob/wizardry/spell/IceCharge.java deleted file mode 100644 index 231d78b7..00000000 --- a/src/main/java/electroblob/wizardry/spell/IceCharge.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityIceCharge; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class IceCharge extends Spell { - - public IceCharge(){ - super(Tier.ADVANCED, 20, Element.ICE, "ice_charge", SpellType.ATTACK, 30, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityIceCharge icecharge = new EntityIceCharge(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(icecharge); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityIceCharge icecharge = new EntityIceCharge(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - icecharge.directTowards(target, 1.5f); - world.spawnEntity(icecharge); - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/IceLance.java b/src/main/java/electroblob/wizardry/spell/IceLance.java deleted file mode 100644 index 88c36745..00000000 --- a/src/main/java/electroblob/wizardry/spell/IceLance.java +++ /dev/null @@ -1,67 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityIceLance; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class IceLance extends Spell { - - public IceLance(){ - super(Tier.ADVANCED, 20, Element.ICE, "ice_lance", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityIceLance iceLance = new EntityIceLance(world, caster, 2 * modifiers.get(WizardryItems.range_upgrade), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(iceLance); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 0.8F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityIceLance iceLance = new EntityIceLance(world, caster, target, - 2 * modifiers.get(WizardryItems.range_upgrade), 4, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(iceLance); - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/IceShard.java b/src/main/java/electroblob/wizardry/spell/IceShard.java deleted file mode 100644 index 79c4f1b7..00000000 --- a/src/main/java/electroblob/wizardry/spell/IceShard.java +++ /dev/null @@ -1,67 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityIceShard; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class IceShard extends Spell { - - public IceShard(){ - super(Tier.APPRENTICE, 10, Element.ICE, "ice_shard", SpellType.ATTACK, 10, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityIceShard iceShard = new EntityIceShard(world, caster, 2 * modifiers.get(WizardryItems.range_upgrade), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(iceShard); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityIceShard iceShard = new EntityIceShard(world, caster, target, - 2 * modifiers.get(WizardryItems.range_upgrade), 4, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(iceShard); - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/IceShroud.java b/src/main/java/electroblob/wizardry/spell/IceShroud.java deleted file mode 100644 index 88a933d9..00000000 --- a/src/main/java/electroblob/wizardry/spell/IceShroud.java +++ /dev/null @@ -1,71 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class IceShroud extends Spell { - - public IceShroud(){ - super(Tier.ADVANCED, 40, Element.ICE, "ice_shroud", SpellType.DEFENCE, 250, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.ice_shroud)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.ice_shroud, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.ice_shroud)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.ice_shroud, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/IceSpikes.java b/src/main/java/electroblob/wizardry/spell/IceSpikes.java index c00590b2..5f7f7d26 100644 --- a/src/main/java/electroblob/wizardry/spell/IceSpikes.java +++ b/src/main/java/electroblob/wizardry/spell/IceSpikes.java @@ -1,114 +1,73 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityIceSpike; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class IceSpikes extends Spell { +public class IceSpikes extends SpellConstructRanged { + + public static final String ICE_SPIKE_COUNT = "ice_spike_count"; public IceSpikes(){ - super(Tier.ADVANCED, 30, Element.ICE, "ice_spikes", SpellType.ATTACK, 75, EnumAction.NONE, false); + super("ice_spikes", EntityIceSpike::new, true); + addProperties(EFFECT_RADIUS, ICE_SPIKE_COUNT, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + this.ignoreUncollidables(true); } - + @Override - public boolean doesSpellRequirePacket(){ - return false; - } + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + BlockPos blockHit = new BlockPos(x, y, z); + if(side != null && side.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE) blockHit = blockHit.offset(side); - RayTraceResult rayTrace = WizardryUtilities.rayTrace(20 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); + if(world.getBlockState(blockHit).isNormalCube()) return false; - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && rayTrace.sideHit == EnumFacing.UP){ + Vec3d origin = new Vec3d(x, y, z); - if(!world.isRemote){ + Vec3d pos = origin.add(new Vec3d(side.getOpposite().getDirectionVec())); + + // Now always spawns a spike exactly at the position aimed at + super.spawnConstruct(world, pos.x, pos.y, pos.z, side, caster, modifiers); + // -1 because of the one spawned above + int quantity = (int)(getProperty(ICE_SPIKE_COUNT).floatValue() * modifiers.get(WizardryItems.blast_upgrade)) - 1; - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; + float maxRadius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); - for(int i = 0; i < (int)(18 * modifiers.get(WizardryItems.blast_upgrade)); i++){ + for(int i=0; i -1){ - EntityIceSpike icespike = new EntityIceSpike(world, x1, y1, z1, caster, - 30 + world.rand.nextInt(15), modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(icespike); - } - } + if(side.getAxis().isHorizontal()) offset = offset.rotateYaw((float)Math.PI/2); + + Integer surface = WizardryUtilities.getNearestSurface(world, new BlockPos(origin.add(offset)), side, + (int)maxRadius, true, WizardryUtilities.SurfaceCriteria.basedOn(World::isBlockFullCube)); + + if(surface != null){ + Vec3d vec = WizardryUtilities.replaceComponent(origin.add(offset), side.getAxis(), surface) + .subtract(new Vec3d(side.getDirectionVec())); + super.spawnConstruct(world, vec.x, vec.y, vec.z, side, caster, modifiers); } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - - double x = target.posX; - double y = target.posY; - double z = target.posZ; - - for(int i = 0; i < (int)(18 * modifiers.get(WizardryItems.blast_upgrade)); i++){ - - float angle = (float)(world.rand.nextFloat() * Math.PI * 2); - double radius = 0.5 + world.rand.nextDouble() * 2 * modifiers.get(WizardryItems.blast_upgrade); - - double x1 = x + radius * MathHelper.sin(angle); - double z1 = z + radius * MathHelper.cos(angle); - double y1 = WizardryUtilities.getNearestFloorLevel(world, - new BlockPos(MathHelper.floor(x1), (int)y, MathHelper.floor(z1)), 2) - 1; - - if(y1 > -1){ - EntityIceSpike icespike = new EntityIceSpike(world, x1, y1, z1, caster, - 30 + world.rand.nextInt(15), modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(icespike); - } - } - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ + return true; } + + @Override + protected void addConstructExtras(EntityIceSpike construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + // In this particular case, lifetime is implemented as a delay instead so is treated differently. + construct.lifetime = 30 + construct.world.rand.nextInt(15); + construct.setFacing(side); + } } diff --git a/src/main/java/electroblob/wizardry/spell/IceStatue.java b/src/main/java/electroblob/wizardry/spell/IceStatue.java index 59938921..35665491 100644 --- a/src/main/java/electroblob/wizardry/spell/IceStatue.java +++ b/src/main/java/electroblob/wizardry/spell/IceStatue.java @@ -1,131 +1,65 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.tileentity.TileEntityStatue; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.monster.EntityBlaze; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class IceStatue extends Spell { - - private static final int baseDuration = 400; +public class IceStatue extends SpellRay { public IceStatue(){ - super(Tier.APPRENTICE, 15, Element.ICE, "ice_statue", SpellType.ATTACK, 40, EnumAction.NONE, false); + super("ice_statue", false, EnumAction.NONE); + this.soundValues(1, 1.4f, 0.4f); + addProperties(EFFECT_DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected SoundEvent[] createSounds(){ + return createSoundsWithSuffixes("shoot", "freeze"); + } - Vec3d look = caster.getLookVec(); + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(target instanceof EntityLiving && !world.isRemote){ + // Unchecked cast is fine because the block is a static final field + if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY - && rayTrace.entityHit instanceof EntityLiving && !world.isRemote){ - - EntityLiving target = (EntityLiving)rayTrace.entityHit; - - BlockPos pos = new BlockPos(target); - - if(target.isBurning()){ - target.extinguish(); - } - - // Stops the entity looking red while frozen and the resulting z-fighting - target.hurtTime = 0; - - if(target instanceof EntityBlaze) WizardryAdvancementTriggers.freeze_blaze.triggerFor(caster); - - // Short mobs such as spiders and pigs - if((target.height < 1.2 || target.isChild()) && WizardryUtilities.canBlockBeReplaced(world, pos)){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 1); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - } - // Normal sized mobs like zombies and skeletons - else if(target.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 2); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 2, 2); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - } - // Tall mobs like endermen and iron golems - else if(WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up()) - && WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 3); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 2, 3); - } - - world.setBlockState(pos.up(2), WizardryBlocks.ice_statue.getDefaultState()); - if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(target, 3, 3); - } - target.setDead(); - target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); + //target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - float brightness = 0.5f + (world.rand.nextFloat() / 2); - - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), brightness, brightness + 0.1f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SNOW, world, x1, y1, z1, 0.0d, -0.02d, 0.0d, - 20 + world.rand.nextInt(10)); - - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.2F); + return true; } + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + float brightness = 0.5f + world.rand.nextFloat() * 0.5f; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)) + .clr(brightness, brightness + 0.1f, 1.0f).spawn(world); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).time(20 + world.rand.nextInt(10)).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Ignite.java b/src/main/java/electroblob/wizardry/spell/Ignite.java index bb5f8705..b686d89d 100644 --- a/src/main/java/electroblob/wizardry/spell/Ignite.java +++ b/src/main/java/electroblob/wizardry/spell/Ignite.java @@ -1,174 +1,75 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Ignite extends Spell { - +public class Ignite extends SpellRay { + public Ignite(){ - super(Tier.BASIC, 5, Element.FIRE, "ignite", SpellType.ATTACK, 10, EnumAction.NONE, false); + super("ignite", false, EnumAction.NONE); + this.soundValues(1, 1, 0.4f); + addProperties(BURN_DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // Entity ray trace is done first because block ray trace passes through entities; if it was the other - // way round, entities would only be hit when there were no blocks in range behind them. - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - // Fire can damage armour stands - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && rayTrace.entityHit instanceof EntityLivingBase){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + // Fire can damage armour stands, so this includes them + if(target instanceof EntityLivingBase) { + if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); }else{ - target.setFire((int)(10 * modifiers.get(WizardryItems.duration_upgrade))); + target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); } - - if(world.isRemote){ - double dx = target.posX - caster.posX; - double dy = (target.getEntityBoundingBox().minY + target.height / 2) - - WizardryUtilities.getPlayerEyesPos(caster); - double dz = target.posZ - caster.posZ; - // i starts at 1 so that particles are not spawned in the player's head. - for(int i = 1; i < 5; i++){ - // WizardryUtilities.spawnParticleAndNotify(world, EnumParticleTypes.FLAME, caster.posX + (i*(dx/5)) - // + world.rand.nextFloat()/5, caster.posY + (i*(dy/5)) + world.rand.nextFloat()/5, caster.posZ + - // (i*(dz/5)) + world.rand.nextFloat()/5, 0, 0, 0, 0, 0, 0, 0); - // WizardryUtilities.spawnParticleAndNotify(world, EnumParticleTypes.FLAME, caster.posX + (i*(dx/5)) - // + world.rand.nextFloat()/5, caster.posY + (i*(dy/5)) + world.rand.nextFloat()/5, caster.posZ + - // (i*(dz/5)) + world.rand.nextFloat()/5, 0, 0, 0, 0, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ITEM_FLINTANDSTEEL_USE, 1.0F, - world.rand.nextFloat() * 0.4F + 0.8F); - + return true; - - }else{ - - rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, caster, - false); - - // Gets block the player is looking at and sets the appropriate surrounding air block to fire. - // Note how the block is set on the server side only (kinda obvious really) but the particles are - // spawned on the client side only. - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); - - if(world.isAirBlock(pos)){ - if(!world.isRemote){ - world.setBlockState(pos, Blocks.FIRE.getDefaultState()); - } - - if(world.isRemote){ - - double dx = pos.getX() + 0.5 - caster.posX; - double dy = pos.getY() + 0.5 - WizardryUtilities.getPlayerEyesPos(caster); - double dz = pos.getZ() + 0.5 - caster.posZ; - - for(int i = 1; i < 5; i++){ - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ITEM_FLINTANDSTEEL_USE, 1.0F, - world.rand.nextFloat() * 0.4F + 0.8F); - return true; - } - } } + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(target != null){ + if(!WizardryUtilities.canDamageBlocks(caster, world)) return false; - if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) - target.setFire((int)(10 * modifiers.get(WizardryItems.duration_upgrade))); - - if(world.isRemote){ - double dx = target.posX - caster.posX; - double dy = (target.getEntityBoundingBox().minY + target.height / 2) - - (caster.posY + caster.getEyeHeight()); - double dz = target.posZ - caster.posZ; - // i starts at 1 so that particles are not spawned in the player's head. - for(int i = 1; i < 5; i++){ - // WizardryUtilities.spawnParticleAndNotify(world, EnumParticleTypes.FLAME, caster.posX + (i*(dx/5)) - // + world.rand.nextFloat()/5, caster.posY + (i*(dy/5)) + world.rand.nextFloat()/5, caster.posZ + - // (i*(dz/5)) + world.rand.nextFloat()/5, 0, 0, 0, 0, 0, 0, 0); - // WizardryUtilities.spawnParticleAndNotify(world, EnumParticleTypes.FLAME, caster.posX + (i*(dx/5)) - // + world.rand.nextFloat()/5, caster.posY + (i*(dy/5)) + world.rand.nextFloat()/5, caster.posZ + - // (i*(dz/5)) + world.rand.nextFloat()/5, 0, 0, 0, 0, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - world.spawnParticle(EnumParticleTypes.FLAME, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - caster.posY + caster.getEyeHeight() + (i * (dy / 5)) + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, 0, 0); - } + pos = pos.offset(side); + + if(world.isAirBlock(pos)){ + + if(!world.isRemote){ + world.setBlockState(pos, Blocks.FIRE.getDefaultState()); } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ITEM_FLINTANDSTEEL_USE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); - + return true; } - + return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); } } diff --git a/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java b/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java index 88e8b5b5..a41cd34b 100644 --- a/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java @@ -1,30 +1,27 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemBow; -import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemSword; +import net.minecraft.item.*; import net.minecraft.util.EnumHand; import net.minecraft.world.World; +import java.util.Arrays; + public class ImbueWeapon extends Spell { public ImbueWeapon(){ - super(Tier.APPRENTICE, 20, Element.SORCERY, "imbue_weapon", SpellType.UTILITY, 50, EnumAction.BOW, false); + super("imbue_weapon", EnumAction.BOW, false); + addProperties(EFFECT_DURATION); } @Override @@ -35,50 +32,58 @@ public class ImbueWeapon extends Spell { for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ - if(stack.getItem() instanceof ItemSword + if(isSword(stack.getItem()) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.magic_sword) && WizardData.get(caster).getImbuementDuration(WizardryEnchantments.magic_sword) <= 0){ // The enchantment level as determined by the damage multiplier. The + 0.5f is so that // weird float processing doesn't incorrectly round it down. - stack.addEnchantment(WizardryEnchantments.magic_sword, modifiers.get(SpellModifiers.DAMAGE) == 1.0f + stack.addEnchantment(WizardryEnchantments.magic_sword, modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1 - : (int)((modifiers.get(SpellModifiers.DAMAGE) - 1.0f) / Constants.DAMAGE_INCREASE_PER_TIER + : (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); WizardData.get(caster).setImbuementDuration(WizardryEnchantments.magic_sword, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade))); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); - }else if(stack.getItem() instanceof ItemBow + }else if(isBow(stack.getItem()) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.magic_bow) && WizardData.get(caster).getImbuementDuration(WizardryEnchantments.magic_bow) <= 0){ // The enchantment level as determined by the damage multiplier. The + 0.5f is so that // weird float processing doesn't incorrectly round it down. - stack.addEnchantment(WizardryEnchantments.magic_bow, modifiers.get(SpellModifiers.DAMAGE) == 1.0f + stack.addEnchantment(WizardryEnchantments.magic_bow, modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1 - : (int)((modifiers.get(SpellModifiers.DAMAGE) - 1.0f) / Constants.DAMAGE_INCREASE_PER_TIER + : (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); WizardData.get(caster).setImbuementDuration(WizardryEnchantments.magic_bow, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade))); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); }else{ continue; } if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.9f, 0.7f, 1.0f); + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.9f, 0.7f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } } return false; } + /** Returns true if the given item counts as a sword, i.e. it extends {@link ItemSword} or is in the whitelist. */ + public static boolean isSword(Item item){ + return item instanceof ItemSword || Arrays.asList(Wizardry.settings.swordItemWhitelist).contains(item.getRegistryName()); + } + + /** Returns true if the given item counts as a bow, i.e. it extends {@link ItemBow} or is in the whitelist. */ + public static boolean isBow(Item item){ + return item instanceof ItemBow || Arrays.asList(Wizardry.settings.bowItemWhitelist).contains(item.getRegistryName()); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Intimidate.java b/src/main/java/electroblob/wizardry/spell/Intimidate.java index ba78e956..6c9889ea 100644 --- a/src/main/java/electroblob/wizardry/spell/Intimidate.java +++ b/src/main/java/electroblob/wizardry/spell/Intimidate.java @@ -1,23 +1,16 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityCreature; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.RandomPositionGenerator; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.pathfinding.PathPoint; @@ -29,14 +22,22 @@ import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.List; + @Mod.EventBusSubscriber public class Intimidate extends Spell { /** The NBT tag name for storing the feared entity's UUID in the target's tag compound. */ public static final String NBT_KEY = "fearedEntity"; + // These aren't spell properties because they're part fo the actual potion effect, not the spell itself. + // However, the avoid distance can be modified using the potion amplifier. + private static final double BASE_AVOID_DISTANCE = 16; + private static final double AVOID_DISTANCE_PER_LEVEL = 4; + public Intimidate(){ - super(Tier.APPRENTICE, 20, Element.NECROMANCY, "intimidate", SpellType.ATTACK, 100, EnumAction.BOW, false); + super("intimidate", EnumAction.BOW, false); + addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); } @Override @@ -45,30 +46,32 @@ public class Intimidate extends Spell { if(!world.isRemote){ List entities = WizardryUtilities.getEntitiesWithinRadius( - 8 * modifiers.get(WizardryItems.range_upgrade), caster.posX, caster.posY, caster.posZ, world, - EntityCreature.class); + getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.range_upgrade), + caster.posX, caster.posY, caster.posZ, world, EntityCreature.class); for(EntityCreature target : entities){ + // Why do we need this here? + //runAway(target, caster); - runAway(target, caster); + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); NBTTagCompound entityNBT = target.getEntityData(); if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - ((EntityLiving)target).addPotionEffect(new PotionEffect(WizardryPotions.fear, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - + target.addPotionEffect(new PotionEffect(WizardryPotions.fear, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier)); } }else{ for(int i = 0; i < 30; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - caster.posX - 1 + world.rand.nextDouble() * 2, - caster.getEntityBoundingBox().minY + 1.5 + world.rand.nextDouble() * 0.5, - caster.posZ - 1 + world.rand.nextDouble() * 2, 0, 0, 0, 0, 0.9f, 0.1f, 0.0f); + double x = caster.posX - 1 + world.rand.nextDouble() * 2; + double y = caster.getEntityBoundingBox().minY + 1.5 + world.rand.nextDouble() * 0.5; + double z = caster.posZ - 1 + world.rand.nextDouble() * 2; + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.9f, 0.1f, 0).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_GROWL, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } @@ -78,13 +81,14 @@ public class Intimidate extends Spell { * * @param target The entity running away * @param caster The entity that is being run away from + * @param distance How far the entity will run from the caster * @return True if a new path was found and set, false if not. */ - public static boolean runAway(EntityCreature target, EntityLivingBase caster){ + public static boolean runAway(EntityCreature target, EntityLivingBase caster, double distance){ - if(target.getDistance(caster) < 16){ + if(target.getDistance(caster) < distance){ - Vec3d Vec3d = RandomPositionGenerator.findRandomTargetBlockAwayFrom(target, 16, 7, + Vec3d Vec3d = RandomPositionGenerator.findRandomTargetBlockAwayFrom(target, (int)distance, (int)(distance/2), new Vec3d(caster.posX, caster.posY, caster.posZ)); if(Vec3d == null){ @@ -92,15 +96,14 @@ public class Intimidate extends Spell { }else{ // In both cases it is necessary to check if the entity already has a path so it doesn't change - // direction - // every tick, unless that path is towards the caster. + // direction every tick, unless that path is towards the caster. // Path path = target.getNavigator().getPathToXYZ(Vec3d.xCoord, Vec3d.yCoord, Vec3d.zCoord); boolean flag = true; if(!target.getNavigator().noPath()){ PathPoint point = target.getNavigator().getPath().getFinalPathPoint(); - if(point != null) flag = caster.getDistance(point.x, point.y, point.z) < 16; + if(point != null) flag = caster.getDistance(point.x, point.y, point.z) < distance; } // Has a built in mind trick effect because for whatever reason this makes it work with skeletons. target.setAttackTarget(null); @@ -126,7 +129,9 @@ public class Intimidate extends Spell { Entity caster = WizardryUtilities.getEntityByUUID(creature.world, entityNBT.getUniqueId(NBT_KEY)); if(caster instanceof EntityLivingBase){ - runAway(creature, (EntityLivingBase)caster); + double distance = BASE_AVOID_DISTANCE + AVOID_DISTANCE_PER_LEVEL + * event.getEntityLiving().getActivePotionEffect(WizardryPotions.fear).getAmplifier(); + runAway(creature, (EntityLivingBase)caster, distance); } } } diff --git a/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java b/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java index 71cf0216..c6c473b8 100644 --- a/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java +++ b/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java @@ -1,58 +1,64 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class InvigoratingPresence extends Spell { public InvigoratingPresence(){ - super(Tier.APPRENTICE, 30, Element.HEALING, "invigorating_presence", SpellType.UTILITY, 60, EnumAction.BOW, - false); + super("invigorating_presence", EnumAction.BOW, false); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ List targets = WizardryUtilities.getEntitiesWithinRadius( - 5 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world, - EntityPlayer.class); + getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade), + caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class); for(EntityPlayer target : targets){ - if(WizardryUtilities.isPlayerAlly(caster, target) || target == caster){ - // Strength 2 for 45 seconds. + if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){ + + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); + target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH, - (int)(900 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier)); } } if(world.isRemote){ + for(int i = 0; i < 50 * modifiers.get(WizardryItems.blast_upgrade); i++){ + double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + radius * Math.cos(angle), caster.getEntityBoundingBox().minY, - caster.posZ + radius * Math.sin(angle), 0, 0.03, 0, 50, 1, 0.2f, 0.2f); + float angle = world.rand.nextFloat() * (float)Math.PI * 2;; + + double x = caster.posX + radius * MathHelper.cos(angle); + double y = caster.getEntityBoundingBox().minY; + double z = caster.posZ + radius * MathHelper.sin(angle); + + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(1, 0.2f, 0.2f).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Invisibility.java b/src/main/java/electroblob/wizardry/spell/Invisibility.java deleted file mode 100644 index ed70f40c..00000000 --- a/src/main/java/electroblob/wizardry/spell/Invisibility.java +++ /dev/null @@ -1,78 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Invisibility extends Spell { - - public Invisibility(){ - super(Tier.ADVANCED, 35, Element.SORCERY, "invisibility", SpellType.UTILITY, 200, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 1.0f, 1.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!caster.isPotionActive(MobEffects.INVISIBILITY)){ - - caster.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.7f, 1.0f, 1.0f); - } - } - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/InvokeWeather.java b/src/main/java/electroblob/wizardry/spell/InvokeWeather.java index edf5f0d6..e62718ac 100644 --- a/src/main/java/electroblob/wizardry/spell/InvokeWeather.java +++ b/src/main/java/electroblob/wizardry/spell/InvokeWeather.java @@ -1,25 +1,26 @@ package electroblob.wizardry.spell; -import java.util.Random; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; +import java.util.Random; + public class InvokeWeather extends Spell { + public static final String THUNDERSTORM_CHANCE = "thunderstorm_chance"; + public InvokeWeather(){ - super(Tier.ADVANCED, 30, Element.LIGHTNING, "invoke_weather", SpellType.UTILITY, 100, EnumAction.BOW, false); + super("invoke_weather", EnumAction.BOW, false); + addProperties(THUNDERSTORM_CHANCE); + soundValues(0.5f, 1, 0); } @Override @@ -28,40 +29,41 @@ public class InvokeWeather extends Spell { if(caster.dimension == 0){ if(!world.isRemote){ - // TODO: Backport these changes. + int standardWeatherTime = (300 + (new Random()).nextInt(600)) * 20; + if(world.isRaining()){ - caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".sun")); + caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".sun"), true); world.getWorldInfo().setCleanWeatherTime(standardWeatherTime); world.getWorldInfo().setRainTime(0); world.getWorldInfo().setThunderTime(0); world.getWorldInfo().setRaining(false); world.getWorldInfo().setThundering(false); }else{ - caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".rain")); + caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".rain"), true); world.getWorldInfo().setCleanWeatherTime(0); world.getWorldInfo().setRainTime(standardWeatherTime); world.getWorldInfo().setThunderTime(standardWeatherTime); world.getWorldInfo().setRaining(true); - // 1/3 chance for a thunderstorm - world.getWorldInfo().setThundering(world.rand.nextInt(3) == 0); + // Thunderstorm is guaranteed if the caster has a bottled thundercloud charm equipped + world.getWorldInfo().setThundering(ItemArtefact.isArtefactActive(caster, WizardryItems.charm_storm) + || world.rand.nextFloat() < getProperty(THUNDERSTORM_CHANCE).floatValue()); } } if(world.isRemote){ for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.5f, 0.7f, 1.0f); + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.5f, 0.7f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_LIGHTNING_THUNDER, 0.5F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } + return false; } diff --git a/src/main/java/electroblob/wizardry/spell/Ironflesh.java b/src/main/java/electroblob/wizardry/spell/Ironflesh.java deleted file mode 100644 index acbbff3c..00000000 --- a/src/main/java/electroblob/wizardry/spell/Ironflesh.java +++ /dev/null @@ -1,79 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Ironflesh extends Spell { - - public Ironflesh(){ - super(Tier.ADVANCED, 30, Element.HEALING, "ironflesh", SpellType.DEFENCE, 100, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 2, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.4f, 0.5f, 0.6f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!caster.isPotionActive(MobEffects.RESISTANCE)){ - - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 2, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.4f, 0.5f, 0.6f); - } - } - - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Leap.java b/src/main/java/electroblob/wizardry/spell/Leap.java index dc426c65..16b69acc 100644 --- a/src/main/java/electroblob/wizardry/spell/Leap.java +++ b/src/main/java/electroblob/wizardry/spell/Leap.java @@ -1,12 +1,7 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; @@ -14,8 +9,13 @@ import net.minecraft.world.World; public class Leap extends Spell { + public static final String HORIZONTAL_SPEED = "horizontal_speed"; + public static final String VERTICAL_SPEED = "vertical_speed"; + public Leap(){ - super(Tier.BASIC, 10, Element.EARTH, "leap", SpellType.UTILITY, 20, EnumAction.NONE, false); + super("leap", EnumAction.NONE, false); + addProperties(HORIZONTAL_SPEED, VERTICAL_SPEED); + soundValues(0.5f, 1, 0); } @Override @@ -23,19 +23,20 @@ public class Leap extends Spell { if(caster.onGround){ - caster.motionY = 0.65 * modifiers.get(SpellModifiers.DAMAGE); - caster.addVelocity(caster.getLookVec().x * 0.3, 0, caster.getLookVec().z * 0.3); + caster.motionY = getProperty(VERTICAL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); + double horizontalSpeed = getProperty(HORIZONTAL_SPEED).floatValue(); + caster.addVelocity(caster.getLookVec().x * horizontalSpeed, 0, caster.getLookVec().z * horizontalSpeed); if(world.isRemote){ for(int i = 0; i < 10; i++){ - double x = (double)(caster.posX + world.rand.nextFloat() - 0.5F); - double y = (double)(caster.getEntityBoundingBox().minY); - double z = (double)(caster.posZ + world.rand.nextFloat() - 0.5F); + double x = caster.posX + world.rand.nextFloat() - 0.5F; + double y = caster.getEntityBoundingBox().minY; + double z = caster.posZ + world.rand.nextFloat() - 0.5F; world.spawnParticle(EnumParticleTypes.CLOUD, x, y, z, 0, 0, 0); } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_FLAP, 0.5F, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); caster.swingArm(hand); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Levitation.java b/src/main/java/electroblob/wizardry/spell/Levitation.java index 03907a3a..f70e8f29 100644 --- a/src/main/java/electroblob/wizardry/spell/Levitation.java +++ b/src/main/java/electroblob/wizardry/spell/Levitation.java @@ -1,40 +1,59 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; import net.minecraft.world.World; public class Levitation extends Spell { + public static final String SPEED = "speed"; + public static final String ACCELERATION = "acceleration"; + public Levitation(){ - super(Tier.ADVANCED, 10, Element.SORCERY, "levitation", SpellType.UTILITY, 0, EnumAction.BOW, true); + super("levitation", EnumAction.BOW, true); + addProperties(SPEED, ACCELERATION); + soundValues(0.5f, 1, 0); + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - caster.fallDistance = 0; + if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0; - caster.motionY = caster.motionY < 0.5d ? caster.motionY + 0.1d : caster.motionY; + caster.motionY = caster.motionY < getProperty(SPEED).floatValue() ? caster.motionY + + getProperty(ACCELERATION).floatValue() : caster.motionY; if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX - 0.25d + world.rand.nextDouble() / 2, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5f, - caster.posZ - 0.25d + world.rand.nextDouble() / 2, 0, -0.1F, 0, 15, 0.5f, 1.0f, 0.7f); - } - if(ticksInUse % 24 == 0 && world.isRemote){ - Wizardry.proxy.playMovingSound(caster, WizardrySounds.SPELL_LOOP_SPARKLE, 0.5F, 1.0f, false); + double x = caster.posX - 0.25 + world.rand.nextDouble() * 0.5; + double y = caster.getEntityBoundingBox().minY; + double z = caster.posZ - 0.25 + world.rand.nextDouble() * 0.5; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(0.5f, 1, 0.7f).spawn(world); } + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; } diff --git a/src/main/java/electroblob/wizardry/spell/LifeDrain.java b/src/main/java/electroblob/wizardry/spell/LifeDrain.java index 0e014595..a4cf6add 100644 --- a/src/main/java/electroblob/wizardry/spell/LifeDrain.java +++ b/src/main/java/electroblob/wizardry/spell/LifeDrain.java @@ -1,119 +1,82 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class LifeDrain extends Spell { +public class LifeDrain extends SpellRay { + + public static final String HEAL_FACTOR = "heal_factor"; public LifeDrain(){ - super(Tier.APPRENTICE, 10, Element.NECROMANCY, "life_drain", SpellType.ATTACK, 0, EnumAction.NONE, true); + super("life_drain", true, EnumAction.NONE); + this.particleVelocity(-0.5); + this.particleSpacing(0.4); + addProperties(DAMAGE, HEAL_FACTOR); + this.soundValues(0.6f, 1, 0); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } - Vec3d look = caster.getLookVec(); + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ if(ticksInUse % 12 == 0){ - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 2.0f * modifiers.get(SpellModifiers.DAMAGE)); - caster.heal(1); + + float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, + DamageType.MAGIC), damage); + + if(caster != null) caster.heal(damage * getProperty(HEAL_FACTOR).floatValue()); } } - if(world.isRemote){ - for(int i = 5; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - if(i % 5 == 0){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.1f, 0.0f, 0.0f); - } - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, -0.05 * look.x * i, - -0.05 * look.y * i, -0.05 * look.z * i, 8 + world.rand.nextInt(6), 0.5f, 0.0f, 0.0f); - } - } - if(ticksInUse % 18 == 0){ - if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 1.0F, 0.6f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_CRACKLE, 2.0F, 1.0f); - } + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - Vec3d vec = new Vec3d(target.posX - caster.posX, target.posY - caster.posY, target.posZ - caster.posZ).normalize(); - - if(ticksInUse % 12 == 0){ - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), - 2.0f * modifiers.get(SpellModifiers.DAMAGE)); - caster.heal(1); - } - - if(world.isRemote){ - for(int i = 5; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + vec.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + vec.y * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + vec.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - if(i % 5 == 0){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.1f, 0.0f, 0.0f); - } - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, -0.05 * vec.x * i, - -0.05 * vec.y * i, -0.05 * vec.z * i, 8 + world.rand.nextInt(6), 0.5f, 0.0f, 0.0f); - } - } - - if(ticksInUse % 18 == 0){ - if(ticksInUse == 0) caster.playSound(WizardrySounds.SPELL_SUMMONING, 1.0F, 0.6f); - caster.playSound(WizardrySounds.SPELL_LOOP_CRACKLE, 2.0F, 1.0f); - } - - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + if(world.rand.nextInt(5) == 0) ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + // This used to multiply the velocity by the distance from the caster + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(6)) + .clr(0.5f, 0, 0).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/Light.java b/src/main/java/electroblob/wizardry/spell/Light.java index 453c2865..95571fb5 100644 --- a/src/main/java/electroblob/wizardry/spell/Light.java +++ b/src/main/java/electroblob/wizardry/spell/Light.java @@ -1,14 +1,11 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.tileentity.TileEntityTimer; +import electroblob.wizardry.util.RayTracer; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; @@ -19,18 +16,21 @@ import net.minecraft.world.World; public class Light extends Spell { public Light(){ - super(Tier.BASIC, 5, Element.SORCERY, "light", SpellType.UTILITY, 15, EnumAction.NONE, false); + super("light", EnumAction.NONE, false); + addProperties(RANGE, DURATION); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - RayTraceResult rayTrace = WizardryUtilities.rayTrace(4, world, caster, false); + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, false); if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ @@ -41,34 +41,35 @@ public class Light extends Spell { if(!world.isRemote){ world.setBlockState(pos, WizardryBlocks.magic_light.getDefaultState()); if(world.getTileEntity(pos) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos)) - .setLifetime((int)(600 * modifiers.get(WizardryItems.duration_upgrade))); + int lifetime = ItemArtefact.isArtefactActive(caster, WizardryItems.charm_light) ? -1 + : (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + ((TileEntityTimer)world.getTileEntity(pos)).setLifetime(lifetime); } } caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } }else{ - int x = (int)(Math.floor(caster.posX) + caster.getLookVec().x * 4); - int y = (int)(Math.floor(caster.posY) + caster.eyeHeight + caster.getLookVec().y * 4); - int z = (int)(Math.floor(caster.posZ) + caster.getLookVec().z * 4); + int x = (int)(Math.floor(caster.posX) + caster.getLookVec().x * range); + int y = (int)(Math.floor(caster.posY) + caster.eyeHeight + caster.getLookVec().y * range); + int z = (int)(Math.floor(caster.posZ) + caster.getLookVec().z * range); BlockPos pos = new BlockPos(x, y, z); if(world.isAirBlock(pos)){ - // world.playSound(x, y, z, "sound.ambient.cave.cave", 1.0f, 1.5f, false); if(!world.isRemote){ world.setBlockState(pos, WizardryBlocks.magic_light.getDefaultState()); if(world.getTileEntity(pos) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos)) - .setLifetime((int)(600 * modifiers.get(WizardryItems.duration_upgrade))); + int lifetime = ItemArtefact.isArtefactActive(caster, WizardryItems.charm_light) ? -1 + : (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + ((TileEntityTimer)world.getTileEntity(pos)).setLifetime(lifetime); } } caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } } diff --git a/src/main/java/electroblob/wizardry/spell/LightningArrow.java b/src/main/java/electroblob/wizardry/spell/LightningArrow.java deleted file mode 100644 index 0940bc3b..00000000 --- a/src/main/java/electroblob/wizardry/spell/LightningArrow.java +++ /dev/null @@ -1,69 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityLightningArrow; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class LightningArrow extends Spell { - - public LightningArrow(){ - super(Tier.APPRENTICE, 15, Element.LIGHTNING, "lightning_arrow", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityLightningArrow lightningArrow = new EntityLightningArrow(world, caster, - 2 * modifiers.get(WizardryItems.range_upgrade), modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(lightningArrow); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, - world.rand.nextFloat() * 0.3F + 1.3F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityLightningArrow lightningArrow = new EntityLightningArrow(world, caster, target, - 2 * modifiers.get(WizardryItems.range_upgrade), 4, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(lightningArrow); - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_LIGHTNING, 1.0F, world.rand.nextFloat() * 0.3F + 1.3F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/LightningBolt.java b/src/main/java/electroblob/wizardry/spell/LightningBolt.java index 85567859..461cb126 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningBolt.java +++ b/src/main/java/electroblob/wizardry/spell/LightningBolt.java @@ -1,121 +1,86 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.effect.EntityLightningBolt; -import net.minecraft.entity.monster.EntityCreeper; -import net.minecraft.entity.passive.EntityPig; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -import net.minecraftforge.event.entity.EntityStruckByLightningEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -@Mod.EventBusSubscriber -public class LightningBolt extends Spell { +//@Mod.EventBusSubscriber +public class LightningBolt extends SpellRay { /** The NBT key used to store the UUID of the player that summoned the lightning bolt. Used for achievements. */ public static final String NBT_KEY = "summoningPlayer"; public LightningBolt(){ - super(Tier.ADVANCED, 40, Element.LIGHTNING, "lightning_bolt", SpellType.ATTACK, 80, EnumAction.NONE, false); + super("lightning_bolt", false, EnumAction.NONE); + this.ignoreLivingEntities(true); } + @Override public boolean requiresPacket(){ return false; } + @Override - public boolean doesSpellRequirePacket(){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(world.canBlockSeeSky(pos.up())){ - RayTraceResult rayTrace = WizardryUtilities.rayTrace(200, world, caster, false); + if(!world.isRemote){ + // Temporarily disable the fire tick gamerule if player block damage is disabled + // Bit of a hack but it works fine! + boolean doFireTick = world.getGameRules().getBoolean("doFireTick"); + if(doFireTick && !Wizardry.settings.playerBlockDamage) world.getGameRules().setOrCreateGameRule("doFireTick", "false"); + EntityLightningBolt entitylightning = new EntityLightningBolt(world, pos.getX(), pos.getY(), + pos.getZ(), false); + world.addWeatherEffect(entitylightning); + // Reset doFireTick to true if it was true before + if(doFireTick && !Wizardry.settings.playerBlockDamage) world.getGameRules().setOrCreateGameRule("doFireTick", "true"); - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - // Not sure why it is up 1 but it has to be for canBlockSeeSky to work properly. - // TODO: Remove this requirement? - if(world.canBlockSeeSky(pos.up())){ - - if(!world.isRemote){ - EntityLightningBolt entitylightning = new EntityLightningBolt(world, pos.getX(), pos.getY(), - pos.getZ(), false); - world.addWeatherEffect(entitylightning); - - // Code for eventhandler recognition; for achievements and such like. Left in for future use. + // Code for eventhandler recognition for achievements + if(caster instanceof EntityPlayer){ NBTTagCompound entityNBT = entitylightning.getEntityData(); entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); } - - caster.swingArm(hand); - return true; } - } + return true; + } + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - int x = (int)target.posX; - int y = (int)target.posY; - int z = (int)target.posZ; - - // Not sure why it is up 1 but it has to be for canBlockSeeSky to work properly. - // TODO: Remove this requirement? - if(world.canBlockSeeSky(new BlockPos(x, y, z))){ - - if(!world.isRemote){ - EntityLightningBolt entitylightning = new EntityLightningBolt(world, x, y, z, false); - world.addWeatherEffect(entitylightning); - } - - caster.swingArm(hand); - return true; - } - } - + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } - @Override - public boolean canBeCastByNPCs(){ - return true; - } - - @SubscribeEvent - public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){ - - if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasUniqueId(NBT_KEY)){ - - EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().world, - event.getLightning().getEntityData().getUniqueId("summoningPlayer")); - - if(event.getEntity() instanceof EntityCreeper){ - WizardryAdvancementTriggers.charge_creeper.triggerFor(player); - } - - if(event.getEntity() instanceof EntityPig){ - WizardryAdvancementTriggers.frankenstein.triggerFor(player); - } - } - - } +// @SubscribeEvent +// public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){ +// +// if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasUniqueId(NBT_KEY)){ +// +// EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().world, +// event.getLightning().getEntityData().getUniqueId("summoningPlayer")); +// +// if(event.getEntity() instanceof EntityCreeper){ +// WizardryAdvancementTriggers.charge_creeper.triggerFor(player); +// } +// +// if(event.getEntity() instanceof EntityPig){ +// WizardryAdvancementTriggers.frankenstein.triggerFor(player); +// } +// } +// } + } diff --git a/src/main/java/electroblob/wizardry/spell/LightningDisc.java b/src/main/java/electroblob/wizardry/spell/LightningDisc.java deleted file mode 100644 index ef01f4f3..00000000 --- a/src/main/java/electroblob/wizardry/spell/LightningDisc.java +++ /dev/null @@ -1,69 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityLightningDisc; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class LightningDisc extends Spell { - - public LightningDisc(){ - super(Tier.ADVANCED, 25, Element.LIGHTNING, "lightning_disc", SpellType.ATTACK, 60, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityLightningDisc lightningdisc = new EntityLightningDisc(world, caster, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(lightningdisc); - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, - world.rand.nextFloat() * 0.3F + 0.8F); - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityLightningDisc lightningdisc = new EntityLightningDisc(world, caster, - modifiers.get(SpellModifiers.DAMAGE)); - lightningdisc.directTowards(target, 1.2f); - world.spawnEntity(lightningdisc); - } - - caster.playSound(WizardrySounds.SPELL_LIGHTNING, 1.0F, world.rand.nextFloat() * 0.3F + 0.8F); - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/LightningHammer.java b/src/main/java/electroblob/wizardry/spell/LightningHammer.java index 6bf29e29..faa1354b 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningHammer.java +++ b/src/main/java/electroblob/wizardry/spell/LightningHammer.java @@ -1,63 +1,34 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityHammer; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; -public class LightningHammer extends Spell { +public class LightningHammer extends SpellConstructRanged { + + public static final String ATTACK_INTERVAL = "attack_interval"; + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; public LightningHammer(){ - super(Tier.MASTER, 100, Element.LIGHTNING, "lightning_hammer", SpellType.ATTACK, 300, EnumAction.BOW, false); + super("lightning_hammer", EntityHammer::new, false); + this.soundValues(3, 1, 0); + this.floor(true); + this.overlap(true); + addProperties(EFFECT_RADIUS, SECONDARY_MAX_TARGETS, ATTACK_INTERVAL, DIRECT_DAMAGE, SPLASH_DAMAGE); } @Override - public boolean doesSpellRequirePacket(){ - return false; + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + if(!world.canBlockSeeSky(new BlockPos(x, y, z))) return false; + return super.spawnConstruct(world, x, y + 50, z, side, caster, modifiers); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(40 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - // Not sure why it is +1 but it has to be to work properly. - if(world.canBlockSeeSky(pos.up())){ - - if(!world.isRemote){ - - EntityHammer hammer = new EntityHammer(world, pos.getX() + 0.5, pos.getY() + 50, pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - - hammer.motionX = 0; - hammer.motionY = -2; - hammer.motionZ = 0; - - world.spawnEntity(hammer); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 3.0f, 1.0f); - return true; - } - } - return false; + protected void addConstructExtras(EntityHammer construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + construct.motionY = -2; } } diff --git a/src/main/java/electroblob/wizardry/spell/LightningPulse.java b/src/main/java/electroblob/wizardry/spell/LightningPulse.java index 85fb7b49..42b8037b 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningPulse.java +++ b/src/main/java/electroblob/wizardry/spell/LightningPulse.java @@ -1,35 +1,36 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityLightningPulse; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.EnumAction; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class LightningPulse extends Spell { + public static final String REPULSION_VELOCITY = "repulsion_velocity"; + public LightningPulse(){ - super(Tier.ADVANCED, 25, Element.LIGHTNING, "lightning_pulse", SpellType.ATTACK, 75, EnumAction.NONE, false); + super("lightning_pulse", EnumAction.NONE, false); + addProperties(EFFECT_RADIUS, DAMAGE, REPULSION_VELOCITY); + this.soundValues(2, 1, 0); } + + // TODO: NPC casting support @Override - public boolean doesSpellRequirePacket(){ - return false; + protected SoundEvent[] createSounds(){ + return createSoundsWithSuffixes("spark", "explosion"); } @Override @@ -38,13 +39,14 @@ public class LightningPulse extends Spell { if(caster.onGround){ List targets = WizardryUtilities.getEntitiesWithinRadius( - 3.0d * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade), + caster.posX, caster.posY, caster.posZ, world); for(EntityLivingBase target : targets){ - if(WizardryUtilities.isValidTarget(caster, target)){ - // Damage is 4 hearts no matter where the target is. + if(AllyDesignationSystem.isValidTarget(caster, target)){ + // Base damage is 4 hearts no matter where the target is. target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 8 * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); if(!world.isRemote){ @@ -55,9 +57,9 @@ public class LightningPulse extends Spell { dx /= vectorLength; dz /= vectorLength; - target.motionX = 0.8 * dx; + target.motionX = getProperty(REPULSION_VELOCITY).floatValue() * dx; target.motionY = 0; - target.motionZ = 0.8 * dz; + target.motionZ = getProperty(REPULSION_VELOCITY).floatValue() * dz; // Player motion is handled on that player's client so needs packets if(target instanceof EntityPlayerMP){ @@ -66,15 +68,15 @@ public class LightningPulse extends Spell { } } } - if(!world.isRemote){ - EntityLightningPulse lightningpulse = new EntityLightningPulse(world, caster.posX, - caster.getEntityBoundingBox().minY, caster.posZ, caster, 7, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(lightningpulse); + + if(world.isRemote){ + ParticleBuilder.create(Type.LIGHTNING_PULSE).pos(caster.posX, caster.getEntityBoundingBox().minY + + WizardryUtilities.ANTI_Z_FIGHTING_OFFSET, caster.posZ) + .scale(modifiers.get(WizardryItems.blast_upgrade)).spawn(world); } + caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0f, 1.0f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 2.0f, 1.0f); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } return false; diff --git a/src/main/java/electroblob/wizardry/spell/LightningRay.java b/src/main/java/electroblob/wizardry/spell/LightningRay.java index ced7a07c..21bb5c0b 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningRay.java +++ b/src/main/java/electroblob/wizardry/spell/LightningRay.java @@ -1,158 +1,98 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.EntityArc; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class LightningRay extends Spell { +public class LightningRay extends SpellRay { public LightningRay(){ - super(Tier.APPRENTICE, 5, Element.LIGHTNING, "lightning_ray", SpellType.ATTACK, 0, EnumAction.NONE, true); + super("lightning_ray", true, EnumAction.NONE); + this.aimAssist(0.6f); + addProperties(DAMAGE); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade), 2.0f); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - Entity target = rayTrace.entityHit; - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - - EntityArc arc = new EntityArc(world); - // The look vec stuff performs a translation on the start point to line it up with the wand. - // EDIT: removed due to 1st/3rd person render differences. - arc.setEndpointCoords(caster.posX, caster.posY + 1.2, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - - arc.lifetime = 1; - - world.spawnEntity(arc); - } - - if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ - if(!world.isRemote && ticksInUse == 1) - caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - }else{ - WizardryUtilities.attackEntityWithoutKnockback(target, - MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); - } - - }else{ - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - } - } - - if(ticksInUse == 1){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, 1.0f); - }else if(ticksInUse > 0 && ticksInUse % 20 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1.0F, 1.0f); - } - - return true; - - }else{ - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - - EntityArc arc = new EntityArc(world); - - arc.setEndpointCoords(caster.posX, caster.posY + 1.2, caster.posZ, - caster.posX + caster.getLookVec().x * 8, - caster.posY + caster.eyeHeight + caster.getLookVec().y * 8, - caster.posZ + caster.getLookVec().z * 8); - - arc.lifetime = 1; - - world.spawnEntity(arc); - } - } - - if(ticksInUse == 1){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, 1.0f); - }else if(ticksInUse > 0 && ticksInUse % 20 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1.0F, 1.0f); - } - - return true; - } + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } - if(target != null){ - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } - EntityArc arc = new EntityArc(world); - // The look vec stuff performs a translation on the start point to line it up with the wand. - // EDIT: removed due to 1st/3rd person render differences. - arc.setEndpointCoords(caster.posX, caster.posY + 1.2, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - arc.lifetime = 1; - - world.spawnEntity(arc); - } + if(WizardryUtilities.isLiving(target)){ + if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) + ((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + // This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle + // with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry. + }else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){ WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + } + + if(world.isRemote){ - }else{ - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); + if(ticksInUse % 3 == 0) ParticleBuilder.create(Type.LIGHTNING).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + + // Particle effect + for(int i=0; i<5; i++){ + ParticleBuilder.create(Type.SPARK, target).spawn(world); } } - - if(ticksInUse == 1){ - caster.playSound(WizardrySounds.SPELL_LIGHTNING, 1.0F, 1.0f); - }else if(ticksInUse > 0 && ticksInUse % 20 == 0){ - caster.playSound(WizardrySounds.SPELL_LOOP_LIGHTNING, 1.0F, 1.0f); - } - - return true; } + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + // This is a nice example of when onMiss is used for more than just returning a boolean + if(world.isRemote && ticksInUse % 4 == 0){ + + // The arc does not reach full range when it has a free end + double freeRange = 0.8 * getRange(world, origin, direction, caster, ticksInUse, modifiers); + + if(caster != null){ + ParticleBuilder.create(Type.LIGHTNING).entity(caster).pos(origin.subtract(caster.getPositionVector())) + .length(freeRange).spawn(world); + }else{ + ParticleBuilder.create(Type.LIGHTNING).pos(origin).target(origin.add(direction.scale(freeRange))).spawn(world); + } + } + return true; } diff --git a/src/main/java/electroblob/wizardry/spell/LightningSigil.java b/src/main/java/electroblob/wizardry/spell/LightningSigil.java deleted file mode 100644 index 14044079..00000000 --- a/src/main/java/electroblob/wizardry/spell/LightningSigil.java +++ /dev/null @@ -1,53 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityLightningSigil; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.world.World; - -public class LightningSigil extends Spell { - - public LightningSigil(){ - super(Tier.APPRENTICE, 10, Element.LIGHTNING, "lightning_sigil", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && rayTrace.sideHit == EnumFacing.UP){ - - if(!world.isRemote){ - double x = rayTrace.hitVec.x; - double y = rayTrace.hitVec.y; - double z = rayTrace.hitVec.z; - EntityLightningSigil lightningsigil = new EntityLightningSigil(world, x, y, z, caster, - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(lightningsigil); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0F, 0.3F); - return true; - } - return false; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/LightningWeb.java b/src/main/java/electroblob/wizardry/spell/LightningWeb.java index 3a86a31f..759ea581 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningWeb.java +++ b/src/main/java/electroblob/wizardry/spell/LightningWeb.java @@ -1,220 +1,162 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.EntityArc; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class LightningWeb extends Spell { +import java.util.List; + +public class LightningWeb extends SpellRay { + + public static final String PRIMARY_DAMAGE = "primary_damage"; + public static final String SECONDARY_DAMAGE = "secondary_damage"; + public static final String TERTIARY_DAMAGE = "tertiary_damage"; + + public static final String SECONDARY_RANGE = "secondary_range"; + public static final String TERTIARY_RANGE = "tertiary_range"; + + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; + public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target public LightningWeb(){ - super(Tier.MASTER, 15, Element.LIGHTNING, "lightning_web", SpellType.ATTACK, 0, EnumAction.NONE, true); + super("lightning_web", true, EnumAction.NONE); + this.aimAssist(0.6f); + addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE, + SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade), 2.0f); + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - Entity target = rayTrace.entityHit; - - if(!world.isRemote){ - - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - EntityArc arc = new EntityArc(world); - // The look vec stuff performs a translation on the start point to line it up with the wand. - // EDIT: removed due to 1st/3rd person render differences. - arc.setEndpointCoords(caster.posX, caster.posY + 1.2, caster.posZ, target.posX, - target.posY + target.height / 2, target.posZ); - arc.lifetime = 1; - world.spawnEntity(arc); - } - - if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ - if(!world.isRemote && ticksInUse == 1) - caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - }else{ - // This motion stuff removes knockback, which is desirable for continuous spells. - double motionX = target.motionX; - double motionY = target.motionY; - double motionZ = target.motionZ; - - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 5.0f * modifiers.get(SpellModifiers.DAMAGE)); - - target.motionX = motionX; - target.motionY = motionY; - target.motionZ = motionZ; - } - }else{ - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - target.posX + world.rand.nextFloat() - 0.5, - target.getEntityBoundingBox().minY + target.height / 2 + world.rand.nextFloat() * 2 - 1, - target.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - } - } + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue() + * modifiers.get(SpellModifiers.POTENCY), ticksInUse); + // Secondary chaining effect - double seekerRange = 5.0d; - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, - target.posX, target.posY + target.height / 2, target.posZ, world); - // This is a MUCH better way of filtering the secondary targets! + List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(SECONDARY_RANGE).floatValue(), target.posX, target.posY + target.height / 2, + target.posZ, world); + secondaryTargets.remove(target); - if(secondaryTargets.size() > 5) secondaryTargets = secondaryTargets.subList(0, 5); + secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + secondaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e)); + if(secondaryTargets.size() > getProperty(SECONDARY_MAX_TARGETS).intValue()) + secondaryTargets = secondaryTargets.subList(0, getProperty(SECONDARY_MAX_TARGETS).intValue()); for(EntityLivingBase secondaryTarget : secondaryTargets){ - if(WizardryUtilities.isValidTarget(caster, secondaryTarget)){ + electrocute(world, caster, target.getPositionVector().add(0, target.height/2, 0), secondaryTarget, + getProperty(SECONDARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY), ticksInUse); - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(target.posX, target.posY + 1.2, target.posZ, secondaryTarget.posX, - secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ); - arc.lifetime = 1; - world.spawnEntity(arc); - } + // Tertiary chaining effect - if(MagicDamage.isEntityImmune(DamageType.SHOCK, secondaryTarget)){ - if(!world.isRemote && ticksInUse == 1) - caster.sendMessage(new TextComponentTranslation("spell.resist", - secondaryTarget.getName(), this.getNameForTranslationFormatted())); - }else{ - // This motion stuff removes knockback, which is desirable for continuous spells. - double motionX = secondaryTarget.motionX; - double motionY = secondaryTarget.motionY; - double motionZ = secondaryTarget.motionZ; + List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(TERTIARY_RANGE).floatValue(), secondaryTarget.posX, + secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world); + + tertiaryTargets.remove(target); + tertiaryTargets.removeAll(secondaryTargets); + tertiaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + tertiaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e)); + if(tertiaryTargets.size() > getProperty(TERTIARY_MAX_TARGETS).intValue()) + tertiaryTargets = tertiaryTargets.subList(0, getProperty(TERTIARY_MAX_TARGETS).intValue()); - secondaryTarget.attackEntityFrom( - MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 4.0f * modifiers.get(SpellModifiers.DAMAGE)); - - secondaryTarget.motionX = motionX; - secondaryTarget.motionY = motionY; - secondaryTarget.motionZ = motionZ; - } - }else{ - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - } - } - - // Tertiary chaining effect - - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, - secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, - secondaryTarget.posZ, world); - tertiaryTargets.remove(target); - tertiaryTargets.removeAll(secondaryTargets); - if(tertiaryTargets.size() > 2) tertiaryTargets = tertiaryTargets.subList(0, 2); - - for(EntityLivingBase tertiaryTarget : tertiaryTargets){ - - if(WizardryUtilities.isValidTarget(caster, tertiaryTarget)){ - - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(secondaryTarget.posX, secondaryTarget.posY + 1.2, - secondaryTarget.posZ, tertiaryTarget.posX, - tertiaryTarget.posY + tertiaryTarget.height / 2, tertiaryTarget.posZ); - arc.lifetime = 1; - world.spawnEntity(arc); - } - - if(MagicDamage.isEntityImmune(DamageType.SHOCK, tertiaryTarget)){ - if(!world.isRemote && ticksInUse == 1) - caster.sendMessage(new TextComponentTranslation("spell.resist", - tertiaryTarget.getName(), this.getNameForTranslationFormatted())); - }else{ - // This motion stuff removes knockback, which is desirable for continuous spells. - double motionX = tertiaryTarget.motionX; - double motionY = tertiaryTarget.motionY; - double motionZ = tertiaryTarget.motionZ; - - tertiaryTarget.attackEntityFrom( - MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 3.0f * modifiers.get(SpellModifiers.DAMAGE)); - - tertiaryTarget.motionX = motionX; - tertiaryTarget.motionY = motionY; - tertiaryTarget.motionZ = motionZ; - } - }else{ - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - tertiaryTarget.posX + world.rand.nextFloat() - 0.5, - tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - tertiaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - } - } - } - } + for(EntityLivingBase tertiaryTarget : tertiaryTargets){ + electrocute(world, caster, secondaryTarget.getPositionVector().add(0, secondaryTarget.height/2, 0), + tertiaryTarget, getProperty(TERTIARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY), ticksInUse); } } + } + + return true; + } - if(ticksInUse == 1){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, 1.0f); - }else if(ticksInUse > 0 && ticksInUse % 20 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1.0F, 1.0f); + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + // This is a nice example of when onMiss is used for more than just returning a boolean + if(world.isRemote){ + + // The arc does not reach full range when it has a free end + double freeRange = 0.8 * getRange(world, origin, direction, caster, ticksInUse, modifiers); + + if(caster != null){ + ParticleBuilder.create(Type.BEAM).entity(caster).pos(origin.subtract(caster.getPositionVector())) + .length(freeRange).clr(0.2f, 0.6f, 1).spawn(world); + }else{ + ParticleBuilder.create(Type.BEAM).pos(origin).target(origin.add(direction.scale(freeRange))) + .clr(0.2f, 0.6f, 1).spawn(world); } - return true; + if(ticksInUse % 4 == 0){ + if(caster != null){ + ParticleBuilder.create(Type.LIGHTNING).entity(caster).pos(origin.subtract(caster.getPositionVector())) + .length(freeRange).spawn(world); + }else{ + ParticleBuilder.create(Type.LIGHTNING).pos(origin).target(origin.add(direction.scale(freeRange))).spawn(world); + } + } + } + + return true; + } + + private void electrocute(World world, Entity caster, Vec3d origin, Entity target, float damage, int ticksInUse){ + if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) + ((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); }else{ - if(!world.isRemote){ - // This statement means the arc only spawns every other tick. - if(ticksInUse % 2 == 0){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(caster.posX, caster.posY + 1.2, caster.posZ, - caster.posX + caster.getLookVec().x * 8, - caster.posY + caster.eyeHeight + caster.getLookVec().y * 8, - caster.posZ + caster.getLookVec().z * 8); - arc.lifetime = 1; - // arc.setOffset(entityplayer.getLookVec().zCoord * 0.5, entityplayer.getLookVec().xCoord * 0.5); - world.spawnEntity(arc); - } + WizardryUtilities.attackEntityWithoutKnockback(target, + MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), damage); + } + + if(world.isRemote){ + + ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f, 1) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + + if(ticksInUse % 3 == 0){ + ParticleBuilder.create(Type.LIGHTNING).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); } - if(ticksInUse == 1){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1.0F, 1.0f); - }else if(ticksInUse > 0 && ticksInUse % 20 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1.0F, 1.0f); + // Particle effect + for(int i=0; i<5; i++){ + ParticleBuilder.create(Type.SPARK, target).spawn(world); } - - return true; } } diff --git a/src/main/java/electroblob/wizardry/spell/MagicMissile.java b/src/main/java/electroblob/wizardry/spell/MagicMissile.java deleted file mode 100644 index 1b42bc93..00000000 --- a/src/main/java/electroblob/wizardry/spell/MagicMissile.java +++ /dev/null @@ -1,71 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityMagicMissile; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class MagicMissile extends Spell { - - public MagicMissile(){ - super(Tier.BASIC, 5, Element.MAGIC, "magic_missile", SpellType.ATTACK, 10, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityMagicMissile magicMissile = new EntityMagicMissile(world, caster, - 2 * modifiers.get(WizardryItems.range_upgrade), modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(magicMissile); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_MAGIC, 1.0F, - world.rand.nextFloat() * 0.4F + 1.2F); - - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityMagicMissile magicMissile = new EntityMagicMissile(world, caster, target, - 2 * modifiers.get(WizardryItems.range_upgrade), 4, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(magicMissile); - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_MAGIC, 1.0F, world.rand.nextFloat() * 0.4F + 1.2F); - - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Metamorphosis.java b/src/main/java/electroblob/wizardry/spell/Metamorphosis.java index 12975e93..77f4fbee 100644 --- a/src/main/java/electroblob/wizardry/spell/Metamorphosis.java +++ b/src/main/java/electroblob/wizardry/spell/Metamorphosis.java @@ -2,58 +2,38 @@ package electroblob.wizardry.spell; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntitySkeletonMinion; -import electroblob.wizardry.entity.living.EntityWitherSkeletonMinion; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.util.NBTExtras; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.monster.EntityCaveSpider; -import net.minecraft.entity.monster.EntityHusk; -import net.minecraft.entity.monster.EntityMagmaCube; -import net.minecraft.entity.monster.EntityPigZombie; -import net.minecraft.entity.monster.EntitySkeleton; -import net.minecraft.entity.monster.EntitySlime; -import net.minecraft.entity.monster.EntitySpider; -import net.minecraft.entity.monster.EntityStray; -import net.minecraft.entity.monster.EntityWitherSkeleton; -import net.minecraft.entity.monster.EntityZombie; -import net.minecraft.entity.passive.EntityBat; -import net.minecraft.entity.passive.EntityChicken; -import net.minecraft.entity.passive.EntityCow; -import net.minecraft.entity.passive.EntityMooshroom; -import net.minecraft.entity.passive.EntityPig; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.monster.*; +import net.minecraft.entity.passive.*; import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Metamorphosis extends Spell { +public class Metamorphosis extends SpellRay { public static final BiMap, Class> TRANSFORMATIONS = HashBiMap.create(); - public Metamorphosis(){ - super(Tier.APPRENTICE, 15, Element.NECROMANCY, "metamorphosis", SpellType.UTILITY, 30, EnumAction.NONE, false); - + static { addTransformation(EntityPig.class, EntityPigZombie.class); addTransformation(EntityCow.class, EntityMooshroom.class); addTransformation(EntityChicken.class, EntityBat.class); addTransformation(EntityZombie.class, EntityHusk.class); - addTransformation(EntitySkeleton.class, EntityWitherSkeleton.class, EntityStray.class); + addTransformation(EntitySkeleton.class, EntityStray.class, EntityWitherSkeleton.class); addTransformation(EntitySpider.class, EntityCaveSpider.class); addTransformation(EntitySlime.class, EntityMagmaCube.class); - addTransformation(EntitySkeletonMinion.class, EntityWitherSkeletonMinion.class); + addTransformation(EntityZombieMinion.class, EntityHuskMinion.class); + addTransformation(EntitySkeletonMinion.class, EntityStrayMinion.class, EntityWitherSkeletonMinion.class); } /** Adds circular mappings between the given entity classes to the transformations map. In other words, given an @@ -66,25 +46,27 @@ public class Metamorphosis extends Spell { previousEntity = entity; } } + + public Metamorphosis(){ + super("metamorphosis", false, EnumAction.NONE); + this.soundValues(0.5f, 1f, 0); + } + + @Override public boolean canBeCastByNPCs() { return false; } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - Vec3d look = caster.getLookVec(); + if(WizardryUtilities.isLiving(target)){ - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - Entity entityHit = rayTrace.entityHit; - double xPos = entityHit.posX; - double yPos = entityHit.posY; - double zPos = entityHit.posZ; + double xPos = target.posX; + double yPos = target.posY; + double zPos = target.posZ; // Sneaking allows the entities to be cycled through in the other direction. - Class newEntityClass = caster.isSneaking() ? - TRANSFORMATIONS.inverse().get(entityHit.getClass()) : TRANSFORMATIONS.get(entityHit.getClass()); + // Dispensers always cycle through entities in the normal direction. + Class newEntityClass = caster != null && caster.isSneaking() ? + TRANSFORMATIONS.inverse().get(target.getClass()) : TRANSFORMATIONS.get(target.getClass()); if(newEntityClass == null) return false; @@ -93,7 +75,8 @@ public class Metamorphosis extends Spell { try { newEntity = newEntityClass.getConstructor(World.class).newInstance(world); } catch (Exception e){ - Wizardry.logger.error("Error while attempting to transform entity " + entityHit.getClass() + " to entity " + newEntityClass); + Wizardry.logger.error("Error while attempting to transform entity " + target.getClass() + " to entity " + + newEntityClass); e.printStackTrace(); } @@ -101,39 +84,39 @@ public class Metamorphosis extends Spell { if(!world.isRemote){ // Transfers attributes from the old entity to the new one. - newEntity.setHealth(((EntityLivingBase)entityHit).getHealth()); + newEntity.setHealth(((EntityLivingBase)target).getHealth()); NBTTagCompound tag = new NBTTagCompound(); - entityHit.writeToNBT(tag); + target.writeToNBT(tag); // Remove the UUID because keeping it the same causes the entity to disappear - WizardryUtilities.removeUniqueId(tag, "UUID"); + NBTExtras.removeUniqueId(tag, "UUID"); newEntity.readFromNBT(tag); - entityHit.setDead(); + target.setDead(); newEntity.setPosition(xPos, yPos, zPos); world.spawnEntity(newEntity); }else{ - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.2f, 0.0f, 0.1f); - } - for(int i = 0; i < 5; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, xPos, yPos, zPos, 0.0d, - 0.0d, 0.0d, 0, 0.1f, 0.0f, 0.0f); + for(int i=0; i<20; i++){ + ParticleBuilder.create(Type.DARK_MAGIC, world.rand, xPos, yPos + 1, zPos, 1, false) + .clr(0.1f, 0, 0).spawn(world); } + ParticleBuilder.create(Type.BUFF).pos(xPos, yPos, zPos).clr(0xd363cb).spawn(world); } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_DEFLECTION, 0.5F, 0.8f); + this.playSound(world, (EntityLivingBase)target, ticksInUse, -1, modifiers); return true; } + + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/Meteor.java b/src/main/java/electroblob/wizardry/spell/Meteor.java index 073d9ba6..a89f1173 100644 --- a/src/main/java/electroblob/wizardry/spell/Meteor.java +++ b/src/main/java/electroblob/wizardry/spell/Meteor.java @@ -1,55 +1,55 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.EntityMeteor; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Meteor extends Spell { +public class Meteor extends SpellRay { + + // It doesn't really make sense to have a blast radius when the explosion is measured by strength + public static final String BLAST_STRENGTH = "blast_strength"; public Meteor(){ - super(Tier.MASTER, 100, Element.FIRE, "meteor", SpellType.ATTACK, 200, EnumAction.NONE, false); + super("meteor", false, EnumAction.NONE); + this.soundValues(3, 1, 0); + this.ignoreLivingEntities(true); + addProperties(BLAST_STRENGTH); } + @Override public boolean requiresPacket(){ return false; } + @Override - public boolean doesSpellRequirePacket(){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(world.canBlockSeeSky(pos.up())){ - RayTraceResult rayTrace = WizardryUtilities.rayTrace(40 * modifiers.get(WizardryItems.range_upgrade), world, - caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - // Not sure why it is +1 but it has to be to work properly. - if(world.canBlockSeeSky(pos.up())){ - - if(!world.isRemote){ - EntityMeteor meteor = new EntityMeteor(world, pos.getX(), pos.getY() + 50, pos.getZ(), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(meteor); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 3.0f, 1.0f); - return true; + if(!world.isRemote){ + EntityMeteor meteor = new EntityMeteor(world, pos.getX(), pos.getY() + 50, pos.getZ(), + modifiers.get(WizardryItems.blast_upgrade), WizardryUtilities.canDamageBlocks(caster, world)); + world.spawnEntity(meteor); } + + return true; } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/MindControl.java b/src/main/java/electroblob/wizardry/spell/MindControl.java index fea21f2a..caa03f9d 100644 --- a/src/main/java/electroblob/wizardry/spell/MindControl.java +++ b/src/main/java/electroblob/wizardry/spell/MindControl.java @@ -1,32 +1,27 @@ package electroblob.wizardry.spell; -import java.util.Arrays; -import java.util.List; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntityEvilWizard; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityList; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.INpc; -import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.*; import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.passive.EntitySheep; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; +import net.minecraft.item.EnumDyeColor; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; @@ -34,121 +29,96 @@ import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -@Mod.EventBusSubscriber -public class MindControl extends Spell { +import java.util.Arrays; +import java.util.List; - /** - * The NBT tag name for storing the controlling entity's UUID in the target's tag compound. Defined here in case it - * changes. - */ +@Mod.EventBusSubscriber +public class MindControl extends SpellRay { + + /** The NBT tag name for storing the controlling entity's UUID in the target's tag compound. */ public static final String NBT_KEY = "controllingEntity"; public MindControl(){ - super(Tier.ADVANCED, 40, Element.NECROMANCY, "mind_control", SpellType.ATTACK, 150, EnumAction.NONE, false); + super("mind_control", false, EnumAction.NONE); + addProperties(EFFECT_DURATION); } - + + @Override public boolean canBeCastByNPCs() { return false; } + @Override public boolean canBeCastByDispensers() { return false; } + @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + if(!canControl(target)){ + if(!world.isRemote){ + if(caster instanceof EntityPlayer){ + // Adds a message saying that the player/boss entity/wizard resisted mind control + ((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + } + } - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 8 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - - if(!world.isRemote){ - if(!canControl(target)){ - // Adds a message saying that the player/boss entity/wizard resisted mind control - caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - - }else if(target instanceof EntityLiving){ + }else if(target instanceof EntityLiving){ + if(!world.isRemote){ if(!MindControl.findMindControlTarget((EntityLiving)target, caster, world)){ // If no valid target was found, this just acts like mind trick. ((EntityLiving)target).setAttackTarget(null); } - - NBTTagCompound entityNBT = target.getEntityData(); - if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - - ((EntityLiving)target).addPotionEffect(new PotionEffect(WizardryPotions.mind_control, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); } - }else{ - for(int i = 0; i < 10; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.8f, 0.2f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.2f, 0.04f, 0.25f); + + if(target instanceof EntitySheep && ((EntitySheep)target).getFleeceColor() == EnumDyeColor.BLUE + && WizardryUtilities.canDamageBlocks(caster, world)){ + if(!world.isRemote) ((EntitySheep)target).setFleeceColor(EnumDyeColor.RED); // Wololo! + world.playSound(caster.posX, caster.posY, caster.posZ, SoundEvents.EVOCATION_ILLAGER_PREPARE_WOLOLO, WizardrySounds.SPELLS, 1, 1, false); + } + + if(!world.isRemote) startControlling((EntityLiving)target, caster, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + } + + if(world.isRemote){ + + for(int i=0; i<10; i++){ + ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX, + target.getEntityBoundingBox().minY + target.getEyeHeight(), target.posZ, 0.25, false) + .clr(0.8f, 0.2f, 1.0f).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX, + target.getEntityBoundingBox().minY + target.getEyeHeight(), target.posZ, 0.25, false) + .clr(0.2f, 0.04f, 0.25f).spawn(world); } } - target.playSound(WizardrySounds.SPELL_SUMMONING, 1.0f, 1.0f); - caster.swingArm(hand); + return true; } + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(!world.isRemote){ - if(canControl(target)){ - - if(!MindControl.findMindControlTarget((EntityLiving)target, caster, world)){ - // If no valid target was found, this just acts like mind trick. - ((EntityLiving)target).setAttackTarget(null); - } - - NBTTagCompound entityNBT = target.getEntityData(); - if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - - ((EntityLiving)target).addPotionEffect(new PotionEffect(WizardryPotions.mind_control, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - }else{ - for(int i = 0; i < 10; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.8f, 0.2f, 1.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.2f, 0.04f, 0.25f); - } - } - target.playSound(WizardrySounds.SPELL_SUMMONING, 1.0f, 1.0f); - caster.swingArm(hand); - return true; - } + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; } /** Returns true if the given entity can be mind controlled (i.e. is not a player, npc, evil wizard or boss). */ - public static boolean canControl(EntityLivingBase target){ + public static boolean canControl(Entity target){ return target instanceof EntityLiving && target.isNonBoss() && !(target instanceof INpc) && !(target instanceof EntityEvilWizard) && !Arrays.asList(Wizardry.settings.mindControlTargetsBlacklist) .contains(EntityList.getKey(target.getClass())); } + public static void startControlling(EntityLiving target, EntityLivingBase controller, int duration){ + target.getEntityData().setUniqueId(NBT_KEY, controller.getUniqueID()); + target.addPotionEffect(new PotionEffect(WizardryPotions.mind_control, duration, 0)); + } + /** * Finds the nearest creature to the given target which it is allowed to attack according to the given caster and * sets it as the target's attack target. Handles both new and old AI and takes follow range into account. Defined @@ -165,7 +135,7 @@ public class MindControl extends Spell { // no longer lasts until the creature dies; instead it is a potion effect which continues to // set the target until it wears off. List possibleTargets = WizardryUtilities.getEntitiesWithinRadius( - ((EntityLiving)target).getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(), + target.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(), target.posX, target.posY, target.posZ, world); possibleTargets.remove(target); @@ -174,7 +144,7 @@ public class MindControl extends Spell { EntityLivingBase newAITarget = null; for(EntityLivingBase possibleTarget : possibleTargets){ - if(WizardryUtilities.isValidTarget(caster, possibleTarget) && (newAITarget == null + if(AllyDesignationSystem.isValidTarget(caster, possibleTarget) && (newAITarget == null || target.getDistance(possibleTarget) < target.getDistance(newAITarget))){ newAITarget = possibleTarget; } @@ -183,7 +153,7 @@ public class MindControl extends Spell { if(newAITarget != null){ // From 1.7.10 - this seems not to work quite right; the entity appears to continue attacking this target // after it gets killed (not noticeable in survival since it will target the player again immediately.) - ((EntityLiving)target).setAttackTarget(newAITarget); + target.setAttackTarget(newAITarget); return true; } @@ -206,7 +176,7 @@ public class MindControl extends Spell { if(caster instanceof EntityLivingBase){ // If the current target is already a valid mind control target, nothing happens. - if(WizardryUtilities.isValidTarget(caster, currentTarget)) return; + if(AllyDesignationSystem.isValidTarget(caster, currentTarget)) return; if(MindControl.findMindControlTarget(entity, (EntityLivingBase)caster, world)){ // If it worked, skip setting the target to null. diff --git a/src/main/java/electroblob/wizardry/spell/MindTrick.java b/src/main/java/electroblob/wizardry/spell/MindTrick.java index d2f5dae2..7aeb5cde 100644 --- a/src/main/java/electroblob/wizardry/spell/MindTrick.java +++ b/src/main/java/electroblob/wizardry/spell/MindTrick.java @@ -1,23 +1,21 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; @@ -25,90 +23,55 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber -public class MindTrick extends Spell { +public class MindTrick extends SpellRay { public MindTrick(){ - super(Tier.BASIC, 10, Element.NECROMANCY, "mind_trick", SpellType.ATTACK, 40, EnumAction.NONE, false); + super("mind_trick", false, EnumAction.NONE); + this.soundValues(0.7f, 1, 0.4f); + addProperties(EFFECT_DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 8 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ if(!world.isRemote){ if(target instanceof EntityPlayer){ - target.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.NAUSEA, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0)); }else if(target instanceof EntityLiving){ ((EntityLiving)target).setAttackTarget(null); - target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0)); } + }else{ - for(int i = 0; i < 10; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.8f, 0.2f, 1.0f); + for(int i=0; i<10; i++){ + ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX, + target.getEntityBoundingBox().minY + target.getEyeHeight(), target.posZ, 0.25, false) + .clr(0.8f, 0.2f, 1.0f).spawn(world); } } - - target.playSound(WizardrySounds.SPELL_DEFLECTION, 0.7F, world.rand.nextFloat() * 0.4F + 0.8F); - caster.swingArm(hand); + return true; } + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(!world.isRemote){ - if(target instanceof EntityPlayer){ - - target.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - - }else if(target instanceof EntityLiving){ - - ((EntityLiving)target).setAttackTarget(null); - target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, - (int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - - } - }else{ - for(int i = 0; i < 10; i++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, - target.posX - 0.25 + world.rand.nextDouble() * 0.5, - target.getEntityBoundingBox().minY + target.getEyeHeight() - 0.25 - + world.rand.nextDouble() * 0.5, - target.posZ - 0.25 + world.rand.nextDouble() * 0.5, 0, 0, 0, 0, 0.8f, 0.2f, 1.0f); - } - } - - target.playSound(WizardrySounds.SPELL_DEFLECTION, 0.7F, world.rand.nextFloat() * 0.4F + 0.8F); - caster.swingArm(hand); - return true; - } + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; } @SubscribeEvent diff --git a/src/main/java/electroblob/wizardry/spell/Mine.java b/src/main/java/electroblob/wizardry/spell/Mine.java new file mode 100644 index 00000000..30389fb9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Mine.java @@ -0,0 +1,157 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.EnumAction; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.event.ForgeEventFactory; + +import java.util.List; + +public class Mine extends SpellRay { + + public Mine(){ + super("mine", false, EnumAction.NONE); + this.ignoreLivingEntities(true); + this.particleSpacing(0.5); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + // Needs to be outside because it gets run on the client-side + if(caster instanceof EntityPlayer){ + if(caster.getHeldItemMainhand().getItem() instanceof ISpellCastingItem){ + caster.swingArm(EnumHand.MAIN_HAND); + }else if(caster.getHeldItemOffhand().getItem() instanceof ISpellCastingItem){ + caster.swingArm(EnumHand.OFF_HAND); + } + } + + if(!world.isRemote){ + + if(WizardryUtilities.isBlockUnbreakable(world, pos)) return false; + // The mine spell ignores the block damage setting for players, since that's the entire point of the spell + // Instead, it triggers block break events at the appropriate points, which protection mods should be able to + // pick up and allow/disallow accordingly + // For the time being, dispensers respect the mobGriefing gamerule + if(!(caster instanceof EntityPlayer) && !WizardryUtilities.canDamageBlocks(caster, world)) return false; + // Can't mine arcane-locked blocks + if(world.getTileEntity(pos) != null && world.getTileEntity(pos).getTileData().hasUniqueId(ArcaneLock.NBT_KEY)) return false; + + IBlockState state = world.getBlockState(pos); + // The maximum harvest level as determined by the potency multiplier. The + 0.5f is so that + // weird float processing doesn't incorrectly round it down. + int harvestLevel = (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f); + + if(harvestLevel > 0) harvestLevel--; // Shifts them all down one since normally novice wands give some potency + + // The >= 3 is to allow master earth wands to break anything. + if(state.getBlock().getHarvestLevel(state) <= harvestLevel || harvestLevel >= 3){ + + boolean flag = false; + + int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f); + // Results in the following patterns: + // 0 blast upgrades: single block + // 1 blast upgrade: 3x3 without corners or edges + // 2 blast upgrades: 3x3 with corners + // 3 blast upgrades: 5x5 without corners or edges + float radius = 0.5f + 0.73f * blastUpgradeCount; + + List sphere = WizardryUtilities.getBlockSphere(pos, radius); + + for(BlockPos pos1 : sphere){ + + if(WizardryUtilities.isBlockUnbreakable(world, pos1)) continue; + + IBlockState state1 = world.getBlockState(pos1); + + if(state1.getBlock().getHarvestLevel(state1) <= harvestLevel || harvestLevel >= 3){ + + if(caster instanceof EntityPlayerMP){ // Everything in here is server-side only so this is fine + + boolean silkTouch = state1.getBlock().canSilkHarvest(world, pos1, state1, (EntityPlayer)caster) + && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_silk_touch); + + // Some protection mods seem to use this event instead so let's trigger it to check + if(ForgeEventFactory.getBreakSpeed((EntityPlayer)caster, state1, 1, pos1) <= 0) continue; + + int xp = ForgeHooks.onBlockBreakEvent(world, + ((EntityPlayerMP)caster).interactionManager.getGameType(), (EntityPlayerMP)caster, pos1); + + if(xp == -1) continue; // Event was cancelled + + if(silkTouch){ + flag = world.destroyBlock(pos1, false); + if(flag) Block.spawnAsEntity(world, pos1, getSilkTouchDrop(state1)); + }else{ + flag = world.destroyBlock(pos1, true); + if(flag) state1.getBlock().dropXpOnBlockBreak(world, pos1, xp); + } + + }else{ + // NPCs can dig the block under the target's feet + flag = world.destroyBlock(pos1, true) || flag; + } + } + } + + return flag; + } + }else{ + return true; + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DUST).pos(x, y, z).time(20 + world.rand.nextInt(5)).clr(0.9f, 0.95f, 1) + .shaded(false).spawn(world); + } + + // Copied from Block, where (for some reason) it's protected + private static ItemStack getSilkTouchDrop(IBlockState state){ + + Item item = Item.getItemFromBlock(state.getBlock()); + int i = 0; + + if(item.getHasSubtypes()){ + i = state.getBlock().getMetaFromState(state); + } + + return new ItemStack(item, 1, i); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/None.java b/src/main/java/electroblob/wizardry/spell/None.java index 23d16860..c627002d 100644 --- a/src/main/java/electroblob/wizardry/spell/None.java +++ b/src/main/java/electroblob/wizardry/spell/None.java @@ -1,8 +1,5 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; @@ -12,16 +9,16 @@ import net.minecraft.world.World; /** * This class represents a blank spell used to fill empty slots on wands. It is unobtainable in-game, except via * commands, and does nothing when the player attempts to cast it. Its instance can be referenced directly using - * {@link electroblob.wizardry.registry.Spells#none WizardryRegistry.none} + * {@link electroblob.wizardry.registry.Spells#none Spells.none} */ public class None extends Spell { public None(){ - super(Tier.BASIC, 0, Element.MAGIC, "none", SpellType.UTILITY, 0, EnumAction.NONE, false); + super("none", EnumAction.NONE, false); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/Oakflesh.java b/src/main/java/electroblob/wizardry/spell/Oakflesh.java deleted file mode 100644 index 0f35c253..00000000 --- a/src/main/java/electroblob/wizardry/spell/Oakflesh.java +++ /dev/null @@ -1,79 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Oakflesh extends Spell { - - public Oakflesh(){ - super(Tier.APPRENTICE, 20, Element.HEALING, "oakflesh", SpellType.DEFENCE, 50, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.5f, 0.4f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!caster.isPotionActive(MobEffects.RESISTANCE)){ - - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false)); - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)caster.posY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.5f, 0.4f); - } - } - - caster.playSound(WizardrySounds.SPELL_HEAL, 0.7F, world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Paralysis.java b/src/main/java/electroblob/wizardry/spell/Paralysis.java new file mode 100644 index 00000000..795a884a --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Paralysis.java @@ -0,0 +1,123 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; +import net.minecraftforge.event.entity.living.LivingHurtEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@Mod.EventBusSubscriber +public class Paralysis extends SpellRay { + + /** Creatures with this much health or less will snap out of paralysis - but only when they take damage, so a + * creature on critical health may still be paralysed, but if it takes any damage at all the paralysis effect + * will end. */ + private static final String CRITICAL_HEALTH = "critical_health"; + + public Paralysis(){ + super("paralysis", false, EnumAction.NONE); + addProperties(DAMAGE, EFFECT_DURATION, CRITICAL_HEALTH); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + if(world.isRemote){ + // Rather neatly, the entity can be set here and if it's null nothing will happen. + ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f, 1) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + ParticleBuilder.create(Type.LIGHTNING).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world); + } + + // This is a lot neater than it was, thanks to the damage type system. + if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", + target.getName(), this.getNameForTranslationFormatted()), true); + }else{ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + } + + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.paralysis, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0)); + } + + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(world.isRemote){ + + if(world.getBlockState(pos).getMaterial().isSolid()){ + Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); + ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(0.4f, 0.8f, 1).spawn(world); + } + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + // This is first because we want the endpoint to be unaffected by the offset + Vec3d endpoint = origin.add(direction.scale(getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade))); + + if(world.isRemote){ + ParticleBuilder.create(Type.LIGHTNING).time(4).pos(origin).target(endpoint).scale(0.5f).spawn(world); + ParticleBuilder.create(Type.BEAM).clr(0.2f, 0.6f, 1).time(4).pos(origin) + .target(endpoint).spawn(world); + } + + return true; + } + + // See WizardryClientEventHandler for prevention of players' movement under the effects of paralysis + + // TODO: (Animated?) screen overlay effect for paralysed players in first-person + + @SubscribeEvent + public static void onLivingUpdateEvent(LivingUpdateEvent event){ + // Disables entities' AI when under the effects of paralysis and re-enables it on the last update of the effect + // - this can't be in the potion class because it requires access to the duration and hence the actual + // PotionEffect instance + if(event.getEntity() instanceof EntityLiving && event.getEntityLiving().isPotionActive(WizardryPotions.paralysis)){ + int timeLeft = event.getEntityLiving().getActivePotionEffect(WizardryPotions.paralysis).getDuration(); + ((EntityLiving)event.getEntity()).setNoAI(timeLeft > 1); + } + } + + @SubscribeEvent + public static void onLivingHurtEvent(LivingHurtEvent event){ + // Paralysed creatures snap out of paralysis when they take critical damage + if(event.getEntityLiving().isPotionActive(WizardryPotions.paralysis) && event.getEntityLiving().getHealth() + - event.getAmount() <= Spells.paralysis.getProperty(CRITICAL_HEALTH).floatValue()){ + event.getEntityLiving().removePotionEffect(WizardryPotions.paralysis); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Petrify.java b/src/main/java/electroblob/wizardry/spell/Petrify.java index b207fd34..de5e6d4b 100644 --- a/src/main/java/electroblob/wizardry/spell/Petrify.java +++ b/src/main/java/electroblob/wizardry/spell/Petrify.java @@ -1,126 +1,58 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.tileentity.TileEntityStatue; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Petrify extends Spell { +public class Petrify extends SpellRay { - private static final int baseDuration = 900; - - /** - * The NBT tag name for storing the petrified flag in the target's tag compound. Defined here in case it changes. - */ - public static final String NBT_KEY = "petrified"; + // This is more descriptive and more accurate than the standard "effect_duration" in this case + public static final String MINIMUM_EFFECT_DURATION = "minimum_effect_duration"; public Petrify(){ - super(Tier.ADVANCED, 40, Element.SORCERY, "petrify", SpellType.ATTACK, 100, EnumAction.NONE, false); + super("petrify", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + addProperties(MINIMUM_EFFECT_DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY - && rayTrace.entityHit instanceof EntityLiving && !world.isRemote){ - - EntityLiving target = (EntityLiving)rayTrace.entityHit; - - if(target.deathTime > 0) return false; - - BlockPos pos = new BlockPos(target); - - target.extinguish(); - - // Short mobs such as spiders and pigs - if((target.height < 1.2 || target.isChild()) && WizardryUtilities.canBlockBeReplaced(world, pos)){ - world.setBlockState(pos, WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 1); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - target.getEntityData().setBoolean(NBT_KEY, true); - target.setDead(); - } - // Normal sized mobs like zombies and skeletons - else if(target.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ - world.setBlockState(pos, WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 2); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 2, 2); - } - target.getEntityData().setBoolean(NBT_KEY, true); - target.setDead(); - } - // Tall mobs like endermen - else if(WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up()) - && WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){ - world.setBlockState(pos, WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart(target, 1, 3); - ((TileEntityStatue)world.getTileEntity(pos)) - .setLifetime((int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade))); - } - - world.setBlockState(pos.up(), WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 2, 3); - } - - world.setBlockState(pos.up(2), WizardryBlocks.petrified_stone.getDefaultState()); - if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){ - ((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(target, 3, 3); - } - target.getEntityData().setBoolean(NBT_KEY, true); - target.setDead(); + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(target instanceof EntityLiving && !world.isRemote){ + // Unchecked cast is fine because the block is a static final field + if(((BlockStatue)WizardryBlocks.petrified_stone).convertToStatue((EntityLiving)target, + (int)(getProperty(MINIMUM_EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.1f, 0.1f, 0.1f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.2f, 0.2f, 0.2f); - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); + return true; } + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.2f, 0.2f, 0.2f).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0.1f, 0.1f).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/PhaseStep.java b/src/main/java/electroblob/wizardry/spell/PhaseStep.java index c7f21d7b..18926341 100644 --- a/src/main/java/electroblob/wizardry/spell/PhaseStep.java +++ b/src/main/java/electroblob/wizardry/spell/PhaseStep.java @@ -2,83 +2,134 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.RayTracer; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class PhaseStep extends Spell { + public static final String WALL_THICKNESS = "wall_thickness"; + public PhaseStep(){ - super(Tier.ADVANCED, 35, Element.SORCERY, "phase_step", SpellType.UTILITY, 40, EnumAction.NONE, false); + super("phase_step", EnumAction.NONE, false); + addProperties(RANGE, WALL_THICKNESS); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - // Phase step does not gain range from range multiplier, instead it increases the thickness - // of the wall you can teleport through. - RayTraceResult rayTrace = WizardryUtilities.rayTrace(5, world, caster, false); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = new BlockPos(rayTrace.getBlockPos().getX(), (int)caster.posY, rayTrace.getBlockPos().getZ()); - - // The maximum wall thickness as determined by the range multiplier. The + 0.5f is so that - // weird float processing doesn't incorrectly round it down. - int maxThickness = 1 + (int)((modifiers.get(WizardryItems.range_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f); - - if(rayTrace.sideHit.getAxis().isHorizontal()){ - - // i represents how far the player needs to teleport to get through the wall - for(int i = 0; i <= maxThickness; i++){ - - BlockPos pos1 = pos.offset(rayTrace.sideHit.getOpposite(), i); - - // Prevents the player from teleporting through unbreakable blocks, so they cannot cheat in other - // mods' mazes and dungeons. - if((WizardryUtilities.isBlockUnbreakable(world, pos1) || WizardryUtilities.isBlockUnbreakable(world, pos1.up())) - && !Wizardry.settings.teleportThroughUnbreakableBlocks) - return false; - - if(!world.getBlockState(pos1).getMaterial().blocksMovement() - && !world.getBlockState(pos1.up()).getMaterial().blocksMovement()){ - - if(!world.isRemote){ - caster.setPositionAndUpdate(pos1.getX() + 0.5, caster.posY, pos1.getZ() + 0.5); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f); - return true; - } - } - } - } + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, false); // This is here because the conditions are false on the client for whatever reason. (see the Javadoc for cast() // for an explanation) if(world.isRemote){ + for(int i = 0; i < 10; i++){ double dx1 = caster.posX; - double dy1 = WizardryUtilities.getPlayerEyesPos(caster) - 1.5 + 2 * world.rand.nextFloat(); + double dy1 = caster.getEntityBoundingBox().minY + 2 * world.rand.nextFloat(); double dz1 = caster.posZ; world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5); } + + // Can't be bothered to route this through the proxies! + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); } - return false; + if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ + + BlockPos pos = rayTrace.getBlockPos(); + + // The maximum wall thickness as determined by the range multiplier. The + 0.5f is so that + // weird float processing doesn't incorrectly round it down. + int maxThickness = getProperty(WALL_THICKNESS).intValue() + + (int)((modifiers.get(WizardryItems.range_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f); + + if(rayTrace.sideHit == EnumFacing.UP) maxThickness++; // Allow space for the player's head + + // i represents how far the player needs to teleport to get through the wall + for(int i = 0; i <= maxThickness; i++){ + + BlockPos pos1 = pos.offset(rayTrace.sideHit.getOpposite(), i); + + // Prevents the player from teleporting through unbreakable blocks, so they cannot cheat in other + // mods' mazes and dungeons. + if((WizardryUtilities.isBlockUnbreakable(world, pos1) || WizardryUtilities.isBlockUnbreakable(world, pos1.up())) + && !Wizardry.settings.teleportThroughUnbreakableBlocks) + break; // Don't return false yet, there are other possible outcomes below now + + if(!world.getBlockState(pos1).getMaterial().blocksMovement() + && !world.getBlockState(pos1.up()).getMaterial().blocksMovement()){ + + // Plays before and after so it is heard from both positions + this.playSound(world, caster, ticksInUse, -1, modifiers); + + if(!world.isRemote){ + caster.setPositionAndUpdate(pos1.getX() + 0.5, pos1.getY() + 0.5, pos1.getZ() + 0.5); + } + + caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + } + + // If no suitable position was found on the other side of the wall, works like blink instead + + // Leave space for the player's head + if(rayTrace.sideHit == EnumFacing.DOWN) pos = pos.down(); + + // This means stuff like snow layers is ignored, meaning when on snow-covered ground the player does + // not teleport 1 block above the ground. + if(rayTrace.sideHit == EnumFacing.UP && !world.getBlockState(pos).getMaterial().blocksMovement()){ + pos = pos.down(); + } + + pos = pos.offset(rayTrace.sideHit); + + // Prevents the player from teleporting into blocks and suffocating + if(world.getBlockState(pos).getMaterial().blocksMovement() + || world.getBlockState(pos.up()).getMaterial().blocksMovement()){ + return false; + } + + // Plays before and after so it is heard from both positions + this.playSound(world, caster, ticksInUse, -1, modifiers); + + if(!world.isRemote) caster.setPositionAndUpdate(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + caster.swingArm(hand); + return true; + + }else{ // The ray trace missed + + Vec3d destination = caster.getPositionVector().add(caster.getLookVec().scale(range)); + BlockPos pos = new BlockPos(destination); + + // Prevents the player from teleporting into blocks and suffocating. + if(world.getBlockState(pos).getMaterial().blocksMovement() + || world.getBlockState(pos.up()).getMaterial().blocksMovement()){ + return false; + } + + if(!world.isRemote) caster.setPositionAndUpdate(destination.x, destination.y, destination.z); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + caster.swingArm(hand); + return true; + } } } diff --git a/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java b/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java index 4559ca53..bf1c9d32 100644 --- a/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java +++ b/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java @@ -1,66 +1,66 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.world.World; +import java.util.List; + public class PlagueOfDarkness extends Spell { public PlagueOfDarkness(){ - super(Tier.MASTER, 75, Element.NECROMANCY, "plague_of_darkness", SpellType.ATTACK, 200, EnumAction.BOW, false); + super("plague_of_darkness", EnumAction.BOW, false); + addProperties(EFFECT_RADIUS, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + soundValues(1, 1.1f, 0.2f); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - List targets = WizardryUtilities.getEntitiesWithinRadius( - 5.0d * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + List targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world); for(EntityLivingBase target : targets){ - if(WizardryUtilities.isValidTarget(caster, target) + if(AllyDesignationSystem.isValidTarget(caster, target) && !MagicDamage.isEntityImmune(DamageType.WITHER, target)){ target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER), - 8.0f * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); target.addPotionEffect(new PotionEffect(MobEffects.WITHER, - (int)(140 * modifiers.get(WizardryItems.duration_upgrade)), 2)); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); } } - if(world.isRemote){ - double particleX, particleZ; - for(int i = 0; i < 40 * modifiers.get(WizardryItems.blast_upgrade); i++){ - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, particleX, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5, particleZ, particleX - caster.posX, 0, - particleZ - caster.posZ, 0, 0.1f, 0.0f, 0.0f); - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, particleX, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5, particleZ, particleX - caster.posX, 0, - particleZ - caster.posZ, 30, 0.1f, 0.0f, 0.05f); - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); + if(world.isRemote){ + + double particleX, particleZ; + + for(int i = 0; i < 40 * modifiers.get(WizardryItems.blast_upgrade); i++){ + + particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); + particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); + ParticleBuilder.create(Type.DARK_MAGIC).pos(particleX, caster.getEntityBoundingBox().minY, particleZ) + .vel(particleX - caster.posX, 0, particleZ - caster.posZ).clr(0.1f, 0, 0).spawn(world); + + particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); + particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); + ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ) + .vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.1f, 0, 0.05f).spawn(world); + + particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); + particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); if(block != null){ @@ -68,10 +68,16 @@ public class PlagueOfDarkness extends Spell { particleZ, particleX - caster.posX, 0, particleZ - caster.posZ, Block.getStateId(block)); } } + + ParticleBuilder.create(Type.SPHERE) + .pos(caster.posX, caster.getEntityBoundingBox().minY + 0.1, caster.posZ) + .scale((float)radius * 0.8f) + .clr(0.8f, 0, 0.05f) + .spawn(world); } + caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_DEATH, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/PocketFurnace.java b/src/main/java/electroblob/wizardry/spell/PocketFurnace.java index 78bc3342..4b9afbc1 100644 --- a/src/main/java/electroblob/wizardry/spell/PocketFurnace.java +++ b/src/main/java/electroblob/wizardry/spell/PocketFurnace.java @@ -1,29 +1,31 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; +import net.minecraft.item.*; import net.minecraft.item.crafting.FurnaceRecipes; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.world.World; +import java.util.Arrays; + public class PocketFurnace extends Spell { + public static final String ITEMS_SMELTED = "items_smelted"; + public PocketFurnace(){ - super(Tier.APPRENTICE, 30, Element.FIRE, "pocket_furnace", SpellType.UTILITY, 40, EnumAction.BOW, false); + super("pocket_furnace", EnumAction.BOW, false); + addProperties(ITEMS_SMELTED); + soundValues(1, 0.75f, 0); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - int usesLeft = 5; + int usesLeft = (int)(getProperty(ITEMS_SMELTED).floatValue() * modifiers.get(SpellModifiers.POTENCY)); ItemStack stack, result; @@ -31,11 +33,14 @@ public class PocketFurnace extends Spell { stack = caster.inventory.getStackInSlot(i); - if(!stack.isEmpty()){ + if(!stack.isEmpty() && !world.isRemote){ result = FurnaceRecipes.instance().getSmeltingResult(stack); - if(!result.isEmpty()){ + if(!result.isEmpty() && !(result.getItem() instanceof ItemTool) && !(result.getItem() instanceof ItemSword) + && !(result.getItem() instanceof ItemArmor) + && !Arrays.asList(Wizardry.settings.pocketFurnaceItemBlacklist).contains(result.getItem().getRegistryName())){ + if(stack.getCount() <= usesLeft){ ItemStack stack2 = new ItemStack(result.getItem(), stack.getCount(), result.getItemDamage()); if(WizardryUtilities.doesPlayerHaveItem(caster, result.getItem())){ @@ -55,12 +60,12 @@ public class PocketFurnace extends Spell { } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_FURNACE_FIRE_CRACKLE, 1, 0.75f); + this.playSound(world, caster, ticksInUse, -1, modifiers); if(world.isRemote){ for(int i = 0; i < 10; i++){ double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); + double y1 = (double)((float)caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); world.spawnParticle(EnumParticleTypes.FLAME, x1, y1, z1, 0, 0.01F, 0); } diff --git a/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java b/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java index 9f047445..fb4ea0d1 100644 --- a/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java +++ b/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java @@ -2,12 +2,7 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.WizardryGuiHandler; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; @@ -16,11 +11,11 @@ import net.minecraft.world.World; public class PocketWorkbench extends Spell { public PocketWorkbench(){ - super(Tier.APPRENTICE, 30, Element.SORCERY, "pocket_workbench", SpellType.UTILITY, 40, EnumAction.BOW, false); + super("pocket_workbench", EnumAction.BOW, false); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @@ -33,7 +28,7 @@ public class PocketWorkbench extends Spell { (int)caster.posY, (int)caster.posZ); } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Poison.java b/src/main/java/electroblob/wizardry/spell/Poison.java index 630c0a3e..5cf6a5f9 100644 --- a/src/main/java/electroblob/wizardry/spell/Poison.java +++ b/src/main/java/electroblob/wizardry/spell/Poison.java @@ -1,117 +1,30 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Poison extends Spell { +public class Poison extends SpellRay { public Poison(){ - super(Tier.APPRENTICE, 10, Element.EARTH, "poison", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; - // Has no effect on undead or spiders. - if(MagicDamage.isEntityImmune(DamageType.POISON, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - }else{ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - target.addPotionEffect(new PotionEffect(MobEffects.POISON, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); - } - } - - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.3f, 0.7f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.1f, 0.4f, 0.0f); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - // Has no effect on undead or spiders. - if(!MagicDamage.isEntityImmune(DamageType.POISON, target) && !world.isRemote){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - target.addPotionEffect(new PotionEffect(MobEffects.POISON, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); - } - - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.3f, 0.7f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.1f, 0.4f, 0.0f); - } - } - - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - return false; + super("poison", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @Override @@ -119,4 +32,41 @@ public class Poison extends Spell { return true; } + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + // Has no effect on undead or spiders. + if(MagicDamage.isEntityImmune(DamageType.POISON, target)){ + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); + }else{ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.POISON, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); + } + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.3f, 0.7f, 0).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.1f, 0.4f, 0).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/PoisonBomb.java b/src/main/java/electroblob/wizardry/spell/PoisonBomb.java deleted file mode 100644 index d4e51911..00000000 --- a/src/main/java/electroblob/wizardry/spell/PoisonBomb.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityPoisonBomb; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class PoisonBomb extends Spell { - - public PoisonBomb(){ - super(Tier.APPRENTICE, 15, Element.EARTH, "poison_bomb", SpellType.ATTACK, 25, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(poisonbomb); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - poisonbomb.directTowards(target, 1.5f); - world.spawnEntity(poisonbomb); - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Possession.java b/src/main/java/electroblob/wizardry/spell/Possession.java new file mode 100644 index 00000000..a7bad811 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Possession.java @@ -0,0 +1,714 @@ +package electroblob.wizardry.spell; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.IVariable; +import electroblob.wizardry.data.IVariable.Variable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.entity.projectile.*; +import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.packet.PacketControlInput; +import electroblob.wizardry.packet.PacketPossession; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.EntityAIAttackMelee; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttribute; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.entity.monster.*; +import net.minecraft.entity.passive.EntityChicken; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.projectile.EntityPotion; +import net.minecraft.entity.projectile.EntitySnowball; +import net.minecraft.init.Enchantments; +import net.minecraft.init.Items; +import net.minecraft.init.PotionTypes; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumAction; +import net.minecraft.item.ItemBow; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.potion.PotionEffect; +import net.minecraft.potion.PotionUtils; +import net.minecraft.util.*; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants.NBT; +import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.event.entity.living.LivingDamageEvent; +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.event.world.BlockEvent; +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; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +@Mod.EventBusSubscriber +public class Possession extends SpellRay { + + /** A {@code ResourceLocation} representing the shader file used when possessing an entity. */ + public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/possession.json"); + + /** The NBT tag name for storing the possessing entity's UUID in the target's tag compound. */ + public static final String NBT_KEY = "possessor"; + /** The NBT tag name for storing the possessor's previous inventory in their tag compound. */ + public static final String INVENTORY_NBT_KEY = "prevInventory"; + + /** The health (in half-hearts) below or equal to which the possessor will automatically stop possessing. */ + public static final String CRITICAL_HEALTH = "critical_health"; + + private static final int PROJECTILE_COOLDOWN = 30; + + public static final IVariable TIMER_KEY = new Variable(Persistence.DIMENSION_CHANGE).withTicker(Possession::update); + public static final IVariable POSSESSEE_KEY = new Variable<>(Persistence.DIMENSION_CHANGE); + public static final IVariable SHOOT_COOLDOWN_KEY = new Variable(Persistence.DIMENSION_CHANGE).withTicker((p, n) -> Math.max(n-1, 0)); + + private static final Multimap, BiConsumer> abilities = HashMultimap.create(); + private static final Map, Function> projectiles = new HashMap<>(); + + private static final Map INHERITED_ATTRIBUTES; + + static { + + INHERITED_ATTRIBUTES = ImmutableMap.of( + SharedMonsterAttributes.MOVEMENT_SPEED, UUID.fromString("f65cfcaf-e7ec-4dfb-aa6c-711735d007e3"), + SharedMonsterAttributes.ATTACK_DAMAGE, UUID.fromString("ab67c89e-74a5-4e27-9621-40bffb4f7a03"), + SharedMonsterAttributes.KNOCKBACK_RESISTANCE, UUID.fromString("05529535-9bcf-42bb-8822-45f5ce6a8f08")); + + addAbility(EntitySpider.class, (spider, player) -> { if(player.collidedHorizontally) player.motionY = 0.2; }); + addAbility(EntityChicken.class, (chicken, player) -> { if(!player.onGround && player.motionY < 0) player.motionY *= 0.6D; }); + addAbility(EntityLiving.class, (entity, player) -> { if(!entity.isImmuneToFire() && player.isBurning()) player.extinguish(); }); + + addProjectile(EntitySnowman.class, EntitySnowball::new); // Woooo snowballs! + addProjectile(EntityBlaze.class, EntityMagicFireball::new); // Ugh normal fireballs don't fit so let's just use mine! + addProjectile(EntityGhast.class, EntityLargeMagicFireball::new); + addProjectile(EntityIceWraith.class, EntityIceShard::new); + addProjectile(EntityShadowWraith.class, EntityDarknessOrb::new); + addProjectile(EntityStormElemental.class, EntityLightningDisc::new); + addProjectile(EntityWitch.class, EntityPotion::new); + + } + + public Possession(){ + super("possession", false, EnumAction.NONE); + addProperties(EFFECT_DURATION, CRITICAL_HEALTH); + } + + @Override public boolean canBeCastByNPCs() { return false; } + @Override public boolean canBeCastByDispensers() { return false; } + + @Override + public boolean requiresPacket(){ + return false; // Has its own packet + } + + @Override + protected SoundEvent[] createSounds(){ + return createSoundsWithSuffixes("possess", "end"); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + Vec3d look = caster.getLookVec(); + Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); + + if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false; + + if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers, "possess"); // TODO: There must be a better way... + return true; + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, + SpellModifiers modifiers){ + + if(target instanceof EntityLiving && caster instanceof EntityPlayer && !isPossessing((EntityPlayer)caster)){ + + EntityPlayer player = (EntityPlayer)caster; + + if(!player.isCreative() && player.getHealth() <= getProperty(CRITICAL_HEALTH).floatValue()){ + player.sendStatusMessage(new TextComponentTranslation( + "spell." + this.getRegistryName() + ".insufficienthealth"), true); + return false; + } + + if(!world.isRemote){ + int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + if(possess(player, (EntityLiving)target, duration)){ + return true; + } + } + } + + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, + SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + // ================================================ Helper methods ================================================ + + /** + * Causes the given player to start possessing the given target for the given duration, and sets all relevant data + * for both entities accordingly. Also takes care of sending packets to update clients. + * @param possessor The player doing the possessing. + * @param target The entity being possessed. + * @param duration The number of ticks for which the possession should last. Pass in a negative integer to make the + * possession last indefinitely (until manually ended with the dismount key). + * @return True if the possession succeeded, false if for some reason it did not (only happens if the player's + * {@link WizardData} is null). + */ + public boolean possess(EntityPlayer possessor, EntityLiving target, int duration){ + + if(WizardData.get(possessor) != null){ + + WizardData.get(possessor).setVariable(POSSESSEE_KEY, target); + WizardData.get(possessor).setVariable(TIMER_KEY, duration); + + possessor.setPositionAndRotation(target.posX, target.posY, target.posZ, target.rotationYaw, target.rotationPitch); + possessor.eyeHeight = target.getEyeHeight(); + setSize(possessor, target.width, target.height); + + target.setDead(); + target.setNoAI(true); + target.setAttackTarget(null); + + // Attributes + + if(target instanceof EntityFlying || target instanceof net.minecraft.entity.passive.EntityFlying){ + possessor.capabilities.allowFlying = true; + possessor.capabilities.isFlying = true; + } + + // Apply attribute modifiers which change the player's attribute value to the target's value + // Uses predefined UUIDs so we can easily remove them later + attributes: + for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){ + + IAttributeInstance instance = target.getAttributeMap().getAttributeInstance(attribute); + + if(instance != null){ + + double targetValue = instance.getAttributeValue(); + double currentValue = possessor.getAttributeMap().getAttributeInstance(attribute).getAttributeValue(); + // Don't ask me why, but the player's base movement speed seems to be 0.1 + if(attribute == SharedMonsterAttributes.MOVEMENT_SPEED) currentValue /= possessor.capabilities.getWalkSpeed(); + + for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()){ + if(target.getItemStackFromSlot(slot).getAttributeModifiers(slot).containsKey(attribute.getName())){ + // If the mob has equipment, use the modifiers for that equipment instead of the mob's normal ones + // Not doing this results in the player being able to one-hit most mobs when possessing a zombie pigman! + continue attributes; + } + } + + possessor.getAttributeMap().getAttributeInstance(attribute).applyModifier(new AttributeModifier( + INHERITED_ATTRIBUTES.get(attribute), "possessionModifier", targetValue / currentValue, + WizardryUtilities.Operations.MULTIPLY_FLAT)); + } + } + + if(possessor.world.isRemote){ + // Shaders and effects + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice... + + }else{ + + // Targeting + + for(EntityLiving creature : WizardryUtilities.getEntitiesWithinRadius(16, possessor.posX, + possessor.posY, possessor.posZ, possessor.world, EntityLiving.class)){ + // Mobs are dumb, if a player possesses something they're like "Huh?! Where'd you go?" + // Of course, this won't last long if the player attacks them, since they'll revenge-target them + if(creature.getAttackTarget() == possessor && !creature.canAttackClass(target.getClass())) + creature.setAttackTarget(null); + } + + // Inventory and items + + if(possessor.getEntityData() != null){ + possessor.getEntityData().setTag(INVENTORY_NBT_KEY, possessor.inventory.writeToNBT(new NBTTagList())); + } + + possessor.inventory.clear(); + possessor.inventoryContainer.detectAndSendChanges(); + + ItemStack stack = target.getHeldItemMainhand().copy(); + + if(target instanceof EntityEnderman && ((EntityEnderman)target).getHeldBlockState() != null){ + stack = new ItemStack(((EntityEnderman)target).getHeldBlockState().getBlock()); + + }else if(stack.getItem() instanceof ItemBow){ + Map enchantments = EnchantmentHelper.getEnchantments(stack); + enchantments.put(Enchantments.INFINITY, 1); + EnchantmentHelper.setEnchantments(enchantments, stack); + ItemStack arrow = new ItemStack(Items.ARROW); + if(target instanceof EntityStray || target instanceof EntityStrayMinion){ + arrow = new ItemStack(Items.TIPPED_ARROW); + PotionUtils.addPotionToItemStack(arrow, PotionTypes.SLOWNESS); + } + possessor.setHeldItem(EnumHand.OFF_HAND, arrow); + } + + possessor.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, stack); + + // Packets + + WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(possessor, target, duration), possessor); + if(possessor instanceof EntityPlayerMP){ + WizardryPacketHandler.net.sendTo(new PacketPossession.Message(possessor, target, duration), (EntityPlayerMP)possessor); + } + } + + return true; + } + + return false; + } + + /** Causes the given player to stop possessing their current possessee, if any, and resets all relevant data for + * both entities. Also takes care of sending packets to update clients. */ + public void endPossession(EntityPlayer player){ + + // Reverts the possessed entity back to normal + + EntityLiving victim = getPossessee(player); + + if(victim != null){ + + victim.isDead = false; + victim.setNoAI(false); + victim.setPosition(player.posX, player.posY, player.posZ); + if(!player.world.isRemote) player.world.spawnEntity(victim); + + for(PotionEffect effect : player.getActivePotionEffects()){ + victim.addPotionEffect(effect); + } + } + + // Reverts the player back to normal + + player.clearActivePotions(); + + player.eyeHeight = player.getDefaultEyeHeight(); // How convenient! + + if(WizardData.get(player) != null){ + WizardData.get(player).setVariable(TIMER_KEY, 0); + WizardData.get(player).setVariable(POSSESSEE_KEY, null); + } + + if(!player.capabilities.isCreativeMode){ + player.capabilities.allowFlying = false; + player.capabilities.isFlying = false; + } + + if(player.world.isRemote){ + net.minecraft.client.Minecraft.getMinecraft().entityRenderer.stopUseShader(); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice... + } + + for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){ + player.getAttributeMap().getAttributeInstance(attribute).removeModifier(INHERITED_ATTRIBUTES.get(attribute)); + } + + if(player instanceof EntityPlayerMP){ + + player.inventory.clear(); + + if(player.getEntityData() != null){ + player.inventory.readFromNBT(player.getEntityData().getTagList(INVENTORY_NBT_KEY, NBT.TAG_COMPOUND)); + } + + player.inventoryContainer.detectAndSendChanges(); + } + + this.playSound(player.world, player, 0, -1, null, "end"); + + if(!player.world.isRemote && player instanceof EntityPlayerMP){ + WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(player, null, 0), player); + WizardryPacketHandler.net.sendTo(new PacketPossession.Message(player, null, 0), (EntityPlayerMP)player); + } + } + + /** Returns the {@code EntityLiving} that is currently being possessed by the given player, or null if the player is + * not currently possessing an entity. */ + @Nullable + public static EntityLiving getPossessee(EntityPlayer player){ + return WizardData.get(player) == null ? null : WizardData.get(player).getVariable(POSSESSEE_KEY); + } + + /** Returns true if the given player is currently possessing an entity, false otherwise. Just a shortcut for + * {@code Possession.getPossessee(player) != null}. */ + public static boolean isPossessing(EntityPlayer player){ + return getPossessee(player) != null; + } + + private static int update(EntityPlayer player, Integer possessionTimer){ + + if(possessionTimer == null) possessionTimer = 0; + + if(possessionTimer > 0){ + + if(isPossessing(player) && !player.isSneaking()){ + + possessionTimer--; + + if(player.world.isRemote){ + ParticleBuilder.create(Type.DARK_MAGIC, player).clr(0.1f, 0, 0.3f).spawn(player.world); + // TODO: This (and a few other similar uses) needs ClientProxy-ing. + if(!net.minecraft.client.Minecraft.getMinecraft().entityRenderer.isShaderActive()) + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + } + + }else{ + ((Possession)Spells.possession).endPossession(player); + return 0; + } + + }else if(isPossessing(player)){ + ((Possession)Spells.possession).endPossession(player); + } + + return possessionTimer; + } + + /** Adds the given {@link BiConsumer} to the list of abilities. An ability is an entity-specific action or + * effect that happens when a certain type of entity is possessed. For example, spiders can climb walls, endermen + * can pick up blocks, creepers explode, etc. Other mods may use this method to */ + public static void addAbility(Class entityType, BiConsumer ability){ + abilities.put(entityType, ability); + } + + @SuppressWarnings("unchecked") // Guess what? Type erasure again! + private static void performAbilities(EntityLiving entity, EntityPlayer player){ + // Now we have a type parameter T to work with we can ram the entity into the consumer without a compiler error + for(Class entityType : abilities.keySet()){ + if(entityType.isAssignableFrom(entity.getClass())){ + abilities.get(entityType).forEach(a -> ((BiConsumer)a).accept((T)entity, player)); + } + } + } + + /** Adds the given factory to the list of projectiles. When a player right-clicks while possessing an entity of the + * given type, the given projectile factory will be invoked to create a projectile, which is then aimed and spawned. */ + public static void addProjectile(Class entityType, Function factory){ + projectiles.put(entityType, factory); + } + + /** Copied from Entity#setSize, with the call to move(...) removed. This is presumably also better than reflecting + * into Entity#setSize, which is protected. */ + private static void setSize(Entity entity, float width, float height){ + + if(width != entity.width || height != entity.height){ + + entity.width = width; + entity.height = height; + + double halfWidth = (double)width / 2.0D; + entity.setEntityBoundingBox(new AxisAlignedBB(entity.posX - halfWidth, entity.posY, entity.posZ - halfWidth, entity.posX + halfWidth, entity.posY + (double)entity.height, entity.posZ + halfWidth)); + } + } + + // ================================================ Event Handlers ================================================ + // We got every kind of event handler goin', folks! + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.phase == TickEvent.Phase.START){ + + EntityLiving possessee = getPossessee(event.player); + + if(possessee != null){ + // Updating these to the player's variables won't have an effect on player movement, but it will + // affect various bits of mob-specific logic + possessee.setPosition(event.player.posX, event.player.posY, event.player.posZ); + possessee.motionX = event.player.motionX; + possessee.motionY = event.player.motionY; + possessee.motionZ = event.player.motionZ; + possessee.onGround = event.player.onGround; + + possessee.onUpdate(); // Event though it's not in the world, it still needs updating + possessee.ticksExisted++; // Normally gets updated from World + + if(possessee.getHealth() <= 0){ + ((Possession)Spells.possession).endPossession(event.player); + } + + performAbilities(possessee, event.player); + } + } + + // Right at the end of EntityPlayer#onUpdate() it calls EntityPlayer#updateSize(), which resets the player's + // size (and is also where this event is fired from, oddly enough) ... but not on my watch! + if(event.phase == TickEvent.Phase.END){ + EntityLiving possessee = getPossessee(event.player); + if(possessee != null){ + setSize(event.player, possessee.width, possessee.height); + } + } + } + + // When possessing, attacks are diverted to the possessed entity for armour, immunity, resistance calculations + // and so on, then when the damage is actually applied, the player is also damaged via onLivingDamageEvent below + @SubscribeEvent(priority = EventPriority.HIGH) + public static void onLivingAttackEvent(LivingAttackEvent event){ + + if(event.getEntity() instanceof EntityPlayer && event.getSource() != DamageSource.OUT_OF_WORLD){ + + EntityLiving possessee = getPossessee((EntityPlayer)event.getEntity()); + + if(possessee != null){ + DamageSafetyChecker.attackEntitySafely(possessee, event.getSource(), event.getAmount(), event.getSource().getDamageType()); + event.setCanceled(true); + } + } + } + + // LivingDamageEvent used in preference to LivingHurtEvent because the player is 'inside' the possessed entity, so + // any damage should come through that entity (and any armour, potions, enchantments etc. it has) first. + @SubscribeEvent + public static void onLivingDamageEvent(LivingDamageEvent event){ + + for(EntityPlayer player : event.getEntity().world.playerEntities){ + + EntityLiving possessee = getPossessee(player); + + if(possessee == event.getEntity()){ + // Possessors take half of all damage taken by the entity they are possessing. If the possessor receives + // fatal/critical damage (i.e. damage that takes them to half a heart or less), their health is reset to half + // a heart and the possession ends. + if(!player.capabilities.isCreativeMode){ + // TODO: Make this a proper DamageSource? + DamageSafetyChecker.attackEntitySafely(player, DamageSource.OUT_OF_WORLD, event.getAmount() / 2, + DamageSource.OUT_OF_WORLD.getDamageType()); + } + + if(player.getHealth() <= Spells.possession.getProperty(CRITICAL_HEALTH).floatValue()){ + player.setHealth(Spells.possession.getProperty(CRITICAL_HEALTH).floatValue()); + ((Possession)Spells.possession).endPossession(player); + } + } + } + } + + // Prevents possessing players from interacting with blocks and controls projectile shooting + @SubscribeEvent + public static void onPlayerInteractEvent(PlayerInteractEvent event){ + + if(event instanceof PlayerInteractEvent.RightClickItem) return; // Can always do this + + EntityLiving possessee = getPossessee(event.getEntityPlayer()); + + if(possessee != null){ + + // Let endermen interact with blocks + if(possessee instanceof EntityEnderman && ( + (event instanceof PlayerInteractEvent.RightClickBlock && ((EntityEnderman)possessee).getHeldBlockState() != null) + || (event instanceof PlayerInteractEvent.LeftClickBlock && ((EntityEnderman)possessee).getHeldBlockState() == null))) + return; + + if(WizardData.get(event.getEntityPlayer()) != null && event.getWorld().isRemote + && (event instanceof PlayerInteractEvent.RightClickEmpty + || event instanceof PlayerInteractEvent.EntityInteract + || event instanceof PlayerInteractEvent.RightClickBlock)){ + + Integer cooldown = WizardData.get(event.getEntityPlayer()).getVariable(SHOOT_COOLDOWN_KEY); + + if(cooldown == null || cooldown == 0){ + + WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.POSSESSION_PROJECTILE)); + WizardData.get(event.getEntityPlayer()).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN); + + if(possessee instanceof EntityLightningWraith){ + Spells.arc.cast(event.getWorld(), event.getEntityPlayer(), EnumHand.MAIN_HAND, 0, new SpellModifiers()); + } + + if(possessee instanceof EntityCreeper){ + ((EntityCreeper)possessee).ignite(); + } + } + } + + if(event.isCancelable()) event.setCanceled(true); + } + } + + /** Called via packets to shoot a projectile if the entity currently possessed by the given player can do so. */ + public static void shootProjectile(EntityPlayer possessor){ + + if(WizardData.get(possessor) != null){ + + Integer cooldown = WizardData.get(possessor).getVariable(SHOOT_COOLDOWN_KEY); + + if(cooldown == null || cooldown == 0){ + + EntityLiving possessee = getPossessee(possessor); + + if(possessee != null){ + + if(possessee instanceof EntityLightningWraith){ + Spells.arc.cast(possessor.world, possessor, EnumHand.MAIN_HAND, 0, new SpellModifiers()); + } + + if(possessee instanceof EntityCreeper){ + ((Possession)Spells.possession).endPossession(possessor); + ((EntityCreeper)possessee).ignite(); // Aaaaaaand.... RUN! + } + + Function factory = projectiles.get(possessee.getClass()); + + if(factory != null){ + + IProjectile projectile = factory.apply(possessor.world); + Vec3d look = possessor.getLookVec(); + ((Entity)projectile).setPosition(possessor.posX + look.x, possessor.posY + possessor.getEyeHeight() + look.y, possessor.posZ + look.z); + projectile.shoot(look.x, look.y, look.z, 1.6f, WizardryUtilities.getDefaultAimingError(possessor.world.getDifficulty())); + + if(projectile instanceof EntityMagicProjectile) ((EntityMagicProjectile)projectile).setCaster(possessor); + else if(projectile instanceof EntityMagicArrow) ((EntityMagicArrow)projectile).setCaster(possessor); + + possessor.world.spawnEntity((Entity)projectile); + + } + + WizardData.get(possessor).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN); + } + } + } + } + + @SubscribeEvent + public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ // Not fired for revenge-targeting + if(event.getTarget() instanceof EntityPlayer && event.getEntityLiving() instanceof EntityLiving){ + EntityLiving possessee = getPossessee((EntityPlayer)event.getTarget()); + EntityLiving attacker = (EntityLiving)event.getEntityLiving(); + if(possessee != null && !attacker.canAttackClass(possessee.getClass())){ + event.setCanceled(true); // Mobs can't target a player possessing an entity they don't normally attack + } + } + } + + // With these two methods I'm pretty sure it's watertight + + @SubscribeEvent + public static void onLivingDeathEvent(LivingDeathEvent event){ + if(event.getEntity() instanceof EntityPlayer && isPossessing((EntityPlayer)event.getEntity())){ + ((Possession)Spells.possession).endPossession((EntityPlayer)event.getEntity()); // Just in case, to make sure the player drops their items + } + } + + @SubscribeEvent + public static void onPlayerLoggedOutEvent(PlayerEvent.PlayerLoggedOutEvent event){ + if(isPossessing(event.player)) ((Possession)Spells.possession).endPossession(event.player); + } + + @SubscribeEvent + public static void onBlockBreakEvent(BlockEvent.BreakEvent event){ + + EntityLiving possessee = getPossessee(event.getPlayer()); + + if(possessee instanceof EntityEnderman){ + if(((EntityEnderman)possessee).getHeldBlockState() == null){ + ((EntityEnderman)possessee).setHeldBlockState(event.getState()); + event.getPlayer().setHeldItem(EnumHand.MAIN_HAND, new ItemStack(event.getState().getBlock())); + event.setExpToDrop(0); + event.getWorld().setBlockToAir(event.getPos()); // Remove block before it can drop + }else{ + event.setCanceled(true); + } + } + } + + @SubscribeEvent + public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){ + + EntityLiving possessee = getPossessee(event.getPlayer()); + + if(possessee instanceof EntityEnderman){ + if(((EntityEnderman)possessee).getHeldBlockState() == event.getState()){ + ((EntityEnderman)possessee).setHeldBlockState(null); + }else{ + event.setCanceled(true); + } + } + } + + @SubscribeEvent + public static void onEntityItemPickupEvent(EntityItemPickupEvent event){ // Why are there two item pickup events? + + EntityLiving possessee = getPossessee(event.getEntityPlayer()); + + if(possessee != null){ + + if(possessee.canPickUpLoot() && possessee.getHeldItemMainhand().isEmpty()){ + possessee.setHeldItem(EnumHand.MAIN_HAND, event.getItem().getItem()); + }else{ + event.setCanceled(true); + } + } + } + + @SubscribeEvent + static void onItemTossEvent(ItemTossEvent event){ + if(isPossessing(event.getPlayer())){ // Can't drop items while possessing + event.setCanceled(true); + event.getPlayer().inventory.addItemStackToInventory(event.getEntityItem().getItem()); + } + } + + @SubscribeEvent + public static void onAttackEntityEvent(AttackEntityEvent event){ + + EntityLiving possessee = getPossessee(event.getEntityPlayer()); + + if(possessee == null) return; + + if(possessee instanceof EntityCreeper){ + event.setCanceled(true); // Why do creepers have a melee AI?! + }else if(possessee.tasks.taskEntries.stream().noneMatch(t -> t.action instanceof EntityAIAttackMelee)){ + event.setCanceled(true); // Can't melee with a non-melee mob + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/RayOfPurification.java b/src/main/java/electroblob/wizardry/spell/RayOfPurification.java new file mode 100644 index 00000000..4367fd39 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/RayOfPurification.java @@ -0,0 +1,103 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.MobEffects; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; + +public class RayOfPurification extends SpellRay { + + /** The number by which this spell's damage is multiplied for undead entities. */ + public static final String UNDEAD_DAMAGE_MULTIPLIER = "undead_damage_multiplier"; + + public RayOfPurification(){ + super("ray_of_purification", true, EnumAction.NONE); + addProperties(DAMAGE, EFFECT_DURATION, BURN_DURATION, UNDEAD_DAMAGE_MULTIPLIER); + } + + // The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy) + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ + + if(MagicDamage.isEntityImmune(DamageType.RADIANT, target)){ + if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster) + .sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), + this.getNameForTranslationFormatted()), true); + }else{ + + float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); + // Fire + if(((EntityLivingBase)target).isEntityUndead()){ + target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + damage *= getProperty(UNDEAD_DAMAGE_MULTIPLIER).floatValue(); + } + // Damage + WizardryUtilities.attackEntityWithoutKnockback(target, + MagicDamage.causeDirectMagicDamage(caster, DamageType.RADIANT), damage); + // Blindness + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))); + } + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, EntityLivingBase caster, double distance){ + + if(caster != null){ + ParticleBuilder.create(Type.BEAM).entity(caster).pos(origin.subtract(caster.getPositionVector())) + .length(distance).clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f) + .scale(MathHelper.sin(world.getTotalWorldTime() * 0.2f) * 0.1f + 1.4f).spawn(world); + }else{ + ParticleBuilder.create(Type.BEAM).pos(origin).target(origin.add(direction.scale(distance))) + .clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f) + .scale(MathHelper.sin(world.getTotalWorldTime() * 0.2f) * 0.1f + 1.4f).spawn(world); + } + } +} diff --git a/src/main/java/electroblob/wizardry/spell/RemoveCurse.java b/src/main/java/electroblob/wizardry/spell/RemoveCurse.java new file mode 100644 index 00000000..927d0b3a --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/RemoveCurse.java @@ -0,0 +1,53 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.potion.Curse; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.potion.PotionEffect; +import net.minecraft.world.World; + +public class RemoveCurse extends SpellBuff { + + public RemoveCurse(){ + super("remove_curse", 1, 1, 0.3f); + this.soundValues(0.7f, 1.2f, 0.4f); + } + + @Override + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + + if(!caster.getActivePotionEffects().isEmpty()){ + + boolean flag = false; + + for(PotionEffect effect : caster.getActivePotionEffects()){ + // The PotionEffect version (as opposed to Potion) does not call cleanup callbacks + if(effect.getPotion() instanceof Curse){ + caster.removePotionEffect(effect.getPotion()); + flag = true; + } + } + + return flag; + } + + return false; + } + + @Override + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + super.spawnParticles(world, caster, modifiers); + + for(int i = 0; i < particleCount*2; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.14, 0).clr(0x0f001b) + .time(20 + world.rand.nextInt(12)).spawn(world); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x0f001b).spawn(world); + } + } +} diff --git a/src/main/java/electroblob/wizardry/spell/ReplenishHunger.java b/src/main/java/electroblob/wizardry/spell/ReplenishHunger.java index e9939d35..f2ec06c2 100644 --- a/src/main/java/electroblob/wizardry/spell/ReplenishHunger.java +++ b/src/main/java/electroblob/wizardry/spell/ReplenishHunger.java @@ -1,45 +1,39 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; -public class ReplenishHunger extends Spell { +public class ReplenishHunger extends SpellBuff { + + public static final String HUNGER_POINTS = "hunger_points"; + public static final String SATURATION_MODIFIER = "saturation_modifier"; public ReplenishHunger(){ - super(Tier.APPRENTICE, 10, Element.HEALING, "replenish_hunger", SpellType.UTILITY, 30, EnumAction.BOW, false); + super("replenish_hunger", 1, 0.7f, 0.3f); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(HUNGER_POINTS, SATURATION_MODIFIER); } - + + @Override public boolean canBeCastByNPCs(){ return false; } + + @Override + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + return true; // In this case the best solution is to remove the functionality of this method and override cast. + } + @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ if(caster.getFoodStats().needFood()){ - int foodAmount = (int)(6 * modifiers.get(SpellModifiers.DAMAGE)); + int foodAmount = (int)(getProperty(HUNGER_POINTS).floatValue() * modifiers.get(SpellModifiers.POTENCY)); // Fixed issue #6: Changed to addStats, since setFoodLevel is client-side only - caster.getFoodStats().addStats(foodAmount, foodAmount * 0.1f); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F - + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 1.0f, 0.7f, 0.3f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; + caster.getFoodStats().addStats(foodAmount, getProperty(SATURATION_MODIFIER).floatValue()); + return super.cast(world, caster, hand, ticksInUse, modifiers); } + return false; } diff --git a/src/main/java/electroblob/wizardry/spell/Resurrection.java b/src/main/java/electroblob/wizardry/spell/Resurrection.java new file mode 100644 index 00000000..1809e07b --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Resurrection.java @@ -0,0 +1,111 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.packet.PacketResurrection; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.EnumAction; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +import java.util.Arrays; +import java.util.Comparator; + +public class Resurrection extends Spell { + + public static final String WAIT_TIME = "wait_time"; + + public Resurrection(){ + super("resurrection", EnumAction.NONE, false); + addProperties(EFFECT_RADIUS, WAIT_TIME); + } + + @Override + public boolean requiresPacket(){ + return false; // Has its own packets + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + WizardData data = WizardData.get(caster); + + double radius = getProperty(EFFECT_RADIUS).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + + if(!world.isRemote && caster.getServer() != null){ + // Potency reduces the time you have to wait to resurrect an ally + int waitTime = (int)(getProperty(WAIT_TIME).floatValue() / modifiers.get(SpellModifiers.POTENCY)); + + EntityPlayerMP nearestDeadAlly = caster.getServer().getPlayerList().getPlayers().stream() + .filter(p -> !p.isEntityAlive() && p.deathTime > waitTime && (data.isPlayerAlly(p) || caster == p) + && p.getDistanceSq(caster) < radius * radius) + .min(Comparator.comparingDouble(caster::getDistanceSq)) + .orElse(null); + + if(nearestDeadAlly != null){ + // When the player entity dies, it is removed from world#loadedEntityList. However, it is NOT removed + // from playerEntityList (and probably a few other places) until respawn is clicked, and since that + // never happens here we need to clean up those references or the player will have duplicate entries + // in some entity lists - and weirdness will ensue! + world.removeEntity(nearestDeadAlly); // Clean up the old entity references + resurrect(nearestDeadAlly); // Reset isDead, must be before spawning the player again + world.spawnEntity(nearestDeadAlly); // Re-add the player to all the relevant entity lists + + // Notify clients to reset the appropriate fields, spawn particles and play sounds + IMessage msg = new PacketResurrection.Message(nearestDeadAlly.getEntityId()); + WizardryPacketHandler.net.sendToDimension(msg, caster.dimension); + + if(caster == nearestDeadAlly){ + caster.getServer().getPlayerList().sendMessage(new TextComponentTranslation( + "spell." + this.getRegistryName() + ".resurrect_self", caster.getDisplayName())); + }else{ + caster.getServer().getPlayerList().sendMessage(new TextComponentTranslation( + "spell." + this.getRegistryName() + ".resurrect_ally", nearestDeadAlly.getDisplayName(), caster.getDisplayName())); + } + + return true; + } + } + + return false; + } + + /** Sets the given player back to alive, sets their health to half-full and (on the client) spawns particles. */ + public void resurrect(EntityPlayer player){ + + player.isDead = false; + player.setHealth(player.getMaxHealth() / 2); + // Experience doesn't normally get reset until respawn, so we need to do that here too + player.deathTime = 0; + player.experience = 0; + player.experienceLevel = 0; + player.experienceTotal = 0; + + if(player.world.isRemote){ + ParticleBuilder.spawnHealParticles(player.world, player); + this.playSound(player.world, player, 0, -1, null); // We know the modifiers parameter isn't used + } + } + + public static int getRemainingWaitTime(int timeSinceDeath){ + return Math.max(0, MathHelper.ceil((Spells.resurrection.getProperty(Resurrection.WAIT_TIME).floatValue() - timeSinceDeath) / 20)); + } + + /** Helper method for detecting if a stack can be used to cast the resurrection spell. */ + public static boolean canStackResurrect(ItemStack stack, EntityPlayer player){ + return stack.getItem() instanceof ISpellCastingItem + && Arrays.asList(((ISpellCastingItem)stack.getItem()).getSpells(stack)).contains(Spells.resurrection) + && ((ISpellCastingItem)stack.getItem()).canCast(stack, Spells.resurrection, player, EnumHand.MAIN_HAND, 0, new SpellModifiers()); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Reversal.java b/src/main/java/electroblob/wizardry/spell/Reversal.java new file mode 100644 index 00000000..f8c79376 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Reversal.java @@ -0,0 +1,83 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Reversal extends SpellRay { + + public static final String REVERSED_EFFECTS = "reversed_effects"; + + public Reversal(){ + super("reversal", false, EnumAction.NONE); + addProperties(REVERSED_EFFECTS); + } + + @Override + public boolean canBeCastByDispensers(){ + return false; + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + // Naturally, this spell won't work unless it has a living caster and target + if(caster != null && target instanceof EntityLivingBase){ + + List negativePotions = new ArrayList<>(caster.getActivePotionEffects()); + negativePotions.removeIf(p -> !p.getPotion().isBadEffect()); + + if(!world.isRemote){ + + if(negativePotions.isEmpty()) return false; // Needs potion effects to reverse! + + // 1 effect for non-necromancy wands, 2 for apprentice necromancy wands, 3 for advanced and 4 for master + int bonusEffects = (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f) - 1; + int n = getProperty(REVERSED_EFFECTS).intValue() + bonusEffects; + + // Chooses n random negative potion effects, where n is the potency level + Collections.shuffle(negativePotions); + negativePotions = negativePotions.subList(0, negativePotions.size() < n ? negativePotions.size() : n); + + // Now reverse them! + negativePotions.forEach(p -> caster.removePotionEffect(p.getPotion())); + negativePotions.forEach(((EntityLivingBase)target)::addPotionEffect); + + }else{ + ParticleBuilder.create(Type.BUFF).entity(caster).clr(1, 1, 0.3f).spawn(world); + } + } + + return true; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onMiss(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.1f, 0, 0.05f).spawn(world); + } +} diff --git a/src/main/java/electroblob/wizardry/spell/RingOfFire.java b/src/main/java/electroblob/wizardry/spell/RingOfFire.java deleted file mode 100644 index 1d95f216..00000000 --- a/src/main/java/electroblob/wizardry/spell/RingOfFire.java +++ /dev/null @@ -1,74 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.construct.EntityFireRing; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class RingOfFire extends Spell { - - public RingOfFire(){ - super(Tier.ADVANCED, 30, Element.FIRE, "ring_of_fire", SpellType.ATTACK, 100, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(caster.onGround){ - if(!world.isRemote){ - EntityFireRing firering = new EntityFireRing(world, caster.posX, caster.posY, caster.posZ, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(firering); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - if(caster.onGround - && world.getEntitiesWithinAABB(EntityFireRing.class, caster.getEntityBoundingBox()).isEmpty()){ - if(!world.isRemote){ - EntityFireRing firering = new EntityFireRing(world, caster.posX, caster.posY, caster.posZ, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), - modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(firering); - } - - caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1); - return true; - } - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Satiety.java b/src/main/java/electroblob/wizardry/spell/Satiety.java new file mode 100644 index 00000000..8f3ce6e4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Satiety.java @@ -0,0 +1,40 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumHand; +import net.minecraft.world.World; + +public class Satiety extends SpellBuff { + + public static final String HUNGER_POINTS = "hunger_points"; + public static final String SATURATION_MODIFIER = "saturation_modifier"; + + public Satiety(){ + super("satiety", 1, 0.7f, 0.3f); + this.soundValues(0.7f, 1.2f, 0.4f); + addProperties(HUNGER_POINTS, SATURATION_MODIFIER); + } + + @Override public boolean canBeCastByNPCs(){ return false; } + + @Override + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + return true; // In this case the best solution is to remove the functionality of this method and override cast. + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(caster.getFoodStats().needFood()){ + int foodAmount = (int)(getProperty(HUNGER_POINTS).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + // Fixed issue #6: Changed to addStats, since setFoodLevel is client-side only + caster.getFoodStats().addStats(foodAmount, getProperty(SATURATION_MODIFIER).floatValue()); + return super.cast(world, caster, hand, ticksInUse, modifiers); + } + + return false; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/ShadowWard.java b/src/main/java/electroblob/wizardry/spell/ShadowWard.java index 594da63e..60e42bf2 100644 --- a/src/main/java/electroblob/wizardry/spell/ShadowWard.java +++ b/src/main/java/electroblob/wizardry/spell/ShadowWard.java @@ -1,23 +1,20 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.item.ItemWand; +import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.registry.Spells; import electroblob.wizardry.util.IElementalDamage; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.item.ItemStack; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.fml.common.Mod; @@ -26,8 +23,27 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber public class ShadowWard extends Spell { + public static final String REFLECTED_FRACTION = "reflected_fraction"; + public ShadowWard(){ - super(Tier.ADVANCED, 10, Element.NECROMANCY, "shadow_ward", SpellType.DEFENCE, 0, EnumAction.BLOCK, true); + super("shadow_ward", EnumAction.BLOCK, true); + addProperties(REFLECTED_FRACTION); + soundValues(0.6f, 1, 0); + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); } @Override @@ -37,11 +53,11 @@ public class ShadowWard extends Spell { double dx = -1 + 2 * world.rand.nextFloat(); double dy = -1 + world.rand.nextFloat(); double dz = -1 + 2 * world.rand.nextFloat(); - world.spawnParticle(EnumParticleTypes.PORTAL, caster.posX, WizardryUtilities.getPlayerEyesPos(caster), caster.posZ, dx, dy, dz); + world.spawnParticle(EnumParticleTypes.PORTAL, caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ, dx, dy, dz); } if(ticksInUse % 50 == 0){ - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_PORTAL_AMBIENT, 0.6f, 1.5f); + this.playSound(world, caster, ticksInUse, -1, modifiers); } return true; @@ -49,22 +65,24 @@ public class ShadowWard extends Spell { @SubscribeEvent public static void onLivingAttackEvent(LivingAttackEvent event){ + if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityLivingBase){ - // There used to be a check that the target was a player here, but I don't see any reason for it. - ItemStack wand = event.getEntityLiving().getActiveItemStack(); - if(wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand - && WandHelper.getCurrentSpell(wand) instanceof ShadowWard && !event.getSource().isUnblockable() + if(WizardryUtilities.isCasting(event.getEntityLiving(), Spells.shadow_ward) && !event.getSource().isUnblockable() && !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){ event.setCanceled(true); + + float reflectedFraction = MathHelper.clamp(Spells.shadow_ward.getProperty(REFLECTED_FRACTION).floatValue(), 0, 1); + // Now we can preserve the original damage source (sort of) as long as we make it retaliatory. // For some reason this isn't working, so I've reverted to plain old magic damage for now. //event.getEntityLiving().attackEntityFrom( // MagicDamage.causeDirectMagicDamage(event.getSource().getTrueSource(), DamageType.MAGIC, true), event.getAmount() * 0.5f); - event.getEntityLiving().attackEntityFrom(DamageSource.MAGIC, event.getAmount() * 0.5f); - ((EntityLivingBase)event.getSource().getTrueSource()).attackEntityFrom( - MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount() * 0.5f); + DamageSafetyChecker.attackEntitySafely(event.getEntity(), DamageSource.MAGIC, event.getAmount() + * (1 - reflectedFraction), event.getSource().getDamageType()); + event.getSource().getTrueSource().attackEntityFrom(MagicDamage.causeDirectMagicDamage( + event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount() * reflectedFraction); } } } diff --git a/src/main/java/electroblob/wizardry/spell/Shield.java b/src/main/java/electroblob/wizardry/spell/Shield.java index 0b974fff..759ee3ed 100644 --- a/src/main/java/electroblob/wizardry/spell/Shield.java +++ b/src/main/java/electroblob/wizardry/spell/Shield.java @@ -1,40 +1,60 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.IVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.EntityShield; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; import net.minecraft.world.World; public class Shield extends Spell { + public static final IVariable SHIELD_KEY = new IVariable.Variable<>(Persistence.NEVER); + public Shield(){ - super(Tier.APPRENTICE, 5, Element.HEALING, "shield", SpellType.DEFENCE, 0, EnumAction.BLOCK, true); + super("shield", EnumAction.BLOCK, true); + addProperties(EFFECT_STRENGTH); + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10, 0, false, false)); + caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10, + getProperty(EFFECT_STRENGTH).intValue(), false, false)); - if(WizardData.get(caster).shield == null){ - WizardData.get(caster).shield = new EntityShield(world, caster); + if(WizardData.get(caster).getVariable(SHIELD_KEY) == null){ + + EntityShield shield = new EntityShield(world, caster); + + WizardData.get(caster).setVariable(SHIELD_KEY, shield); if(!world.isRemote){ - world.spawnEntity(WizardData.get(caster).shield); + world.spawnEntity(shield); } } - if(ticksInUse == 0){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); - } + + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Shockwave.java b/src/main/java/electroblob/wizardry/spell/Shockwave.java index 60d2fedc..abfd6767 100644 --- a/src/main/java/electroblob/wizardry/spell/Shockwave.java +++ b/src/main/java/electroblob/wizardry/spell/Shockwave.java @@ -1,18 +1,11 @@ package electroblob.wizardry.spell; -import java.util.List; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityLivingBase; @@ -22,36 +15,59 @@ import net.minecraft.item.EnumAction; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; +import java.util.List; + public class Shockwave extends Spell { + public static final String MAX_REPULSION_VELOCITY = "max_repulsion_velocity"; + /** The radius within which maximum damage is dealt and maximum repulsion velocity is applied. */ + private static final double EPICENTRE_RADIUS = 1; + public Shockwave(){ - super(Tier.MASTER, 65, Element.SORCERY, "shockwave", SpellType.ATTACK, 150, EnumAction.BOW, false); + super("shockwave", EnumAction.BOW, false); + this.soundValues(2, 0.5f, 0); + addProperties(BLAST_RADIUS, DAMAGE, MAX_REPULSION_VELOCITY); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - List targets = WizardryUtilities.getEntitiesWithinRadius( - 5.0d * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + double radius = getProperty(BLAST_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + List targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world); for(EntityLivingBase target : targets){ - if(WizardryUtilities.isValidTarget(caster, target)){ + + if(target instanceof EntityPlayer && (!Wizardry.settings.playersMoveEachOther + || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){ + + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell.resist", + target.getName(), this.getNameForTranslationFormatted()), true); + return false; + } + + if(AllyDesignationSystem.isValidTarget(caster, target)){ + + // Produces a linear profile from 0 at the edge of the radius to 1 at the epicentre radius, then + // a constant value of 1 within the epicentre radius. + float proximity = (float)(1 - (Math.max(target.getDistance(caster) - EPICENTRE_RADIUS, 0))/(radius - EPICENTRE_RADIUS)); + // Damage increases closer to player up to a maximum of 4 hearts (at 1 block distance). - float damage = Math.min(8.0f / target.getDistance(caster), 8.0f); target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), - damage * modifiers.get(SpellModifiers.DAMAGE)); + getProperty(DAMAGE).floatValue() * proximity * modifiers.get(SpellModifiers.POTENCY)); if(!world.isRemote){ // Entity speed increases closer to the player to a maximum of 3 (at 1 block distance). // This is the entity's speed compared to its distance from the player. Used for a similar triangles // based x, y and z speed calculation. - double velocityFactor = Math.min(5 / target.getDistanceSq(caster), 3.0d); + double velocityFactor = proximity * getProperty(MAX_REPULSION_VELOCITY).floatValue(); double dx = target.posX - caster.posX; - double dy = target.posY + 1 - caster.posY; + double dy = target.getEntityBoundingBox().minY + 1 - caster.posY; double dz = target.posZ - caster.posZ; target.motionX = velocityFactor * dx; @@ -65,23 +81,25 @@ public class Shockwave extends Spell { } } } + if(world.isRemote){ double particleX, particleZ; + for(int i = 0; i < 40; i++){ - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, particleX, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5, particleZ, particleX - caster.posX, 0, - particleZ - caster.posZ, 30, 0.8f, 0.8f, 1.0f); - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, particleX, - WizardryUtilities.getPlayerEyesPos(caster) - 1.5, particleZ, particleX - caster.posX, 0, - particleZ - caster.posZ, 30, 0.9f, 0.9f, 0.9f); - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); +// particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); +// particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); +// ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ) +// .vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.8f, 0.8f, 1).spawn(world); +// +// particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); +// particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); +// ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ) +// .vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.9f, 0.9f, 0.9f).spawn(world); + + particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); + particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); if(block != null){ @@ -90,13 +108,19 @@ public class Shockwave extends Spell { } } + ParticleBuilder.create(Type.SPHERE) + .pos(caster.posX, caster.getEntityBoundingBox().minY + 0.1, caster.posZ) + .scale((float)radius * 0.8f) + .clr(0.8f, 0.9f, 1) + .spawn(world); + world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX, caster.getEntityBoundingBox().minY + 0.1, caster.posZ, 0, 0, 0); } + caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 1.0f, 0.7f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 2.0f, 0.3f); + playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java b/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java new file mode 100644 index 00000000..8b59fdf4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java @@ -0,0 +1,101 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityShulkerBullet; +import net.minecraft.item.EnumAction; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.List; + +public class ShulkerBullet extends Spell { + + public ShulkerBullet(){ + super("shulker_bullet", EnumAction.NONE, false); + this.soundValues(2, 1, 0.3f); + addProperties(RANGE); + } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers(){ return true; } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + if(!shoot(world, caster, caster.posX, caster.posY, caster.posZ, EnumFacing.UP, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + if(!shoot(world, caster, caster.posX, caster.posY, caster.posZ, EnumFacing.UP, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int duration, int ticksInUse, SpellModifiers modifiers){ + if(!shoot(world, null, x, y, z, direction, modifiers)) return false; + this.playSound(world, x, y, z, ticksInUse, -1, modifiers); + return true; + } + + private boolean shoot(World world, @Nullable EntityLivingBase caster, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers){ + + if(!world.isRemote){ + + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + List possibleTargets = WizardryUtilities.getEntitiesWithinRadius(range, x, y, z, world); + + possibleTargets.remove(caster); + possibleTargets.removeIf(t -> t instanceof EntityArmorStand); + + if(possibleTargets.isEmpty()) return false; + + // getDistanceSq doesn't require square-rooting so it's faster when only comparing + possibleTargets.sort(Comparator.comparingDouble(t -> t.getDistanceSq(x, y, z))); + + Entity target = possibleTargets.get(0); + + // Y axis because the player is always upright + if(caster != null){ + world.spawnEntity(new EntityShulkerBullet(world, caster, target, direction.getAxis())); + }else{ + // Can't use the normal constructor because doesn't accept null for the owner + EntityShulkerBullet bullet = new EntityShulkerBullet(world); + bullet.setLocationAndAngles(x, y, z, bullet.rotationYaw, bullet.rotationPitch); + + // Where there's a will there's a way... + NBTTagCompound nbt = new NBTTagCompound(); + bullet.writeToNBT(nbt); + nbt.setInteger("Dir", direction.getIndex()); + BlockPos pos = new BlockPos(target); + NBTTagCompound targetTag = NBTUtil.createUUIDTag(target.getUniqueID()); + targetTag.setInteger("X", pos.getX()); + targetTag.setInteger("Y", pos.getY()); + targetTag.setInteger("Z", pos.getZ()); + nbt.setTag("Target", targetTag); + bullet.readFromNBT(nbt); // LOL I just modified private fields without reflection + + world.spawnEntity(bullet); + } + } + + return true; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/SilverfishSwarm.java b/src/main/java/electroblob/wizardry/spell/SilverfishSwarm.java deleted file mode 100644 index d626418e..00000000 --- a/src/main/java/electroblob/wizardry/spell/SilverfishSwarm.java +++ /dev/null @@ -1,49 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntitySilverfishMinion; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SilverfishSwarm extends Spell { - - public SilverfishSwarm(){ - super(Tier.MASTER, 80, Element.EARTH, "silverfish_swarm", SpellType.MINION, 300, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - for(int i = 0; i < 20; i++){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 3, 6); - // The spell instantly fails if no space was found (see javadoc for the above method). - if(pos == null) return false; - - EntitySilverfishMinion silverfish = new EntitySilverfishMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(silverfish); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - // Can't possibly get this far if nothing was spawned. - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SixthSense.java b/src/main/java/electroblob/wizardry/spell/SixthSense.java index b33ac6c9..b311c7cb 100644 --- a/src/main/java/electroblob/wizardry/spell/SixthSense.java +++ b/src/main/java/electroblob/wizardry/spell/SixthSense.java @@ -1,43 +1,60 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.PotionEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +@Mod.EventBusSubscriber public class SixthSense extends Spell { + /** A {@code ResourceLocation} representing the shader file used when under the effects of sixth sense. */ + public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/sixth_sense.json"); + public SixthSense(){ - super(Tier.APPRENTICE, 20, Element.EARTH, "sixth_sense", SpellType.UTILITY, 100, EnumAction.BOW, false); + super("sixth_sense", EnumAction.BOW, false); + addProperties(EFFECT_DURATION, EFFECT_RADIUS); + soundValues(1, 1.1f, 0.2f); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - // Cannot be cast when it has already been cast - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense, - (int)(400 * modifiers.get(WizardryItems.duration_upgrade)), - (int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL))); + caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + (int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL))); + + if(world.isRemote && caster == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } + @SubscribeEvent + public static void onPotionAddedEvent(PotionEvent.PotionAddedEvent event){ + if(event.getEntity().world.isRemote && event.getPotionEffect().getPotion() == WizardryPotions.sixth_sense + && event.getEntity() == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); + } + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Slime.java b/src/main/java/electroblob/wizardry/spell/Slime.java index 6ba62b8e..14bbfaf0 100644 --- a/src/main/java/electroblob/wizardry/spell/Slime.java +++ b/src/main/java/electroblob/wizardry/spell/Slime.java @@ -1,126 +1,65 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntityMagicSlime; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.monster.EntitySkeleton; import net.minecraft.entity.monster.EntitySlime; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Slime extends Spell { +public class Slime extends SpellRay { public Slime(){ - super(Tier.ADVANCED, 20, Element.EARTH, "slime", SpellType.ATTACK, 50, EnumAction.NONE, false); + super("slime", false, EnumAction.NONE); + addProperties(DURATION); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 8 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.entityHit != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target) && !(target instanceof EntityMagicSlime)){ if(target instanceof EntitySlime){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); - }else if(!(target instanceof EntityMagicSlime)){ - - if(target instanceof EntitySkeleton) WizardryAdvancementTriggers.slime_skeleton.triggerFor(caster); + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); + }else{ if(!world.isRemote){ - EntityMagicSlime slime = new EntityMagicSlime(world, caster, target, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade))); + EntityMagicSlime slime = new EntityMagicSlime(world, caster, (EntityLivingBase)target, + (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); world.spawnEntity(slime); } } } - - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.SLIME, x1, y1, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.2f, 0.8f, 0.1f); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SLIME_ATTACK, 1.0F, 0.5F); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null && !(target instanceof EntitySlime) && !(target instanceof EntityMagicSlime)){ - - if(!world.isRemote){ - EntityMagicSlime slime = new EntityMagicSlime(world, caster, target, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(slime); - } - - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - world.spawnParticle(EnumParticleTypes.SLIME, x1, y1, z1, 0.0d, 0.0d, 0.0d); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.2f, 0.8f, 0.1f); - } - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0F, 0.5F); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + world.spawnParticle(EnumParticleTypes.SLIME, x, y, z, 0, 0, 0); + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.2f, 0.8f, 0.1f).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/SlowTime.java b/src/main/java/electroblob/wizardry/spell/SlowTime.java new file mode 100644 index 00000000..04ce8150 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SlowTime.java @@ -0,0 +1,46 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +public class SlowTime extends SpellBuff { + + /** A {@code ResourceLocation} representing the shader file used when possessing an entity. */ + public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/slow_time.json"); + + public SlowTime(){ + super("slow_time", 0.2f, 0.8f, 0.8f, () -> WizardryPotions.slow_time); + addProperties(EFFECT_RADIUS); + soundValues(0.6f, 1.5f, 0); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(caster.world.isRemote && caster == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + } + + return super.cast(world, caster, hand, ticksInUse, modifiers); + } + + @Override + public boolean requiresPacket(){ + return false; + } + + @Override + public boolean canBeCastByDispensers(){ + return false; + } + + @Override + public boolean canBeCastByNPCs(){ + return false; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/SmokeBomb.java b/src/main/java/electroblob/wizardry/spell/SmokeBomb.java deleted file mode 100644 index c10a4b5b..00000000 --- a/src/main/java/electroblob/wizardry/spell/SmokeBomb.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntitySmokeBomb; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class SmokeBomb extends Spell { - - public SmokeBomb(){ - super(Tier.BASIC, 10, Element.FIRE, "smoke_bomb", SpellType.ATTACK, 20, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntitySmokeBomb smokebomb = new EntitySmokeBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(smokebomb); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntitySmokeBomb smokebomb = new EntitySmokeBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - smokebomb.directTowards(target, 1.5f); - world.spawnEntity(smokebomb); - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Snare.java b/src/main/java/electroblob/wizardry/spell/Snare.java index d8a5f262..4b70e60d 100644 --- a/src/main/java/electroblob/wizardry/spell/Snare.java +++ b/src/main/java/electroblob/wizardry/spell/Snare.java @@ -1,78 +1,61 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryBlocks; -import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityPlayerSave; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Snare extends Spell { +public class Snare extends SpellRay { public Snare(){ - super(Tier.BASIC, 10, Element.EARTH, "snare", SpellType.ATTACK, 10, EnumAction.NONE, false); + super("snare", false, EnumAction.NONE); + this.soundValues(1, 1.4f, 0.4f); + this.ignoreLivingEntities(true); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, true); - - // Gets block the player is looking at and places snare - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - BlockPos pos = rayTrace.getBlockPos(); - - if(rayTrace.sideHit == EnumFacing.UP && world.isSideSolid(pos, EnumFacing.UP) - && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ - - if(!world.isRemote){ - world.setBlockState(pos.up(), WizardryBlocks.snare.getDefaultState()); - ((TileEntityPlayerSave)world.getTileEntity(pos.up())).setCaster(caster); - } - - double dx = pos.getX() + 0.5 - caster.posX; - double dy = pos.getY() + 1.5 - (caster.posY + caster.height / 2); - double dz = pos.getZ() + 0.5 - caster.posZ; - - if(world.isRemote){ - for(int i = 1; i < 5; i++){ - float brightness = world.rand.nextFloat() / 4; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0.0d, 0.0d, 0.0d, - 20 + world.rand.nextInt(8), brightness, brightness + 0.1f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.LEAF, world, - caster.posX + (i * (dx / 5)) + world.rand.nextFloat() / 5, - WizardryUtilities.getPlayerEyesPos(caster) + (i * (dy / 5)) - + world.rand.nextFloat() / 5, - caster.posZ + (i * (dz / 5)) + world.rand.nextFloat() / 5, 0, -0.01, 0, - 40 + world.rand.nextInt(10)); - } - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_GRASS_PLACE, 1.0F, - world.rand.nextFloat() * 0.4F + 1.2F); - return true; - } - } + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(side == EnumFacing.UP && world.isSideSolid(pos, EnumFacing.UP) + && WizardryUtilities.canBlockBeReplaced(world, pos.up())){ + + if(!world.isRemote){ + world.setBlockState(pos.up(), WizardryBlocks.snare.getDefaultState()); + ((TileEntityPlayerSave)world.getTileEntity(pos.up())).setCaster(caster); + } + + return true; + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + float brightness = world.rand.nextFloat() * 0.25f; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(20 + world.rand.nextInt(8)) + .clr(brightness, brightness + 0.1f, 0).spawn(world); + ParticleBuilder.create(Type.LEAF).pos(x, y, z).vel(0, -0.01, 0).time(40 + world.rand.nextInt(10)).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/Snowball.java b/src/main/java/electroblob/wizardry/spell/Snowball.java index 0f7f71e5..500488fd 100644 --- a/src/main/java/electroblob/wizardry/spell/Snowball.java +++ b/src/main/java/electroblob/wizardry/spell/Snowball.java @@ -1,25 +1,24 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntitySnowball; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; public class Snowball extends Spell { public Snowball(){ - super(Tier.BASIC, 1, Element.ICE, "snowball", SpellType.ATTACK, 1, EnumAction.NONE, false); + super("snowball", EnumAction.NONE, false); + addProperties(RANGE); + soundValues(0.5f, 0.4f, 0.2f); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @@ -27,13 +26,18 @@ public class Snowball extends Spell { public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ if(!world.isRemote){ + // Trajectory calculation - see SpellProjectile for a more detailed explanation + float g = 0.03f; + float launchHeight = caster.getEyeHeight(); + float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + float velocity = MathHelper.sqrt(MathHelper.sqrt(g*g * (launchHeight*launchHeight + range*range)) - g*launchHeight); + EntitySnowball snowball = new EntitySnowball(world, caster); - snowball.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, 1.5f, 1.0f); + snowball.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, velocity, 1.0f); world.spawnEntity(snowball); } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); + this.playSound(world, caster, ticksInUse, -1, modifiers); caster.swingArm(hand); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/SparkBomb.java b/src/main/java/electroblob/wizardry/spell/SparkBomb.java deleted file mode 100644 index b8adbdb5..00000000 --- a/src/main/java/electroblob/wizardry/spell/SparkBomb.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntitySparkBomb; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class SparkBomb extends Spell { - - public SparkBomb(){ - super(Tier.APPRENTICE, 15, Element.LIGHTNING, "spark_bomb", SpellType.ATTACK, 25, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntitySparkBomb sparkBomb = new EntitySparkBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - world.spawnEntity(sparkBomb); - } - - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, - 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntitySparkBomb sparkBomb = new EntitySparkBomb(world, caster, modifiers.get(SpellModifiers.DAMAGE), - modifiers.get(WizardryItems.blast_upgrade)); - sparkBomb.directTowards(target, 1.5f); - world.spawnEntity(sparkBomb); - } - - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F)); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SpectralPathway.java b/src/main/java/electroblob/wizardry/spell/SpectralPathway.java index db2b3c59..adc6c73d 100644 --- a/src/main/java/electroblob/wizardry/spell/SpectralPathway.java +++ b/src/main/java/electroblob/wizardry/spell/SpectralPathway.java @@ -1,11 +1,7 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.tileentity.TileEntityTimer; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; @@ -19,13 +15,17 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public class SpectralPathway extends Spell { + + /** The base length of the conjured bridge, in blocks. */ + public static final String LENGTH = "length"; public SpectralPathway(){ - super(Tier.ADVANCED, 40, Element.SORCERY, "spectral_pathway", SpellType.UTILITY, 300, EnumAction.BOW, false); + super("spectral_pathway", EnumAction.BOW, false); + addProperties(LENGTH, DURATION); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @@ -44,8 +44,6 @@ public class SpectralPathway extends Spell { if(!world.isRemote){ - int baseLength = 15; - // Gets the coordinates of the nearest block intersection to the player's feet. // Remember that a block always takes the coordinates of its northwestern corner. BlockPos origin = new BlockPos(Math.round(caster.posX), (int)caster.getEntityBoundingBox().minY - 1, @@ -53,7 +51,7 @@ public class SpectralPathway extends Spell { int startPoint = direction.getAxisDirection() == AxisDirection.POSITIVE ? -1 : 0; - for(int i = 0; i < (int)(baseLength * modifiers.get(WizardryItems.range_upgrade)); i++){ + for(int i = 0; i < (int)(getProperty(LENGTH).floatValue() * modifiers.get(WizardryItems.range_upgrade)); i++){ // If either a block gets placed or one has already been placed, flag is set to true. flag = placePathwayBlockIfPossible(world, origin.offset(direction, startPoint + i), modifiers.get(WizardryItems.duration_upgrade)) || flag; @@ -63,17 +61,17 @@ public class SpectralPathway extends Spell { modifiers.get(WizardryItems.duration_upgrade)) || flag; } } - // TODO: There may be some client/server discrepancies here. - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION_LARGE, 1.0f, 1.0f); + + this.playSound(world, caster, ticksInUse, -1, modifiers); return flag; } - private static boolean placePathwayBlockIfPossible(World world, BlockPos pos, float durationMultiplier){ - if(WizardryUtilities.canBlockBeReplacedB(world, pos)){ + private boolean placePathwayBlockIfPossible(World world, BlockPos pos, float durationMultiplier){ + if(WizardryUtilities.canBlockBeReplaced(world, pos, true)){ world.setBlockState(pos, WizardryBlocks.spectral_block.getDefaultState()); if(world.getTileEntity(pos) instanceof TileEntityTimer){ - ((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(1200 * durationMultiplier)); + ((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(getProperty(DURATION).floatValue() * durationMultiplier)); } return true; } diff --git a/src/main/java/electroblob/wizardry/spell/SpeedTime.java b/src/main/java/electroblob/wizardry/spell/SpeedTime.java new file mode 100644 index 00000000..4692b176 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpeedTime.java @@ -0,0 +1,133 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ITickable; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a blank spell used to fill empty slots on wands. It is unobtainable in-game, except via + * commands, and does nothing when the player attempts to cast it. Its instance can be referenced directly using + * {@link electroblob.wizardry.registry.Spells#none Spells.none} + */ +public class SpeedTime extends Spell { + + /** The base number of ticks to add to the world time for each tick the spell is cast. */ + public static final String TIME_INCREMENT = "time_increment"; + /** The number of extra times to tick each nearby block, entity and tile entity each tick the spell is cast. */ + public static final String EXTRA_TICKS = "extra_ticks"; + + public SpeedTime(){ + super("speed_time", EnumAction.BOW, true); + addProperties(EFFECT_RADIUS, TIME_INCREMENT, EXTRA_TICKS); + } + + @Override + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } + + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + boolean flag = false; + + // Hold onto your hats ladies and gentlemen, this effect scales with potency modifiers! Speeeeeeeeed! + if(Wizardry.settings.worldTimeManipulation){ + world.setWorldTime(world.getWorldTime() + (long)(getProperty(TIME_INCREMENT).floatValue() * modifiers.get(SpellModifiers.POTENCY))); + flag = true; + } + + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + // Doubles the normal effect of the modifier + float potencyLevel = ((modifiers.get(SpellModifiers.POTENCY) - 1) * 2 + 1) * getProperty(EXTRA_TICKS).floatValue(); + + // Ticks all the entities near the caster + List entities = new ArrayList<>(world.loadedEntityList); + entities.removeIf(e -> e instanceof EntityPlayer); + entities.removeIf(e -> caster.getDistance(e) > radius); + + if(!entities.isEmpty()){ + for(int i = 0; i < potencyLevel; i++){ + entities.forEach(Entity::onUpdate); + } + flag = true; + } + + // Ticks all the tile entities near the caster + // Copy the list first! + List tileentities = new ArrayList<>(world.tickableTileEntities); + tileentities.removeIf(t -> caster.getDistanceSq(t.getPos()) > radius*radius); + + if(!tileentities.isEmpty()){ + for(int i = 0; i < potencyLevel; i++){ + tileentities.forEach(t -> ((ITickable)t).update()); + } + flag = true; + } + + if(!world.isRemote){ + + List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), radius); + + for(BlockPos pos : sphere){ + + if(world.getBlockState(pos).getBlock().getTickRandomly()){ + for(int i = 0; i < potencyLevel; i++){ + world.getBlockState(pos).getBlock().randomTick(world, pos, world.getBlockState(pos), world.rand); + flag = true; + } + } + } + } + + // Particle effects + if(world.isRemote){ + + for(int i=1; i<3; i++){ + + double particleSpread = 2; + double x = caster.posX + 2; + double y = caster.getEntityBoundingBox().minY + caster.height / 2; + double z = caster.posZ; + + ParticleBuilder.create(ParticleBuilder.Type.SPARKLE, world.rand, x, y, z, particleSpread, false) + .vel(-0.25, 0, 0).time(16).clr(1f, 1f, 1f).spawn(world); + + ParticleBuilder.create(ParticleBuilder.Type.FLASH, world.rand, x, y, z, particleSpread, false) + .vel(-0.25, 0, 0).time(16).scale(0.5f).clr(0.6f + world.rand.nextFloat() * 0.4f, + 0.6f + world.rand.nextFloat() * 0.4f, 0.6f + world.rand.nextFloat() * 0.4f).spawn(world); + } + } + + if(flag) playSound(world, caster, ticksInUse, -1, modifiers); + // Always return true if the world time was changed, otherwise return false if nothing was ticked. + return flag; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Spell.java b/src/main/java/electroblob/wizardry/spell/Spell.java index c13ac7b0..8ed6516e 100644 --- a/src/main/java/electroblob/wizardry/spell/Spell.java +++ b/src/main/java/electroblob/wizardry/spell/Spell.java @@ -1,26 +1,28 @@ package electroblob.wizardry.spell; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; - import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.SpellType; import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntityWizard; +import electroblob.wizardry.item.ItemScroll; +import electroblob.wizardry.item.ItemSpellBook; +import electroblob.wizardry.packet.PacketSpellProperties; +import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import net.minecraft.client.resources.I18n; +import electroblob.wizardry.util.SpellProperties; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; @@ -31,24 +33,30 @@ import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistryEntry; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + /** * Generic spell class which is the superclass to all spells in wizardry. When extending this class, you must do the * following: - *

    + *

    * - Have a constructor which passes all necessary constants into the super constructor. I define the constants here so * that the constructor for an individual spell has no parameters, but you may prefer to pass in the parameters when the * spell is registered, so all the mana costs etc. are in one place like a sort of sandbox. - *

    + *

    * - Implement the {@link Spell#cast(World, EntityPlayer, EnumHand, int, SpellModifiers)} method, in which you should * execute the code that makes the spell work, and return true or false depending on whether the spell succeeded and * therefore whether mana should be used up. - *

    + *

    * - Register the spell using {@link RegistryEvent.Register}, with {@link Spell} as the type parameter. Each spell * should have a single instance, like blocks and items. As of Wizardry 2.1, spells use the Forge registry system. - * Related methods such as {@link Spell#id()} and {@link Spell#get(int)} have been re-routed to use this system, leaving + * Related methods such as {@link Spell#metadata()} and {@link Spell#byMetadata(int)} have been re-routed to use this system, leaving * minimal external changes. Note also that the constructor automatically sets the registry name for you, though you may * change it afterwards if necessary. - *

    + *

    * Also note that you can override some other methods from this class. For example, to add a specific kind of formatting * to a spell name or description, you can override {@link Spell#getDisplayName()}, * {@link Spell#getDisplayNameWithFormatting()} or {@link Spell#getDescription()} and append the formatting code (though @@ -56,8 +64,8 @@ import net.minecraftforge.registries.IForgeRegistryEntry; * {@link SummonShadowWraith#getDescription()} for an example. *
    * This class is also home to some useful static methods for interacting with the spell registry: - *

    - * {@link Spell#get(int)} gets a spell instance from its integer id, which corresponds to the metadata of its spell + *

    + * {@link Spell#byMetadata(int)} gets a spell instance from its integer metadata, which corresponds to the metadata of its spell * book.
    * {@link Spell#get(String)} gets a spell instance from its unlocalised name.
    * {@link Spell#getSpells(Predicate)} returns a list of spell instances that match the given {@link Predicate}.
    @@ -68,29 +76,48 @@ import net.minecraftforge.registries.IForgeRegistryEntry; * the order of which is as defined in {@link Element} (i.e. magic, fire, ice, lightning, necromancy, earth, sorcery, * healing). *
    - * + * * @since Wizardry 1.0 - * @see electroblob.wizardry.item.ItemSpellBook ItemSpellBook - * @see electroblob.wizardry.item.ItemScroll ItemScroll + * @see ItemSpellBook ItemSpellBook + * @see ItemScroll ItemScroll * @see Spells */ public abstract class Spell extends IForgeRegistryEntry.Impl implements Comparable { + // Spell checklist: + // - Create and register spell, add texture and properties json file + // - Add name AND description to lang files + // - Add sound(s) to sounds.json + // - Add to advancements/all_spells.json + + // Common property identifiers + public static final String DAMAGE = "damage"; + public static final String RANGE = "range"; + public static final String DURATION = "duration"; + public static final String EFFECT_RADIUS = "effect_radius"; + public static final String BLAST_RADIUS = "blast_radius"; + public static final String EFFECT_DURATION = "effect_duration"; + public static final String EFFECT_STRENGTH = "effect_strength"; + public static final String BURN_DURATION = "burn_duration"; + public static final String DIRECT_DAMAGE = "direct_damage"; + public static final String SPLASH_DAMAGE = "splash_damage"; + public static final String HEALTH = "health"; + public static final String SEEKING_STRENGTH = "seeking_strength"; + public static final String DIRECT_EFFECT_DURATION = "direct_effect_duration"; + public static final String DIRECT_EFFECT_STRENGTH = "direct_effect_strength"; + public static final String SPLASH_EFFECT_DURATION = "splash_effect_duration"; + public static final String SPLASH_EFFECT_STRENGTH = "splash_effect_strength"; + /** Forge registry-based replacement for the internal spells list. */ public static IForgeRegistry registry; - /** The tier this spell belongs to. */ - public final Tier tier; - /** Mana cost of the spell. If it is a continuous spell the cost is per second. */ - public final int cost; - /** The element this spell belongs to. */ - public final Element element; /** The unlocalised name of the spell. */ private final String unlocalisedName; - /** The type of spell this is classified as. */ - public final SpellType type; - /** Cooldown for the spell in ticks */ - public final int cooldown; + /** This spell's associated SpellProperties object. */ + private SpellProperties properties; + /** Used in initialisation. */ + private Set propertyKeys = new HashSet<>(); + /** The action the player does when this spell is cast. */ public final EnumAction action; /** Whether or not the spell is continuous (keeps going as long as the mouse button is held) */ @@ -98,84 +125,194 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C /** ResourceLocation of the spell icon. */ private final ResourceLocation icon; - /** Mod ID of the mod that added this spell; defaults to {@link Wizardry#MODID} if not specified. */ - private final String modID; - /** - * False if the spell has been disabled in the config file, true otherwise. This is now encapsulated to stop it - * being fiddled with. - */ - private boolean isEnabled = true; + /** False if the spell has been disabled in the config file, true otherwise. This is now encapsulated to stop it + * being fiddled with. */ + private boolean enabled = true; + + /** The sound(s) played when this spell is cast. */ + @Nullable + protected final SoundEvent[] sounds; + /** The volume of the sound played when this spell is cast. Defaults to 1. */ + protected float volume = 1; + /** The pitch of the sound played when this spell is cast. Defaults to 1. */ + protected float pitch = 1; + /** The pitch variation of the sound played when this spell is cast. Defaults to 0. */ + protected float pitchVariation = 0; + + private static int nextSpellId = 0; + /** The spell's integer ID, mainly used for networking. */ + // This was added after I learnt the hard way why you can't assume Forge's registry IDs are sequential... + private final int id; /** * This constructor should be called from any subclasses, either feeding in the constants directly or through their * own constructor from wherever the spell is registered. This is the constructor for wizardry's own spells; spells * added by other mods should use - * {@link Spell#Spell(Tier, int, Element, String, SpellType, int, EnumAction, boolean, String)}. - * - * @param tier The tier this spell belongs to. - * @param cost The amount of mana used to cast the spell. If this is a continuous spell, it represents mana cost per - * second and should be a multiple of 5. - * @param element The element this spell belongs to. + * {@link Spell#Spell(String, String, EnumAction, boolean)}. * @param name The registry name of the spell. This will also be the name of the icon file. The spell's * unlocalised name will be a resource location with the format [modid]:[name]. - * @param cooldown The cooldown time for this spell in ticks. * @param action The vanilla usage action to be displayed when casting this spell. * @param isContinuous Whether this spell is continuous, meaning you cast it for a length of time by holding the - * right mouse button. */ - public Spell(Tier tier, int cost, Element element, String name, SpellType type, int cooldown, EnumAction action, - boolean isContinuous){ - this(tier, cost, element, name, type, cooldown, action, isContinuous, Wizardry.MODID); + public Spell(String name, EnumAction action, boolean isContinuous){ + this(Wizardry.MODID, name, action, isContinuous); } /** * This constructor should be called from any subclasses, either feeding in the constants directly or through their * own constructor from wherever the spell is registered. - * - * @param tier The tier this spell belongs to. - * @param cost The amount of mana used to cast the spell. If this is a continuous spell, it represents mana cost per - * second and should be a multiple of 5. - * @param element The element this spell belongs to. - * @param name The registry name of the spell, excluding the mod id. This will also be the name of the icon - * file. The spell's unlocalised name will be a resource location with the format [modid]:[name]. - * @param cooldown The cooldown time for this spell in ticks. - * @param action The vanilla usage action to be displayed when casting this spell (see {@link}EnumAction) - * @param isContinuous Whether this spell is continuous, meaning you cast it for a length of time by holding the - * right mouse button. * @param modID The mod id of the mod that added this spell. This allows wizardry to use the correct file path for * the spell icon, and also more generally to distinguish between original and addon spells. + * @param name The registry name of the spell, excluding the mod id. This will also be the name of the icon + * file. The spell's unlocalised name will be a resource location with the format [modid]:[name]. + * @param action The vanilla usage action to be displayed when casting this spell (see {@link}EnumAction) + * @param isContinuous Whether this spell is continuous, meaning you cast it for a length of time by holding the */ - public Spell(Tier tier, int cost, Element element, String name, SpellType type, int cooldown, EnumAction action, - boolean isContinuous, String modID){ - this.tier = tier; - this.cost = cost; - this.element = element; - this.type = type; - this.cooldown = cooldown; - this.action = action; - this.isContinuous = isContinuous; - this.modID = modID; + public Spell(String modID, String name, EnumAction action, boolean isContinuous){ this.setRegistryName(modID, name); this.unlocalisedName = this.getRegistryName().toString(); - this.icon = new ResourceLocation(this.modID, "textures/spells/" + name + ".png"); + this.action = action; + this.isContinuous = isContinuous; + this.icon = new ResourceLocation(modID, "textures/spells/" + name + ".png"); + this.sounds = createSounds(); + this.id = nextSpellId++; } + // ========================================= Initialisation methods =========================================== + + /** Called from {@code init()} in the main mod class. Used to initialise spell fields and properties that depend on + * other things being registered (e.g. potions). Always initialise things in the constructor wherever possible. */ + public void init(){} + + /** + * Called from the constructor to initialise this spell's sounds. By default, this creates and returns a 1-element + * array containing a single sound event called {@code spell.[unlocalised name]}. Override this to add a custom + * sound array, perhaps using one of the convenience methods (see below). + * @return An array of sound events played by this spell. + * @see Spell#createSoundWithSuffix(String) + * @see Spell#createContinuousSpellSounds + * @see Spell#playSound(World, double, double, double, int, int, SpellModifiers, String...) + */ + protected SoundEvent[] createSounds(){ + return new SoundEvent[]{WizardrySounds.createSound("spell." + this.getRegistryName().getPath())}; + } + + // Note 1: The aim here is conciseness. Keeping the identifiers in the spell classes means we don't usually have to + // qualify them with a class name, we can simply type RANGE or whatever. + // Note 2: Identifiers should be named concisely but descriptively. Usually, "range" will suffice, but "duration" + // is somewhat ambiguous - is it the duration of a potion effect, a conjured item, a summoned mob or the casting + // itself? This is why it is qualified as "effect_duration", "minion_lifetime", etc. + + /** + * Adds the given JSON identifiers to the configurable base properties of this spell. This should be called from + * the constructor or {@link Spell#init()}. It is highly recommended that property keys be defined as constants, + * as they will be needed later to retrieve the properties during the casting methods. + *

    + * General spell classes will call this method to set any properties they require in order to work properly, and + * the relevant keys will be public constants. + * @param keys One or more spell property keys to add to the spell. By convention, these are lowercase_with_underscores. + * If any of these already exists, a warning will be printed to the console. + * @return The spell instance, allowing this method to be chained onto the constructor. + * @throws IllegalStateException if this method is called after the spell properties have been initialised. + */ + // Nobody can remove property keys, which guarantees that spell classes always have the properties they need. + // It also means that subclasses need not worry about properties already defined and used in their superclass. + // Conversely, general spell classes ONLY EVER define the properties they ACTUALLY USE. + public final Spell addProperties(String... keys){ + + if(properties != null) throw new IllegalStateException("Tried to add spell properties after they were initialised"); + + for(String key : keys) if(propertyKeys.contains(key)) Wizardry.logger.warn("Tried to add a duplicate property key '" + + key + "' to spell " + this.getRegistryName()); + + Collections.addAll(propertyKeys, keys); + + return this; + } + + /** Internal, do not use. */ + public final String[] getPropertyKeys(){ + return propertyKeys.toArray(new String[0]); + } + + /** Sets this spell's properties to the given {@link SpellProperties} object, but only if it doesn't already + * have one. This prevents spell properties from being changed after initialisation. */ + public void setProperties(@Nonnull SpellProperties properties){ + + if(this.properties == null){ + this.properties = properties; + }else{ + Wizardry.logger.info("A mod attempted to set a spell's properties, but they were already initialised."); + } + } + + /** Called from the event handler when a player logs in. */ + public static void syncProperties(EntityPlayer player){ + if(player instanceof EntityPlayerMP){ + // On the server side, send a packet to the player to synchronise their spell properties + // To avoid sending extra data unnecessarily, the spell properties are sent in order of spell ID + List spells = new ArrayList<>(registry.getValuesCollection()); + spells.sort(Comparator.comparingInt(Spell::networkID)); + WizardryPacketHandler.net.sendToAll(new PacketSpellProperties.Message(spells.stream() + .map(s -> s.properties).toArray(SpellProperties[]::new))); + }else{ + // On the client side, wipe the spell properties so the new ones can be set + for(Spell spell : registry){ + spell.properties = null; // TESTME: Can we guarantee this happens before the packet arrives? + } + } + } + + // These three methods are final because they are for use by subclasses (and are pseudo-static, so to speak). + + /** + * Convenience method that generates a sound event for this spell, with the given suffix. + * @param suffix The suffix to use in the name of the returned sound event (excluding the dot) + * @return A sound event called {@code spell.[unlocalised name].[suffix]}, where [suffix] is the given string. + * @see Spell#createSoundsWithSuffixes(String[]) + */ + public final SoundEvent createSoundWithSuffix(String suffix){ + return WizardrySounds.createSound("spell." + this.getRegistryName().getPath() + "." + suffix); + } + + /** + * Compact version of {@link Spell#createSoundWithSuffix(String)} which accepts multiple suffixes and packs them + * into an array. + * @param suffixes 1 or more suffixes to use in the names of the returned sound events (excluding dots) + * @return An array of the resulting sound events. + */ + public final SoundEvent[] createSoundsWithSuffixes(String... suffixes){ + return Arrays.stream(suffixes).map(this::createSoundWithSuffix).toArray(SoundEvent[]::new); + } + + /** + * Convenience method that generates an array of 3 sound events which can be fed directly into either of the + * continuous spell sound classes' constructors. + * @return An array of three sound events called {@code spell.[unlocalised name].start}, + * {@code spell.[unlocalised name].loop} and {@code spell.[unlocalised name].end} respectively. + */ + public final SoundEvent[] createContinuousSpellSounds(){ + return createSoundsWithSuffixes("start", "loop", "end"); + } + + // ============================================ Casting methods ============================================== + /** * Casts the spell. Each subclass must override this method and within it execute the code to make the spell work. * Returns a boolean so that the main onItemRightClick or onUsingItemTick method can check if the spell was actually * cast or whether a spell specific condition caused it not to be (for example, heal won't work if the player is on * full health), preventing unfair drain of mana. - *

    + *

    * Each spell must return true when it works or the spell will not use up mana. Note that (!world.isRemote) does not * count as a condition; return true should be outside it - in other words, return a value on both the client and * the server. - *

    + *

    * It's worth noting that on the client side, this method only gets called if the server side cast() method * succeeded, so you can put any particle spawning code outside of any success conditions if there are discrepancies * between client and server. - * - * @param world A reference to the world object. Again this is for convenience, you can also use caster.world. + * + * @param world The world in which the spell is being cast. * @param caster The EntityPlayer that cast the spell. * @param hand The hand that is holding the item used to cast the spell. If no item was used, this will be the main * hand. @@ -187,29 +324,28 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * {@code new SpellModifiers()}. * @return True if the spell succeeded and mana should be used up, false if not. */ - public abstract boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, - SpellModifiers modifiers); + public abstract boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers); /** * Casts the spell, but with an EntityLiving as the caster. Each subclass can optionally override this method and * within it execute the code to make the spell work. Returns a boolean to allow whatever calls this method to check * if the spell was actually cast or whether a spell specific condition caused it not to be (for example, heal won't * work if the caster is on full health). - *

    + *

    * This method is intended for use by NPCs (see {@link EntityWizard}) so that they can cast spells. Override it if * you want a spell to be cast by wizards. Note that you must also override {@link Spell#canBeCastByNPCs()} to * return true to allow wizards to select the spell. For some spells, this method may well be exactly the same as * the regular cast method; for others it won't be - for example, projectile-based spells are normally done using * the player's look vector, but NPCs need to use a target-based method instead. - *

    + *

    * Each spell must return true when it works. Note that (!world.isRemote) does not count as a condition; return true * should be outside it - in other words, return a value on both the client and the server. - *

    + *

    * It's worth noting that on the client side, this method only gets called if the server side cast() method * succeeded, so you can put any particle spawning code outside of any success conditions if there are discrepancies * between client and server. - * - * @param world A reference to the world object. This is for convenience, you can also use caster.world. + * + * @param world The world in which the spell is being cast. * @param caster The EntityLiving that cast the spell. * @param hand The hand that is holding the item used to cast the spell. This will almost certainly be the main * hand. @@ -226,6 +362,69 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return false; } + /** + * Casts the spell, but with an origin and a direction instead of a caster. Each subclass can optionally override this + * method and within it execute the code to make the spell work. Returns a boolean to allow whatever calls this method + * to check if the spell was actually cast or whether a spell specific condition caused it not to be (for example, heal + * won't work if the caster is on full health). + *

    + * This method is intended for use by dispensers and command blocks so that they can cast spells. Override it if + * you want a spell to be cast by dispensers. Note that you must also override {@link Spell#canBeCastByDispensers()} to + * return true to allow dispensers to select the spell. For some spells, this method may well be exactly the same as + * the regular cast method; for others it won't be - for example, projectile-based spells are normally done using + * the player's look vector, but dispensers need to use a facing-based method instead. + *

    + * Each spell must return true when it works. Note that (!world.isRemote) does not count as a condition; return true + * should be outside it - in other words, return a value on both the client and the server. + *

    + * It's worth noting that on the client side, this method only gets called if the server side cast() method + * succeeded, so you can put any particle spawning code outside of any success conditions if there are discrepancies + * between client and server. + * + * @param world The world in which the spell is being cast. + * @param x The x coordinate of the origin point of the spell. + * @param y The y coordinate of the origin point of the spell. + * @param z The z coordinate of the origin point of the spell. + * @param direction The cardinal (UDNSEW) direction in which the spell is being cast. + * @param ticksInUse The number of ticks the spell has already been cast for. For all non-continuous spells, this is + * 0 and is not used. + * @param duration The duration this spell will be cast for, or -1 if it will be cast indefinitely. For all + * non-continuous spells, this is 0 and is not used. This is intended for use in sound loops; there + * should be no need to use it for anything else. + * @param modifiers A {@link SpellModifiers} object containing the modifiers that have been applied to the spell. + * See the javadoc for that class for more information. If no modifiers are required, pass in + * {@code new SpellModifiers()}. + * @return True if the spell succeeded, false if not. Returns false by default. + */ + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + return false; + } + + /** + * Called when the spell stops being cast, either from running out of mana, being stopped by the caster, or due + * to a stack of scrolls running out. Only ever called for continuous spells. This method is mostly used + * for adding particle effects and sounds on spell finish. + *

    + * Because this method is not used in the majority of cases, it was deemed excessive to have three separate + * methods for players, NPCs and dispensers. Instead, some parameters may be null depending on the circumstances, + * similar to the implementation in {@link electroblob.wizardry.event.SpellCastEvent SpellCastEvent}. + * Be sure to check for this before using them! + * + * @param world The world in which the spell was cast. + * @param caster The player or NPC that cast the spell, or null if it was cast from a dispenser. + * @param x The x coordinate of the origin point of the spell, or NaN if the spell wasn't cast from a dispenser. + * @param y The y coordinate of the origin point of the spell, or NaN if the spell wasn't cast from a dispenser. + * @param z The z coordinate of the origin point of the spell, or NaN if the spell wasn't cast from a dispenser. + * @param direction The cardinal (UDNSEW) direction in which the spell was cast, or null if the spell wasn't cast + * from a dispenser. + * @param duration The number of ticks the spell was cast for. + * @param modifiers The modifiers the spell was cast with. + */ + // Conveniently, we can't always get a reference to the target for NPC casting once the spell ends (because it + // might have died or run off, or the NPC might have lost interest...) - so let's just not bother! + public void finishCasting(World world, @Nullable EntityLivingBase caster, double x, double y, double z, + @Nullable EnumFacing direction, int duration, SpellModifiers modifiers){} + /** * Whether NPCs such as wizards can cast this spell. If you have overridden * {@link Spell#cast(World, EntityLiving, EnumHand, int, EntityLivingBase, SpellModifiers)}, you should override @@ -235,13 +434,22 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return false; } + /** + * Whether dispensers can cast this spell. If you have overridden + * {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this + * to return true. + */ + public boolean canBeCastByDispensers(){ + return false; + } + /** * Whether this spell requires a packet to be sent when it is cast. Returns true by default, but can be overridden * to return false if the spell's cast() method does not use any code that must be executed client-side (i.e. - * particle spawning). Does nothing for continuous spells, because they never need to send packets. - *

    + * particle spawning). This is not checked for continuous spells, because they never need to send packets. + *

    * If in doubt, leave this method as is; it is purely an optimisation. - * + * * @return false if the spell code should only be run on the server and the client of the player casting * it
    * true if the spell code should be run on the server and all clients in the dimension @@ -251,33 +459,100 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C // Also, now I think about it, this method isn't going to make the slightest bit of difference to the item usage // actions since setItemInUse() is called in ItemWand, not the spell class - so the only thing that matters here is // the particles. - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return true; } - /** - * Returns this spell's id number, which now corresponds to its position in the spell registry. Returns -1 if the - * spell has not been registered. - */ - // This is final so nothing can override it, because that would cause all kinds of problems! - public final int id(){ + // ============================================ Getter methods ============================================== + + /** Returns the metadata for this spell, which corresponds to its registry ID, or -1 if the spell has not been + * registered.
    + *
    + * Because of how the registry system works, this won't change once assigned for a given world so is guaranteed to + * be backwards-compatible by design. However, for this reason if spells are removed there may be gaps in the ID + * numbers. If a continuous set of IDs is required (for networking), use {@link Spell#networkID()}. */ + public final int metadata(){ return ((ForgeRegistry)registry).getID(this); } - /** - * Returns the mod ID for this spell, which should be the ID of the mod that added it. The mod ID is used to tell - * wizardry which filepath to use for the spell's icon. As of Wizardry 1.2, the field itself is private, but this - * getter is provided for external use (though it is never called in the main Wizardry mod). - */ - public final String getModID(){ - return modID; + /** Returns this spell's network ID number, similar to mod-specific entity IDs.
    + *
    + * Unlike {@link Spell#metadata()}, this is guaranteed to be sequential so is suitable for indexed lookup. + * However, it may change if spells are removed so is not backwards-compatible. This means it should not be + * used for data storage. */ + public final int networkID(){ + return id; } - /** Returns the ResourceLocation for this spell's icon. */ + /** Returns the {@code ResourceLocation} for this spell's icon. */ public final ResourceLocation getIcon(){ return icon; } + /** Returns the {@code SoundEvent}s for this spell's sound. */ + public final SoundEvent[] getSounds(){ + return sounds; + } + + // Property getters - these are final to force addon devs to use the JSON system instead of just overriding them + + /** Returns the tier that this spell belongs to. */ + public final Tier getTier(){ + return properties.tier; + } + + /** Returns the element that this spell belongs to. */ + public final Element getElement(){ + return properties.element; + } + + /** Returns the type of spell this is classified as. */ + public final SpellType getType(){ + return properties.type; + } + + /** Returns the mana cost of the spell. If it is a continuous spell the cost is per second. */ + public final int getCost(){ + return properties.cost; + } + + /** Returns the charge-up time for the spell in ticks. */ + public final int getChargeup(){ + return properties.chargeup; + } + + /** Returns the cooldown for the spell in ticks. */ + public final int getCooldown(){ + return properties.cooldown; + } + + /** + * Returns the base value specified in JSON for the given identifier. This may be used from within the spell + * class, or from elsewhere (entities, items, etc.) via the spell's instance. + * + * @param identifier The JSON identifier for the required property. This must have been defined using + * {@link Spell#addProperties(String...)} or an exception will be thrown. + * @return The base value of the property, as a {@code Number} object. Internally this is handled as a float, but + * it is passed through as a {@code Number} to avoid casting. Be careful with rounding when extracting integer + * values! The JSON parser cannot guarantee that the property file has an integer value. + * @throws IllegalArgumentException if no property was defined with the given identifier. */ + public final Number getProperty(String identifier){ + return properties.getBaseValue(identifier); + } + + /** Returns whether the spell is enabled in any of the given {@link electroblob.wizardry.util.SpellProperties.Context Context}s. + * A spell may be disabled globally in the config, or it may be disabled for one or more specific contexts in + * its JSON file using a resource pack. If called with no arguments, defaults to any context, i.e. only returns + * false if the spell is completely disabled in all contexts. */ + public final boolean isEnabled(SpellProperties.Context... contexts){ + return enabled && (contexts.length == 0 || properties.isEnabled(contexts)); + } + + /** Sets whether the spell is enabled or not. */ + public final void setEnabled(boolean isEnabled){ + this.enabled = isEnabled; + } + /** * Returns the unlocalised name of the spell, without any prefixes or suffixes, e.g. "flame_ray". This should * only be used for translation purposes. @@ -286,6 +561,8 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return unlocalisedName; } + // ========================================== Translation methods ============================================ + /* The general idea with translation is to use net.minecraft.client.resources.I18n directly on the client side (and * just prepend formatting codes where necessary), and to use TextComponentTranslation on the server (setting the * style as necessary). TextComponentTranslation effectively stores what needs to be translated, without actually @@ -298,14 +575,14 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C */ @SideOnly(Side.CLIENT) public String getDisplayName(){ - return I18n.format("spell." + unlocalisedName); + return net.minecraft.client.resources.I18n.format("spell." + unlocalisedName); } /** * Returns a {@code TextComponentTranslation} which will be translated to the display name of the spell, without * formatting (i.e. not coloured). */ - public TextComponentTranslation getNameForTranslation(){ + public ITextComponent getNameForTranslation(){ return new TextComponentTranslation("spell." + unlocalisedName); } @@ -315,7 +592,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C */ @SideOnly(Side.CLIENT) public String getDisplayNameWithFormatting(){ - return this.element.getFormattingCode() + I18n.format("spell." + unlocalisedName); + return this.getElement().getFormattingCode() + net.minecraft.client.resources.I18n.format("spell." + unlocalisedName); } /** @@ -323,7 +600,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * formatting (i.e. coloured). */ public ITextComponent getNameForTranslationFormatted(){ - return new TextComponentTranslation("spell." + unlocalisedName).setStyle(this.element.getColour()); + return new TextComponentTranslation("spell." + unlocalisedName).setStyle(this.getElement().getColour()); } /** @@ -332,17 +609,124 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C */ @SideOnly(Side.CLIENT) public String getDescription(){ - return I18n.format("spell." + unlocalisedName + ".desc"); + return net.minecraft.client.resources.I18n.format("spell." + unlocalisedName + ".desc"); } - /** Returns whether the spell is enabled in the config. */ - public final boolean isEnabled(){ - return isEnabled; + // ============================================ Sound methods ============================================== + + /** + * Sets the sound parameters for this spell. + * @param volume The volume of the sound played by this spell, relative to 1. + * @param pitch The pitch of the sound played by this spell, relative to 1. + * @param pitchVariation The random variation in the pitch of the sound played by this spell. The pitch at which the + * sound is played will be randomly chosen from the range: {@code pitch +/- pitchVariation}. + * @return The spell instance, allowing this method to be chained onto the constructor. Note that since this method + * only returns a {@code Spell}, if you are chaining multiple methods onto the constructor this should be called last. + */ + public Spell soundValues(float volume, float pitch, float pitchVariation){ + this.volume = volume; + this.pitch = pitch; + this.pitchVariation = pitchVariation; + return this; } - /** Sets whether the spell is enabled or not. */ - public final void setEnabled(boolean isEnabled){ - this.isEnabled = isEnabled; + // The general motivation for the spell-based sound system is as follows: + // - There are a lot of spells, and each spell has at least one sound event, which means a lot of sounds! + // - Blocks and entities also define their own sounds, though this system is a bit half-hearted because they + // still have to be registered manually + // - Most spell sounds are used only from within their respective spells + // - I don't want hundreds of simple spell sounds that aren't referenced elsewhere cluttering up WizardrySounds, + // so I'm keeping them in the spell instead - if you really want them you can use getSounds, but really it's bad + // practice to reuse other sound events (something I found out the hard way!) + // - The pitch variation thing is annoying to keep repeating so it's also centralised here + + /** + * Plays this spell's sound at the given entity in the given world. This calls {@link Spell#playSound(World, double, double, double, int, int, SpellModifiers, String...)}, passing in the given entity's position as the xyz coordinates. Also checks if the given entity + * is silent, and if so, does not play the sound. + *

    + * If you are overriding the {@code Spell.playSound} methods, it is recommended that you override the xyz version + * instead of this one, since this method calls that one anyway - unless you want different behaviour for entities. + * @param world The world to play the sound in. + * @param entity The entity to play the sound at, provided it is not silent. + * @param ticksInUse The number of ticks this spell has already been cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param duration The number of ticks this spell will be cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param modifiers The modifiers this spell was cast with, passed in from the {@code cast(...)} methods. + * @param sounds A number of strings representing the sounds to be played. If omitted, all of this spell's sounds + * will be played at once. String format is as passed to {@link Spell#createSoundWithSuffix(String)}. + */ + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + if(!entity.isSilent()){ + this.playSound(world, entity.posX, entity.posY, entity.posZ, ticksInUse, duration, modifiers, sounds); + } + } + + /** + * Plays this spell's sound at the given position in the given world. This is a vector-based wrapper for + * {@link Spell#playSound(World, double, double, double, int, int, SpellModifiers, String...)}. + *

    + * If you are overriding the {@code Spell.playSound} methods, it is recommended that you override the xyz version + * instead of this one, since this method calls that one anyway. + * @param world The world to play the sound in. + * @param pos A vector representing the position to play the sound at. + * @param ticksInUse The number of ticks this spell has already been cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param duration The number of ticks this spell will be cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param modifiers The modifiers this spell was cast with, passed in from the {@code cast(...)} methods. + */ + protected void playSound(World world, Vec3d pos, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSound(world, pos.x, pos.y, pos.z, ticksInUse, duration, modifiers, sounds); + } + + /** + * Plays this spell's sounds at the given position in the given world. This is not called automatically; subclasses + * should call it at the appropriate point(s) in the cast methods. By default, it checks whether each sound is null + * before playing, so callers shouldn't have to. + *

    + * Usually, this method will be called by the general spell classes; this is clearly stated in those classes. When + * extending such a class, it is also possible to override this method to add extra sounds or change the sound + * behaviour entirely (for example, playing a continuous spell sound). + * @param world The world to play the sound in. + * @param x The x position to play the sound at. + * @param y The y position to play the sound at. + * @param z The z position to play the sound at. + * @param ticksInUse The number of ticks this spell has already been cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param duration The number of ticks this spell will be cast for, passed in from the {@code cast(...)} + * methods. Not used in the base method, but included for use by subclasses overriding this method. + * @param modifiers The modifiers this spell was cast with, passed in from the {@code cast(...)} methods. + */ + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + + List identifiers = Arrays.stream(sounds).map(s -> "spell." + this.getRegistryName().getPath() + "." + s) + .collect(Collectors.toList()); + + if(this.sounds != null){ + for(SoundEvent sound : this.sounds){ + ResourceLocation soundName = SoundEvent.REGISTRY.getNameForObject(sound); + if(soundName != null && (identifiers.size() == 0 || identifiers.contains(soundName.getPath()))){ + world.playSound(null, x, y, z, sound, WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f)); + } + } + } + } + + /** Helper method which plays a standard continuous spell sound loop on the first casting tick, which moves + * with the given entity. */ + protected final void playSoundLoop(World world, EntityLivingBase entity, int ticksInUse){ + if(ticksInUse == 0 && world.isRemote) Wizardry.proxy.playSpellSoundLoop(entity, this, this.sounds, + WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f)); + } + + /** Helper method which plays a standard continuous spell sound loop on the first casting tick, at the given + * coordinates. If the given duration is -1, the coordinates must be those of a dispenser. */ + protected final void playSoundLoop(World world, double x, double y, double z, int ticksInUse, int duration){ + if(ticksInUse == 0 && world.isRemote){ + Wizardry.proxy.playSpellSoundLoop(world, x, y, z, this, this.sounds, + WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f), duration); + } } // Spells are sorted according to tier and element. Where several spells have the same tier and element, @@ -350,57 +734,54 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C @Override public final int compareTo(Spell spell){ - if(this.tier.ordinal() > spell.tier.ordinal()){ + if(this.getTier().ordinal() > spell.getTier().ordinal()){ return 1; - }else if(this.tier.ordinal() < spell.tier.ordinal()){ + }else if(this.getTier().ordinal() < spell.getTier().ordinal()){ return -1; }else{ - if(this.element.ordinal() > spell.element.ordinal()){ - return 1; - }else if(this.element.ordinal() < spell.element.ordinal()){ - return -1; - }else{ - return 0; - } + return Integer.compare(this.getElement().ordinal(), spell.getElement().ordinal()); } } - // ================================================ Static methods ================================================== + // ============================================ Static methods ============================================== /** * Returns the total number of registered spells, excluding the 'None' spell. Returns the same number that would be - * returned by Spell.getSpells(Spell.allSpells).size(), but this method is more efficient. + * returned by {@code Spell.getSpells(Spell.allSpells).size()}, but this method is more efficient. */ public static int getTotalSpellCount(){ return registry.getValuesCollection().size() - 1; } /** - * Gets a spell instance from its integer id, which now corresponds to its id in the spell registry. If the given id - * has no spell (i.e. is less than 0 or greater than the total number of spells - 1) then it will return the - * {@link None} spell. - *

    - * If you are calling this from inside a loop in which you are iterating through the spells, there is probably a - * better way; see {@link Spell#getSpells(Predicate)}. + * Gets a spell instance from its integer metadata, which corresponds to its ID in the spell registry. If the given + * metadata has no spell assigned then the {@link None} spell will be returned. */ - public static Spell get(int id){ - if(id < 0 || id >= registry.getValuesCollection().size()){ - return Spells.none; - } - Spell spell = ((ForgeRegistry)registry).getValue(id); + public static Spell byMetadata(int metadata){ + Spell spell = ((ForgeRegistry)registry).getValue(metadata); return spell == null ? Spells.none : spell; } + /** Gets a spell instance from its network ID. Or the {@link None} spell if no such spell exists. */ + public static Spell byNetworkID(int id){ + if(id < 0 || id >= registry.getValuesCollection().size()){ + return Spells.none; + } + return registry.getValuesCollection().stream().filter(s -> s.id == id).findAny().orElse(Spells.none); + } + /** - * Returns the spell with the given registry name, or null if no such spell exists. - * + * Returns the spell with the given registry name, or null if no such spell exists. This is really only intended + * for cases where the user has input a name (currently commands and loot functions) and may have omitted the mod + * ID for spells in the base mod. Otherwise, use {@code Spell.registry.getValue(ResourceLocation)}. + * * @param name The registry name of the spell, in the form [mod id]:[spell name]. If no mod id is specified, it * defaults to {@link Wizardry#MODID}. */ public static Spell get(String name){ ResourceLocation key = new ResourceLocation(name); if(key.getNamespace().equals("minecraft")) key = new ResourceLocation(Wizardry.MODID, name); - return ((ForgeRegistry)registry).getValue(key); + return registry.getValue(key); } /** Returns a list of all registered spells' registry names, excluding the 'none' spell. Used in commands. */ @@ -416,34 +797,30 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * internal spells list; any changes you make to the returned list will have no effect on wizardry since the * returned list is local to this method. Never includes the {@link None} spell. For convenience, there are some * predefined predicates in the Spell class (some of these really aren't shortcuts any more): - *

    + *

    * {@link Spell#allSpells} will allow all spells to be returned
    - * {@link Spell#enabledSpells} will filter out any spells that are disabled in the config
    * {@link Spell#npcSpells} will only allow enabled spells that can be cast by NPCs (see * {@link Spell#canBeCastByNPCs()})
    * {@link Spell#nonContinuousSpells} will filter out continuous spells but not disabled spells
    - * {@link Spell.TierElementFilter} will only allow enabled spells of the specified tier and element - * + * {@link TierElementFilter} will only allow enabled spells of the specified tier and element + * * @param filter A Predicate<Spell> that the returned spells must satisfy. - * + * * @return A local, modifiable list of spells matching the given predicate. Note that this list may be * empty. */ public static List getSpells(Predicate filter){ - return registry.getValuesCollection().stream().filter(filter.and(p -> p != Spells.none)).collect(Collectors.toList()); + return registry.getValuesCollection().stream().filter(filter.and(s -> s != Spells.none)).collect(Collectors.toList()); } /** Predicate which allows all spells. */ public static Predicate allSpells = s -> true; - /** Predicate which allows all enabled spells. */ - public static Predicate enabledSpells = Spell::isEnabled; - /** Predicate which allows all non-continuous spells, even those that have been disabled. */ public static Predicate nonContinuousSpells = s -> !s.isContinuous; /** Predicate which allows all enabled spells for which {@link Spell#canBeCastByNPCs()} returns true. */ - public static Predicate npcSpells = s -> s.isEnabled() && s.canBeCastByNPCs(); + public static Predicate npcSpells = s -> s.isEnabled(SpellProperties.Context.NPCS) && s.canBeCastByNPCs(); /** * Predicate which allows all enabled spells of the given tier and element (create an instance of this class each @@ -454,23 +831,29 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C private Tier tier; private Element element; + private SpellProperties.Context[] contexts; /** * Creates a new TierElementFilter that checks for the given tier and element. Does not allow spells that have - * been disabled in the config. - * + * been disabled in the config or in their JSON files. + * * @param tier The EnumTier to check for. Pass in null to allow all tiers. * @param element The EnumElement to check for. Pass in null to allow all elements. + * @param contexts The {@link electroblob.wizardry.util.SpellProperties.Context Context}s in which to check + * for enabled spells. The spell must be enabled in at least one of these contexts to pass + * the filter. If omitted, defaults to all contexts i.e. only completely disabled spells are + * filtered out. */ - public TierElementFilter(Tier tier, Element element){ + public TierElementFilter(Tier tier, Element element, SpellProperties.Context... contexts){ this.tier = tier; this.element = element; + this.contexts = contexts; } @Override public boolean test(Spell spell){ - return spell.isEnabled() && (this.tier == null || spell.tier == this.tier) - && (this.element == null || spell.element == this.element); + return spell.isEnabled(contexts) && (this.tier == null || spell.getTier() == this.tier) + && (this.element == null || spell.getElement() == this.element); } - }; + } } diff --git a/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java b/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java new file mode 100644 index 00000000..f310ee2f --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java @@ -0,0 +1,109 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import java.util.List; + +/** [NYI] */ +public abstract class SpellAreaEffect extends Spell { + + // TODO: This class doesn't really work as it is right now, it needs rethinking. The aim is to try and have all the + // different casting methods call a single (abstract) positional method to do the actual AoE. + + /** The average number of particles to spawn per block in this spell's area of effect. */ + protected float particleDensity = 0.65f; + + public SpellAreaEffect(String name, EnumAction action){ + this(Wizardry.MODID, name, action); + } + + public SpellAreaEffect(String modID, String name, EnumAction action){ + super(modID, name, action, false); + this.addProperties(EFFECT_RADIUS); + } + + /** + * Sets the number of particles to spawn per block for this spell. + * @param particleDensity The average number of particles to spawn per block in this spell's area of effect. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellAreaEffect particleDensity(float particleDensity) { + this.particleDensity = particleDensity; + return this; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + List targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).floatValue() + * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + + targets.removeIf(target -> !AllyDesignationSystem.isValidTarget(caster, target)); + + for(EntityLivingBase target : targets){ + affectEntity(world, caster, target, modifiers); + } + + if(world.isRemote){ + spawnParticleEffect(world, caster, modifiers); + } + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + + } + + /** + * Called to do something to each entity within the spell's area of effect. + * @param world The world in which the spell was cast. + * @param caster The entity that cast the spell. + * @param target The entity to do something to. + * @param modifiers The modifiers the spell was cast with. + */ + protected abstract void affectEntity(World world, EntityLivingBase caster, EntityLivingBase target, SpellModifiers modifiers); + + /** + * Called to spawn the spell's particle effect. By default, this generates a set of random points within the spell's + * area of effect and calls {@link SpellAreaEffect#spawnParticle(World, double, double, double)} at each to spawn + * the individual particles. Only called client-side. Override to add a custom particle effect. + * @param world The world to spawn the particles in. + * @param caster The caster of the spell. + * @param modifiers The modifiers the spell was cast with. + */ + protected void spawnParticleEffect(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + double maxRadius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + int particleCount = (int)Math.round(particleDensity * Math.PI * maxRadius * maxRadius); + + for(int i=0; i

    + * Properties added by this type of spell: {@link Spell#RANGE} + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public class SpellArrow extends Spell { + + private static final float DISPENSER_INACCURACY = 1; // This is the same as for players + private static final float FALLBACK_VELOCITY = 2; // 2 seems to be a pretty standard value + + // The general contract for these spell subtypes is that any required parameters are set via the constructor and are + // final, whereas any non-critical parameters are set via chainable setters with sensible defaults if not. For example, + // the actual sound to play is required, but it makes sense for its volume and pitch to default to 1 if unspecified. + + /** A factory that creates projectile entities. */ + protected final Function arrowFactory; + + public SpellArrow(String name, Function arrowFactory){ + this(Wizardry.MODID, name, arrowFactory); + } + + public SpellArrow(String modID, String name, Function arrowFactory){ + super(modID, name, EnumAction.NONE, false); + this.arrowFactory = arrowFactory; + this.addProperties(RANGE); + } + + @Override public boolean requiresPacket(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + /** Computes the velocity the projectile should be launched at to achieve the required range. */ + // Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if + // the velocity is masquerading under the tag 'range' - so we'll let the code do the heavy lifting so people can + // input something meaningful. + protected float calculateVelocity(EntityMagicArrow projectile, SpellModifiers modifiers, float launchHeight){ + // The required range + float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + if(!projectile.doGravity()){ + // No sensible spell will do this - range is meaningless if the particle has no gravity or lifetime + if(projectile.getLifetime() <= 0) return FALLBACK_VELOCITY; + // Speed = distance/time (trivial, I know, but I've put it here for the sake of completeness) + return range / projectile.getLifetime(); + }else{ + // Arrows have gravity 0.05 + float g = 0.05f; + // Assume horizontal projection + return range / MathHelper.sqrt(2 * launchHeight/g); + } + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = arrowFactory.apply(world); + // Sets the necessary parameters + projectile.aim(caster, calculateVelocity(projectile, modifiers, caster.getEyeHeight() + - (float)EntityMagicArrow.LAUNCH_Y_OFFSET)); + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + addArrowExtras(projectile, caster, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + + caster.swingArm(hand); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + + if(target != null){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = arrowFactory.apply(world); + // Sets the necessary parameters + int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty()) + : WizardryUtilities.getDefaultAimingError(world.getDifficulty()); + projectile.aim(caster, target, calculateVelocity(projectile, modifiers, caster.getEyeHeight() + - (float)EntityMagicProjectile.LAUNCH_Y_OFFSET), aimingError); + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + addArrowExtras(projectile, caster, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + + caster.swingArm(hand); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + + return true; + } + + return false; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = arrowFactory.apply(world); + // Sets the necessary parameters + projectile.setPosition(x, y, z); + Vec3i vec = direction.getDirectionVec(); + projectile.shoot(vec.getX(), vec.getY(), vec.getZ(), calculateVelocity(projectile, modifiers, + 0.375f), DISPENSER_INACCURACY); // 0.375 is the height of the hole in a dispenser + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + addArrowExtras(projectile, null, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + + return true; + } + + /** + * Called just before the arrow is spawned. Does nothing by default, but subclasses can override to call extra + * methods on the spawned arrow. This method is only called server-side so cannot be used to spawn particles directly. + * @param arrow The entity being spawned. + * @param caster The caster of this spell, or null if it was cast by a dispenser. + * @param modifiers The modifiers this spell was cast with. + */ + protected void addArrowExtras(T arrow, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + // Subclasses can put spell-specific stuff here + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellBuff.java b/src/main/java/electroblob/wizardry/spell/SpellBuff.java new file mode 100644 index 00000000..5a7273a5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellBuff.java @@ -0,0 +1,206 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Generic superclass for all spells which buff their caster. + * This allows all the relevant code to be centralised, since these spells all work in the same way. Usually, a simple + * instantiation of this class is sufficient to create a buff spell; if something extra needs to be done, such as + * applying a non-potion buff, then methods can be overridden (perhaps using an anonymous class) to add the required + * functionality. + *

    + * Properties added by this type of spell: {@link SpellBuff#getDurationKey(Potion)}, {@link SpellBuff#getStrengthKey(Potion)} + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public class SpellBuff extends Spell { + + /** An array of factories for the status effects that this spell applies to its caster. The effect factory + * avoids the issue of the potions being registered after the spell. */ + protected final Supplier[] effects; + /** A set of all the different potions (status effects) that this spell applies to its caster. Loaded during + * init(). */ + protected Set potionSet; + /** The RGB colour values of the particles spawned when this spell is cast. */ + protected final float r, g, b; + + /** The number of sparkle particles spawned when this spell is cast. Defaults to 10. */ + protected float particleCount = 10; + + @SafeVarargs + public SpellBuff(String name, float r, float g, float b, Supplier... effects){ + this(Wizardry.MODID, name, r, g, b, effects); + } + + @SafeVarargs + public SpellBuff(String modID, String name, float r, float g, float b, Supplier... effects){ + super(modID, name, EnumAction.BOW, false); + this.effects = effects; + this.r = r; + this.g = g; + this.b = b; + } + + @Override + public void init(){ + // Loads the potion set + this.potionSet = Arrays.stream(effects).map(Supplier::get).collect(Collectors.toSet()); + + for(Potion potion : potionSet){ + // I don't like having this for all buff spells when some potions aren't affected by amplifiers + // TODO: Find a way of only adding the strength key if the potion is affected by amplifiers (dynamically if possible) + // BrewingRecipeRegistry#getOutput might be a good place to start + addProperties(getStrengthKey(potion)); + if(!potion.isInstant()) addProperties(getDurationKey(potion)); + } + } + + // Potion-specific equivalent to defining the identifiers as constants + + protected static String getDurationKey(Potion potion){ + return potion.getRegistryName().getPath() + "_duration"; + } + + protected static String getStrengthKey(Potion potion){ + return potion.getRegistryName().getPath() + "_strength"; + } + + /** + * Sets the number of sparkle particles spawned when this spell is cast. + * @param particleCount The number of particles. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellBuff particleCount(int particleCount){ + this.particleCount = particleCount; + return this; + } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + // Only return on the server side or the client probably won't spawn particles + if(!this.applyEffects(caster, modifiers) && !world.isRemote) return false; + if(world.isRemote) this.spawnParticles(world, caster, modifiers); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + // Wizards can only cast a buff spell if they don't already have its effects. + if(caster.getActivePotionMap().keySet().containsAll(potionSet)) return false; + // Only return on the server side or the client probably won't spawn particles + if(!this.applyEffects(caster, modifiers) && !world.isRemote) return false; + if(world.isRemote) this.spawnParticles(world, caster, modifiers); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + // Gets a 1x1x1 bounding box corresponding to the block in front of the dispenser + AxisAlignedBB boundingBox = new AxisAlignedBB(new BlockPos(x, y, z)); + List entities = world.getEntitiesWithinAABB(EntityLivingBase.class, boundingBox); + + float distance = -1; + EntityLivingBase nearestEntity = null; + // Finds the nearest entity within the bounding box + for(EntityLivingBase entity : entities){ + float newDistance = (float)entity.getDistance(x, y, z); + if(distance == -1 || newDistance < distance){ + distance = newDistance; + nearestEntity = entity; + } + } + + if(nearestEntity == null) return false; + + // Only return on the server side or the client probably won't spawn particles + if(!this.applyEffects(nearestEntity, modifiers) && !world.isRemote) return false; + if(world.isRemote) this.spawnParticles(world, nearestEntity, modifiers); + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + + return true; + } + + /** Actually applies the status effects to the caster. By default, this iterates through the array of effects and + * applies each in turn, multiplying the duration and amplifier by the appropriate modifiers. Particles are always + * hidden and isAmbient is always set to false. Override to do something special, like apply a non-potion buff. + * Returns a boolean to allow subclasses to cause the spell to fail if for some reason the effect cannot be applied + * (for example, {@link Heal} fails if the caster is on full health). */ + protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ + // This will generate 0 for novice and apprentice, and 1 for advanced and master + // TODO: Once we've found a way of detecting if amplifiers actually affect the potion type, implement it here. + int bonusAmplifier = getBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); + + for(Potion potion : potionSet){ + caster.addPotionEffect(new PotionEffect(potion, potion.isInstant() ? 1 : + (int)(getProperty(getDurationKey(potion)).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + (int)getProperty(getStrengthKey(potion)).floatValue() + bonusAmplifier, + false, true)); + } + + return true; + } + + /** Returns the number to be added to the potion amplifier(s) based on the given potency modifier. Override + * to define custom modifier handling. Delegates to {@link SpellBuff#getStandardBonusAmplifier(float)} by + * default. */ + protected int getBonusAmplifier(float potencyModifier){ + return getStandardBonusAmplifier(potencyModifier); + } + + /** Returns a number to be added to potion amplifiers based on the given potency modifier. This method uses + * a standard calculation which results in zero extra levels for novice and apprentice wands and one extra + * level for advanced and master wands (this generally seems to give about the right weight to potency + * modifiers). This is public static because it is useful in a variety of places. */ + public static int getStandardBonusAmplifier(float potencyModifier){ + return (int)((potencyModifier - 1) / 0.4); + } + + /** Spawns buff particles around the caster. Override to add a custom particle effect. Only called client-side. */ + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i = 0; i < particleCount; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(r, g, b).spawn(world); + } + + ParticleBuilder.create(Type.BUFF).entity(caster).clr(r, g, b).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellConjuration.java b/src/main/java/electroblob/wizardry/spell/SpellConjuration.java new file mode 100644 index 00000000..b982f9ea --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellConjuration.java @@ -0,0 +1,99 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.IConjuredItem; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.world.World; + +/** + * Generic superclass for all spells which conjure an item for a certain duration. + * This allows all the relevant code to be centralised, since these spells all work in the same way. Usually, a simple + * instantiation of this class is sufficient to create a conjuration spell; if something extra needs to be done, such as + * a custom particle effect or conjuring the item in a specific slot, then methods can be overridden (perhaps using an + * anonymous class) to add the required functionality. + *

    + * Properties added by this type of spell: {@link SpellConjuration#ITEM_LIFETIME} + *

    + * By default, this type of spell cannot be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell cannot be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + * @see IConjuredItem + */ +public class SpellConjuration extends Spell { + + public static final String ITEM_LIFETIME = "item_lifetime"; + + /** The item that is conjured by this spell. Should implement {@link IConjuredItem}. */ + protected final Item item; + + public SpellConjuration(String name, Item item){ + this(Wizardry.MODID, name, item); + } + + public SpellConjuration(String modID, String name, Item item){ + super(modID, name, EnumAction.BOW, false); + this.item = item; + addProperties(ITEM_LIFETIME); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(conjureItem(caster, modifiers)){ + + if(world.isRemote) spawnParticles(world, caster, modifiers); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + return false; + } + + /** Spawns sparkle particles around the caster. Override to add a custom particle effect. Only called client-side. */ + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = caster.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.7f, 0.9f, 1).spawn(world); + } + } + + /** Adds this spell's item to the given player's inventory, placing it in the main hand if the main hand is empty. + * Returns true if the item was successfully added to the player's inventory, false if there as no space or if the + * player already had the item. Override to add special conjuring behaviour. */ + protected boolean conjureItem(EntityPlayer caster, SpellModifiers modifiers){ + + ItemStack stack = new ItemStack(item); + + IConjuredItem.setDurationMultiplier(stack, modifiers.get(WizardryItems.duration_upgrade)); + IConjuredItem.setDamageMultiplier(stack, modifiers.get(SpellModifiers.POTENCY)); + + if(WizardryUtilities.doesPlayerHaveItem(caster, item)) return false; + + if(caster.getHeldItemMainhand().isEmpty()){ + caster.setHeldItem(EnumHand.MAIN_HAND, stack); + return true; + }else{ + return caster.inventory.addItemStackToInventory(stack); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellConstruct.java b/src/main/java/electroblob/wizardry/spell/SpellConstruct.java new file mode 100644 index 00000000..62483f62 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellConstruct.java @@ -0,0 +1,190 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.construct.EntityMagicConstruct; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.function.Function; + +/** + * Generic superclass for all spells which conjure constructs (i.e. instances of {@link EntityMagicConstruct}). + * This allows all the relevant code to be centralised, since these spells all work in a similar way. Usually, a simple + * instantiation of this class is sufficient to create a construct spell; if something extra needs to be done, such as + * particle spawning, then methods can be overridden (perhaps using an anonymous class) to add the required functionality. + * It is encouraged, however, to put extra functionality in the construct entity class instead whenever possible. + *

    + * This class spawns the construct entity at the caster's feet, like ring of fire and healing aura. Use + * {@link SpellConstructRanged} (which extends this class) for spells that spawn constructs at an aimed-at position. + *

    + * Properties added by this type of spell: {@link Spell#DURATION} (if the construct is not permanent) + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + * @see SpellConstructRanged + */ +public class SpellConstruct extends Spell { + + /** A factory that creates construct entities. */ + protected final Function constructFactory; + /** Whether the construct lasts indefinitely, i.e. does not disappear after a set time. */ + protected final boolean permanent; + /** Whether the construct must be spawned on the ground. Defaults to false. */ + protected boolean requiresFloor = false; + /** Whether constructs spawned by this spell may overlap. Defaults to false. */ + protected boolean allowOverlap = false; + + public SpellConstruct(String name, EnumAction action, Function constructFactory, boolean permanent){ + this(Wizardry.MODID, name, action, constructFactory, permanent); + } + + public SpellConstruct(String modID, String name, EnumAction action, Function constructFactory, boolean permanent){ + super(modID, name, action, false); + this.constructFactory = constructFactory; + this.permanent = permanent; + if(!permanent) this.addProperties(DURATION); + } + + @Override public boolean requiresPacket(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + /** + * Sets whether the construct must be spawned on the ground. + * @param requiresFloor True to require that the construct be spawned on the ground, false to allow it in mid-air. + * Defaults to false. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellConstruct floor(boolean requiresFloor){ + this.requiresFloor = requiresFloor; + return this; + } + + /** + * Sets whether constructs spawned by this spell may overlap. + * @param allowOverlap True to allow overlapping, false to prevent it. Defaults to false. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellConstruct overlap(boolean allowOverlap){ + this.allowOverlap = allowOverlap; + return this; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(caster.onGround || !requiresFloor){ + if(!spawnConstruct(world, caster.posX, caster.posY, caster.posZ, caster.onGround ? EnumFacing.UP : null, + caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + return false; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + + if(target != null){ + if(caster.onGround || !requiresFloor){ + if(!spawnConstruct(world, caster.posX, caster.posY, caster.posZ, caster.onGround ? EnumFacing.UP : null, + caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + } + + return false; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + Integer floor = (int)y; + + if(requiresFloor){ + floor = WizardryUtilities.getNearestFloor(world, new BlockPos(x, y, z), 1); + direction = EnumFacing.UP; + } + + if(floor != null){ + if(!spawnConstruct(world, x, floor, z, direction, null, modifiers)) return false; + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + return true; + } + + return false; + } + + /** + * Actually spawns the construct. By default, spawns the construct at the position of the caster and always returns + * true. Returning false will cause the spell to fail. + * @param world The world to spawn the construct in. + * @param x The x coordinate to spawn the construct at. + * @param y The y coordinate to spawn the construct at. + * @param z The z coordinate to spawn the construct at. + * @param side The side of a block that was hit, or null if the construct is being spawned in mid-air (only happens + * if {@link SpellConstruct#requiresFloor} is true). + * @param caster The EntityLivingBase that cast this spell. + * @param modifiers The modifiers with which the spell was cast. + * @return false to cause the spell to fail, true to continue with casting. + */ + protected boolean spawnConstruct(World world, double x, double y, double z, @Nullable EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + + if(!world.isRemote){ + // Creates a new construct using the supplied factory + T construct = constructFactory.apply(world); + // Sets the position of the construct (and initialises its bounding box) + construct.setPosition(x, y, z); + // Sets the various parameters + construct.setCaster(caster); + construct.lifetime = permanent ? -1 : (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + construct.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + addConstructExtras(construct, side, caster, modifiers); + // Prevents overlapping of multiple constructs of the same type. Since we have an instance here this is + // very simple. The trade-off is that we have to create the entity before the spell fails, but unless + // world.spawnEntity(...) is called, its scope is limited to this method so it should be fine. + // Needs to be last in case addConstructExtras modifies the bounding box + if(!allowOverlap && !world.getEntitiesWithinAABB(construct.getClass(), construct.getEntityBoundingBox()).isEmpty()) return false; + // Spawns the construct in the world + world.spawnEntity(construct); + } + + return true; + } + + /** + * Called just before each construct is spawned. Does nothing by default, but is provided to allow subclasses to call + * extra methods on the spawned entity. This method is only called server-side so cannot be used to spawn particles + * directly. + * @param construct The entity being spawned. + * @param side The side of a block that was hit, or null if the construct is being spawned in mid-air (only happens + * if {@link SpellConstruct#requiresFloor} is true). + * @param caster The caster of this spell, or null if it was cast by a dispenser. + * @param modifiers The modifiers this spell was cast with. + */ + // This is the reason this class is generic: it allows subclasses to do whatever they want to their specific entity, + // without needing to cast to it. + protected void addConstructExtras(T construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){} + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java b/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java new file mode 100644 index 00000000..05d1b618 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java @@ -0,0 +1,199 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.construct.EntityMagicConstruct; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.RayTracer; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.function.Function; + +/** + * Generic superclass for all spells which conjure constructs (i.e. instances of {@link EntityMagicConstruct}) at an + * aimed-at position (players and dispensers) or target (non-player spell casters). + * This allows all the relevant code to be centralised, since these spells all work in a similar way. Usually, a simple + * instantiation of this class is sufficient to create a construct spell; if something extra needs to be done, such as + * particle spawning, then methods can be overridden (perhaps using an anonymous class) to add the required functionality. + * It is encouraged, however, to put extra functionality in the construct entity class instead whenever possible. + *

    + * Properties added by this type of spell: {@link Spell#RANGE}, {@link Spell#DURATION} (if the construct is not + * permanent) + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + * @see SpellConstruct + */ +public class SpellConstructRanged extends SpellConstruct { + + /** Whether liquids count as blocks when raytracing. Defaults to false. */ + protected boolean hitLiquids = false; + /** Whether to ignore uncollidable blocks when raytracing. Defaults to false. */ + protected boolean ignoreUncollidables = false; + + public SpellConstructRanged(String name, Function constructFactory, boolean permanent){ + this(Wizardry.MODID, name, constructFactory, permanent); + } + + public SpellConstructRanged(String modID, String name, Function constructFactory, boolean permanent){ + super(modID, name, EnumAction.NONE, constructFactory, permanent); + this.addProperties(RANGE); + } + + /** + * Sets whether liquids count as blocks when raytracing. + * @param hitLiquids Whether to hit liquids when raytracing. If this is false, the spell will pass through + * liquids as if they weren't there. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell hitLiquids(boolean hitLiquids){ + this.hitLiquids = hitLiquids; + return this; + } + + /** + * Sets whether uncollidable blocks are ignored when raytracing. + * @param ignoreUncollidables Whether to hit uncollidable blocks when raytracing. If this is false, the spell will + * pass through uncollidable blocks as if they weren't there. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell ignoreUncollidables(boolean ignoreUncollidables){ + this.ignoreUncollidables = ignoreUncollidables; + return this; + } + + @Override public boolean requiresPacket(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + double range = getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, hitLiquids, ignoreUncollidables, false); + + if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && (rayTrace.sideHit == EnumFacing.UP || + !requiresFloor)){ + + if(!world.isRemote){ + + double x = rayTrace.hitVec.x; + double y = rayTrace.hitVec.y; + double z = rayTrace.hitVec.z; + + if(!spawnConstruct(world, x, y, z, rayTrace.sideHit, caster, modifiers)) return false; + } + + }else if(!requiresFloor){ + + if(!world.isRemote){ + + Vec3d look = caster.getLookVec(); + + double x = caster.posX + look.x * range; + double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() + look.y * range; + double z = caster.posZ + look.z * range; + + if(!spawnConstruct(world, x, y, z, null, caster, modifiers)) return false; + } + + }else{ + return false; + } + + caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, + SpellModifiers modifiers){ + + double range = getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + + if(target != null && caster.getDistance(target) <= range){ + + if(!world.isRemote){ + + double x = target.posX; + double y = target.posY; + double z = target.posZ; + + EnumFacing side = null; + + // If the target is not on the ground but the construct must be placed on the floor, searches for the + // floor under the caster and returns false if it does not find one within 3 blocks. + if(!target.onGround && requiresFloor){ + Integer floor = WizardryUtilities.getNearestFloor(world, new BlockPos(x, y, z), 3); + if(floor == null) return false; + y = floor; + side = EnumFacing.UP; + } + + if(!spawnConstruct(world, x, y, z, side, caster, modifiers)) return false; + } + + caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + return false; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + double range = getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + Vec3d origin = new Vec3d(x, y, z); + Vec3d endpoint = origin.add(new Vec3d(direction.getDirectionVec()).scale(range)); + RayTraceResult rayTrace = world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, false); + + if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK && (rayTrace.sideHit == EnumFacing.UP || + !requiresFloor)){ + + if(!world.isRemote){ + + double x1 = rayTrace.hitVec.x; + double y1 = rayTrace.hitVec.y; + double z1 = rayTrace.hitVec.z; + + if(!spawnConstruct(world, x1, y1, z1, rayTrace.sideHit, null, modifiers)) return false; + } + + }else if(!requiresFloor){ + + if(!world.isRemote){ + + if(!spawnConstruct(world, endpoint.x, endpoint.y, endpoint.z, null, null, modifiers)) return false; + } + + }else{ + return false; + } + + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + return true; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellMinion.java b/src/main/java/electroblob/wizardry/spell/SpellMinion.java new file mode 100644 index 00000000..f492b07a --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellMinion.java @@ -0,0 +1,220 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.living.ISummonedCreature; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.IEntityLivingData; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.function.Function; + +/** + * Generic superclass for all spells which summon minions (i.e. instances of {@link ISummonedCreature}). + * This allows all the relevant code to be centralised, since these spells all work in the same way. Usually, a simple + * instantiation of this class is sufficient to create a minion spell; if something extra needs to be done, such as + * particle spawning, then methods can be overridden (perhaps using an anonymous class) to add the required functionality. + * It is encouraged, however, to put extra functionality in the summoned creature class instead whenever possible. + *

    + * Properties added by this type of spell: {@link SpellMinion#MINION_LIFETIME} + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public class SpellMinion extends Spell { + + public static final String MINION_LIFETIME = "minion_lifetime"; + public static final String MINION_COUNT = "minion_count"; + public static final String SUMMON_RADIUS = "summon_radius"; + + /** The string identifier for the minion health spell modifier, which doubles as the identifier for the + * entity attribute modifier. */ + public static final String HEALTH_MODIFIER = "minion_health"; + /** The string identifier for the potency attribute modifier. */ + private static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; + + /** A factory that creates summoned creature entities. */ + protected final Function minionFactory; + /** Whether the minions are spawned in mid-air. Defaults to false. */ + protected boolean flying = false; + + public SpellMinion(String name, Function minionFactory){ + this(Wizardry.MODID, name, minionFactory); + } + + public SpellMinion(String modID, String name, Function minionFactory){ + super(modID, name, EnumAction.BOW, false); + this.minionFactory = minionFactory; + addProperties(MINION_LIFETIME, MINION_COUNT, SUMMON_RADIUS); + } + + /** + * Sets whether the minions are spawned in mid-air. + * @param flying True to spawn the minions in mid-air, false to spawn them on the ground. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellMinion flying(boolean flying){ + this.flying = flying; + return this; + } + + @Override public boolean requiresPacket(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(!this.spawnMinions(world, caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, + SpellModifiers modifiers){ + + if(!this.spawnMinions(world, caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + BlockPos pos = new BlockPos(x, y, z); + + // In this case it looks nice to have them all explode out from one position! (It also makes the code simpler...) + if(!world.isRemote){ + for(int i=0; i

    + * Properties added by this type of spell: {@link Spell#RANGE} + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public class SpellProjectile extends Spell { + + private static final float DISPENSER_INACCURACY = 1; // This is the same as for players + private static final float FALLBACK_VELOCITY = 1.5f; // 1.5 seems to be a pretty standard value + + // The general contract for these spell subtypes is that any required parameters are set via the constructor and are + // final, whereas any non-critical parameters are set via chainable setters with sensible defaults if not. For example, + // the actual sound to play is required, but it makes sense for its volume and pitch to default to 1 if unspecified. + + /** A factory that creates projectile entities. */ + protected final Function projectileFactory; + + public SpellProjectile(String name, Function projectileFactory) { + this(Wizardry.MODID, name, projectileFactory); + } + + public SpellProjectile(String modID, String name, Function projectileFactory){ + super(modID, name, EnumAction.NONE, false); + this.projectileFactory = projectileFactory; + addProperties(RANGE); + } + + @Override public boolean requiresPacket(){ return false; } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + /** Computes the velocity the projectile should be launched at to achieve the required range. */ + // Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if + // the velocity is masquerading under the tag 'range' - so we'll let the code do the heavy lifting so people can + // input something meaningful. + protected float calculateVelocity(EntityMagicProjectile projectile, SpellModifiers modifiers, float launchHeight){ + // The required range + float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + if(projectile.hasNoGravity()){ + // No sensible spell will do this - range is meaningless if the projectile has no gravity or lifetime + if(projectile.getLifetime() <= 0) return FALLBACK_VELOCITY; + // Speed = distance/time (trivial, I know, but I've put it here for the sake of completeness) + return range / projectile.getLifetime(); + }else{ + // It seems that in Minecraft, g is usually* 0.03 - the getter method is protected unfortunately + // * Potions and xp bottles seem to have more gravity (because that makes sense...) + float g = 0.03f; + // Assume horizontal projection + return range / MathHelper.sqrt(2 * launchHeight/g); + } + } + + // Previously we assumed the base range specified refers to the absolute maximum possible range + // when launched at the ideal angle of projection. See the following: + // https://math.stackexchange.com/questions/127300/maximum-range-of-a-projectile-launched-from-an-elevation + // The above link gives the formula: Rmax = u/g * sqrt(u^2 + 2gH), so u = sqrt(sqrt(g^2*(h^2+r^2)) - gh) + // This is probably overkill on the accuracy front, but... hey, I'm a perfectionist, what can I say? + //return MathHelper.sqrt(MathHelper.sqrt(g*g * (launchHeight*launchHeight + range*range)) - g*launchHeight); + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = projectileFactory.apply(world); + // Sets the necessary parameters + projectile.aim(caster, calculateVelocity(projectile, modifiers, caster.getEyeHeight() + - (float)EntityMagicProjectile.LAUNCH_Y_OFFSET)); + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade); + addProjectileExtras(projectile, caster, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + + caster.swingArm(hand); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + + if(target != null){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = projectileFactory.apply(world); + // Sets the necessary parameters + int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty()) + : WizardryUtilities.getDefaultAimingError(world.getDifficulty()); + projectile.aim(caster, target, calculateVelocity(projectile, modifiers, caster.getEyeHeight() + - (float)EntityMagicProjectile.LAUNCH_Y_OFFSET), aimingError); + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade); + addProjectileExtras(projectile, caster, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + + caster.swingArm(hand); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + + return true; + } + + return false; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + if(!world.isRemote){ + // Creates a projectile from the supplied factory + T projectile = projectileFactory.apply(world); + // Sets the necessary parameters + projectile.setPosition(x, y, z); + Vec3i vec = direction.getDirectionVec(); + projectile.shoot(vec.getX(), vec.getY(), vec.getZ(), calculateVelocity(projectile, modifiers, + 0.375f), DISPENSER_INACCURACY); // 0.375 is the height of the hole in a dispenser + projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade); + addProjectileExtras(projectile, null, modifiers); + // Spawns the projectile in the world + world.spawnEntity(projectile); + } + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + + return true; + } + + /** + * Called just before the projectile is spawned. Does nothing by default, but subclasses can override to call extra + * methods on the spawned projectile. This method is only called server-side so cannot be used to spawn particles directly. + * @param projectile The entity being spawned. + * @param caster The caster of this spell, or null if it was cast by a dispenser. + * @param modifiers The modifiers this spell was cast with. + */ + protected void addProjectileExtras(T projectile, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + // Subclasses can put spell-specific stuff here + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpellRay.java b/src/main/java/electroblob/wizardry/spell/SpellRay.java new file mode 100644 index 00000000..1dc46232 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/SpellRay.java @@ -0,0 +1,406 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.RayTracer; +import electroblob.wizardry.util.SpellModifiers; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +/** + * Generic superclass for all spells which use a raytrace to do something and (optionally) spawn particles along that + * trajectory. This is for both continuous ('stream') spells and non-continuous ('bolt') spells This allows all the + * relevant code to be centralised. This class differs from most other spell superclasses in that it is abstract and as + * such must be subclassed to define what the spell actually does. This is because ray-like spells do a wider variety of + * different things, so it does not make sense to define more specific functions in this class since they would be + * redundant in the majority of cases. + *

    + * N.B. The three abstract methods in this class have a {@link Nullable} caster parameter (the caster is null when + * the spell is cast by a dispenser). When implementing these methods, be sure to check whether the caster is + * {@code null} and deal with it appropriately. + *

    + * Properties added by this type of spell: {@link Spell#RANGE} + *

    + * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} + *

    + * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} + *

    + * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public abstract class SpellRay extends Spell { + + /** The distance below the caster's eyes that the bolt particles start from. */ + protected static final double Y_OFFSET = 0.25; + + /** The distance between spawned particles. Defaults to 0.85. */ + // 0.85 was chosen to keep it similar to the most common method used previously, which gave an effective spacing of + // 10/12 = 0.8333 when the spell did not hit anything. + protected double particleSpacing = 0.85; + /** The maximum jitter (random position offset) for spawned particles. Defaults to 0.1. */ + protected double particleJitter = 0.1; + /** The velocity of spawned particles in the direction the caster is aiming, can be negative. Defaults to 0. */ + protected double particleVelocity = 0; + /** Whether living entities are ignored when raytracing. Defaults to false. */ + protected boolean ignoreLivingEntities = false; + /** Whether liquids count as blocks when raytracing. Defaults to false. */ + protected boolean hitLiquids = false; + /** Whether to ignore uncollidable blocks when raytracing. Defaults to true. */ + protected boolean ignoreUncollidables = true; + /** The aim assist to use when raytracing. Defaults to 0. */ + protected float aimAssist = 0; + + public SpellRay(String name, boolean isContinuous, EnumAction action){ + this(Wizardry.MODID, name, isContinuous, action); + } + + public SpellRay(String modID, String name, boolean isContinuous, EnumAction action){ + super(modID, name, action, isContinuous); + this.addProperties(RANGE); + } + + // Although this class is abstract, someone might instantiate one of its subclasses more than once to make two + // different spells, which may require different parameters. These methods allow such instances to neatly set any + // relevant parameters by chaining them onto the constructor. + + /** + * Sets the distance between spawned particles. + * @param particleSpacing The distance between particles in the ray effect. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell particleSpacing(double particleSpacing){ + this.particleSpacing = particleSpacing; + return this; + } + + /** + * Sets the maximum jitter (random position offset) for spawned particles. + * @param particleJitter The maximum jitter for particles in the ray effect. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell particleJitter(double particleJitter){ + this.particleJitter = particleJitter; + return this; + } + + /** + * Sets the velocity of spawned particles; usually used for continuous spells. + * @param particleVelocity The velocity of spawned particles in the direction the caster is aiming, can be negative. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell particleVelocity(double particleVelocity){ + this.particleVelocity = particleVelocity; + return this; + } + + /** + * Sets whether entities are ignored when raytracing. + * @param ignoreLivingEntities Whether to ignore living entities when raytracing. If this is true, the spell + * will pass through living entities as if they weren't there. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell ignoreLivingEntities(boolean ignoreLivingEntities){ + this.ignoreLivingEntities = ignoreLivingEntities; + return this; + } + + /** + * Sets whether liquids count as blocks when raytracing. + * @param hitLiquids Whether to hit liquids when raytracing. If this is false, the spell will pass through + * liquids as if they weren't there. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell hitLiquids(boolean hitLiquids){ + this.hitLiquids = hitLiquids; + return this; + } + + /** + * Sets whether uncollidable blocks are ignored when raytracing. + * @param ignoreUncollidables Whether to hit uncollidable blocks when raytracing. If this is true, the spell will + * pass through uncollidable blocks as if they weren't there. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell ignoreUncollidables(boolean ignoreUncollidables){ + this.ignoreUncollidables = ignoreUncollidables; + return this; + } + + /** + * Sets the aim assist to use when raytracing. + * @param aimAssist The aim assist to use when raytracing. See {@link RayTracer#rayTrace(World, Vec3d, Vec3d, float, boolean, boolean, boolean, Class, java.util.function.Predicate)} for more details. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public Spell aimAssist(float aimAssist){ + this.aimAssist = aimAssist; + return this; + } + + @Override public boolean canBeCastByNPCs(){ return true; } + + @Override public boolean canBeCastByDispensers() { return true; } + + // Finally everything in here is standardised and written in a form that's actually readable - it was long overdue! + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + Vec3d look = caster.getLookVec(); + Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); + + if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false; + + if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + // IDEA: Add in an aiming error and trigger onMiss accordingly + Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); + Vec3d direction = null; + + boolean flag = false; + + if(target != null){ + + if((!ignoreLivingEntities || !WizardryUtilities.isLiving(target)) + && onEntityHit(world, target, null, caster, origin, ticksInUse, modifiers)){ + + direction = new Vec3d(target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ) + .subtract(origin); + flag = true; + + }else{ // Will run if the spell does not do anything special on entity hit. + + int x = MathHelper.floor(target.posX); + int y = (int)target.getEntityBoundingBox().minY - 1; // -1 because we need the block under the target + int z = MathHelper.floor(target.posZ); + BlockPos pos = new BlockPos(x, y, z); + + // This works as if the NPC had actually aimed at the floor beneath the target, so it needs to check + // that the block is not air and (optionally) not a liquid. + if(!world.isAirBlock(pos) && (!world.getBlockState(pos).getMaterial().isLiquid() || hitLiquids) + && onBlockHit(world, pos, EnumFacing.UP, null, caster, origin, ticksInUse, modifiers)){ + + direction = new Vec3d(x + 0.5, y + 1, z + 0.5).subtract(origin); + flag = true; + } + } + } + + // Wizards don't miss... yet + if(!flag) return false; + + // Particle spawning + if(world.isRemote){ + spawnParticleRay(world, origin, direction.normalize(), caster, direction.length()); + } + + if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + + Vec3d vec = new Vec3d(direction.getDirectionVec()); + Vec3d origin = new Vec3d(x, y, z); + + if(!shootSpell(world, origin, vec, null, ticksInUse, modifiers)) return false; + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + return true; + } + + /** + * Hook allowing subclasses to override the default range calculation on a per-cast basis. For example, grapple + * overrides this to change the range based on casting time so that its vine attaches to entities/blocks at the + * correct point and moves them accordingly. + * + * @param world The world in which the spell is being cast. + * @param origin A vector representing the coordinates of the origin point of the spell. + * @param direction A normalised vector representing the direction in which the spell is being cast. + * @param caster The entity casting the spell, or null if the spell is being cast from a dispenser. + * @param ticksInUse The number of ticks the spell has already been cast for. For all non-continuous spells, + * this is 0 and is not used. + * @param modifiers The SpellModifiers object with which this spell is being cast. + * @return The range to be used for this particular casting of the spell. + */ + // Technically you could alter the range in the SpellModifiers object by overriding the cast method but that + // would be a bit of a hack since it's not really what spell modifiers are for. + protected double getRange(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){ + return getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + } + + /** + * Hook allowing subclasses to determine whether the caster swings their arm when casting the spell. By default, + * returns false for continuous spells and true for all others. + * + * @param world A reference to the world object. This is for convenience, you can also use caster.world. + * @param caster The EntityLivingBase that cast the spell. + * @param hand The hand that is holding the item used to cast the spell. If no item was used, this will be the + * main hand. + * @param ticksInUse The number of ticks the spell has already been cast for. For all non-continuous spells, this is + * 0 and is not used. For continuous spells, it is passed in as the maximum use duration of the item minus + * the count parameter in onUsingItemTick and therefore it increases by 1 each tick. + * @return True if the caster should swing their arm when casting this spell, false if not. + */ + protected boolean casterSwingsArm(World world, EntityLivingBase caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + return !this.isContinuous; + } + + /** Player and dispenser casting are almost identical so this takes care of the shared stuff. This is mainly for internal use. */ + protected boolean shootSpell(World world, Vec3d origin, Vec3d direction, @Nullable EntityPlayer caster, int ticksInUse, SpellModifiers modifiers){ + + double range = getRange(world, origin, direction, caster, ticksInUse, modifiers); + Vec3d endpoint = origin.add(direction.scale(range)); + + // Change the filter depending on whether living entities are ignored or not + RayTraceResult rayTrace = RayTracer.rayTrace(world, origin, endpoint, aimAssist, hitLiquids, + ignoreUncollidables, false, Entity.class, ignoreLivingEntities ? WizardryUtilities::isLiving + : RayTracer.ignoreEntityFilter(caster)); + + boolean flag = false; + + if(rayTrace != null){ + // Doesn't matter which way round these are, they're mutually exclusive + if(rayTrace.typeOfHit == RayTraceResult.Type.ENTITY){ + // Do whatever the spell does when it hits an entity + flag = onEntityHit(world, rayTrace.entityHit, rayTrace.hitVec, caster, origin, ticksInUse, modifiers); + // If the spell succeeded, clip the particles to the correct distance so they don't go through the entity + if(flag) range = origin.distanceTo(rayTrace.hitVec); + + }else if(rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ + // Do whatever the spell does when it hits an block + flag = onBlockHit(world, rayTrace.getBlockPos(), rayTrace.sideHit, rayTrace.hitVec, caster, origin, ticksInUse, modifiers); + // Clip the particles to the correct distance so they don't go through the block + // Unlike with entities, this is done regardless of whether the spell succeeded, since no spells go + // through blocks (and in fact, even the ray tracer itself doesn't do that) + range = origin.distanceTo(rayTrace.hitVec); + } + } + + // If flag is false, either the spell missed or the relevant entity/block hit method returned false + if(!flag && !onMiss(world, caster, origin, direction, ticksInUse, modifiers)) return false; + + // Particle spawning + if(world.isRemote){ + spawnParticleRay(world, origin, direction, caster, range); + } + + return true; + } + + // The exact behaviour of the returned values of the following three methods can be a little confusing. Normally, + // either onEntityHit or onBlockHit (or both) will return true when the spell succeeded in hitting the block or + // entity, and false if not (note that those two methods are mutually exclusive). If false is returned, onMiss will + // be called - if either of the other methods returns true, onMiss will only be called for a complete miss. + + /** + * Called when the spell hits an entity. Will never be called if ignoreLivingEntities is true. + * @param world The world the entity is in. + * @param target The entity that was hit. + * @param hit A vector representing the exact position at which the spell first hit the entity. Usually used for + * particle spawning. + * @param caster The caster of this spell, or null if this spell was cast from a dispenser. N.B. It is strongly + * recommended that the origin parameter is used instead of taking the caster's position directly. + * @param origin The position at which this spell originated. If the caster is not null, this will be at the caster's + * eyes. + * @param ticksInUse The number of ticks the spell has already been cast for (used only for continuous spells). + * @param modifiers The modifiers this spell was cast with. + * @return True to continue with spell casting and spawn particles, false to trigger a miss (N.B. you will need to + * return false from {@link SpellRay#onMiss(World, EntityLivingBase, Vec3d, Vec3d, int, SpellModifiers)} if a miss + * should not consume mana). Returning false from this method will make it look as if the spell passed right + * through it, so if a spell spawns particles when it misses this method should return true even for non-living + * entities. + */ + protected abstract boolean onEntityHit(World world, Entity target, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers); + + /** + * Called when the spell hits a block. + * @param world The world the block is in. + * @param pos The BlockPos of the block that was hit. + * @param side The side of the block that was hit. + * @param hit A vector representing the exact position at which the spell first hit the block. Usually used for + * particle spawning. + * @param caster The caster of this spell, or null if this spell was cast from a dispenser. + * @param origin The position at which this spell originated. If the caster is not null, this will be at the caster's + * eyes. + * @param ticksInUse The number of ticks the spell has already been cast for (used only for continuous spells). + * @param modifiers The modifiers this spell was cast with. + * @return True to continue with spell casting and spawn particles, false to trigger a miss (N.B. you will need to + * return false from {@link SpellRay#onMiss(World, EntityLivingBase, Vec3d, Vec3d, int, SpellModifiers)} if a miss should not consume + * mana). + */ + protected abstract boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers); + + /** + * Called when the spell does not hit anything or when the spell hits something it has no effect on. Most of the time + * this will just return true or false, but some spells may, for example, display a chat readout or spawn custom + * particles. It is worth noting that this can affect how easy the spell is to identify. + * @param world The world the spell is in. + * @param caster The caster of this spell, or null if this spell was cast from a dispenser. + * @param origin The position at which this spell originated. If the caster is not null, this will be at the caster's + * eyes. + * @param direction A normalised vector in the direction this spell was cast (useful for custom particle effects). + * @param ticksInUse The number of ticks the spell has already been cast for (used only for continuous spells). + * @param modifiers The modifiers this spell was cast with. + * @return True to continue with spell casting and spawn particles, false to cause the spell to fail. + */ + protected abstract boolean onMiss(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers); + + /** + * Highest-level particle spawning method, only called client-side. 'Normal' subclasses should not need to override + * this method; by default it spawns a line of particles, applying jitter and then calling + * {@link SpellRay#spawnParticle(World, double, double, double, double, double, double)} at each point. Override to replace this with + * an entirely custom particle effect - this is done by a few spells in the main mod to spawn beam-type particles. + * @param world The world in which to spawn the particles. + * @param origin A vector representing the start point of the line of particles. + * @param direction A normalised vector representing the direction of the line of particles. + * @param caster The entity that cast this spell, or null if it was cast by a dispenser. + * @param distance The length of the line of particles, already set to the appropriate distance based on the spell's + */ + // The caster argument is only really useful for spawning targeted particles continuously + protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, EntityLivingBase caster, double distance){ + + Vec3d velocity = direction.scale(particleVelocity); + + for(double d = particleSpacing; d <= distance; d += particleSpacing){ + double x = origin.x + d*direction.x + particleJitter * (world.rand.nextDouble()*2 - 1); + double y = origin.y + d*direction.y + particleJitter * (world.rand.nextDouble()*2 - 1); + double z = origin.z + d*direction.z + particleJitter * (world.rand.nextDouble()*2 - 1); + spawnParticle(world, x, y, z, velocity.x, velocity.y, velocity.z); + } + } + + /** + * Called at each point along the spell trajectory to spawn one or more particles at that point. Only called + * client-side. Does nothing by default. + * @param world The world in which to spawn the particle. + * @param x The x-coordinate to spawn the particle at, with jitter already applied. + * @param y The y-coordinate to spawn the particle at, with jitter already applied. + * @param z The z-coordinate to spawn the particle at, with jitter already applied. + * @param vx The x velocity to spawn the particle with. Usually this is only non-zero for continuous spells. + * @param vy The y velocity to spawn the particle with. Usually this is only non-zero for continuous spells. + * @param vz The z velocity to spawn the particle with. Usually this is only non-zero for continuous spells. + */ + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){} + +} diff --git a/src/main/java/electroblob/wizardry/spell/SpiderSwarm.java b/src/main/java/electroblob/wizardry/spell/SpiderSwarm.java deleted file mode 100644 index fbb9d926..00000000 --- a/src/main/java/electroblob/wizardry/spell/SpiderSwarm.java +++ /dev/null @@ -1,76 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntitySpiderMinion; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SpiderSwarm extends Spell { - - public SpiderSwarm(){ - super(Tier.ADVANCED, 45, Element.EARTH, "spider_swarm", SpellType.MINION, 200, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - for(int i = 0; i < 5; i++){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 3, 6); - // The spell instantly fails if no space was found (see javadoc for the above method). - if(pos == null) return false; - - EntitySpiderMinion spider = new EntitySpiderMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(spider); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - // Can't possibly get this far if nothing was spawned. - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - for(int i = 0; i < 5; i++){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 3, 6); - // The spell instantly fails if no space was found (see javadoc for the above method). - if(pos == null) return false; - - EntitySpiderMinion spider = new EntitySpiderMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(spider); - } - } - - caster.playSound(SoundEvents.BLOCK_FIRE_EXTINGUISH, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - // Can't possibly get this far if nothing was spawned. - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } -} diff --git a/src/main/java/electroblob/wizardry/spell/StaticAura.java b/src/main/java/electroblob/wizardry/spell/StaticAura.java deleted file mode 100644 index b04cc612..00000000 --- a/src/main/java/electroblob/wizardry/spell/StaticAura.java +++ /dev/null @@ -1,71 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class StaticAura extends Spell { - - public StaticAura(){ - super(Tier.ADVANCED, 40, Element.LIGHTNING, "static_aura", SpellType.DEFENCE, 250, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.static_aura)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.static_aura, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SPARK, 1.0F, - world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - return false; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - // Cannot be cast when it has already been cast - if(!caster.isPotionActive(WizardryPotions.static_aura)){ - if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.static_aura, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - } - caster.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.4F); - return true; - } - return false; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonBlaze.java b/src/main/java/electroblob/wizardry/spell/SummonBlaze.java deleted file mode 100644 index 9a67355b..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonBlaze.java +++ /dev/null @@ -1,71 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityBlazeMinion; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonBlaze extends Spell { - - public SummonBlaze(){ - super(Tier.ADVANCED, 40, Element.FIRE, "summon_blaze", SpellType.MINION, 200, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityBlazeMinion blaze = new EntityBlazeMinion(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(blaze); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityBlazeMinion blaze = new EntityBlazeMinion(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(blaze); - } - - caster.playSound(SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonIceGiant.java b/src/main/java/electroblob/wizardry/spell/SummonIceGiant.java deleted file mode 100644 index 00fdc4bf..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonIceGiant.java +++ /dev/null @@ -1,53 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityIceGiant; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonIceGiant extends Spell { - - public SummonIceGiant(){ - super(Tier.MASTER, 100, Element.ICE, "summon_ice_giant", SpellType.MINION, 400, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - if(!world.isRemote){ - - EntityIceGiant icegiant = new EntityIceGiant(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(icegiant); - } - - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)pos.getX() + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)pos.getY() + 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)pos.getZ() + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); - } - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, - world.rand.nextFloat() * 0.1F + 0.2F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonIceWraith.java b/src/main/java/electroblob/wizardry/spell/SummonIceWraith.java deleted file mode 100644 index 83571178..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonIceWraith.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityIceWraith; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonIceWraith extends Spell { - - public SummonIceWraith(){ - super(Tier.ADVANCED, 40, Element.ICE, "summon_ice_wraith", SpellType.MINION, 200, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityIceWraith iceWraith = new EntityIceWraith(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(iceWraith); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityIceWraith iceWraith = new EntityIceWraith(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(iceWraith); - } - caster.playSound(SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java b/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java index d7f48844..9feaca7d 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java +++ b/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java @@ -1,15 +1,11 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.monster.EntityIronGolem; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; @@ -18,33 +14,37 @@ import net.minecraft.world.World; public class SummonIronGolem extends Spell { public SummonIronGolem(){ - super(Tier.MASTER, 175, Element.SORCERY, "summon_iron_golem", SpellType.MINION, 400, EnumAction.BOW, false); + super("summon_iron_golem", EnumAction.BOW, false); + addProperties(SpellMinion.SUMMON_RADIUS); + soundValues(1, 1.1f, 0.2f); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, getProperty(SpellMinion.SUMMON_RADIUS).intValue(), + getProperty(SpellMinion.SUMMON_RADIUS).intValue()); + if(pos == null) return false; if(!world.isRemote){ + EntityIronGolem golem = new EntityIronGolem(world); golem.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + golem.setPlayerCreated(true); world.spawnEntity(golem); - } - - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)pos.getX() + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)pos.getY() + 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)pos.getZ() + world.rand.nextFloat() * 2 - 1.0F); - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); + + }else{ + + for(int i=0; i<10; i++){ + double x = pos.getX() + world.rand.nextDouble() * 2 - 1; + double y = pos.getY() + 0.5 + world.rand.nextDouble(); + double z = pos.getZ() + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).clr(0.6f, 0.6f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/SummonLightningWraith.java b/src/main/java/electroblob/wizardry/spell/SummonLightningWraith.java deleted file mode 100644 index 0baff375..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonLightningWraith.java +++ /dev/null @@ -1,70 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityLightningWraith; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonLightningWraith extends Spell { - - public SummonLightningWraith(){ - super(Tier.ADVANCED, 40, Element.LIGHTNING, "summon_lightning_wraith", SpellType.MINION, 200, EnumAction.BOW, - false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityLightningWraith lightningWraith = new EntityLightningWraith(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(lightningWraith); - } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityLightningWraith lightningWraith = new EntityLightningWraith(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(lightningWraith); - } - caster.playSound(SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonPhoenix.java b/src/main/java/electroblob/wizardry/spell/SummonPhoenix.java deleted file mode 100644 index c74b39d9..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonPhoenix.java +++ /dev/null @@ -1,46 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityPhoenix; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonPhoenix extends Spell { - - public SummonPhoenix(){ - super(Tier.MASTER, 150, Element.FIRE, "summon_phoenix", SpellType.MINION, 400, EnumAction.BOW, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityPhoenix phoenix = new EntityPhoenix(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, caster, - (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(phoenix); - } - - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonShadowWraith.java b/src/main/java/electroblob/wizardry/spell/SummonShadowWraith.java index 6f00f8f2..46f58165 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonShadowWraith.java +++ b/src/main/java/electroblob/wizardry/spell/SummonShadowWraith.java @@ -1,48 +1,14 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntityShadowWraith; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -public class SummonShadowWraith extends Spell { +public class SummonShadowWraith extends SpellMinion { public SummonShadowWraith(){ - super(Tier.MASTER, 100, Element.NECROMANCY, "summon_shadow_wraith", SpellType.MINION, 400, EnumAction.BOW, - false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityShadowWraith shadowWraith = new EntityShadowWraith(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(shadowWraith); - } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; + super("summon_shadow_wraith", EntityShadowWraith::new); + this.soundValues(1, 1.1f, 0.1f); } @SideOnly(Side.CLIENT) diff --git a/src/main/java/electroblob/wizardry/spell/SummonSkeleton.java b/src/main/java/electroblob/wizardry/spell/SummonSkeleton.java index 68bcbcf1..095be078 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSkeleton.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSkeleton.java @@ -1,75 +1,38 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntitySkeletonMinion; +import electroblob.wizardry.entity.living.EntityStrayMinion; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.inventory.EntityEquipmentSlot; -import net.minecraft.item.EnumAction; import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public class SummonSkeleton extends Spell { +public class SummonSkeleton extends SpellMinion { public SummonSkeleton(){ - super(Tier.APPRENTICE, 15, Element.NECROMANCY, "summon_skeleton", SpellType.MINION, 50, EnumAction.BOW, false); + super("summon_skeleton", EntitySkeletonMinion::new); + this.soundValues(7, 0.6f, 0); } @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntitySkeletonMinion skeleton = new EntitySkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - world.spawnEntity(skeleton); + protected EntitySkeletonMinion createMinion(World world, EntityLivingBase caster, SpellModifiers modifiers){ + if(caster instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_minion_variants)){ + return new EntityStrayMinion(world); + }else{ + return super.createMinion(world, caster, modifiers); } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntitySkeletonMinion skeleton = new EntitySkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - world.spawnEntity(skeleton); - } - caster.playSound(WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; + protected void addMinionExtras(EntitySkeletonMinion minion, BlockPos pos, EntityLivingBase caster, SpellModifiers modifiers, int alreadySpawned){ + minion.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + minion.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); } } diff --git a/src/main/java/electroblob/wizardry/spell/SummonSkeletonLegion.java b/src/main/java/electroblob/wizardry/spell/SummonSkeletonLegion.java index 02defe38..72ecd26f 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSkeletonLegion.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSkeletonLegion.java @@ -1,78 +1,50 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntitySkeletonMinion; +import electroblob.wizardry.entity.living.EntityStrayMinion; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; -import net.minecraft.init.SoundEvents; import net.minecraft.inventory.EntityEquipmentSlot; -import net.minecraft.item.EnumAction; import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public class SummonSkeletonLegion extends Spell { +public class SummonSkeletonLegion extends SpellMinion { public SummonSkeletonLegion(){ - super(Tier.MASTER, 100, Element.NECROMANCY, "summon_skeleton_legion", SpellType.MINION, 400, EnumAction.BOW, - false); + super("summon_skeleton_legion", EntitySkeletonMinion::new); + this.soundValues(1, 1.1f, 0.1f); } @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - // Archers - for(int i = 0; i < 3; i++){ - // This is all so much neater now thanks to this method. - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 3, 6); - // The spell instantly fails if no space was found (see javadoc for the above method). - if(pos == null) return false; - - EntitySkeletonMinion skeleton = new EntitySkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(1200 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); - skeleton.setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(Items.CHAINMAIL_HELMET)); - skeleton.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.CHAINMAIL_CHESTPLATE)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - skeleton.setDropChance(EntityEquipmentSlot.HEAD, 0.0f); - skeleton.setDropChance(EntityEquipmentSlot.CHEST, 0.0f); - world.spawnEntity(skeleton); - } - // Swordsmen - for(int i = 0; i < 3; i++){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 3, 6); - // The spell instantly fails if no space was found (see javadoc for the above method). - if(pos == null) return false; - - EntitySkeletonMinion skeleton = new EntitySkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(1200 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); - skeleton.setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(Items.CHAINMAIL_HELMET)); - skeleton.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.CHAINMAIL_CHESTPLATE)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - skeleton.setDropChance(EntityEquipmentSlot.HEAD, 0.0f); - skeleton.setDropChance(EntityEquipmentSlot.CHEST, 0.0f); - world.spawnEntity(skeleton); - } + protected EntitySkeletonMinion createMinion(World world, EntityLivingBase caster, SpellModifiers modifiers){ + if(caster instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_minion_variants)){ + return new EntityStrayMinion(world); + }else{ + return super.createMinion(world, caster, modifiers); } + } - // Can't possibly get this far if nothing was spawned. - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; + @Override + protected void addMinionExtras(EntitySkeletonMinion minion, BlockPos pos, EntityLivingBase caster, SpellModifiers modifiers, int alreadySpawned){ + + if(alreadySpawned % 2 == 0){ + // Archers + minion.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.BOW)); + }else{ + // Swordsmen + minion.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); + } + + minion.setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(Items.CHAINMAIL_HELMET)); + minion.setItemStackToSlot(EntityEquipmentSlot.CHEST, new ItemStack(Items.CHAINMAIL_CHESTPLATE)); + minion.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); + minion.setDropChance(EntityEquipmentSlot.HEAD, 0.0f); + minion.setDropChance(EntityEquipmentSlot.CHEST, 0.0f); } } diff --git a/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java b/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java index 06c71eee..2f77947c 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java @@ -1,12 +1,8 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.monster.EntitySnowman; import net.minecraft.entity.player.EntityPlayer; @@ -18,7 +14,9 @@ import net.minecraft.world.World; public class SummonSnowGolem extends Spell { public SummonSnowGolem(){ - super(Tier.APPRENTICE, 15, Element.ICE, "summon_snow_golem", SpellType.MINION, 20, EnumAction.BOW, false); + super("summon_snow_golem", EnumAction.BOW, false); + this.soundValues(1, 1, 0.4f); + addProperties(SpellMinion.SUMMON_RADIUS); } @Override @@ -28,21 +26,22 @@ public class SummonSnowGolem extends Spell { if(pos == null) return false; if(!world.isRemote){ + EntitySnowman snowman = new EntitySnowman(world); snowman.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); world.spawnEntity(snowman); - } - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)pos.getX() + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)pos.getY() + 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)pos.getZ() + world.rand.nextFloat() * 2 - 1.0F); - if(world.isRemote){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.6f, 0.6f, 1.0f); + + }else{ + + for(int i=0; i<10; i++){ + double x = pos.getX() + world.rand.nextDouble() * 2 - 1; + double y = pos.getY() + 0.5 + world.rand.nextDouble(); + double z = pos.getZ() + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(0.6f, 0.6f, 1).spawn(world); } } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); + + playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java b/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java index 359e2b45..a9f02984 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java @@ -1,53 +1,83 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.IStoredVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.EntitySpiritHorse; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.Entity; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttribute; +import net.minecraft.entity.passive.AbstractHorse; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; + +import java.util.UUID; public class SummonSpiritHorse extends Spell { + /** The string identifier for the potency attribute modifier. */ + private static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; + + private static final IAttribute JUMP_STRENGTH; + // Why is this protected? Doesn't that defeat the point of the attribute system? + static { + // Great, now I have to reflect into this class too. + JUMP_STRENGTH = ObfuscationReflectionHelper.getPrivateValue(AbstractHorse.class, null, "field_110271_bv"); + } + + public static final IStoredVariable UUID_KEY = IStoredVariable.StoredVariable.ofUUID("spiritHorseUUID", Persistence.ALWAYS); + public SummonSpiritHorse(){ - super(Tier.ADVANCED, 50, Element.EARTH, "summon_spirit_horse", SpellType.MINION, 150, EnumAction.BOW, false); + super("summon_spirit_horse", EnumAction.BOW, false); + addProperties(SpellMinion.SUMMON_RADIUS); + soundValues(0.7f, 1.2f, 0.4f); + WizardData.registerStoredVariables(UUID_KEY); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - WizardData properties = WizardData.get(caster); + WizardData data = WizardData.get(caster); - if(!properties.hasSpiritHorse){ - if(!world.isRemote){ + if(!world.isRemote){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; + Entity oldHorse = WizardryUtilities.getEntityByUUID(world, data.getVariable(UUID_KEY)); - EntitySpiritHorse horse = new EntitySpiritHorse(world); - horse.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); - horse.setTamedBy(caster); - horse.setHorseSaddled(true); - world.spawnEntity(horse); - } - properties.hasSpiritHorse = true; - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; + if(oldHorse != null) oldHorse.setDead(); + + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + if(pos == null) return false; + + EntitySpiritHorse horse = new EntitySpiritHorse(world); + horse.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + horse.setTamedBy(caster); + horse.setHorseSaddled(true); + world.spawnEntity(horse); + + horse.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).applyModifier( + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, Operations.MULTIPLY_CUMULATIVE)); + // Jump strength increases ridiculously fast, so we're reducing the effect of the modifier by 75% + horse.getEntityAttribute(JUMP_STRENGTH).applyModifier(new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, + modifiers.amplified(SpellModifiers.POTENCY, 0.25f) - 1, Operations.MULTIPLY_CUMULATIVE)); + + data.setVariable(UUID_KEY, horse.getUniqueID()); } - return false; + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; } } diff --git a/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java b/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java index 4d0ce4c3..f4a8d6eb 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java @@ -1,53 +1,75 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.IStoredVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.EntitySpiritWolf; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.Entity; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import java.util.UUID; + public class SummonSpiritWolf extends Spell { + /** The string identifier for the potency attribute modifier. */ + private static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; + + public static final IStoredVariable UUID_KEY = IStoredVariable.StoredVariable.ofUUID("spiritWolfUUID", Persistence.ALWAYS); + public SummonSpiritWolf(){ - super(Tier.APPRENTICE, 25, Element.EARTH, "summon_spirit_wolf", SpellType.MINION, 100, EnumAction.BOW, false); + super("summon_spirit_wolf", EnumAction.BOW, false); + addProperties(SpellMinion.SUMMON_RADIUS); + soundValues(0.7f, 1.2f, 0.4f); + WizardData.registerStoredVariables(UUID_KEY); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - WizardData properties = WizardData.get(caster); + WizardData data = WizardData.get(caster); - if(!properties.hasSpiritWolf){ - if(!world.isRemote){ + if(!world.isRemote){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; + Entity oldWolf = WizardryUtilities.getEntityByUUID(world, data.getVariable(UUID_KEY)); - EntitySpiritWolf wolf = new EntitySpiritWolf(world); - wolf.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); - wolf.setTamed(true); - wolf.setOwnerId(caster.getUniqueID()); - world.spawnEntity(wolf); - } - properties.hasSpiritWolf = true; - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; + if(oldWolf != null) oldWolf.setDead(); + + BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + if(pos == null) return false; + + EntitySpiritWolf wolf = new EntitySpiritWolf(world); + wolf.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5); + wolf.setTamed(true); + wolf.setOwnerId(caster.getUniqueID()); + // Potency gives the wolf more strength AND more health + wolf.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).applyModifier( + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, Operations.MULTIPLY_CUMULATIVE)); + wolf.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).applyModifier( + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.amplified(SpellModifiers.POTENCY, 1.5f) - 1, Operations.MULTIPLY_CUMULATIVE)); + wolf.setHealth(wolf.getMaxHealth()); + + world.spawnEntity(wolf); + + data.setVariable(UUID_KEY, wolf.getUniqueID()); } - return false; + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } } diff --git a/src/main/java/electroblob/wizardry/spell/SummonStormElemental.java b/src/main/java/electroblob/wizardry/spell/SummonStormElemental.java deleted file mode 100644 index ed41e623..00000000 --- a/src/main/java/electroblob/wizardry/spell/SummonStormElemental.java +++ /dev/null @@ -1,45 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.EntityStormElemental; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; - -public class SummonStormElemental extends Spell { - - public SummonStormElemental(){ - super(Tier.MASTER, 100, Element.LIGHTNING, "summon_storm_elemental", SpellType.MINION, 400, EnumAction.BOW, - false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityStormElemental stormElemental = new EntityStormElemental(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(stormElemental); - } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_AMBIENT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } -} diff --git a/src/main/java/electroblob/wizardry/spell/SummonWitherSkeleton.java b/src/main/java/electroblob/wizardry/spell/SummonWitherSkeleton.java index 58372589..d452c83c 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonWitherSkeleton.java +++ b/src/main/java/electroblob/wizardry/spell/SummonWitherSkeleton.java @@ -1,76 +1,24 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.living.EntityWitherSkeletonMinion; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.inventory.EntityEquipmentSlot; -import net.minecraft.item.EnumAction; import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -public class SummonWitherSkeleton extends Spell { +public class SummonWitherSkeleton extends SpellMinion { public SummonWitherSkeleton(){ - super(Tier.ADVANCED, 35, Element.NECROMANCY, "summon_wither_skeleton", SpellType.MINION, 150, EnumAction.BOW, - false); + super("summon_wither_skeleton", EntityWitherSkeletonMinion::new); + this.soundValues(7, 0.6f, 0); } - + @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityWitherSkeletonMinion skeleton = new EntityWitherSkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - world.spawnEntity(skeleton); - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityWitherSkeletonMinion skeleton = new EntityWitherSkeletonMinion(world, pos.getX() + 0.5, pos.getY(), - pos.getZ() + 0.5, caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - skeleton.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); - skeleton.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); - world.spawnEntity(skeleton); - } - caster.playSound(WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; + protected void addMinionExtras(EntityWitherSkeletonMinion minion, BlockPos pos, EntityLivingBase caster, SpellModifiers modifiers, int alreadySpawned){ + minion.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.STONE_SWORD)); + minion.setDropChance(EntityEquipmentSlot.MAINHAND, 0.0f); } } diff --git a/src/main/java/electroblob/wizardry/spell/SummonZombie.java b/src/main/java/electroblob/wizardry/spell/SummonZombie.java index 8e8e684f..1663b329 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonZombie.java +++ b/src/main/java/electroblob/wizardry/spell/SummonZombie.java @@ -1,67 +1,28 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.entity.living.EntityHuskMinion; import electroblob.wizardry.entity.living.EntityZombieMinion; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public class SummonZombie extends Spell { +public class SummonZombie extends SpellMinion { public SummonZombie(){ - super(Tier.BASIC, 10, Element.NECROMANCY, "summon_zombie", SpellType.MINION, 40, EnumAction.BOW, false); + super("summon_zombie", EntityZombieMinion::new); + this.soundValues(7, 0.6f, 0); } @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - EntityZombieMinion zombie = new EntityZombieMinion(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(zombie); + protected EntityZombieMinion createMinion(World world, EntityLivingBase caster, SpellModifiers modifiers){ + if(caster instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_minion_variants)){ + return new EntityHuskMinion(world); + }else{ + return super.createMinion(world, caster, modifiers); } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(!world.isRemote){ - - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); - if(pos == null) return false; - - EntityZombieMinion zombie = new EntityZombieMinion(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, - caster, (int)(600 * modifiers.get(WizardryItems.duration_upgrade))); - world.spawnEntity(zombie); - } - caster.playSound(WizardrySounds.SPELL_SUMMONING, 7.0f, 0.6f); - return true; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; } } diff --git a/src/main/java/electroblob/wizardry/spell/Telekinesis.java b/src/main/java/electroblob/wizardry/spell/Telekinesis.java index 40b7f34b..9d97aaa6 100644 --- a/src/main/java/electroblob/wizardry/spell/Telekinesis.java +++ b/src/main/java/electroblob/wizardry/spell/Telekinesis.java @@ -1,118 +1,81 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class Telekinesis extends Spell { +public class Telekinesis extends SpellRay { public Telekinesis(){ - super(Tier.BASIC, 5, Element.SORCERY, "telekinesis", SpellType.UTILITY, 5, EnumAction.NONE, false); + super("telekinesis", false, EnumAction.NONE); } + @Override public boolean requiresPacket(){ return false; } + @Override - public boolean doesSpellRequirePacket(){ - return false; - } + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(target instanceof EntityItem){ - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + target.motionX = (origin.x - target.posX) / 6; + target.motionY = (origin.y - target.posY) / 6; + target.motionZ = (origin.z - target.posZ) / 6; + return true; - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 8 * modifiers.get(WizardryItems.range_upgrade), 3.0f); - - if(rayTrace != null && rayTrace.entityHit != null){ - - if(rayTrace.entityHit instanceof EntityItem){ - - Entity entityHit = rayTrace.entityHit; - entityHit.motionX = (caster.posX - entityHit.posX) / 6; - entityHit.motionY = (caster.posY + caster.eyeHeight - entityHit.posY) / 6; - entityHit.motionZ = (caster.posZ - entityHit.posZ) / 6; - entityHit.playSound(WizardrySounds.SPELL_CONJURATION, 1.0F, 1.0f); - caster.swingArm(hand); - return true; - - }else if(rayTrace.entityHit instanceof EntityPlayer && Wizardry.settings.telekineticDisarmament){ - - EntityPlayer target = (EntityPlayer)rayTrace.entityHit; - - if(!target.getHeldItemMainhand().isEmpty()){ - - if(!world.isRemote){ - EntityItem item = target.entityDropItem(target.getHeldItemMainhand(), 0.0f); - // Makes the item move towards the caster - item.motionX = (caster.posX - target.posX) / 20; - item.motionZ = (caster.posZ - target.posZ) / 20; - } - - target.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY); - - target.playSound(WizardrySounds.SPELL_CONJURATION, 1.0F, 1.0f); - caster.swingArm(hand); - return true; - } - } - } - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - - IBlockState blockstate = world.getBlockState(new BlockPos(rayTrace.getBlockPos())); - - if(blockstate.getBlock().onBlockActivated(world, rayTrace.getBlockPos(), blockstate, caster, hand, - rayTrace.sideHit, 0, 0, 0)){ - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0F, 1.0f); - caster.swingArm(hand); - return true; - } - } - return false; - } - - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target instanceof EntityPlayer && !target.getHeldItemMainhand().isEmpty()){ + }else if(target instanceof EntityPlayer && (Wizardry.settings.telekineticDisarmament || !(caster instanceof EntityPlayer))){ + EntityPlayer player = (EntityPlayer)target; + // IDEA: Disarm the offhand if the mainhand is empty or otherwise harmless? - if(!world.isRemote){ - EntityItem item = target.entityDropItem(target.getHeldItemMainhand(), 0.0f); - // Makes the item move towards the caster - item.motionX = (caster.posX - target.posX) / 20; - item.motionZ = (caster.posZ - target.posZ) / 20; + if(!player.getHeldItemMainhand().isEmpty()){ + + if(!world.isRemote){ + EntityItem item = player.entityDropItem(player.getHeldItemMainhand(), 0); + // Makes the item move towards the caster + item.motionX = (origin.x - player.posX) / 20; + item.motionZ = (origin.z - player.posZ) / 20; + } + + player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY); + + return true; } - - target.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY); - - target.playSound(WizardrySounds.SPELL_CONJURATION, 1.0F, 1.0f); - caster.swingArm(hand); - return true; } - + return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(caster instanceof EntityPlayer){ + + IBlockState blockstate = world.getBlockState(pos); + + if(blockstate.getBlock().onBlockActivated(world, pos, blockstate, (EntityPlayer)caster, EnumHand.MAIN_HAND, + side, 0, 0, 0)){ + return true; + } + } + + return false; + } + + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/Thunderbolt.java b/src/main/java/electroblob/wizardry/spell/Thunderbolt.java deleted file mode 100644 index ecdf0762..00000000 --- a/src/main/java/electroblob/wizardry/spell/Thunderbolt.java +++ /dev/null @@ -1,68 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.projectile.EntityThunderbolt; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class Thunderbolt extends Spell { - - public Thunderbolt(){ - super(Tier.BASIC, 10, Element.LIGHTNING, "thunderbolt", SpellType.ATTACK, 15, EnumAction.NONE, false); - } - - @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - EntityThunderbolt thunderbolt = new EntityThunderbolt(world, caster, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(thunderbolt); - } - - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.8F, - world.rand.nextFloat() * 0.2F + 0.8F); - caster.swingArm(hand); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - EntityThunderbolt thunderbolt = new EntityThunderbolt(world, caster, - modifiers.get(SpellModifiers.DAMAGE)); - thunderbolt.directTowards(target, 2.5f); - world.spawnEntity(thunderbolt); - } - - caster.playSound(WizardrySounds.SPELL_ICE, 0.8F, world.rand.nextFloat() * 0.2F + 0.8F); - caster.swingArm(hand); - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Thunderstorm.java b/src/main/java/electroblob/wizardry/spell/Thunderstorm.java index 3a3e88a5..05e7884f 100644 --- a/src/main/java/electroblob/wizardry/spell/Thunderstorm.java +++ b/src/main/java/electroblob/wizardry/spell/Thunderstorm.java @@ -1,135 +1,132 @@ package electroblob.wizardry.spell; -import java.util.List; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.EntityArc; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.effect.EntityLightningBolt; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; -import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import java.util.List; + public class Thunderstorm extends Spell { + public static final String LIGHTNING_BOLTS = "lightning_bolts"; + + public static final String SECONDARY_DAMAGE = "secondary_damage"; + public static final String TERTIARY_DAMAGE = "tertiary_damage"; + + public static final String SECONDARY_RANGE = "secondary_range"; + public static final String TERTIARY_RANGE = "tertiary_range"; + + public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; + public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target + + private static final float CENTRE_RADIUS_FRACTION = 0.5f; + public Thunderstorm(){ - super(Tier.MASTER, 100, Element.LIGHTNING, "thunderstorm", SpellType.ATTACK, 250, EnumAction.BOW, false); + super("thunderstorm", EnumAction.BOW, false); + this.soundValues(1, 1.7f, 0.2f); + addProperties(EFFECT_RADIUS, LIGHTNING_BOLTS, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, + TERTIARY_RANGE, SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - + return doCasting(world, caster, modifiers); + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + return doCasting(world, caster, modifiers); + } + + // This spell is exactly the same for players and NPCs. + private boolean doCasting(World world, EntityLivingBase caster, SpellModifiers modifiers){ + if(world.canBlockSeeSky(new BlockPos(caster))){ - for(int r = 0; r < 10; r++){ + double maxRadius = getProperty(EFFECT_RADIUS).doubleValue(); - double radius = 4 + world.rand.nextDouble() * 6 * modifiers.get(WizardryItems.blast_upgrade); - double angle = world.rand.nextDouble() * Math.PI * 2; + for(int i = 0; i < getProperty(LIGHTNING_BOLTS).intValue(); i++){ - double x = caster.posX + radius * Math.cos(angle); - double z = caster.posZ + radius * Math.sin(angle); - double y = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(x, caster.posY, z), 10); + double radius = maxRadius * CENTRE_RADIUS_FRACTION + world.rand.nextDouble() * maxRadius + * (1 - CENTRE_RADIUS_FRACTION) * modifiers.get(WizardryItems.blast_upgrade); + float angle = world.rand.nextFloat() * (float)Math.PI * 2; - if(!world.isRemote){ - EntityLightningBolt entitylightning = new EntityLightningBolt(world, x, y, z, false); - world.addWeatherEffect(entitylightning); - } + double x = caster.posX + radius * MathHelper.cos(angle); + double z = caster.posZ + radius * MathHelper.sin(angle); + Integer y = WizardryUtilities.getNearestFloor(world, new BlockPos(x, caster.posY, z), (int)maxRadius); - // Code for eventhandler recognition; for achievements and such like. Left in for future use. - // NBTTagCompound entityNBT = entitylightning.getEntityData(); - // entityNBT.setInteger("summoningPlayer", entityplayer.entityId); + if(y != null){ - // Secondary chaining effect - double seekerRange = 10.0d; + if(!world.isRemote){ + EntityLightningBolt entitylightning = new EntityLightningBolt(world, x, y, z, false); + world.addWeatherEffect(entitylightning); + } - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, x, - y + 1, z, world); + // Code for eventhandler recognition; for achievements and such like. Left in for future use. + // NBTTagCompound entityNBT = entitylightning.getEntityData(); + // entityNBT.setInteger("summoningPlayer", entityplayer.entityId); - // For this spell there is no limit to the amount of secondary targets! - for(EntityLivingBase secondaryTarget : secondaryTargets){ + // Secondary chaining effect + List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(SECONDARY_RANGE).doubleValue(), x, y + 1, z, world); - if(WizardryUtilities.isValidTarget(caster, secondaryTarget)){ + for(int j = 0; j < Math.min(secondaryTargets.size(), getProperty(SECONDARY_MAX_TARGETS).intValue()); j++){ - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(x, y + 1, z, secondaryTarget.posX, - secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ); - world.spawnEntity(arc); - }else{ - for(int j = 0; j < 8; j++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, - secondaryTarget.posX + world.rand.nextFloat() - 0.5, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - secondaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); + EntityLivingBase secondaryTarget = secondaryTargets.get(j); + + if(AllyDesignationSystem.isValidTarget(caster, secondaryTarget)){ + + if(world.isRemote){ + + ParticleBuilder.create(Type.LIGHTNING).pos(x, y, z).target(secondaryTarget).spawn(world); + + ParticleBuilder.spawnShockParticles(world, secondaryTarget.posX, + secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2, + secondaryTarget.posZ); } - } - secondaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, - world.rand.nextFloat() * 0.4F + 1.5F); + playSound(world, secondaryTarget, 0, -1, modifiers); - secondaryTarget.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 10.0f * modifiers.get(SpellModifiers.DAMAGE)); + secondaryTarget.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), + getProperty(SECONDARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - // Tertiary chaining effect + // Tertiary chaining effect - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, - secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, - secondaryTarget.posZ, world); + List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + getProperty(TERTIARY_RANGE).doubleValue(), secondaryTarget.posX, + secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world); - for(int j = 0; j < Math.min(tertiaryTargets.size(), 3); j++){ + for(int k = 0; k < Math.min(tertiaryTargets.size(), getProperty(TERTIARY_MAX_TARGETS).intValue()); k++){ - EntityLivingBase tertiaryTarget = (EntityLivingBase)tertiaryTargets.get(j); + EntityLivingBase tertiaryTarget = tertiaryTargets.get(k); - if(!secondaryTargets.contains(tertiaryTarget) - && WizardryUtilities.isValidTarget(caster, tertiaryTarget)){ + if(!secondaryTargets.contains(tertiaryTarget) + && AllyDesignationSystem.isValidTarget(caster, tertiaryTarget)){ - if(!world.isRemote){ - EntityArc arc = new EntityArc(world); - arc.setEndpointCoords(secondaryTarget.posX, - secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, - tertiaryTarget.posX, tertiaryTarget.posY + tertiaryTarget.height / 2, - tertiaryTarget.posZ); - world.spawnEntity(arc); - }else{ - for(int k = 0; k < 8; k++){ - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARK, world, - tertiaryTarget.posX + world.rand.nextFloat() - 0.5, - tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - tertiaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0, 3); - world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, - tertiaryTarget.posX + world.rand.nextFloat() - 0.5, - tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2 - + world.rand.nextFloat() * 2 - 1, - tertiaryTarget.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0); + if(world.isRemote){ + ParticleBuilder.create(Type.LIGHTNING).entity(secondaryTarget) + .pos(0, secondaryTarget.height / 2, 0).target(tertiaryTarget).spawn(world); + ParticleBuilder.spawnShockParticles(world, tertiaryTarget.posX, + tertiaryTarget.getEntityBoundingBox().minY + tertiaryTarget.height / 2, + tertiaryTarget.posZ); } + + playSound(world, tertiaryTarget, 0, -1, modifiers); + + tertiaryTarget.attackEntityFrom( + MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), + getProperty(TERTIARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } - - tertiaryTarget.playSound(WizardrySounds.SPELL_SPARK, 1.0F, - world.rand.nextFloat() * 0.4F + 1.5F); - - tertiaryTarget.attackEntityFrom( - MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), - 8.0f * modifiers.get(SpellModifiers.DAMAGE)); } } } diff --git a/src/main/java/electroblob/wizardry/spell/Tornado.java b/src/main/java/electroblob/wizardry/spell/Tornado.java index bae3d7ee..f5045b05 100644 --- a/src/main/java/electroblob/wizardry/spell/Tornado.java +++ b/src/main/java/electroblob/wizardry/spell/Tornado.java @@ -1,77 +1,25 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.entity.construct.EntityTornado; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; +import net.minecraft.util.EnumFacing; -public class Tornado extends Spell { +public class Tornado extends SpellConstruct { + + public static final String SPEED = "speed"; + public static final String UPWARD_ACCELERATION = "upward_acceleration"; public Tornado(){ - super(Tier.ADVANCED, 35, Element.EARTH, "tornado", SpellType.ATTACK, 80, EnumAction.NONE, false); + super("tornado", EnumAction.NONE, EntityTornado::new, false); + addProperties(EFFECT_RADIUS, SPEED, DAMAGE, UPWARD_ACCELERATION); } @Override - public boolean doesSpellRequirePacket(){ - return false; - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - if(!world.isRemote){ - double x = caster.posX + caster.getLookVec().x; - double y = caster.posY; - double z = caster.posZ + caster.getLookVec().z; - - EntityTornado tornado = new EntityTornado(world, x, y, z, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), caster.getLookVec().x / 3, - caster.getLookVec().z / 3, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(tornado); - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - return true; - } - - @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - double x = caster.posX + caster.getLookVec().x; - double y = caster.posY; - double z = caster.posZ + caster.getLookVec().z; - - EntityTornado tornado = new EntityTornado(world, x, y, z, caster, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), caster.getLookVec().x / 3, - caster.getLookVec().z / 3, modifiers.get(SpellModifiers.DAMAGE)); - world.spawnEntity(tornado); - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 1.0F, 1.0F); - - return true; - } - - return false; - } - - @Override - public boolean canBeCastByNPCs(){ - return true; + protected void addConstructExtras(EntityTornado construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + float speed = getProperty(SPEED).floatValue(); + construct.setHorizontalVelocity(caster.getLookVec().x * speed, caster.getLookVec().z * speed); } } diff --git a/src/main/java/electroblob/wizardry/spell/Transience.java b/src/main/java/electroblob/wizardry/spell/Transience.java index faee9216..b47fdfcf 100644 --- a/src/main/java/electroblob/wizardry/spell/Transience.java +++ b/src/main/java/electroblob/wizardry/spell/Transience.java @@ -1,48 +1,60 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingAttackEvent; -import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.event.entity.living.PotionEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @Mod.EventBusSubscriber public class Transience extends Spell { + /** A {@code ResourceLocation} representing the shader file used when under the effects of transience. */ + public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/transience.json"); + public Transience(){ - super(Tier.ADVANCED, 50, Element.HEALING, "transience", SpellType.DEFENCE, 100, EnumAction.BOW, false); + super("transience", EnumAction.BOW, false); + addProperties(EFFECT_DURATION); } @Override - public boolean doesSpellRequirePacket(){ - return false; + public boolean requiresPacket(){ + return true; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + if(world.isRemote && caster == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); + } + if(!caster.isPotionActive(WizardryPotions.transience)){ + if(!world.isRemote){ - caster.addPotionEffect(new PotionEffect(WizardryPotions.transience, - (int)(400 * modifiers.get(WizardryItems.duration_upgrade)), 0)); - caster.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, - (int)(400 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f); + + int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + + caster.addPotionEffect(new PotionEffect(WizardryPotions.transience, duration, 0)); + caster.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, duration, 0, false, false)); + + this.playSound(world, caster, ticksInUse, duration, modifiers); } + return true; } return false; @@ -53,7 +65,7 @@ public class Transience extends Spell { if(event.getSource() != null){ // Prevents all blockable damage while transience is active if(event.getEntityLiving().isPotionActive(WizardryPotions.transience) - && !event.getSource().isUnblockable()){ + && event.getSource() != DamageSource.OUT_OF_WORLD){ event.setCanceled(true); } // Prevents transient entities from causing any damage @@ -65,20 +77,19 @@ public class Transience extends Spell { } @SubscribeEvent - public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){ - // Prevents transient players from placing blocks - if(event.getPlayer().isPotionActive(WizardryPotions.transience)){ + public static void onPlayerInteractEvent(PlayerInteractEvent event){ + // Prevents transient players from interacting with the world in any way + if(event.isCancelable() && event.getEntityPlayer().isPotionActive(WizardryPotions.transience)){ event.setCanceled(true); - return; } } @SubscribeEvent - public static void onBlockBreakEvent(BlockEvent.BreakEvent event){ - // Prevents transient players from breaking blocks - if(event.getPlayer().isPotionActive(WizardryPotions.transience)){ - event.setCanceled(true); - return; + public static void onPotionAddedEvent(PotionEvent.PotionAddedEvent event){ + if(event.getEntity().world.isRemote && event.getPotionEffect().getPotion() == WizardryPotions.transience + && event.getEntity() == net.minecraft.client.Minecraft.getMinecraft().player){ + if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER); + electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); } } diff --git a/src/main/java/electroblob/wizardry/spell/Transportation.java b/src/main/java/electroblob/wizardry/spell/Transportation.java index 51b8cedd..07e7d9a6 100644 --- a/src/main/java/electroblob/wizardry/spell/Transportation.java +++ b/src/main/java/electroblob/wizardry/spell/Transportation.java @@ -1,63 +1,197 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.WizardData; import electroblob.wizardry.block.BlockTransportationStone; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.IStoredVariable; +import electroblob.wizardry.data.Persistence; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.packet.PacketTransportation; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.Location; +import electroblob.wizardry.util.NBTExtras; import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; +import net.minecraft.nbt.NBTTagList; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; public class Transportation extends Spell { + public static final String TELEPORT_COUNTDOWN = "teleport_countdown"; + + public static final int MAX_REMEMBERED_LOCATIONS = 4; + + // For some reason 'the diamond' doesn't work if I chain methods onto this. Type inference is weird. + public static final IStoredVariable> LOCATIONS_KEY = new IStoredVariable.StoredVariable, NBTTagList>("stoneCirclePos", + s -> NBTExtras.listToNBT(s, Location::toNBT), t -> new ArrayList<>(NBTExtras.NBTToList(t, Location::fromNBT)), Persistence.ALWAYS).setSynced(); + public static final IStoredVariable COUNTDOWN_KEY = IStoredVariable.StoredVariable.ofInt("tpCountdown", Persistence.NEVER).withTicker(Transportation::update); + public Transportation(){ - super(Tier.ADVANCED, 100, Element.SORCERY, "transportation", SpellType.UTILITY, 100, EnumAction.BOW, false); + super("transportation", EnumAction.BOW, false); + addProperties(TELEPORT_COUNTDOWN); + WizardData.registerStoredVariables(LOCATIONS_KEY, COUNTDOWN_KEY); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - WizardData properties = WizardData.get(caster); + WizardData data = WizardData.get(caster); // Fixes the sound not playing in first person. - if(world.isRemote) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_PORTAL_TRIGGER, 1.0f, 1.0f); + if(world.isRemote) this.playSound(world, caster, ticksInUse, -1, modifiers); // Only works when the caster is in the same dimension. - if(properties != null && properties.getTpCountdown() == 0){ - if(caster.dimension == properties.getStoneCircleDimension()){ - // Has to be y since x and z could reasonably be -1. - if(properties.getStoneCircleLocation() != null){ - if(BlockTransportationStone.testForCircle(world, properties.getStoneCircleLocation())){ - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_PORTAL_TRIGGER, 1.0f, 1.0f); - caster.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 150, 0)); - properties.setTpCountdown(75); - return true; - }else{ - if(!world.isRemote) - caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".missing")); - } - }else{ - if(!world.isRemote) - caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".undefined")); + if(data != null){ + + Integer countdown = data.getVariable(COUNTDOWN_KEY); + + if(countdown == null || countdown == 0){ + + List locations = data.getVariable(Transportation.LOCATIONS_KEY); + + if(locations == null) data.setVariable(Transportation.LOCATIONS_KEY, locations = new ArrayList<>(Transportation.MAX_REMEMBERED_LOCATIONS)); + + if(locations.isEmpty()){ + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".undefined"), true); + return false; + } + + if(ItemArtefact.isArtefactActive(caster, WizardryItems.charm_transportation)){ + + List locationsInDimension = locations.stream().filter(l -> l.dimension == caster.dimension).collect(Collectors.toList()); + + if(locationsInDimension.isEmpty()){ + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension"), true); + return false; + } + + Location destination = getLocationAimedAt(caster, locationsInDimension, 1); + + if(destination == null) return false; // None of them were aimed at + + if(attemptTravelTo(caster, world, destination.pos, modifiers)){ + // Move the selected destination to the end of the list, making it the 'most recent' one + // This makes my life easier in update() below, and is a kind of useful feature too + locations.remove(destination); + locations.add(destination); + if(!world.isRemote) data.sync(); + return true; + } + + }else{ + + Location destination = locations.get(locations.size() - 1); // The most recent one, or the only one + + if(destination.dimension == caster.dimension){ + return attemptTravelTo(caster, world, destination.pos, modifiers); + }else{ + if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension"), true); + } + } - }else{ - if(!world.isRemote) - caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".wrongdimension")); } } + return false; } + // The following four methods centralise and neaten up the code + // Since the deviation angle is also used by the UI renderer, this also ensures they use the same calculation + + /** Returns the location from the given list that the give player is aiming at, or null if they are not aiming + * at any of them. */ + public static Location getLocationAimedAt(EntityPlayer player, List locations, float partialTicks){ + return locations.stream() + .filter(l -> isLocationAimedAt(player, l.pos, partialTicks)) + .min(Comparator.comparingDouble(l -> getLookDeviationAngle(player, l.pos, partialTicks))) + .orElse(null); + } + + public static boolean isLocationAimedAt(EntityPlayer player, BlockPos pos, float partialTicks){ + + Vec3d origin = player.getPositionEyes(partialTicks); + Vec3d centre = WizardryUtilities.getCentre(pos); + Vec3d direction = centre.subtract(origin); + double distance = direction.length(); + + return getLookDeviationAngle(player, pos, partialTicks) < getIconSize(distance); + } + + public static double getLookDeviationAngle(EntityPlayer player, BlockPos pos, float partialTicks){ + + Vec3d origin = player.getPositionEyes(partialTicks); + Vec3d look = player.getLook(partialTicks); + Vec3d centre = WizardryUtilities.getCentre(pos); + Vec3d direction = centre.subtract(origin); + double distance = direction.length(); + + return Math.acos(direction.dotProduct(look) / distance); // Angle between a and b = acos((a.b) / (|a|*|b|)) + } + + public static double getIconSize(double distance){ + return 0.05 + 2/(distance + 5); + } + + private boolean attemptTravelTo(EntityPlayer player, World world, BlockPos destination, SpellModifiers modifiers){ + + WizardData data = WizardData.get(player); + + if(BlockTransportationStone.testForCircle(world, destination)){ + this.playSound(world, player, 0, -1, modifiers); + player.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 150, 0)); + data.setVariable(COUNTDOWN_KEY, getProperty(TELEPORT_COUNTDOWN).intValue()); + return true; + }else{ + if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".missing"), true); + return false; + } + } + + private static int update(EntityPlayer player, Integer countdown){ + + if(countdown == null) return 0; + + if(!player.world.isRemote){ + + WizardData data = WizardData.get(player); + + List locations = data.getVariable(Transportation.LOCATIONS_KEY); + if(locations == null || locations.isEmpty()) return 0; + + // If the location was selected, either it was already at the end of the list or it was moved there + Location destination = locations.get(locations.size() - 1); + + if(countdown == 1 && destination.dimension == player.dimension){ + player.setPositionAndUpdate(destination.pos.getX() + 0.5, destination.pos.getY(), destination.pos.getZ() + 0.5); + player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0)); + IMessage msg = new PacketTransportation.Message(player.getEntityId()); + WizardryPacketHandler.net.sendToDimension(msg, player.world.provider.getDimension()); + } + + if(countdown > 0){ + countdown--; + } + } + + return countdown; + } + } diff --git a/src/main/java/electroblob/wizardry/spell/VanishingBox.java b/src/main/java/electroblob/wizardry/spell/VanishingBox.java index 76bf97ee..471450de 100644 --- a/src/main/java/electroblob/wizardry/spell/VanishingBox.java +++ b/src/main/java/electroblob/wizardry/spell/VanishingBox.java @@ -1,12 +1,7 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.SoundEvents; import net.minecraft.inventory.InventoryEnderChest; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; @@ -15,13 +10,10 @@ import net.minecraft.world.World; public class VanishingBox extends Spell { public VanishingBox(){ - super(Tier.ADVANCED, 45, Element.SORCERY, "vanishing_box", SpellType.UTILITY, 70, EnumAction.BOW, false); + super("vanishing_box", EnumAction.BOW, false); } - @Override - public boolean doesSpellRequirePacket(){ - return false; - } + @Override public boolean requiresPacket(){ return false; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ @@ -35,7 +27,7 @@ public class VanishingBox extends Spell { } } - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_ENDERCHEST_OPEN, 1, 1); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/WallOfFrost.java b/src/main/java/electroblob/wizardry/spell/WallOfFrost.java index 4220fdde..2c059bef 100644 --- a/src/main/java/electroblob/wizardry/spell/WallOfFrost.java +++ b/src/main/java/electroblob/wizardry/spell/WallOfFrost.java @@ -1,103 +1,115 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class WallOfFrost extends Spell { +public class WallOfFrost extends SpellRay { + private static final int MINIMUM_PLACEMENT_RANGE = 2; + public WallOfFrost(){ - super(Tier.MASTER, 15, Element.ICE, "wall_of_frost", SpellType.UTILITY, 0, EnumAction.NONE, true); + super("wall_of_frost", true, EnumAction.NONE); + this.particleVelocity(1); + this.particleSpacing(0.5); + addProperties(DURATION); + soundValues(0.5f, 1, 0); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected SoundEvent[] createSounds(){ + return this.createContinuousSpellSounds(); + } - // IDEA: Use frosted ice instead of ice statue + @Override + protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, entity, ticksInUse); + } - Vec3d look = caster.getLookVec(); + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + this.playSoundLoop(world, x, y, z, ticksInUse, duration); + } - RayTraceResult rayTrace = WizardryUtilities.rayTrace(10 * modifiers.get(WizardryItems.range_upgrade), world, - caster, true); + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + // Wall of frost now freezes entities solid too! + if(target instanceof EntityLiving && !world.isRemote){ + // Unchecked cast is fine because the block is a static final field + if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target, + (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ + + target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); + } + } + + return true; + } - if(rayTrace != null && !world.isRemote){ + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - BlockPos pos = rayTrace.getBlockPos(); + if(!world.isRemote && WizardryUtilities.canDamageBlocks(caster, world)){ // Stops the ice being placed floating above snow and grass. Directions other than up included for // completeness. if(WizardryUtilities.canBlockBeReplaced(world, pos)){ // Moves the blockpos back into the block - pos = pos.offset(rayTrace.sideHit.getOpposite()); + pos = pos.offset(side.getOpposite()); } - if(caster.getDistance(pos.getX(), pos.getY(), pos.getZ()) > 2 - && world.getBlockState(pos).getBlock() != WizardryBlocks.ice_statue){ + if(origin.squareDistanceTo(pos.getX(), pos.getY(), pos.getZ()) > MINIMUM_PLACEMENT_RANGE * MINIMUM_PLACEMENT_RANGE + && world.getBlockState(pos).getBlock() != WizardryBlocks.ice_statue && world.getBlockState(pos).getBlock() != WizardryBlocks.dry_frosted_ice){ - pos = pos.offset(rayTrace.sideHit); + pos = pos.offset(side); + + int duration = (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); if(WizardryUtilities.canBlockBeReplaced(world, pos)){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); + world.setBlockState(pos, WizardryBlocks.dry_frosted_ice.getDefaultState()); + world.scheduleUpdate(pos.toImmutable(), WizardryBlocks.dry_frosted_ice, duration); } // Builds a 2 block high wall if it hits the ground - if(rayTrace.sideHit == EnumFacing.UP){ - pos = pos.offset(rayTrace.sideHit); + if(side == EnumFacing.UP){ + pos = pos.offset(side); if(WizardryUtilities.canBlockBeReplaced(world, pos)){ - world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState()); + world.setBlockState(pos, WizardryBlocks.dry_frosted_ice.getDefaultState()); + world.scheduleUpdate(pos.toImmutable(), WizardryBlocks.dry_frosted_ice, duration); } } } } - - for(int i = 0; i < 20; i++){ - - if(world.isRemote){ - - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 0.4f, - 0.6f, 1.0f); - - x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, - look.x * modifiers.get(WizardryItems.range_upgrade), - look.y * modifiers.get(WizardryItems.range_upgrade), - look.z * modifiers.get(WizardryItems.range_upgrade), 8 + world.rand.nextInt(12), 1.0f, - 1.0f, 1.0f); - } - } - - if(ticksInUse % 12 == 0){ - if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.5F, 1.0f); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_ICE, 0.5F, 1.0f); - } - + return true; } + @Override + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return true; + } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + float brightness = world.rand.nextFloat(); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)) + .clr(0.4f + 0.6f * brightness, 0.6f + 0.4f*brightness, 1).spawn(world); + ParticleBuilder.create(Type.SNOW).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/WaterBreathing.java b/src/main/java/electroblob/wizardry/spell/WaterBreathing.java deleted file mode 100644 index 8effe686..00000000 --- a/src/main/java/electroblob/wizardry/spell/WaterBreathing.java +++ /dev/null @@ -1,44 +0,0 @@ -package electroblob.wizardry.spell; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; -import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.world.World; - -public class WaterBreathing extends Spell { - - public WaterBreathing(){ - super(Tier.ADVANCED, 30, Element.EARTH, "water_breathing", SpellType.UTILITY, 250, EnumAction.BOW, false); - } - - @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - caster.addPotionEffect(new PotionEffect(MobEffects.WATER_BREATHING, - (int)(1200 * modifiers.get(WizardryItems.duration_upgrade)), 0, false, false)); - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x1 = (double)((float)caster.posX + world.rand.nextFloat() * 2 - 1.0F); - double y1 = (double)((float)WizardryUtilities.getPlayerEyesPos(caster) - 0.5F + world.rand.nextFloat()); - double z1 = (double)((float)caster.posZ + world.rand.nextFloat() * 2 - 1.0F); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0, 0.1F, 0, - 48 + world.rand.nextInt(12), 0.3f, 0.3f, 1.0f); - } - } - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F, - world.rand.nextFloat() * 0.4F + 1.0F); - return true; - } - -} diff --git a/src/main/java/electroblob/wizardry/spell/Whirlwind.java b/src/main/java/electroblob/wizardry/spell/Whirlwind.java index 526b8dbb..733c891a 100644 --- a/src/main/java/electroblob/wizardry/spell/Whirlwind.java +++ b/src/main/java/electroblob/wizardry/spell/Whirlwind.java @@ -1,44 +1,55 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.EnumAction; import net.minecraft.network.play.server.SPacketEntityVelocity; -import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Whirlwind extends Spell { +public class Whirlwind extends SpellRay { + + public static final String REPULSION_VELOCITY = "repulsion_velocity"; public Whirlwind(){ - super(Tier.APPRENTICE, 10, Element.EARTH, "whirlwind", SpellType.DEFENCE, 15, EnumAction.NONE, false); + super("whirlwind", false, EnumAction.NONE); + this.soundValues(0.8f, 0.7f, 0.2f); + addProperties(REPULSION_VELOCITY); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); + if(target instanceof EntityPlayer && ((caster instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther) + || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){ + + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); + return false; + } // Left as EntityLivingBase because why not be able to move armour stands around? - if(rayTrace != null && rayTrace.entityHit instanceof EntityLivingBase){ - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; + if(target instanceof EntityLivingBase){ + + Vec3d vec = target.getPositionVector().add(0, target.getEyeHeight(), 0).subtract(origin).normalize(); if(!world.isRemote){ - target.motionX = caster.getLookVec().x * 2; - target.motionY = caster.getLookVec().y * 2 + 1; - target.motionZ = caster.getLookVec().z * 2; + float velocity = getProperty(REPULSION_VELOCITY).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + target.motionX = vec.x * velocity; + target.motionY = vec.y * velocity + 1; + target.motionZ = vec.z * velocity; // Player motion is handled on that player's client so needs packets if(target instanceof EntityPlayerMP){ @@ -47,67 +58,31 @@ public class Whirlwind extends Spell { } if(world.isRemote){ + + double distance = target.getDistance(origin.x, origin.y, origin.z); + for(int i = 0; i < 10; i++){ - double x2 = (double)(caster.posX + world.rand.nextFloat() - 0.5F - + caster.getLookVec().x * caster.getDistance(target) * 0.5); - double y2 = (double)(WizardryUtilities.getPlayerEyesPos(caster) + world.rand.nextFloat() - 0.5F - + caster.getLookVec().y * caster.getDistance(target) * 0.5); - double z2 = (double)(caster.posZ + world.rand.nextFloat() - 0.5F - + caster.getLookVec().z * caster.getDistance(target) * 0.5); - world.spawnParticle(EnumParticleTypes.CLOUD, x2, y2, z2, caster.getLookVec().x, - caster.getLookVec().y, caster.getLookVec().z); - // Minecraft.getMinecraft().effectRenderer.addEffect(new EntitySparkleFX(world, x2, y2, z2, - // entityplayer.getLookVec().xCoord, entityplayer.getLookVec().yCoord, - // entityplayer.getLookVec().zCoord, null, 1.0f, 1.0f, 0.8f, 10)); + double x = origin.x + world.rand.nextDouble() - 0.5 + vec.x * distance * 0.5; + double y = origin.y + world.rand.nextDouble() - 0.5 + vec.y * distance * 0.5; + double z = origin.z + world.rand.nextDouble() - 0.5 + vec.z * distance * 0.5; + world.spawnParticle(EnumParticleTypes.CLOUD, x, y, z, vec.x, vec.y, vec.z); } } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.8F, - world.rand.nextFloat() * 0.2F + 0.6F); + return true; } + return false; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - - if(!world.isRemote){ - target.motionX = caster.getLookVec().x * 2; - target.motionY = caster.getLookVec().y * 2 + 1; - target.motionZ = caster.getLookVec().z * 2; - - // Player motion is handled on that player's client so needs packets - if(target instanceof EntityPlayerMP){ - ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); - } - } - if(world.isRemote){ - for(int i = 0; i < 10; i++){ - double x2 = (double)(caster.posX + world.rand.nextFloat() - 0.5F - + caster.getLookVec().x * caster.getDistance(target) * 0.5); - double y2 = (double)(caster.posY + caster.getEyeHeight() + world.rand.nextFloat() - 0.5F - + caster.getLookVec().y * caster.getDistance(target) * 0.5); - double z2 = (double)(caster.posZ + world.rand.nextFloat() - 0.5F - + caster.getLookVec().z * caster.getDistance(target) * 0.5); - world.spawnParticle(EnumParticleTypes.CLOUD, x2, y2, z2, caster.getLookVec().x, - caster.getLookVec().y, caster.getLookVec().z); - } - } - caster.swingArm(hand); - caster.playSound(WizardrySounds.SPELL_ICE, 0.8F, world.rand.nextFloat() * 0.2F + 0.6F); - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ - return true; + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/Wither.java b/src/main/java/electroblob/wizardry/spell/Wither.java index 88712ceb..f649d9b8 100644 --- a/src/main/java/electroblob/wizardry/spell/Wither.java +++ b/src/main/java/electroblob/wizardry/spell/Wither.java @@ -1,125 +1,67 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryParticleType; import electroblob.wizardry.util.WizardryUtilities; -import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -public class Wither extends Spell { +public class Wither extends SpellRay { public Wither(){ - super(Tier.APPRENTICE, 10, Element.NECROMANCY, "wither", SpellType.ATTACK, 20, EnumAction.NONE, false); + super("wither", false, EnumAction.NONE); + this.soundValues(1, 1.1f, 0.2f); + addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - - Vec3d look = caster.getLookVec(); - - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, caster, - 10 * modifiers.get(WizardryItems.range_upgrade)); - - if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.ENTITY && WizardryUtilities.isLiving(rayTrace.entityHit)){ - - EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit; + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(WizardryUtilities.isLiving(target)){ // Has no effect on withers or wither skeletons. if(MagicDamage.isEntityImmune(DamageType.WITHER, target)){ - if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("spell.resist", target.getName(), - this.getNameForTranslationFormatted())); + if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( + new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); }else{ target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - target.addPotionEffect(new PotionEffect(MobEffects.WITHER, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.WITHER, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)))); } } - if(world.isRemote){ - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - // I figured it out! when on client side, entityplayer.posY is at the eyes, not the feet! - // This is a test for lining up the ray with the wand tip. Not sure if I like it or not. - /* Vec3d origin = Wizardry.proxy.getWandTipPosition(caster); double x1 = origin.xCoord + look.xCoord*i/2 - * + world.rand.nextFloat()/5 - 0.1f; double y1 = origin.yCoord + look.yCoord*i/2 + - * world.rand.nextFloat()/5 - 0.1f; double z1 = origin.zCoord + look.zCoord*i/2 + - * world.rand.nextFloat()/5 - 0.1f; */ - double x1 = caster.posX + look.x * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = WizardryUtilities.getPlayerEyesPos(caster) - 0.4f + look.y * i / 2 - + world.rand.nextFloat() / 5 - 0.1f; - double z1 = caster.posZ + look.z * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - // world.spawnParticle("mobSpell", x1, y1, z1, -1*look.xCoord, -1*look.yCoord, -1*look.zCoord); - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, 0, - 0.1f, 0.0f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.1f, 0.0f, 0.05f); - } - } - caster.swingArm(hand); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_HURT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + return true; } @Override - public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, - SpellModifiers modifiers){ - - if(target != null){ - // Has no effect on withers or wither skeletons. - if(!MagicDamage.isEntityImmune(DamageType.WITHER, target) && !world.isRemote){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER), - 1.0f * modifiers.get(SpellModifiers.DAMAGE)); - target.addPotionEffect(new PotionEffect(MobEffects.WITHER, - (int)(200 * modifiers.get(WizardryItems.duration_upgrade)), 1)); - } - - if(world.isRemote){ - - double dx = (target.posX - caster.posX) / caster.getDistance(target); - double dy = (target.posY - caster.posY) / caster.getDistance(target); - double dz = (target.posZ - caster.posZ) / caster.getDistance(target); - - for(int i = 1; i < (int)(25 * modifiers.get(WizardryItems.range_upgrade)); i += 2){ - - double x1 = caster.posX + dx * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - double y1 = caster.posY + caster.getEyeHeight() - 0.4f + dy * i / 2 + world.rand.nextFloat() / 5 - - 0.1f; - double z1 = caster.posZ + dz * i / 2 + world.rand.nextFloat() / 5 - 0.1f; - - Wizardry.proxy.spawnParticle(WizardryParticleType.DARK_MAGIC, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 0, 0.1f, 0.0f, 0.0f); - Wizardry.proxy.spawnParticle(WizardryParticleType.SPARKLE, world, x1, y1, z1, 0.0d, 0.0d, 0.0d, - 12 + world.rand.nextInt(8), 0.1f, 0.0f, 0.05f); - } - } - caster.swingArm(hand); - caster.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); - return true; - } - + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override - public boolean canBeCastByNPCs(){ + protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return true; } + + @Override + protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){ + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.1f, 0, 0.05f).spawn(world); + } } diff --git a/src/main/java/electroblob/wizardry/spell/WitherSkull.java b/src/main/java/electroblob/wizardry/spell/WitherSkull.java index b6bd4549..8370ad26 100644 --- a/src/main/java/electroblob/wizardry/spell/WitherSkull.java +++ b/src/main/java/electroblob/wizardry/spell/WitherSkull.java @@ -1,46 +1,63 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.SpellType; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntityWitherSkull; -import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraftforge.event.entity.EntityMobGriefingEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +@Mod.EventBusSubscriber public class WitherSkull extends Spell { + public static final String ACCELERATION = "acceleration"; + public WitherSkull(){ - super(Tier.ADVANCED, 20, Element.NECROMANCY, "wither_skull", SpellType.ATTACK, 30, EnumAction.NONE, false); + super("wither_skull", EnumAction.NONE, false); + addProperties(ACCELERATION); + soundValues(1, 1.1f, 0.2f); } @Override - public boolean doesSpellRequirePacket(){ + public boolean requiresPacket(){ return false; } + @Override + public boolean canBeCastByNPCs(){ + return true; + } + @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ Vec3d look = caster.getLookVec(); if(!world.isRemote){ + EntityWitherSkull witherskull = new EntityWitherSkull(world, caster, 1, 1, 1); - witherskull.setPosition(caster.posX + look.x, caster.posY + look.y + 1.3, - caster.posZ + look.z); - witherskull.accelerationX = look.x * 0.1; - witherskull.accelerationY = look.y * 0.1; - witherskull.accelerationZ = look.z * 0.1; + + witherskull.setPosition(caster.posX + look.x, caster.posY + look.y + 1.3, caster.posZ + look.z); + + double acceleration = getProperty(ACCELERATION).doubleValue() * modifiers.get(WizardryItems.range_upgrade); + + witherskull.accelerationX = look.x * acceleration; + witherskull.accelerationY = look.y * acceleration; + witherskull.accelerationZ = look.z * acceleration; + + witherskull.shootingEntity = caster; world.spawnEntity(witherskull); - WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, - world.rand.nextFloat() * 0.2F + 1.0F); + + this.playSound(world, caster, ticksInUse, -1, modifiers); } caster.swingArm(hand); return true; @@ -65,10 +82,11 @@ public class WitherSkull extends Spell { witherskull.accelerationY = dy / caster.getDistance(target) * 0.1; witherskull.accelerationZ = dz / caster.getDistance(target) * 0.1; + witherskull.shootingEntity = caster; witherskull.setPosition(caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ); world.spawnEntity(witherskull); - caster.playSound(SoundEvents.ENTITY_WITHER_SHOOT, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F); + this.playSound(world, caster, ticksInUse, -1, modifiers); } caster.swingArm(hand); @@ -78,9 +96,12 @@ public class WitherSkull extends Spell { return false; } - @Override - public boolean canBeCastByNPCs(){ - return true; + @SubscribeEvent + public static void onEntityMobGriefingEvent(EntityMobGriefingEvent event){ + if(event.getEntity() instanceof EntityPlayer){ + // If a player shot the wither skull, it should ignore the mob griefing gamerule and use playerBlockDamage instead + event.setResult(Wizardry.settings.playerBlockDamage ? Event.Result.ALLOW : Event.Result.DENY); + } } } diff --git a/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java b/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java index 8615c69f..7582ba0e 100644 --- a/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java +++ b/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java @@ -1,74 +1,62 @@ package electroblob.wizardry.tileentity; -import java.util.HashSet; -import java.util.Set; - -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Constants; -import electroblob.wizardry.constants.Tier; import electroblob.wizardry.event.SpellBindEvent; -import electroblob.wizardry.item.ItemArcaneTome; -import electroblob.wizardry.item.ItemArmourUpgrade; +import electroblob.wizardry.item.IWorkbenchItem; import electroblob.wizardry.item.ItemSpellBook; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.item.ItemWizardArmour; -import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.WandHelper; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.Container; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import net.minecraftforge.common.MinecraftForge; +import java.util.HashSet; +import java.util.Set; + public class ContainerArcaneWorkbench extends Container { /** The arcane workbench tile entity associated with this container. */ public TileEntityArcaneWorkbench tileentity; - public static final ResourceLocation EMPTY_SLOT_CRYSTAL = new ResourceLocation(Wizardry.MODID, - "gui/empty_slot_crystal"); - public static final ResourceLocation EMPTY_SLOT_UPGRADE = new ResourceLocation(Wizardry.MODID, - "gui/empty_slot_upgrade"); + public static final ResourceLocation EMPTY_SLOT_CRYSTAL = new ResourceLocation(Wizardry.MODID, "gui/empty_slot_crystal"); + public static final ResourceLocation EMPTY_SLOT_UPGRADE = new ResourceLocation(Wizardry.MODID, "gui/empty_slot_upgrade"); public static final int CRYSTAL_SLOT = 8; - public static final int WAND_SLOT = 9; + public static final int CENTRE_SLOT = 9; public static final int UPGRADE_SLOT = 10; - - private static final int[][][] SPELL_BOOK_SLOT_COORDS = { - {{80, 22}, {121, 51}, {106, 98}, {54, 98}, {39, 51}, {-999, -999}, {-999, -999}, {-999, -999}}, - {{80, 22}, {117, 43}, {117, 85}, {80, 106}, {43, 85}, {43, 43}, {-999, -999}, {-999, -999}}, - {{80, 22}, {113, 38}, {121, 74}, {98, 102}, {62, 102}, {39, 74}, {47, 38}, {-999, -999}}, - {{80, 22}, {111, 33}, {122, 64}, {111, 95}, {80, 106}, {49, 95}, {38, 64}, {49, 33}}}; + + public static final int SLOT_RADIUS = 42; public ContainerArcaneWorkbench(IInventory inventory, TileEntityArcaneWorkbench tileentity){ this.tileentity = tileentity; - ItemStack wand = tileentity.getStackInSlot(WAND_SLOT); + ItemStack wand = tileentity.getStackInSlot(CENTRE_SLOT); for(int i = 0; i < 8; i++){ - this.addSlotToContainer(new SlotItemList(tileentity, i, -999, -999, 1, WizardryItems.spell_book)); + Slot slot = new SlotItemClassList(tileentity, i, -999, -999, 1, ItemSpellBook.class); + this.addSlotToContainer(slot); } - this.addSlotToContainer(new SlotItemList(tileentity, CRYSTAL_SLOT, 8, 88, 64, WizardryItems.magic_crystal)) + this.addSlotToContainer(new SlotItemList(tileentity, CRYSTAL_SLOT, 13, 101, 64, + WizardryItems.magic_crystal, WizardryItems.crystal_shard, WizardryItems.grand_crystal)) .setBackgroundName(EMPTY_SLOT_CRYSTAL.toString()); - this.addSlotToContainer(new SlotWandArmour(tileentity, WAND_SLOT, 80, 64, this)); + this.addSlotToContainer(new SlotWorkbenchItem(tileentity, CENTRE_SLOT, 80, 64, this)); - Set upgrades = new HashSet(WandHelper.getSpecialUpgrades()); // Can't be done statically. + Set upgrades = new HashSet<>(WandHelper.getSpecialUpgrades()); // Can't be done statically. upgrades.add(WizardryItems.arcane_tome); upgrades.add(WizardryItems.armour_upgrade); - this.addSlotToContainer(new SlotItemList(tileentity, UPGRADE_SLOT, 8, 106, 1, upgrades.toArray(new Item[0]))) + this.addSlotToContainer(new SlotItemList(tileentity, UPGRADE_SLOT, 147, 17, 1, upgrades.toArray(new Item[0]))) .setBackgroundName(EMPTY_SLOT_UPGRADE.toString()); for(int x = 0; x < 9; x++){ @@ -81,112 +69,113 @@ public class ContainerArcaneWorkbench extends Container { } } - this.onSlotChanged(WAND_SLOT, wand, null); + this.onSlotChanged(CENTRE_SLOT, wand, null); } @Override public boolean canInteractWith(EntityPlayer player){ return this.tileentity.isUsableByPlayer(player); } + + /** + * Shows the given slot in the container GUI at the given position. Intended to do the opposite of + * {@link ContainerArcaneWorkbench#hideSlot(int, EntityPlayer)}. + * @param index The index of the slot to show. + * @param x The x position to put the slot in. + * @param y The y position to put the slot in. + */ + private void showSlot(int index, int x, int y){ + + Slot slot = this.getSlot(index); + slot.xPos = x; + slot.yPos = y; + } + + /** + * Hides the given slot from the container GUI (moves it off the screen) and returns its contents to the given + * player. If some or all of the items do not fit in the player's inventory, or if the player is null, they are + * dropped on the floor. + * @param index The index of the slot to hide. + * @param player The player that is using this container. + */ + private void hideSlot(int index, EntityPlayer player){ + + Slot slot = this.getSlot(index); + + // 'Removes' the slot from the container (moves it off the screen) + slot.xPos = -999; + slot.yPos = -999; + + ItemStack stack = slot.getStack(); + // This doesn't cause an infinite loop because slot i can never be a SlotWandArmour. In effect, it's + // exactly the same as shift-clicking the slot, so why re-invent the wheel? + ItemStack remainder = this.transferStackInSlot(player, index); + + if(remainder == ItemStack.EMPTY && stack != ItemStack.EMPTY){ + slot.putStack(ItemStack.EMPTY); + // The second parameter is never used... + if(player != null) player.dropItem(stack, false); + } + } /** Called from the central wand/armour slot when its item is changed or removed. */ // In case I forget again and think it should have @Override: I wrote this! public void onSlotChanged(int slotNumber, ItemStack stack, EntityPlayer player){ - if(slotNumber == WAND_SLOT){ + if(slotNumber == CENTRE_SLOT){ - if(!(stack.getItem() instanceof ItemWand) && stack.getItem() != WizardryItems.blank_scroll){ - // If the stack has been removed + if(stack.isEmpty()){ + // If the stack has been removed, hide all the spell book slots for(int i = 0; i < CRYSTAL_SLOT; i++){ - Slot slot1 = this.getSlot(i); - // 'Removes' the slot from the container (moves it off the screen) - slot1.xPos = -100; - slot1.yPos = -100; - - ItemStack stack1 = slot1.getStack(); - // This doesn't cause an infinite loop because slot i can never be a SlotWandArmour. In effect, it's - // exactly the same as shift-clicking the slot, so why re-invent the wheel? - ItemStack remainder = this.transferStackInSlot(player, i); - - if(remainder == ItemStack.EMPTY && stack1 != ItemStack.EMPTY){ - slot1.putStack(ItemStack.EMPTY); - // The second parameter is never used... - if(player != null) player.dropItem(stack1, false); - } + this.hideSlot(i, player); } }else{ - - if(stack.getItem() == WizardryItems.blank_scroll){ - // If a blank scroll is added - // The first slot is shown - this.getSlot(0).xPos = SPELL_BOOK_SLOT_COORDS[0][0][0]; - this.getSlot(0).yPos = SPELL_BOOK_SLOT_COORDS[0][0][1]; - - // The rest of the slots are hidden - for(int i = 1; i < CRYSTAL_SLOT; i++){ - - Slot slot1 = this.getSlot(i); - - slot1.xPos = -100; - slot1.yPos = -100; - - ItemStack stack1 = slot1.getStack(); - // This doesn't cause an infinite loop because slot i can never be a SlotWandArmour. In effect, - // it's - // exactly the same as shift-clicking the slot, so why re-invent the wheel? - ItemStack remainder = this.transferStackInSlot(player, i); - - if(remainder == ItemStack.EMPTY && stack1 != ItemStack.EMPTY){ - slot1.putStack(ItemStack.EMPTY); - // The second parameter is never used... - if(player != null) player.dropItem(stack1, false); - } + + if(stack.getItem() instanceof IWorkbenchItem){ // Should always be true here. + + int spellSlots = ((IWorkbenchItem)stack.getItem()).getSpellSlotCount(stack); + + int centreX = this.getSlot(CENTRE_SLOT).xPos; + int centreY = this.getSlot(CENTRE_SLOT).yPos; + + // Show however many spell book slots are necessary + for(int i = 0; i < spellSlots; i++){ + + float angle = i * (2 * (float)Math.PI)/spellSlots; + int x = centreX + Math.round(SLOT_RADIUS * MathHelper.sin(angle)); + // -cos because +y is downwards + int y = centreY + Math.round(SLOT_RADIUS * -MathHelper.cos(angle)); + + showSlot(i, x, y); } - - }else{ - - for(int i = 0; i < CRYSTAL_SLOT; i++){ - - int n = WandHelper.getUpgradeLevel(stack, WizardryItems.attunement_upgrade); - int[] coords = SPELL_BOOK_SLOT_COORDS[n][i]; - - Slot slot1 = this.getSlot(i); - // Puts the slot back in the correct position - slot1.xPos = coords[0]; - slot1.yPos = coords[1]; - - if(slot1.xPos < 0 || slot1.yPos < 0){ - - ItemStack stack1 = slot1.getStack(); - // This doesn't cause an infinite loop because slot i can never be a SlotWandArmour. In - // effect, it's - // exactly the same as shift-clicking the slot, so why re-invent the wheel? - ItemStack remainder = this.transferStackInSlot(player, i); - - if(remainder == ItemStack.EMPTY && stack1 != ItemStack.EMPTY){ - slot1.putStack(ItemStack.EMPTY); - // The second parameter is never used... - if(player != null) player.dropItem(stack1, false); - } - } + + // Hide the rest + for(int i = spellSlots; i < CRYSTAL_SLOT; i++){ + hideSlot(i, player); } + } } } // FIXME: It only seems to be syncing correctly when a stack is put into the slot, not taken out. - // Was this broken in 1.7.10 as well? + // This is because markDirty isn't called in the tileentity, I think. this.tileentity.sync(); } + // FIXME: 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. @Override public ItemStack transferStackInSlot(EntityPlayer player, int clickedSlotId){ ItemStack remainder = ItemStack.EMPTY; - Slot slot = (Slot)this.inventorySlots.get(clickedSlotId); + Slot slot = this.inventorySlots.get(clickedSlotId); if(slot != null && slot.getHasStack()){ + ItemStack stack = slot.getStack(); // The stack that was there originally remainder = stack.copy(); // A copy of that stack @@ -206,20 +195,18 @@ public class ContainerArcaneWorkbench extends Container { if(stack.getItem() instanceof ItemSpellBook){ minSlotId = 0; maxSlotId = CRYSTAL_SLOT - 1; - }else if(stack.getItem() == WizardryItems.magic_crystal){ + }else if(getSlot(CRYSTAL_SLOT).isItemValid(stack)){ minSlotId = CRYSTAL_SLOT; maxSlotId = CRYSTAL_SLOT; - }else if(stack.getItem() instanceof ItemWand || stack.getItem() instanceof ItemWizardArmour - || stack.getItem() == WizardryItems.blank_scroll){ - minSlotId = WAND_SLOT; - maxSlotId = WAND_SLOT; - }else if(stack.getItem() instanceof ItemArcaneTome || stack.getItem() instanceof ItemArmourUpgrade - || WandHelper.isWandUpgrade(stack.getItem())){ + }else if(getSlot(CENTRE_SLOT).isItemValid(stack)){ + minSlotId = CENTRE_SLOT; + maxSlotId = CENTRE_SLOT; + }else if(getSlot(UPGRADE_SLOT).isItemValid(stack)){ minSlotId = UPGRADE_SLOT; maxSlotId = UPGRADE_SLOT; }else{ - return ItemStack.EMPTY; // If none of the above cases were true, then the item won't fit in the - // workbench. + // If none of the above cases were true, then the item won't fit in the workbench. + return ItemStack.EMPTY; } if(!this.mergeItemStack(stack, minSlotId, maxSlotId + 1, false)){ @@ -261,204 +248,24 @@ public class ContainerArcaneWorkbench extends Container { * Called (via {@link electroblob.wizardry.packet.PacketControlInput PacketControlInput}) when the apply button in * the arcane workbench GUI is pressed. */ - // All operations on the items contained in the inventory simply call the corresponding methods in the tileentity. // As of 2.1, for the sake of events and neatness of code, this was moved here from TileEntityArcaneWorkbench. + // As of 4.2, the spell binding/charging/upgrading code was delegated (via IWorkbenchItem) to the items themselves. public void onApplyButtonPressed(EntityPlayer player){ - ItemStack wand = this.getSlot(WAND_SLOT).getStack(); - ItemStack[] spellBooks = new ItemStack[CRYSTAL_SLOT]; - for(int i = 0; i < spellBooks.length; i++){ - spellBooks[i] = this.getSlot(i).getStack(); - } - ItemStack crystals = this.getSlot(CRYSTAL_SLOT).getStack(); - ItemStack upgrade = this.getSlot(UPGRADE_SLOT).getStack(); - if(MinecraftForge.EVENT_BUS.post(new SpellBindEvent(player, this))) return; + + Slot centre = this.getSlot(CENTRE_SLOT); + + if(centre.getStack().getItem() instanceof IWorkbenchItem){ // Should always be true, but no harm in checking. + + Slot[] spellBooks = this.inventorySlots.subList(0, 8).toArray(new Slot[8]); + + if(((IWorkbenchItem)centre.getStack().getItem()) + .onApplyButtonPressed(player, centre, this.getSlot(CRYSTAL_SLOT), this.getSlot(UPGRADE_SLOT), spellBooks)){ - // Since the workbench now accepts armour as well as wands, this check is needed. - if(wand.getItem() instanceof ItemWand){ - - // Upgrades wand if necessary. Damage is copied, preserving remaining durability, - // and also the entire NBT tag compound. - if(upgrade.getItem() == WizardryItems.arcane_tome){ - - ItemStack newWand; - - switch(Tier.values()[upgrade.getItemDamage()]){ - - case APPRENTICE: - if(((ItemWand)wand.getItem()).tier == Tier.BASIC){ - newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], - ((ItemWand)wand.getItem()).element)); - newWand.setTagCompound(wand.getTagCompound()); - // This needs to be done after copying the tag compound so the max damage for the new wand - // takes storage - // upgrades into account. - newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage())); - this.putStackInSlot(WAND_SLOT, newWand); - this.putStackInSlot(UPGRADE_SLOT, ItemStack.EMPTY); - WizardryAdvancementTriggers.apprentice.triggerFor(player); - } - break; - - case ADVANCED: - if(((ItemWand)wand.getItem()).tier == Tier.APPRENTICE){ - newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], - ((ItemWand)wand.getItem()).element)); - newWand.setTagCompound(wand.getTagCompound()); - newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage())); - this.putStackInSlot(WAND_SLOT, newWand); - this.putStackInSlot(UPGRADE_SLOT, ItemStack.EMPTY); - } - break; - - case MASTER: - if(((ItemWand)wand.getItem()).tier == Tier.ADVANCED){ - newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], - ((ItemWand)wand.getItem()).element)); - newWand.setTagCompound(wand.getTagCompound()); - newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage())); - this.putStackInSlot(WAND_SLOT, newWand); - this.putStackInSlot(UPGRADE_SLOT, ItemStack.EMPTY); - WizardryAdvancementTriggers.master.triggerFor(player); - } - break; - - default: - break; + if(player instanceof EntityPlayerMP){ + WizardryAdvancementTriggers.arcane_workbench.trigger((EntityPlayerMP)player, centre.getStack()); } - - // This needs to happen so the charging works on the new wand, not the old one. - wand = this.getSlot(WAND_SLOT).getStack(); - - }else if(WandHelper.isWandUpgrade(upgrade.getItem())){ - - // Special upgrades - - // Used to preserve existing mana when upgrading storage rather than creating free mana. - int prevMana = wand.getMaxDamage() - wand.getItemDamage(); - - if(WandHelper.getTotalUpgrades(wand) < ((ItemWand)wand.getItem()).tier.upgradeLimit - && WandHelper.getUpgradeLevel(wand, upgrade.getItem()) < Constants.UPGRADE_STACK_LIMIT){ - - WandHelper.applyUpgrade(wand, upgrade.getItem()); - - // Special behaviours for specific upgrades - if(upgrade.getItem() == WizardryItems.storage_upgrade){ - wand.setItemDamage(wand.getMaxDamage() - prevMana); - } - if(upgrade.getItem() == WizardryItems.attunement_upgrade){ - - Spell[] spells = WandHelper.getSpells(wand); - Spell[] newSpells = new Spell[5 - + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)]; - - for(int i = 0; i < newSpells.length; i++){ - // Prevents both NPEs and AIOOBEs - newSpells[i] = i < spells.length && spells[i] != null ? spells[i] : Spells.none; - } - - WandHelper.setSpells(wand, newSpells); - - int[] cooldown = WandHelper.getCooldowns(wand); - int[] newCooldown = new int[5 - + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)]; - - if(cooldown.length > 0){ - for(int i = 0; i < cooldown.length; i++){ - newCooldown[i] = cooldown[i]; - } - } - - WandHelper.setCooldowns(wand, newCooldown); - } - - this.getSlot(UPGRADE_SLOT).decrStackSize(1); - WizardryAdvancementTriggers.special_upgrade.triggerFor(player); - - if(WandHelper.getTotalUpgrades(wand) == Tier.MASTER.upgradeLimit){ - WizardryAdvancementTriggers.max_out_wand.triggerFor(player); - } - } - } - - // Reads NBT spell id array to variable, edits this, then writes it back to NBT. - // Original spells are preserved; if a slot is left empty the existing spell binding will remain. - // Accounts for spells which cannot be applied because they are above the wand's tier; these spells - // will not bind but the existing spell in that slot will remain and other applicable spells will - // be bound as normal, along with any upgrades and crystals. - Spell[] spells = WandHelper.getSpells(wand); - if(spells.length <= 0){ - // 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades - spells = new Spell[5]; - } - for(int i = 0; i < spells.length; i++){ - if(spellBooks[i] != ItemStack.EMPTY && !(Spell - .get(spellBooks[i].getItemDamage()).tier.level > ((ItemWand)wand.getItem()).tier.level)){ - spells[i] = Spell.get(spellBooks[i].getItemDamage()); - } - } - WandHelper.setSpells(wand, spells); - - // Charges wand by appropriate amount - if(crystals != ItemStack.EMPTY){ - int chargeDepleted = wand.getItemDamage(); - // System.out.println("Charge depleted: " + chargeDepleted); - // System.out.println("Crystals found: " + crystals.getCount()); - if(crystals.getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){ - // System.out.println("charging"); - wand.setItemDamage(chargeDepleted - crystals.getCount() * Constants.MANA_PER_CRYSTAL); - this.getSlot(CRYSTAL_SLOT).decrStackSize(crystals.getCount()); - }else if(chargeDepleted != 0){ - // System.out.println((int)Math.ceil(((double)chargeDepleted)/50)); - this.getSlot(CRYSTAL_SLOT) - .decrStackSize((int)Math.ceil(((double)chargeDepleted) / Constants.MANA_PER_CRYSTAL)); - wand.setItemDamage(0); - } - } - } - - // Armour - else if(wand.getItem() instanceof ItemWizardArmour){ - // Applies legendary upgrade - if(upgrade.getItem() == WizardryItems.armour_upgrade){ - if(!wand.hasTagCompound()){ - wand.setTagCompound(new NBTTagCompound()); - } - if(!wand.getTagCompound().hasKey("legendary")){ - wand.getTagCompound().setBoolean("legendary", true); - this.putStackInSlot(UPGRADE_SLOT, ItemStack.EMPTY); - WizardryAdvancementTriggers.legendary.triggerFor(player); - } - } - // Charges armour by appropriate amount - if(crystals != ItemStack.EMPTY){ - int chargeDepleted = wand.getItemDamage(); - if(crystals.getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){ - wand.setItemDamage(chargeDepleted - crystals.getCount() * Constants.MANA_PER_CRYSTAL); - this.getSlot(CRYSTAL_SLOT).decrStackSize(crystals.getCount()); - }else if(chargeDepleted != 0){ - this.getSlot(CRYSTAL_SLOT) - .decrStackSize((int)Math.ceil(((double)chargeDepleted) / Constants.MANA_PER_CRYSTAL)); - wand.setItemDamage(0); - } - } - } - - // Scrolls - else if(wand.getItem() == WizardryItems.blank_scroll){ - // Spells can only be bound to scrolls if the player has already cast them (prevents casting of master - // spells without getting a master wand) - // This restriction does not apply in creative mode - if(spellBooks[0] != ItemStack.EMPTY - && (player.capabilities.isCreativeMode || (WizardData.get(player) != null - && WizardData.get(player).hasSpellBeenDiscovered(Spell.get(spellBooks[0].getItemDamage())))) - && crystals != ItemStack.EMPTY && crystals.getCount() - * Constants.MANA_PER_CRYSTAL > Spell.get(spellBooks[0].getItemDamage()).cost){ - - this.getSlot(CRYSTAL_SLOT).decrStackSize((int)Math - .ceil(((double)Spell.get(spellBooks[0].getItemDamage()).cost) / Constants.MANA_PER_CRYSTAL)); - this.putStackInSlot(WAND_SLOT, new ItemStack(WizardryItems.scroll, 1, spellBooks[0].getItemDamage())); } } } diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java b/src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java new file mode 100644 index 00000000..783249d9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java @@ -0,0 +1,40 @@ +package electroblob.wizardry.tileentity; + +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +/** + * Simple extension of {@link Slot} which only accepts items from an array of item classes defined in the constructor. + * + * @author Electroblob + * @since Wizardry 1.0 + */ +public class SlotItemClassList extends Slot { + + private final Class[] itemClasses; + private int stackLimit; + + @SafeVarargs + public SlotItemClassList(IInventory inventory, int index, int x, int y, int stackLimit, Class... allowedItemClasses){ + super(inventory, index, x, y); + this.itemClasses = allowedItemClasses; + this.stackLimit = stackLimit; + } + + public int getSlotStackLimit(){ + return stackLimit; + } + + public boolean isItemValid(ItemStack stack){ + + for(Class itemClass : itemClasses){ + if(itemClass.isAssignableFrom(stack.getItem().getClass())){ + return true; + } + } + + return false; + } +} diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java b/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java index 93115155..db95b5e0 100644 --- a/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java +++ b/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java @@ -6,18 +6,18 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; /** - * Simple extension of {@link Slot} which only accepts items from an array defined in the constructor. + * Simple extension of {@link Slot} which only accepts items from an array of items defined in the constructor. * * @author Electroblob * @since Wizardry 1.0 */ public class SlotItemList extends Slot { - private Item[] items; + private final Item[] items; private int stackLimit; - public SlotItemList(IInventory par1iInventory, int index, int x, int y, int stackLimit, Item... allowedItems){ - super(par1iInventory, index, x, y); + public SlotItemList(IInventory inventory, int index, int x, int y, int stackLimit, Item... allowedItems){ + super(inventory, index, x, y); this.items = allowedItems; this.stackLimit = stackLimit; } @@ -27,11 +27,13 @@ public class SlotItemList extends Slot { } public boolean isItemValid(ItemStack stack){ - for(int i = 0; i < items.length; i++){ - if(stack.getItem() == this.items[i]){ + + for(Item item : items){ + if(stack.getItem() == item){ return true; } } + return false; } } diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotWandArmour.java b/src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java similarity index 58% rename from src/main/java/electroblob/wizardry/tileentity/SlotWandArmour.java rename to src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java index 19f24a60..0e0a2f7d 100644 --- a/src/main/java/electroblob/wizardry/tileentity/SlotWandArmour.java +++ b/src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java @@ -1,8 +1,6 @@ package electroblob.wizardry.tileentity; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.item.ItemWizardArmour; -import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.item.IWorkbenchItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; @@ -14,12 +12,12 @@ import net.minecraft.item.ItemStack; * @author Electroblob * @since Wizardry 1.0 */ -public class SlotWandArmour extends Slot { +public class SlotWorkbenchItem extends Slot { private ContainerArcaneWorkbench container; - public SlotWandArmour(IInventory par1iInventory, int index, int x, int y, ContainerArcaneWorkbench container){ - super(par1iInventory, index, x, y); + public SlotWorkbenchItem(IInventory inventory, int index, int x, int y, ContainerArcaneWorkbench container){ + super(inventory, index, x, y); this.container = container; } @@ -37,12 +35,11 @@ public class SlotWandArmour extends Slot { @Override public int getSlotStackLimit(){ - return 1; + return 16; } @Override - public boolean isItemValid(ItemStack itemstack){ - return (itemstack.getItem() instanceof ItemWand || itemstack.getItem() instanceof ItemWizardArmour - || itemstack.getItem() == WizardryItems.blank_scroll); + public boolean isItemValid(ItemStack stack){ + return stack.getItem() instanceof IWorkbenchItem && ((IWorkbenchItem)stack.getItem()).canPlace(stack); } } diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java index 5e6c4b04..991098b7 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java @@ -1,11 +1,10 @@ package electroblob.wizardry.tileentity; -import java.util.HashSet; -import java.util.Set; - import electroblob.wizardry.Wizardry; -import electroblob.wizardry.item.ItemWand; -import electroblob.wizardry.item.ItemWizardArmour; +import electroblob.wizardry.item.IManaStoringItem; +import electroblob.wizardry.item.IWorkbenchItem; +import electroblob.wizardry.item.ItemCrystal; +import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.WandHelper; @@ -26,16 +25,15 @@ import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import java.util.HashSet; +import java.util.Set; + public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, ITickable { /** The inventory of the arcane workbench. */ private NonNullList inventory; - /** Controls the rotation of the rune. */ + /** Controls the rotating rune and floating wand animations. */ public float timer = 0; - /** Controls the change of yOffset. */ - public int yTimer = 0; - /** Controls the height of the wand. */ - public int yOffset = 300; public TileEntityArcaneWorkbench(){ inventory = NonNullList.withSize(ContainerArcaneWorkbench.UPGRADE_SLOT + 1, ItemStack.EMPTY); @@ -44,8 +42,6 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, @Override public void onLoad(){ timer = 0; - yTimer = 0; - yOffset = 300; } /** Called to manually sync the tile entity with clients. */ @@ -56,29 +52,18 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, @Override public void update(){ - ItemStack itemstack = this.getStackInSlot(ContainerArcaneWorkbench.WAND_SLOT); + ItemStack stack = this.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT); // Decrements wand damage (increases mana) every 1.5 seconds if it has a condenser upgrade - if(itemstack.getItem() instanceof ItemWand && !this.world.isRemote && itemstack.isItemDamaged() - && this.world.getWorldTime() % electroblob.wizardry.constants.Constants.CONDENSER_TICK_INTERVAL == 0){ + if(stack.getItem() instanceof IManaStoringItem && !this.world.isRemote && !((IManaStoringItem)stack.getItem()).isManaFull(stack) + && this.world.getTotalWorldTime() % electroblob.wizardry.constants.Constants.CONDENSER_TICK_INTERVAL == 0){ // If the upgrade level is 0, this does nothing anyway. - itemstack.setItemDamage( - itemstack.getItemDamage() - WandHelper.getUpgradeLevel(itemstack, WizardryItems.condenser_upgrade)); + ((IManaStoringItem)stack.getItem()).rechargeMana(stack, WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade)); } // The server doesn't care what these are, and there's no need for them to be synced or saved. if(this.world.isRemote){ - if(timer < 359){ - timer++; - }else{ - timer = 0; - } - if(yOffset > 0){ - yTimer--; - }else{ - yTimer++; - } - yOffset += yTimer; + timer++; } } @@ -93,33 +78,42 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, } @Override - public ItemStack decrStackSize(int slot, int amt){ + public ItemStack decrStackSize(int slot, int amount){ + ItemStack stack = getStackInSlot(slot); + if(!stack.isEmpty()){ - if(stack.getCount() <= amt){ + if(stack.getCount() <= amount){ setInventorySlotContents(slot, ItemStack.EMPTY); }else{ - stack = stack.splitStack(amt); + stack = stack.splitStack(amount); if(stack.getCount() == 0){ setInventorySlotContents(slot, ItemStack.EMPTY); } } + this.markDirty(); } + return stack; } @Override public ItemStack removeStackFromSlot(int slot){ + ItemStack stack = getStackInSlot(slot); + if(!stack.isEmpty()){ setInventorySlotContents(slot, ItemStack.EMPTY); } + return stack; } @Override public void setInventorySlotContents(int slot, ItemStack stack){ + inventory.set(slot, stack); + if(!stack.isEmpty() && stack.getCount() > getInventoryStackLimit()){ stack.setCount(getInventoryStackLimit()); } @@ -161,17 +155,16 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, if(itemstack == ItemStack.EMPTY) return true; if(slotNumber >= 0 && slotNumber < ContainerArcaneWorkbench.CRYSTAL_SLOT){ - return itemstack.getItem() == WizardryItems.spell_book; + return itemstack.getItem() instanceof ItemSpellBook; }else if(slotNumber == ContainerArcaneWorkbench.CRYSTAL_SLOT){ - return itemstack.getItem() == WizardryItems.magic_crystal; + return itemstack.getItem() instanceof ItemCrystal; - }else if(slotNumber == ContainerArcaneWorkbench.WAND_SLOT){ - return (itemstack.getItem() instanceof ItemWand || itemstack.getItem() instanceof ItemWizardArmour - || itemstack.getItem() == WizardryItems.blank_scroll); + }else if(slotNumber == ContainerArcaneWorkbench.CENTRE_SLOT){ + return itemstack.getItem() instanceof IWorkbenchItem; }else if(slotNumber == ContainerArcaneWorkbench.UPGRADE_SLOT){ - Set upgrades = new HashSet(WandHelper.getSpecialUpgrades()); + Set upgrades = new HashSet<>(WandHelper.getSpecialUpgrades()); upgrades.add(WizardryItems.arcane_tome); upgrades.add(WizardryItems.armour_upgrade); return upgrades.contains(itemstack.getItem()); @@ -188,16 +181,12 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, NBTTagList tagList = tagCompound.getTagList("Inventory", NBT.TAG_COMPOUND); for(int i = 0; i < tagList.tagCount(); i++){ - NBTTagCompound tag = (NBTTagCompound)tagList.getCompoundTagAt(i); + NBTTagCompound tag = tagList.getCompoundTagAt(i); byte slot = tag.getByte("Slot"); if(slot >= 0 && slot < getSizeInventory()){ setInventorySlotContents(slot, new ItemStack(tag)); } } - - // timer = tagCompound.getFloat("timer"); - // yTimer = tagCompound.getInteger("yTimer"); - // yOffset = tagCompound.getInteger("yOffset"); } @Override @@ -217,10 +206,6 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, } tagCompound.setTag("Inventory", itemList); - // tagCompound.setFloat("timer", timer); - // tagCompound.setInteger("yTimer", yTimer); - // tagCompound.setInteger("yOffset", yOffset); - return tagCompound; } @@ -240,6 +225,7 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, } @SideOnly(Side.CLIENT) + @Override public AxisAlignedBB getRenderBoundingBox(){ AxisAlignedBB bb = INFINITE_EXTENT_AABB; Block type = getBlockType(); diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java index 7a43f568..2fa0ab68 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java @@ -19,6 +19,11 @@ public class TileEntityMagicLight extends TileEntityTimer { randomiser2[0] = -1; } + @Override + public boolean shouldRenderInPass(int pass){ + return pass == 1; + } + @Override public void update(){ diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java index d9ff7e86..6e91f534 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java @@ -1,43 +1,26 @@ package electroblob.wizardry.tileentity; -import java.lang.ref.WeakReference; -import java.util.UUID; - +import electroblob.wizardry.Wizardry; import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.ITickable; -public class TileEntityPlayerSave extends TileEntity implements ITickable { +import javax.annotation.Nullable; +import java.util.UUID; - /** The entity that created this construct */ - private WeakReference caster; +public class TileEntityPlayerSave extends TileEntity { - /** - * The UUID of the caster. Note that this is only for loading purposes; during normal updates the actual entity - * instance is stored (so that getEntityByUUID is not called constantly), so this will not always be synced (this is - * why it is private). - */ + /** The UUID of the caster. As of Wizardry 4.2, this is synced, and rather than storing the caster + * instance via a weak reference, it is fetched from the UUID each time it is needed in + * {@link TileEntityPlayerSave#getCaster()}. */ private UUID casterUUID; + public TileEntityPlayerSave(){} + public TileEntityPlayerSave(EntityLivingBase caster){ - this.caster = new WeakReference(caster); - } - - public TileEntityPlayerSave(){ - - } - - @Override - public void update(){ - if(this.getCaster() == null && this.casterUUID != null){ - Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); - if(entity instanceof EntityLivingBase){ - this.caster = new WeakReference((EntityLivingBase)entity); - } - } + this.casterUUID = caster.getUniqueID(); } @Override @@ -52,7 +35,7 @@ public class TileEntityPlayerSave extends TileEntity implements ITickable { super.writeToNBT(tagCompound); if(this.getCaster() != null){ - tagCompound.setUniqueId("casterUUID", this.getCaster().getUniqueID()); + tagCompound.setUniqueId("casterUUID", casterUUID); } return tagCompound; @@ -63,12 +46,21 @@ public class TileEntityPlayerSave extends TileEntity implements ITickable { * may no longer exist are: entity died or was deleted, mob despawned, player logged out, entity teleported to * another dimension, or this construct simply had no caster in the first place. */ + @Nullable public EntityLivingBase getCaster(){ - return caster == null ? null : caster.get(); + + Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); + + if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen + Wizardry.logger.warn("{} has a non-living owner!", this); + entity = null; + } + + return (EntityLivingBase)entity; } - public void setCaster(EntityLivingBase caster){ - this.caster = new WeakReference(caster); + public void setCaster(@Nullable EntityLivingBase caster){ + this.casterUUID = caster == null ? null : caster.getUniqueID(); } } diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java new file mode 100644 index 00000000..92f4ab37 --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java @@ -0,0 +1,69 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.block.BlockThorns; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.util.ITickable; + +public class TileEntityPlayerSaveTimed extends TileEntityPlayerSave implements ITickable { + + public int timer = 0; + public int maxTimer; + + public TileEntityPlayerSaveTimed(int maxTimer){ + this.maxTimer = maxTimer; + } + + @Override + public void update(){ + + timer++; + + if(timer > maxTimer && !this.world.isRemote){ + this.world.destroyBlock(pos, false); + } + + if(timer % 2 == 0 && world.getBlockState(pos).getValue(BlockThorns.AGE) < BlockThorns.GROWTH_STAGES - 1){ + world.setBlockState(pos, world.getBlockState(pos).withProperty(BlockThorns.AGE, world.getBlockState(pos).getValue(BlockThorns.AGE) + 1), 2); + } + } + + public void setLifetime(int lifetime){ + this.maxTimer = lifetime; + } + + @Override + public void readFromNBT(NBTTagCompound tagCompound){ + super.readFromNBT(tagCompound); + timer = tagCompound.getInteger("timer"); + maxTimer = tagCompound.getInteger("maxTimer"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound tagCompound){ + super.writeToNBT(tagCompound); + tagCompound.setInteger("timer", timer); + tagCompound.setInteger("maxTimer", maxTimer); + return tagCompound; + } + + @Override + public final NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + NBTTagCompound tag = new NBTTagCompound(); + writeToNBT(tag); + return new SPacketUpdateTileEntity(pos, 1, tag); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + NBTTagCompound tag = pkt.getNbtCompound(); + readFromNBT(tag); + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java new file mode 100644 index 00000000..2fdd0393 --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java @@ -0,0 +1,218 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockPedestal; +import electroblob.wizardry.entity.living.EntityEvilWizard; +import electroblob.wizardry.entity.living.EntityWizard; +import electroblob.wizardry.packet.PacketConquerShrine; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.potion.PotionContainment; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.ArcaneLock; +import electroblob.wizardry.util.NBTExtras; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.potion.PotionEffect; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ITickable; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fml.common.network.NetworkRegistry; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class TileEntityShrineCore extends TileEntity implements ITickable { + + private static final double ACTIVATION_RADIUS = 5; + + private boolean activated = false; + private AxisAlignedBB containmentField; + private final UUID[] linkedWizards = new UUID[3]; + private TileEntity linkedContainer; + private BlockPos linkedContainerPos; // Temporary stores the container position read from NBT until the world is set + + @Override + public void setPos(BlockPos pos){ + super.setPos(pos); + initContainmentField(pos); + } + + private void initContainmentField(BlockPos pos){ + float r = PotionContainment.getContainmentDistance(0); + this.containmentField = new AxisAlignedBB(-r, -r, -r, r, r, r).offset(WizardryUtilities.getCentre(pos)); + } + + public void linkContainer(TileEntity container){ + this.linkedContainer = container; + } + + @Override + public void update(){ + + if(this.linkedContainer == null && this.linkedContainerPos != null){ + this.linkContainer(world.getTileEntity(this.linkedContainerPos)); + } + + double x = this.pos.getX() + 0.5; + double y = this.pos.getY() + 0.5; + double z = this.pos.getZ() + 0.5; + + if(!activated && world.getClosestPlayer(x, y, z, ACTIVATION_RADIUS, false) != null){ + + this.activated = true; + + if(world.isRemote){ + ParticleBuilder.create(Type.SPHERE).pos(x, y + 1, z).clr(0xf06495).scale(5).time(12).spawn(world); + } + + world.playSound(x, y, z, + WizardrySounds.BLOCK_PEDESTAL_ACTIVATE, SoundCategory.BLOCKS, 1.5f, 1, false); + + if(!world.isRemote){ + + EntityEvilWizard[] wizards = new EntityEvilWizard[linkedWizards.length]; + + for(int i = 0; i < linkedWizards.length; i++){ + + EntityEvilWizard wizard = new EntityEvilWizard(world); + + float angle = world.rand.nextFloat() * 2 * (float)Math.PI; + double x1 = this.pos.getX() + 0.5 + 5 * MathHelper.sin(angle); + double z1 = this.pos.getZ() + 0.5 + 5 * MathHelper.cos(angle); + Integer y1 = WizardryUtilities.getNearestFloor(world, new BlockPos(x1, this.pos.getY(), z1), 8); + if(y1 == null){ + // Fallback to the position of the shrine core if it failed to find a position (unlikely) + x1 = this.pos.getX() + 1; // Offset it so the wizard isn't inside the block + y1 = this.pos.getY(); + z1 = this.pos.getZ(); + } + + wizard.setLocationAndAngles(x1, y1 + 0.5, z1, 0, 0); + wizard.setElement(world.getBlockState(pos).getValue(BlockPedestal.ELEMENT)); + wizard.onInitialSpawn(world.getDifficultyForLocation(pos), null); + wizard.hasStructure = true; + + world.spawnEntity(wizard); + wizards[i] = wizard; + linkedWizards[i] = wizard.getUniqueID(); + } + + for(EntityEvilWizard wizard : wizards) wizard.groupUUIDs.addAll(Arrays.asList(linkedWizards)); + } + + containNearbyTargets(); + } + + if(activated && world.getTotalWorldTime() % 20L == 0) containNearbyTargets(); + + if(activated && areWizardsDead() && !world.isRemote){ + conquer(); + } + } + + private boolean areWizardsDead(){ + + for(UUID uuid : linkedWizards){ + Entity entity = WizardryUtilities.getEntityByUUID(world, uuid); + if(entity instanceof EntityEvilWizard && entity.isEntityAlive()) return false; + } + + return true; + } + + public void conquer(){ + + double x = this.pos.getX() + 0.5; + double y = this.pos.getY() + 0.5; + double z = this.pos.getZ() + 0.5; + + if(!world.isRemote){ + WizardryPacketHandler.net.sendToAllAround(new PacketConquerShrine.Message(this.pos), + new NetworkRegistry.TargetPoint(this.world.provider.getDimension(), x, y, z, 64)); + } + + world.setBlockState(pos, WizardryBlocks.runestone_pedestal.getDefaultState() + .withProperty(BlockPedestal.ELEMENT, world.getBlockState(pos).getValue(BlockPedestal.ELEMENT))); + + world.markTileEntityForRemoval(this); + + if(!world.isRemote){ + if(linkedContainer != null) NBTExtras.removeUniqueId(linkedContainer.getTileData(), ArcaneLock.NBT_KEY); + }else{ + TileEntity tileEntity = world.getTileEntity(this.pos.up()); + if(tileEntity != null){ // Bit of a dirty fix but it's only visual, so meh + NBTExtras.removeUniqueId(tileEntity.getTileData(), ArcaneLock.NBT_KEY); + } + } + + world.playSound(x, y, z, WizardrySounds.BLOCK_PEDESTAL_CONQUER, SoundCategory.BLOCKS, 1, 1, false); + + if(world.isRemote){ + ParticleBuilder.create(Type.SPHERE).scale(5).pos(x, y + 1, z).clr(0xf06495).time(12).spawn(world); + for(int i=0; i<5; i++){ + float brightness = 0.8f + world.rand.nextFloat() * 0.2f; + ParticleBuilder.create(Type.SPARKLE, world.rand, x, y + 1, z, 1, true) + .clr(1, brightness, brightness).spawn(world); + } + } + } + + private void containNearbyTargets(){ + + List entities = world.getEntitiesWithinAABB(EntityLivingBase.class, containmentField, + e -> e instanceof EntityPlayer || e instanceof EntityWizard || e instanceof EntityEvilWizard); + + for(EntityLivingBase entity : entities){ + entity.addPotionEffect(new PotionEffect(WizardryPotions.containment, 219)); + entity.getEntityData().setTag(PotionContainment.ENTITY_TAG, NBTUtil.createPosTag(this.pos)); + } + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound compound){ + + compound.setBoolean("activated", this.activated); + if(linkedContainer != null) compound.setTag("linkedContainerPos", NBTUtil.createPosTag(linkedContainer.getPos())); + + NBTTagList tagList = new NBTTagList(); + for(UUID uuid : linkedWizards){ + if(uuid != null) tagList.appendTag(NBTUtil.createUUIDTag(uuid)); + } + compound.setTag("wizards", tagList); + + return super.writeToNBT(compound); + } + + @Override + public void readFromNBT(NBTTagCompound compound){ + + this.activated = compound.getBoolean("activated"); + this.linkedContainerPos = NBTUtil.getPosFromTag(compound.getCompoundTag("linkedContainerPos")); + + NBTTagList tagList = compound.getTagList("wizards", Constants.NBT.TAG_COMPOUND); + int i = 0; + for(NBTBase tag : tagList){ + if(tag instanceof NBTTagCompound) linkedWizards[i++] = NBTUtil.getUUIDFromTag((NBTTagCompound)tag); + else Wizardry.logger.warn("Unexpected tag type in NBT tag list of compound tags!"); + } + + super.readFromNBT(compound); + // Must be after super + initContainmentField(this.pos); + } +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java index f93c7851..ab999d7d 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java @@ -18,7 +18,7 @@ public class TileEntityStatue extends TileEntity implements ITickable { public EntityLiving creature; private NBTTagCompound entityCompound; - private String entityName; + private ResourceLocation entityName; private float entityYawHead; private float entityYawOffset; public boolean isIce; @@ -95,8 +95,7 @@ public class TileEntityStatue extends TileEntity implements ITickable { // System.out.println(entityName); if(this.creature == null && entityName != null){ - this.creature = (EntityLiving)EntityList.createEntityByIDFromName(new ResourceLocation(this.entityName), - this.world); + this.creature = (EntityLiving)EntityList.createEntityByIDFromName(this.entityName, this.world); if(this.creature != null){ this.creature.readFromNBT(entityCompound); this.creature.rotationYawHead = this.entityYawHead; @@ -133,7 +132,7 @@ public class TileEntityStatue extends TileEntity implements ITickable { position = tagCompound.getInteger("position"); parts = tagCompound.getInteger("parts"); entityCompound = tagCompound.getCompoundTag("entity"); - entityName = tagCompound.getString("entityName"); + entityName = new ResourceLocation(tagCompound.getString("entityName")); timer = tagCompound.getInteger("timer"); lifetime = tagCompound.getInteger("lifetime"); isIce = tagCompound.getBoolean("isIce"); @@ -149,7 +148,7 @@ public class TileEntityStatue extends TileEntity implements ITickable { entityCompound = new NBTTagCompound(); if(creature != null){ creature.writeToNBT(entityCompound); - tagCompound.setString("entityName", EntityList.getEntityString(creature)); + tagCompound.setString("entityName", EntityList.getKey(creature).toString()); tagCompound.setFloat("entityYawHead", creature.rotationYawHead); tagCompound.setFloat("entityYawOffset", creature.renderYawOffset); } diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java index c8eb6062..185bbd55 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java @@ -22,11 +22,10 @@ public class TileEntityTimer extends TileEntity implements ITickable { @Override public void update(){ + timer++; - if(!this.world.isRemote){ - // System.out.println("Timer: " + timer + "/" + maxTimer); - } - if(timer > maxTimer && !this.world.isRemote){// && this.world.getBlockId(xCoord, yCoord, zCoord) == + + if(maxTimer > 0 && timer > maxTimer && !this.world.isRemote){// && this.world.getBlockId(xCoord, yCoord, zCoord) == // Wizardry.magicLight.blockID){ if(this.getBlockType() instanceof BlockVanishingCobweb){ // destroyBlock breaks the block as if broken by a player, with sound and particles. diff --git a/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java b/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java new file mode 100644 index 00000000..a066a9eb --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java @@ -0,0 +1,234 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.MindControl; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.IEntityOwnable; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * Contains some useful static methods for interacting with the ally designation system. Also handles the friendly fire + * setting. This was split off from {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to + * navigate. + */ +@Mod.EventBusSubscriber +public final class AllyDesignationSystem { + + private AllyDesignationSystem(){} // No instances! + + /** Set of constants for each of the four friendly fire settings. */ + public enum FriendlyFire { + + ALL("All", false, false), + ONLY_PLAYERS("Only players", false, true), + ONLY_OWNED("Only summoned/tamed creatures", true, false), + NONE("None", true, true); + + /** Constant array storing the names of each of the constants, in the order they are declared. */ + public static final String[] names; + + static { + names = new String[values().length]; + for(FriendlyFire setting : values()){ + names[setting.ordinal()] = setting.name; + } + } + + /** The readable name for this friendly fire setting that will be displayed on the button in the config GUI. */ + public final String name; + public final boolean blockPlayers; + public final boolean blockOwned; + + FriendlyFire(String name, boolean blockPlayers, boolean blockOwned){ + this.name = name; + this.blockPlayers = blockPlayers; + this.blockOwned = blockOwned; + } + + /** + * Gets a friendly fire setting from its string name (ignoring case), or ALL if the given name is not a valid + * setting. + */ + public static FriendlyFire fromName(String name){ + + for(FriendlyFire setting : values()){ + if(setting.name.equalsIgnoreCase(name)) return setting; + } + + Wizardry.logger.info("Invalid string for the friendly fire setting. Using default (all) instead."); + return ALL; + } + + } + + /** + * Returns whether the given target can be attacked by the given attacker. It is up to the caller of this method to + * work out what this means; it doesn't necessarily mean the target is completely immune (for example, revenge + * targeting might reasonably bypass this). This method is intended for use where the damage is indirect and/or + * unavoidable; direct attacks should not check this method. Currently this means the following situations check + * this method: + *

    + * - AI targeting for summoned creatures
    + * - AI targeting for mind-controlled creatures
    + * - Constructs with an area of effect
    + * - Instantaneous spells with an area of effect around the caster (e.g. forest's curse, thunderstorm)
    + * - Any lightning chaining effects
    + * - Any projectiles which seek targets + *

    + * Also note that the friendly fire option is dealt with in the event handler. This method acts as a sort of wrapper + * for all the AllyDesignationSystem stuff in {@link WizardData}; more details about the ally designation system can be found there. + * + * @param attacker The entity that cast the spell originally + * @param target The entity being attacked + * + * @return False under any of the following circumstances, true otherwise: + *

    + * - The target is null + *

    + * - The target is the attacker (this isn't as stupid as it sounds - anything with an AoE might cause this + * to be true, as can summoned creatures) + *

    + * - The target and the attacker are both players and the target is an ally of the attacker (but the + * attacker need not be an ally of the target) + *

    + * - The target is a creature that was summoned/controlled by the attacker or by an ally of the attacker. + *

    + * - The target is a creature that was tamed by the attacker or by an ally of the attacker + * (see {@link net.minecraft.entity.IEntityOwnable}). + *

    + * As of wizardry 4.1.2, this method now returns true instead of false if the attacker is null. This + * is because in the vast majority of cases, it makes more sense this way: if a construct has no caster, it + * should affect all entities; if a minion has no caster it should target all entities; etc. + */ + public static boolean isValidTarget(Entity attacker, Entity target){ + + // Always return true if the attacker is null + if(attacker == null) return true; + + // Always return false if the target is null + if(target == null) return false; + + // Tests whether the target is the attacker + if(target == attacker) return false; + + // I really shouldn't need to do this, but fake players seem to break stuff... + if(target instanceof FakePlayer) return false; + + // Tests whether the target is a creature that was summoned by the attacker +// if(target instanceof ISummonedCreature && ((ISummonedCreature)target).getCaster() == attacker){ +// return false; +// } + + // Tests whether the target is a creature that was summoned/tamed (or is otherwise owned) by the attacker + if(target instanceof IEntityOwnable && ((IEntityOwnable)target).getOwner() == attacker){ + return false; + } + + // Tests whether the target is a creature that was mind controlled by the attacker + if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ + + NBTTagCompound entityNBT = target.getEntityData(); + + if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ + if(attacker == WizardryUtilities.getEntityByUUID(target.world, + entityNBT.getUniqueId(MindControl.NBT_KEY))){ + return false; + } + } + } + + // Ally section + if(attacker instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) != null){ + + if(target instanceof EntityPlayer){ + // Tests whether the target is an ally of the attacker + if(WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)target)){ + return false; + } + +// }else if(target instanceof ISummonedCreature){ +// // Tests whether the target is a creature that was summoned by an ally of the attacker +// if(((ISummonedCreature)target).getCaster() instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) +// .isPlayerAlly((EntityPlayer)((ISummonedCreature)target).getCaster())){ +// return false; +// } + + }else if(target instanceof IEntityOwnable){ + // Tests whether the target is a creature that was summoned/tamed by an ally of the attacker + if(isOwnerAlly((EntityPlayer)attacker, (IEntityOwnable)target)); + + }else if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ + // Tests whether the target is a creature that was mind controlled by an ally of the attacker + NBTTagCompound entityNBT = target.getEntityData(); + + if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){ + + Entity controller = WizardryUtilities.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); + + if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){ + return false; + } + } + } + } + + return true; + } + + /** Umbrella method that covers both {@link AllyDesignationSystem#isPlayerAlly(EntityPlayer, EntityPlayer)} and + * {@link AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable)}, returning true if the given + * {@link EntityLivingBase} is either owned by the given player, an ally of the given player or owned by an ally + * of the given player. This is generally used to determine targets for healing or other group buffs. */ + public static boolean isAllied(EntityPlayer allyOf, EntityLivingBase possibleAlly){ + return (possibleAlly instanceof EntityPlayer && isPlayerAlly(allyOf, (EntityPlayer)possibleAlly)) + || (possibleAlly instanceof IEntityOwnable && (((IEntityOwnable)possibleAlly).getOwner() == allyOf + || isOwnerAlly(allyOf, (IEntityOwnable)possibleAlly))); + } + + /** Helper method for testing if the second player is an ally of the first player. Makes the code neater. + * @see AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable) */ + public static boolean isPlayerAlly(EntityPlayer allyOf, EntityPlayer possibleAlly){ + WizardData data = WizardData.get(allyOf); + return data != null && data.isPlayerAlly(possibleAlly); + } + + /** Helper method for testing if the given {@link net.minecraft.entity.IEntityOwnable}'s owner is an ally of the + * given player. This works even when the owner is not logged in, though it may not correctly respect teams when + * that is the case. */ + public static boolean isOwnerAlly(EntityPlayer allyOf, IEntityOwnable ownable){ + WizardData data = WizardData.get(allyOf); + if(data == null) return false; + Entity owner = ownable.getOwner(); + return owner instanceof EntityPlayer ? data.isPlayerAlly((EntityPlayer)owner) : data.isPlayerAlly(ownable.getOwnerId()); + } + + @SubscribeEvent + public static void onLivingAttackEvent(LivingAttackEvent event){ + + if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityPlayer + && event.getSource() instanceof IElementalDamage){ + + if(event.getEntity() instanceof EntityPlayer){ + // Prevents any magic damage to allied players if friendly fire is disabled for players + if(Wizardry.settings.friendlyFire.blockPlayers && isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(), (EntityPlayer)event.getEntity())){ + event.setCanceled(true); + } + }else{ + // Prevents any magic damage to entities owned by allied players if friendly fire is disabled for owned creatures + // Since we're dealing with players separately we might as well just use isAllied + if(Wizardry.settings.friendlyFire.blockOwned && isAllied((EntityPlayer)event.getSource().getTrueSource(), event.getEntityLiving())){ + event.setCanceled(true); + } + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/util/CustomSoundCategory.java b/src/main/java/electroblob/wizardry/util/CustomSoundCategory.java new file mode 100644 index 00000000..a0fb1394 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/CustomSoundCategory.java @@ -0,0 +1,86 @@ +package electroblob.wizardry.util; + +import com.google.common.collect.Maps; +import net.minecraft.util.SoundCategory; +import net.minecraftforge.common.util.EnumHelper; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.Map; + +/** + * Add a new CONSTANT and reference name to net.minecraft.util.SoundCategory + * + * This allows the display of a volume control in the "Music & Sound Options" dialog. + * Unfortunately the GuiScreenOptionsSounds dialog does not auto size + * properly and move the Done button lower on the screen. + * + * To initialize the class create an instance during FMLPreInitializationEvent in + * the file with the @Mod annotation or your common proxy class. + * + * Usage example: static final SoundCategory SC_MXTUNE = MODSoundCategory.add("MXTUNE"); + * + * The language file key is "soundCategory.mxtune" + * The game settings "options.txt" key is "soundCategory_mxtune" + * + * To use the MXTUNE enum constant in code it must be referenced by name because + * SoundCategory.MXTUNE does not exist at compile time. + * e.g. SoundCategory.getByName("mxtune"); + * + * @author Paul Boese aka Aeronica (modified for 1.12.2 and for conciseness/clarity by Electroblob) + * @see + * www.minecraftforge.net/forum/topic/42439-adding-additional-soundcategorys/ + */ +public final class CustomSoundCategory { + + private static final String SRG_soundLevels = "field_186714_aM"; + private static final String SRG_SOUND_CATEGORIES = "field_187961_k"; + // >> Electroblob: Don't know why this was instantiated at all, surely it's a static helper class? + + private CustomSoundCategory(){} + + /** + * Adds a new custom sound category, performing the necessary changes to GameSettings and + * + * @param name A unique name for the sound category + * @return The resulting SoundCategory object + * @throws IllegalArgumentException if name is not unique + */ + public static SoundCategory add(String name){ + + Map SOUND_CATEGORIES; + + String constantName; + String referenceName; + SoundCategory soundCategory; + // >> Electroblob: Constructors were unnecessary since strings are immutable + constantName = name.toUpperCase().replace(" ", ""); + referenceName = constantName.toLowerCase(); + // >> Electroblob: Removed array surrounding varargs argument + soundCategory = EnumHelper.addEnum(SoundCategory.class , constantName, new Class[]{String.class}, referenceName); + SOUND_CATEGORIES = ObfuscationReflectionHelper.getPrivateValue(SoundCategory.class, SoundCategory.VOICE ,"SOUND_CATEGORIES", SRG_SOUND_CATEGORIES); + if (SOUND_CATEGORIES.containsKey(referenceName)) + // >> Electroblob: changed from Error to IllegalArgumentException + throw new IllegalArgumentException("Clash in Sound Category name pools! Cannot insert " + constantName); + SOUND_CATEGORIES.put(referenceName, soundCategory); + if (FMLLaunchHandler.side() == Side.CLIENT) setSoundLevels(); + + return soundCategory; + } + + /** Game sound level options settings only exist on the client side */ + @SideOnly(Side.CLIENT) + private static void setSoundLevels(){ + // SoundCategory now contains 'name' sound category so build a new map + // >> Electroblob: Converted to local variable + Map soundLevels = Maps.newEnumMap(SoundCategory.class); + // Replace the map in the GameSettings.class + // >> Electroblob: Fully qualified names, because this class gets loaded on both sides + ObfuscationReflectionHelper.setPrivateValue(net.minecraft.client.settings.GameSettings.class, + net.minecraft.client.Minecraft.getMinecraft().gameSettings, soundLevels, + "soundLevels", SRG_soundLevels); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/util/IElementalDamage.java b/src/main/java/electroblob/wizardry/util/IElementalDamage.java index 47fad34a..a3d12aeb 100644 --- a/src/main/java/electroblob/wizardry/util/IElementalDamage.java +++ b/src/main/java/electroblob/wizardry/util/IElementalDamage.java @@ -1,9 +1,7 @@ package electroblob.wizardry.util; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.util.MagicDamage.DamageType; import net.minecraft.entity.monster.EntityCreeper; -import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -43,10 +41,6 @@ public interface IElementalDamage { && ((IElementalDamage)event.getSource()).getType() == DamageType.SHOCK){ // Charges creepers when they are hit by shock damage WizardryUtilities.chargeCreeper((EntityCreeper)event.getEntityLiving()); - // Gives the player that caused the shock damage the 'It's Gonna Blow' achievement - if(event.getSource().getTrueSource() instanceof EntityPlayer){ - WizardryAdvancementTriggers.charge_creeper.triggerFor((EntityPlayer)event.getSource().getTrueSource()); - } } } } diff --git a/src/main/java/electroblob/wizardry/util/Location.java b/src/main/java/electroblob/wizardry/util/Location.java new file mode 100644 index 00000000..dfeec264 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/Location.java @@ -0,0 +1,47 @@ +package electroblob.wizardry.util; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.util.math.BlockPos; + +import javax.annotation.concurrent.Immutable; + +/** Simple wrapper class that stores a {@link BlockPos} and an integer dimension ID. */ +@Immutable +public class Location { + + public final BlockPos pos; + public final int dimension; + + public Location(BlockPos pos, int dimension){ + this.pos = pos; + this.dimension = dimension; + } + + /** Returns true if the given location refers to the same coordinates and dimension as this one. */ + @Override + public boolean equals(Object that){ + + if(this == that) return true; + + if(that instanceof Location){ + return this.pos.equals(((Location)that).pos) && this.dimension == ((Location)that).dimension; + } + + return false; + } + + /** Creates and returns an {@link NBTTagCompound} representing this location. The returned compound tag is the + * same as that returned by {@link NBTUtil#createPosTag(BlockPos)}, but with an extra "dimension" key. */ + public NBTTagCompound toNBT(){ + NBTTagCompound nbt = NBTUtil.createPosTag(pos); + nbt.setInteger("dimension", dimension); + return nbt; + } + + /** Creates a new {@code Location} from the given {@link NBTTagCompound}. The given compound tag should be the + * same as that returned by {@link NBTUtil#createPosTag(BlockPos)}, but with an extra "dimension" key. */ + public static Location fromNBT(NBTTagCompound nbt){ + return new Location(NBTUtil.getPosFromTag(nbt), nbt.getInteger("dimension")); + } +} diff --git a/src/main/java/electroblob/wizardry/util/MagicDamage.java b/src/main/java/electroblob/wizardry/util/MagicDamage.java index 5d849035..23ce536e 100644 --- a/src/main/java/electroblob/wizardry/util/MagicDamage.java +++ b/src/main/java/electroblob/wizardry/util/MagicDamage.java @@ -1,36 +1,15 @@ package electroblob.wizardry.util; -import java.util.Collections; -import java.util.HashMap; -import java.util.EnumSet; -import java.util.Map; - -import electroblob.wizardry.entity.living.EntityBlazeMinion; -import electroblob.wizardry.entity.living.EntityIceGiant; -import electroblob.wizardry.entity.living.EntityIceWraith; -import electroblob.wizardry.entity.living.EntityLightningWraith; -import electroblob.wizardry.entity.living.EntityPhoenix; -import electroblob.wizardry.entity.living.EntityShadowWraith; -import electroblob.wizardry.entity.living.EntitySkeletonMinion; -import electroblob.wizardry.entity.living.EntitySpiderMinion; -import electroblob.wizardry.entity.living.EntityStormElemental; -import electroblob.wizardry.entity.living.EntityZombieMinion; +import electroblob.wizardry.entity.living.*; import net.minecraft.entity.Entity; import net.minecraft.entity.boss.EntityDragon; import net.minecraft.entity.boss.EntityWither; -import net.minecraft.entity.monster.EntityBlaze; -import net.minecraft.entity.monster.EntityCaveSpider; -import net.minecraft.entity.monster.EntityGhast; -import net.minecraft.entity.monster.EntityMagmaCube; -import net.minecraft.entity.monster.EntityPigZombie; -import net.minecraft.entity.monster.EntitySkeleton; -import net.minecraft.entity.monster.EntitySnowman; -import net.minecraft.entity.monster.EntitySpider; -import net.minecraft.entity.monster.EntityWitherSkeleton; -import net.minecraft.entity.monster.EntityZombie; +import net.minecraft.entity.monster.*; import net.minecraft.util.DamageSource; import net.minecraft.util.EntityDamageSource; +import java.util.*; + // A note on the use of the vanilla damagesources: // When using indirect damage sources, the SECOND argument is the original entity (i.e. the caster), and the // FIRST argument is the actual projectile or whatever that does the damage. getTrueSource() will return @@ -45,11 +24,13 @@ import net.minecraft.util.EntityDamageSource; // type 'attributes'. This is my attempt to collect all of these into a reasonably coherent system. /** + * "Ouch, that hurt!" + *

    * As of wizardry 1.1, this class has replaced the damagesource-related methods in WizardryUtilities, allowing a * {@link DamageType} to be specified with the damage. The main reason for this is so that damage sources can fit with * the vanilla behaviour on armour enchantments and such like whilst still being classified as wizardry damage for the * purposes of friendly fire, etc. - *

    + *

    * In the future, there is scope for an entirely standalone mod based on this idea. Perhaps 'Elemental Damage' could * be a config-only mod which allows its users to define any number of specific damage types, then give any creature a * resistance, immunity or vulnerability to each, as well as being able to specify the sources for each type, such as @@ -79,29 +60,29 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * them. */ public enum DamageType { - /** Generic magic damage from the wizardry mod. Like vanilla magic damage, except it doesn't bypass armour. */ + /** Generic magic damage from wizardry. Like vanilla magic damage, except it doesn't bypass armour. */ MAGIC, - /** Fire damage from the wizardry mod. Counts as fire damage in the vanilla system, so is blocked by any mobs + /** Fire damage from wizardry. Counts as fire damage in the vanilla system, so is blocked by any mobs * that are immune to fire and entities with the fire resistance effect, and is affected by the fire protection * enchantment. */ FIRE, - /** Frost (ice) damage from the wizardry mod. Snow golems, ice wraiths and ice giants are immune. */ + /** Frost (ice) damage from wizardry. Snow golems, ice wraiths and ice giants are immune. */ FROST, - /** Shock (lightning) damage from the wizardry mod. Lightning wraiths and storm elementals are immune. */ + /** Shock (lightning) damage from wizardry. Lightning wraiths and storm elementals are immune. */ SHOCK, - /** Wither damage from the wizardry mod. Withers, wither skeletons and shadow wraiths are immune. */ + /** Wither damage from wizardry. Withers, wither skeletons and shadow wraiths are immune. */ WITHER, - /** Poison damage from the wizardry mod. Spiders, cave spiders and undead mobs are immune. */ + /** Poison damage from wizardry. Spiders, cave spiders and undead mobs are immune. */ POISON, - /** Force damage from the wizardry mod. */ // Insubstantial creatures (ghast, shadow wraith, etc.) are immune? + /** Force damage from wizardry. */ // Insubstantial creatures (ghast, shadow wraith, etc.) are immune? FORCE, - /** Blast damage from the wizardry mod. Affected by the blast protection enchantment. */ + /** Blast damage from wizardry. Affected by the blast protection enchantment. */ BLAST, - /** Radiant damage from the wizardry mod. */ - RADIANT; + /** Radiant damage from wizardry. */ + RADIANT } - static{ + static { // Of course, the entities that are immune to fire already are since there's a vanilla system for that, but // they're included here anyway for completeness and in case anyone wants to check if an entity is immune to // an unspecified element for reasons other than dealing damage. @@ -115,6 +96,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage setEntityImmunities(EntityStormElemental.class, DamageType.FIRE, DamageType.SHOCK); setEntityImmunities(EntityWither.class, DamageType.FIRE, DamageType.WITHER); setEntityImmunities(EntitySnowman.class, DamageType.FROST); + setEntityImmunities(EntityPolarBear.class, DamageType.FROST); setEntityImmunities(EntityIceWraith.class, DamageType.FROST); setEntityImmunities(EntityIceGiant.class, DamageType.FROST); setEntityImmunities(EntityLightningWraith.class, DamageType.SHOCK); @@ -176,7 +158,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * not bypass armour and has a player as the source (rather than nothing). It is still classed as magic damage (for * the record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway). * isRetaliatory defaults to false. - *

    + *

    * Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the * entire mod just to get rid of this method and use the constructor instead. * @@ -184,7 +166,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * @param type The type that this damage belongs to; used for resistances and wand perks. Use * {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element * even though the spell has one - for example, not all necromancy spells are 'withery', so some of them - * might reasonably affect creatures that are ususally immune to wither effects). + * might reasonably affect creatures that are usually immune to wither effects). * @return A damagesource object of type EntityDamageSource */ public static DamageSource causeDirectMagicDamage(Entity caster, DamageType type){ @@ -196,7 +178,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * types to allow things to distinguish between magic and regular melee/swords. Unlike DamageSource.MAGIC, it does * not bypass armour and has a player as the source (rather than nothing). It is still classed as magic damage (for * the record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway). - *

    + *

    * Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the * entire mod just to get rid of this method and use the constructor instead. * @@ -204,7 +186,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * @param type The type that this damage belongs to; used for resistances and wand perks. Use * {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element * even though the spell has one - for example, not all necromancy spells are 'withery', so some of them - * might reasonably affect creatures that are ususally immune to wither effects). + * might reasonably affect creatures that are usually immune to wither effects). * @param isRetaliatory whether this damage source came from a retaliatory attack; prevents infinite loops occurring * when two entities damage each other with retaliatory effects. * @return A damagesource object of type EntityDamageSource @@ -219,7 +201,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * DamageSource.causeIndirectMagicDamage, it does not bypass armour. It is still classed as magic damage (for the * record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway). * isRetaliatory defaults to false. - *

    + *

    * Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the * entire mod just to get rid of this method and use the constructor instead. * @@ -228,7 +210,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * @param type The type that this damage belongs to; used for resistances and wand perks. Use * {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element * even though the spell has one - for example, not all necromancy spells are 'withery', so some of them - * might reasonably affect creatures that are ususally immune to wither effects). + * might reasonably affect creatures that are usually immune to wither effects). * @return A damagesource object of type EntityDamageSourceIndirect */ public static DamageSource causeIndirectMagicDamage(Entity magic, Entity caster, DamageType type){ @@ -240,7 +222,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * types to allow things to distinguish between magic and regular arrows/throwables. Unlike * DamageSource.causeIndirectMagicDamage, it does not bypass armour. It is still classed as magic damage (for the * record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway). - *

    + *

    * Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the * entire mod just to get rid of this method and use the constructor instead. * @@ -249,7 +231,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage * @param type The type that this damage belongs to; used for resistances and wand perks. Use * {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element * even though the spell has one - for example, not all necromancy spells are 'withery', so some of them - * might reasonably affect creatures that are ususally immune to wither effects). + * might reasonably affect creatures that are usually immune to wither effects). * @param isRetaliatory whether this damage source came from a retaliatory attack; prevents infinite loops occurring * when two entities damage each other with retaliatory effects. * @return A damagesource object of type EntityDamageSourceIndirect diff --git a/src/main/java/electroblob/wizardry/util/NBTExtras.java b/src/main/java/electroblob/wizardry/util/NBTExtras.java new file mode 100644 index 00000000..309fa217 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/NBTExtras.java @@ -0,0 +1,231 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.Wizardry; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import java.util.*; +import java.util.function.Function; + +/** + * Contains a number of useful static methods for interacting with NBT data, particularly involving collections. + * This was split off from {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public final class NBTExtras { + + private NBTExtras(){} // No instances! + + /** + * Generic method that stores any Map to an NBTTagList, given two functions that convert the key and value types in + * that map to subclasses of NBTBase. For what it's worth, there is very little point in using this unless you can + * use something more concise than an anonymous class to do the conversion. A lambda expression, or better, a method + * reference, would fit nicely. For example, take ExtendedPlayer's use of this to store conjured item durations: + *

    + * properties.setTag("conjuredItems", WizardryUtilities.mapToNBT(this.conjuredItemDurations, + * item -> new NBTTagInt(Item.getIdFromItem((Item)item)), NBTTagInt::new)); + *

    + * This is a lot nicer than simply iterating through the map, because for that you need to use the entry list, which + * introduces local variables that aren't really necessary. Notice that, since the values V in the map are simply + * Integer objects, a simple constructor reference to NBTTagInt::new can be used instead of a lambda expression (the + * Integer is auto-unboxed to int). + * + * @param The type of key stored in the given Map. + * @param The type of value stored in the given Map. + * @param The subtype of NBTBase that the keys (of type K) will be converted to. + * @param The subtype of NBTBase that the values (of type V) will be converted to. + * @param map The Map to be stored. + * @param keyFunction A Function that converts the keys in the map to NBT objects that can be stored. + * @param valueFunction A Function that converts the values in the map to NBT objects that can be stored. + * @param keyTagName The tag name to use for the key tags. + * @param valueTagName The tag name to use for the value tags. + * @return An NBTTagList that represents the given Map. + */ + public static NBTTagList mapToNBT(Map map, + Function keyFunction, Function valueFunction, String keyTagName, String valueTagName){ + + NBTTagList tagList = new NBTTagList(); + + for(Map.Entry entry : map.entrySet()){ + NBTTagCompound mapping = new NBTTagCompound(); + mapping.setTag(keyTagName, keyFunction.apply(entry.getKey())); + mapping.setTag(valueTagName, valueFunction.apply(entry.getValue())); + tagList.appendTag(mapping); + } + + return tagList; + } + + /** + * See {@link NBTExtras#mapToNBT(Map, Function, Function, String, String)}; this version is for when the + * names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). + */ + public static NBTTagList mapToNBT(Map map, + Function keyFunction, Function valueFunction){ + return mapToNBT(map, keyFunction, valueFunction, "key", "value"); + } + + /** + * Generic method that reads a Map from an NBTTagList, given two functions that convert the key and value tag types + * into the key and value types in the returned map. The given NBTTagList remains unchanged after calling this + * method. + * + * @param The type of key stored in the returned Map. + * @param The type of value stored in the returned Map. + * @param The subtype of NBTBase that the keys are stored as. + * @param The subtype of NBTBase that the values are stored as. + * @param tagList The NBTTagList to be converted. This must be a list of compound tags. + * @param keyFunction A Function that converts the generic NBTBase tags in the list to keys of type K for the map. + * @param valueFunction A Function that converts the generic NBTBase tags in the list to values of type V for the + * map. + * @param keyTagName The tag name used for the key tags. + * @param valueTagName The tag name used for the value tags. + * @return A Map containing the keys and values stored in the given NBTTagList. Can be empty, but not null. + * @throws ClassCastException If the tags are not of the expected type. + * @see NBTExtras#mapToNBT(Map, Function, Function, String, String) + */ + @SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here. + public static Map NBTToMap(NBTTagList tagList, + Function keyFunction, Function valueFunction, String keyTagName, String valueTagName){ + + Map map = new HashMap<>(); + + for(int i = 0; i < tagList.tagCount(); i++){ + NBTTagCompound mapping = tagList.getCompoundTagAt(i); + NBTBase keyTag = mapping.getTag(keyTagName); + NBTBase valueTag = mapping.getTag(valueTagName); + K key = null; + try{ + key = keyFunction.apply((L)keyTag); + }catch (ClassCastException e){ + Wizardry.logger.error( + "Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[keyTag.getId()], e); + } + V value = null; + try{ + value = valueFunction.apply((W)valueTag); + }catch (ClassCastException e){ + Wizardry.logger.error( + "Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[valueTag.getId()], + e); + } + map.put(key, value); + } + + return map; + } + + /** + * See {@link NBTExtras#NBTToMap(NBTTagList, Function, Function, String, String)}; this version is for when + * the names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). + */ + public static Map NBTToMap(NBTTagList tagList, + Function keyFunction, Function valueFunction){ + return NBTToMap(tagList, keyFunction, valueFunction, "key", "value"); + } + + /** + * Stores the given {@link Collection} to an {@link NBTTagList} and returns it, converting the elements in the + * collection to NBT tags (subclasses of {@link NBTBase}) according to the supplied mapper function. + * + * @param The type of element stored in the given collection. + * @param The NBT tag type that the elements will be converted to. + * @param list The collection to be stored. + * @param mapper A function that converts the elements in the collection to NBT objects that can be stored. + * @return An {@code NBTTagList} that represents the given collection. + */ + public static NBTTagList listToNBT(Collection list, Function mapper){ + + NBTTagList tagList = new NBTTagList(); + // If the collection is ordered, it will preserve the order, even though we don't know what type it is yet. + for(E element : list){ + tagList.appendTag(mapper.apply(element)); + } + + return tagList; + } + + /** + * Reads a {@link Collection} from the given {@link NBTTagList}, given a function that converts the element tag + * types to the element types in the returned collection. The given {@code NBTTagList} remains unchanged after + * calling this method. Unless the target variable for this method is of type {@code Collection}, you will need to + * create a new collection containing the elements in the returned collection via that collection's constructor (e.g. + * {@code new HashSet(collection)}). + *

    + * Although this method returns a Collection rather than any of its subtypes, it uses + * an ArrayList internally to guarantee the order of the elements in the returned collection is the same as the + * order in which they were stored. + * + * @param The type of element stored in the returned Collection. + * @param The subtype of NBTBase that the elements are stored as. + * @param tagList The NBTTagList to be converted. + * @param function A Function that converts the generic NBTBase tags in the list to elements for the collection. + * Chances are you will need to cast the NBTBase tag to whichever NBT tag type you are expecting in order to + * access the appropriate getter method. + * @return A Collection containing the elements stored in the given NBTTagList. Can be empty, but not null. + * @throws ClassCastException If the tags are not of the expected type. + */ + @SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here. + public static Collection NBTToList(NBTTagList tagList, Function function){ + // Uses an ArrayList to guarantee iteration order, and also to permit duplicate elements (which are + // perfectly reasonable in this context). + Collection list = new ArrayList<>(); + // The original tag list should remain unchanged, hence the copy. + NBTTagList tagList2 = tagList.copy(); + + while(!tagList2.isEmpty()){ + NBTBase tag = tagList2.removeTag(0); + // Why oh why is NBTTagList not parametrised? It even has a tagType field, so it must know! + try{ + list.add(function.apply((T)tag)); + }catch (ClassCastException e){ + Wizardry.logger.error( + "Error when reading list from NBT: unexpected tag type " + NBTBase.NBT_TYPES[tag.getId()], e); + } + } + + return list; + + } + + /** + * Removes the UUID with the given key from the given NBT tag, if any. Why this doesn't exist in vanilla I have + * no idea. + *

    + * Usage note: this method complements {@link NBTTagCompound#setUniqueId(String, UUID)} and + * {@link NBTTagCompound#getUniqueId(String)}, which store UUIDs by appending "Most" and "Least" to the given + * key to store the most and least significant UUID bits respectively. It will not work for the UUID methods in + * {@link net.minecraft.nbt.NBTUtil}, which store the long values under "M" and "L" in their own compound tag. + */ + public static void removeUniqueId(NBTTagCompound tag, String key){ + tag.removeTag(key + "Most"); + tag.removeTag(key + "Least"); + } + + /** + * Returns an NBTTagCompound which contains only the given UUID, stored using + * {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists. + * @deprecated Use {@link net.minecraft.nbt.NBTUtil#createUUIDTag(UUID)}. Note that this will break backwards + * compatibility because it uses "M" and "L" instead of "uuidMost" and "uuidLeast". + */ + @Deprecated + public static NBTTagCompound UUIDtoTagCompound(UUID id){ + NBTTagCompound tag = new NBTTagCompound(); + tag.setUniqueId("uuid", id); + return tag; + } + + /** + * Wrapper for {@link NBTTagCompound#getUniqueId(String)} which converts an NBTTagCompound directly to a UUID. + * Intended to be used as the inverse of {@link NBTExtras#UUIDtoTagCompound(UUID)}. + * @deprecated Use {@link net.minecraft.nbt.NBTUtil#getUUIDFromTag(NBTTagCompound)}. Note that this will break + * backwards compatibility because it uses "M" and "L" instead of "uuidMost" and "uuidLeast". + */ + @Deprecated + public static UUID tagCompoundToUUID(NBTTagCompound tag){ + return tag.getUniqueId("uuid"); + } +} diff --git a/src/main/java/electroblob/wizardry/util/ParticleBuilder.java b/src/main/java/electroblob/wizardry/util/ParticleBuilder.java new file mode 100644 index 00000000..273875b2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/ParticleBuilder.java @@ -0,0 +1,777 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.Wizardry; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.Random; + +/** + * "Don't waste time spawning particles manually - let {@code ParticleBuilder} do the work for you!" + *

    + * Singleton class that builds wizardry particles. This is an alternative (and neater, I think) solution to vanilla's + * varargs-based system. All building methods are chainable, so particles can be created using only one line of code, + * similar to how {@code BufferBuilder} is used for drawing vertices. This class replaces the particle spawning methods + * in wizardry's proxies. + *

    + * {@link ParticleBuilder#instance} retrieves the static instance of the particle builder. Use + * {@link ParticleBuilder#particle(ResourceLocation)} to start building a particle, or alternatively use the static + * convenience version {@link ParticleBuilder#create(ResourceLocation)}. Use {@link ParticleBuilder#spawn(World)} + * to finish building and spawn the particle. Between these two, a variety of parameters can be set using the various + * setter methods (see individual method descriptions for more details). These, along with {@code ParticleBuilder.particle(...)}, + * return the particle builder instance, allowing them to be chained together to spawn particles using a single line of code. + * If any parameters are unspecified these will default to certain values, which may or may not depend on the particle type. + * Not all parameters affect all particles. Again, see individual method descriptions for more details. + *

    + * For example, a typical call to the particle builder might look something like this: + *

    + * ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).clr(r, g, b).spawn(world); + *

    + * It also goes without saying that this class should only ever be used client-side. Attempting to spawn particles + * on the server side will not work and will print a warning to the console. + * @author Electroblob + * @since Wizardry 4.2 + */ +// The number of different combinations of parameters now required for the various particle +// types in wizardry made the method overloads in the proxies very cumbersome and inevitably resulted in redundant +// parameters, which made the code messy and hard to read. Those methods have now been removed. + +// Strictly speaking, this isn't a builder class in the traditional sense, because rather than returning the built +// object at the end, it sends it to be processed instead and returns nothing. Additionally, unlike most builders +// it's a singleton, because it's likely to be called very frequently and there's no point making a new instance +// every time and clogging the heap with objects. It's also lazy, see the comment about builder variables below. +public final class ParticleBuilder { + + /** The static instance of the particle builder. */ + public static final ParticleBuilder instance = new ParticleBuilder(); + + /** Whether the particle builder is currently building or not. */ + private boolean building = false; + + // Builder variables + // We can't just store a particle and set its parameters in the builder methods, because the server won't like having + // a field of a client-only type + private ResourceLocation type; + private double x, y, z; + private double vx, vy, vz; + private float r, g, b; + private float fr, fg, fb; + private double radius; + private double rpt; + private int lifetime; + private boolean gravity; + private boolean shaded; + private boolean collide; + private float scale; + private Entity entity; + private float yaw, pitch; + private double tx, ty, tz; + private double tvx, tvy, tvz; + private Entity target; + private long seed; + private double length; + + /** + * {@link ResourceLocation} constants representing the different types of particle added by wizardry. These + * effectively replace the enum {@code WizardryParticleType} from previous versions. + *

    + * Individual constants have comments detailing their corresponding default parameters. A range of values indicates + * randomness. + *

    + * To register your own particle types, use {@link electroblob.wizardry.client.particle.ParticleWizardry#registerParticle( + * ResourceLocation, electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory) + * ParticleWizardry.registerParticle(ResourceLocation, IWizardryParticleFactory)}. + */ + // This was originally an enum, but I think having 'Type' explicitly declared is quite nice so I've left it as a + // nested class. + public static class Type { + /** 3D-rendered light-beam particle.

    Defaults:
    Lifetime: 1 tick
    Colour: white */ + public static final ResourceLocation BEAM = new ResourceLocation(Wizardry.MODID,"beam"); + /** Helical animated 'buffing' particle.

    Defaults:
    Lifetime: 15 ticks + *
    Velocity: (0, 0.27, 0)
    Colour: white */ + public static final ResourceLocation BUFF = new ResourceLocation(Wizardry.MODID,"buff"); + /** Spiral particle, like potions.

    Defaults:
    Lifetime: 8-40 ticks
    Colour: white */ + public static final ResourceLocation DARK_MAGIC = new ResourceLocation(Wizardry.MODID,"dark_magic"); + /** Single pixel particle.

    Defaults:
    Lifetime: 16-80 ticks
    Colour: white */ + public static final ResourceLocation DUST = new ResourceLocation(Wizardry.MODID,"dust"); + /** Rapid flash, like fireworks.

    Defaults:
    Lifetime: 6 ticks
    Colour: white */ + public static final ResourceLocation FLASH = new ResourceLocation(Wizardry.MODID,"flash"); + /** Small shard of ice.

    Defaults:
    Lifetime: 8-40 ticks
    Gravity: true */ + public static final ResourceLocation ICE = new ResourceLocation(Wizardry.MODID,"ice"); + /** Single leaf.

    Defaults:
    Lifetime: 10-15 ticks
    Velocity: (0, -0.03, 0) + *
    Colour: green/brown */ + public static final ResourceLocation LEAF = new ResourceLocation(Wizardry.MODID,"leaf"); + /** 3D-rendered lightning particle.

    Defaults:
    Lifetime: 3 ticks
    Colour: blue */ + public static final ResourceLocation LIGHTNING = new ResourceLocation(Wizardry.MODID,"lightning"); + /** 2D lightning effect, normally on the ground.

    Defaults:
    Lifetime: 7 ticks + *
    Facing: up */ + public static final ResourceLocation LIGHTNING_PULSE = new ResourceLocation(Wizardry.MODID,"lightning_pulse"); + /** Bubble that doesn't burst in air.

    Defaults:
    Lifetime: 8-40 ticks */ + public static final ResourceLocation MAGIC_BUBBLE = new ResourceLocation(Wizardry.MODID,"magic_bubble"); + /** Animated flame.

    Defaults:
    Lifetime: 12-16 ticks
    */ + public static final ResourceLocation MAGIC_FIRE = new ResourceLocation(Wizardry.MODID,"magic_fire"); + /** Soft-edged round particle.

    Defaults:
    Lifetime: 8-40 ticks
    Colour: white */ + public static final ResourceLocation PATH = new ResourceLocation(Wizardry.MODID,"path"); + /** Scorch mark.

    Defaults:
    Lifetime: 100-140 ticks
    Colour: black
    Fade: black */ + public static final ResourceLocation SCORCH = new ResourceLocation(Wizardry.MODID,"scorch"); + /** Snowflake particle.

    Defaults:
    Lifetime: 40-50 ticks
    Velocity: (0, -0.02, 0) */ + public static final ResourceLocation SNOW = new ResourceLocation(Wizardry.MODID,"snow"); + /** Animated lightning particle.

    Defaults:
    Lifetime: 3 ticks */ + public static final ResourceLocation SPARK = new ResourceLocation(Wizardry.MODID,"spark"); + /** Animated sparkle particle.

    Defaults:<
    Lifetime: 48-60 ticks
    Colour: white */ + public static final ResourceLocation SPARKLE = new ResourceLocation(Wizardry.MODID,"sparkle"); + /** 3D-rendered expanding sphere.

    Defaults:<
    Lifetime: 6 ticks
    Colour: white */ + public static final ResourceLocation SPHERE = new ResourceLocation(Wizardry.MODID,"sphere"); + /** Wrapped animated 'summoning' particle.

    Defaults:<
    Lifetime: 15 ticks */ + public static final ResourceLocation SUMMON = new ResourceLocation(Wizardry.MODID,"summon"); + /** 3D-rendered vine particle.

    Defaults:
    Lifetime: 1 tick
    Colour: green */ + public static final ResourceLocation VINE = new ResourceLocation(Wizardry.MODID,"vine"); + } + + private ParticleBuilder(){ + reset(); + } + + // ============================================= Core builder methods ============================================= + + /** + * Starts building a particle of the given type. Static convenience version of + * {@link ParticleBuilder#particle(ResourceLocation)}; makes code more concise. + * @param type The type of particle to build + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is already building. + */ + public static ParticleBuilder create(ResourceLocation type){ + return ParticleBuilder.instance.particle(type); + } + + /** + * Starts building a particle of the given type. + * @param type The type of particle to build + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is already building. + */ + public ParticleBuilder particle(ResourceLocation type){ + if(building) throw new IllegalStateException("Already building! Particle being built: " + getCurrentParticleString()); + this.type = type; + this.building = true; + return this; + } + + /** Gets a readable string representation of the current builder parameters; used in error messages. */ + private String getCurrentParticleString(){ + return String.format("[ Type: %s, Position: (%s, %s, %s), Velocity: (%s, %s, %s), Colour: (%s, %s, %s), " + + "Fade Colour: (%s, %s, %s), Radius: %s, Revs/tick: %s, Lifetime: %s, Gravity: %s, Shaded: %s, " + + "Scale: %s, Entity: %s ]", + type, x, y, z, vx, vy, vz, r, g, b, fr, fg, fb, radius, rpt, lifetime, gravity, shaded, scale, entity); + } + + /** + * Sets the position of the particle being built. If unspecified, this defaults to the origin (0, 0, 0). If an entity + * is specified using {@link ParticleBuilder#entity(Entity)}, this will be relative to that entity's position. + *

    + * Affects: All particle types + * @param x The x coordinate to set + * @param y The y coordinate to set + * @param z The z coordinate to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder pos(double x, double y, double z){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * Sets the position of the particle being built. This is a vector-based alternative to {@link ParticleBuilder#pos( + * double, double, double)}, allowing for even more concise code when a vector is available. + *

    + * Affects: All particle types + * @param pos A vector representing the coordinates of the particle to be built. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder pos(Vec3d pos){ + return pos(pos.x, pos.y, pos.z); + } + + /** + * Sets the velocity of the particle being built. If unspecified, this defaults to the particle's default velocity, + * specified within its constructor. + *

    + * Affects: All particle types + * @param vx The x velocity to set + * @param vy The y velocity to set + * @param vz The z velocity to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder vel(double vx, double vy, double vz){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.vx = vx; + this.vy = vy; + this.vz = vz; + return this; + } + + /** + * Sets the velocity of the particle being built. This is a vector-based alternative to {@link ParticleBuilder#vel( + * double, double, double)}, allowing for even more concise code when a vector is available. + *

    + * Affects: All particle types except + * @param vel A vector representing the velocity of the particle to be built. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder vel(Vec3d vel){ + return vel(vel.x, vel.y, vel.z); + } + + /** + * Sets the colour of the particle being built. If unspecified, this defaults to the particle's default colour, + * specified within its constructor. If all colour components are 0 or 1, at least one must have the float suffix + * ({@code f} or {@code F}) or the integer overload will be used instead, causing the particle to appear black! + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param r The red colour component to set; will be clamped to between 0 and 1 + * @param g The green colour component to set; will be clamped to between 0 and 1 + * @param b The blue colour component to set; will be clamped to between 0 and 1 + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder clr(float r, float g, float b){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.r = MathHelper.clamp(r, 0, 1); + this.g = MathHelper.clamp(g, 0, 1); + this.b = MathHelper.clamp(b, 0, 1); + return this; + } + + /** + * Sets the colour of the particle being built. This is an 8-bit (0-255) integer version of + * {@link ParticleBuilder#clr(float, float, float)}. + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param r The red colour component to set; will be clamped to between 0 and 255 + * @param g The green colour component to set; will be clamped to between 0 and 255 + * @param b The blue colour component to set; will be clamped to between 0 and 255 + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder clr(int r, int g, int b){ + return this.clr(r/255f, g/255f, b/255f); // Yes, 255 is correct and not 256, or else we can't have pure white + } + + /** + * Sets the colour of the particle being built. This is a 6-digit hex colour version of + * {@link ParticleBuilder#clr(float, float, float)}. + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param hex The colour to be set, as a packed 6-digit hex integer (e.g. 0xff0000). + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder clr(int hex){ + int r = (hex & 0xFF0000) >> 16; + int g = (hex & 0xFF00) >> 8; + int b = (hex & 0xFF); + return this.clr(r, g, b); + } + + /** + * Sets the fade colour of the particle being built. If unspecified, this defaults to the whatever the particle's base + * colour is. If all colour components are 0 or 1, at least one must have the float suffix + * ({@code f} or {@code F}) or the integer overload will be used instead, causing the particle to appear black! + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param r The red colour component to set; will be clamped to between 0 and 1 + * @param g The green colour component to set; will be clamped to between 0 and 1 + * @param b The blue colour component to set; will be clamped to between 0 and 1 + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder fade(float r, float g, float b){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.fr = MathHelper.clamp(r, 0, 1); + this.fg = MathHelper.clamp(g, 0, 1); + this.fb = MathHelper.clamp(b, 0, 1); + return this; + } + + /** + * Sets the fade colour of the particle being built. This is an 8-bit (0-255) integer version of + * {@link ParticleBuilder#fade(float, float, float)}. + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param r The red colour component to set; will be clamped to between 0 and 255 + * @param g The green colour component to set; will be clamped to between 0 and 255 + * @param b The blue colour component to set; will be clamped to between 0 and 255 + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder fade(int r, int g, int b){ + return this.clr(r/255f, g/255f, b/255f); // Yes, 255 is correct and not 256, or else we can't have pure white + } + + /** + * Sets the fade colour of the particle being built. This is a 6-digit hex colour version of + * {@link ParticleBuilder#fade(float, float, float)}. + *

    + * Affects: All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE} + * and {@link Type#MAGIC_FIRE MAGIC_FIRE} + * @param hex The colour to be set, as a packed 6-digit hex integer (e.g. 0xff0000). + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder fade(int hex){ + int r = (hex & 0xFF0000) >> 16; + int g = (hex & 0xFF00) >> 8; + int b = (hex & 0xFF); + return this.clr(r, g, b); + } + + /** + * Sets the scale of the particle being built. If unspecified, this defaults to 1. + *

    + * Affects: All particle types + * @param scale The scale to set, as a multiple of the particle's default scale + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder scale(float scale){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.scale = scale; + return this; + } + + /** + * Sets the lifetime of the particle being built. If unspecified, this defaults to the particle's default lifetime, + * specified within its constructor. + *

    + * Affects: All particle types + * @param lifetime The lifetime to set in ticks + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder time(int lifetime){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.lifetime = lifetime; + return this; + } + + /** + * Sets the seed of the particle being built. If unspecified, this defaults to the particle's default seed, + * specified within its constructor (this is normally chosen at random). + *

    + * Pro tip: to get a particle to stay the same while a continuous spell is in use (but change between casts), + * use {@code .seed(world.getTotalWorldTime() - ticksInUse)}. + *

    + * Affects: All particle types + * @param seed The seed to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder seed(long seed){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.seed = seed; + return this; + } + + /** + * Sets the spin parameters of the particle being built. If unspecified, these both default to 0. + *

    + * Affects: All particle types + * @param radius The rotation radius to set + * @param speed The rotation speed to set, in revolutions per tick + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder spin(double radius, double speed){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.radius = radius; + this.rpt = speed; + return this; + } + + // Used to say Affects: {@link Type#ICE ICE}, {@link Type#SPARKLE SPARKLE} - not sure that's true any more + /** + * Sets the gravity of the particle being built. If unspecified, this defaults to false. + *

    + * Affects: All particle types + * @param gravity True to enable gravity for the particle, false to disable + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder gravity(boolean gravity){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.gravity = gravity; + return this; + } + + /** + * Sets the shading of the particle being built. If unspecified, this defaults to false. + *

    + * Affects: All particle types + * @param shaded True to enable shading for the particle, false for full brightness + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder shaded(boolean shaded){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.shaded = shaded; + return this; + } + + /** + * Sets the collisions of the particle being built. If unspecified, this defaults to false. + *

    + * Affects: All particle types + * @param collide True to enable block collisions for the particle, false to disable + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder collide(boolean collide){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.collide = collide; + return this; + } + + /** + * Sets the entity of the particle being built. This will cause the particle to move with the given entity, and will + * make the position specified using {@link ParticleBuilder#pos(double, double, double)} relative to that + * entity's position. + *

    + * Affects: All particle types + * @param entity The entity to set (passing in null will do nothing but will not cause any problems, so for the sake + * of conciseness it is not necessary to perform a null check on the passed-in argument) + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder entity(Entity entity){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.entity = entity; + return this; + } + + /** + * Sets the rotation of the particle being built. If unspecified, the particle will use the default behaviour and + * rotate to face the viewer. + *

    + * Affects: All particle types + * @param yaw The yaw angle to set in degrees, where 0 is south. + * @param pitch The pitch angle to set in degrees, where 0 is horizontal. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder face(float yaw, float pitch){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.yaw = yaw; + this.pitch = pitch; + return this; + } + + /** + * Sets the rotation of the particle being built. This is an {@code EnumFacing}-based alternative to {@link + * ParticleBuilder#face(float, float)} which sets the yaw and pitch to the appropriate angles for the given facing. + * For example, if the given facing is {@code NORTH}, the particle will render parallel to the north face of blocks. + * If unspecified, the particle will use the default behaviour and rotate to face the viewer. + *

    + * Affects: All particle types + * @param direction The {@code EnumFacing} direction to set. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder face(EnumFacing direction){ + return face(direction.getHorizontalAngle(), direction.getAxis().isVertical() ? direction.getAxisDirection().getOffset() * 90 : 0); + } + + // ============================================= Targeted-only methods ============================================= + + /** + * Sets the target of the particle being built. This will cause the particle to stretch to touch the given position. + *

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param x The target x-coordinate to set + * @param y The target y-coordinate to set + * @param z The target z-coordinate to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder target(double x, double y, double z){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.tx = x; + this.ty = y; + this.tz = z; + return this; + } + + /** + * Sets the target of the particle being built. This is a vector-based alternative to + * {@link ParticleBuilder#target(double, double, double)}, allowing for even more concise code when a vector is + * available. + *

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param pos A vector representing the target position of the particle to be built. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder target(Vec3d pos){ + return target(pos.x, pos.y, pos.z); + } + + /** + * Sets the target point velocity of the particle being built. This will cause the position it stretches to touch to move + * at the given velocity. Has no effect unless {@link ParticleBuilder#target(double, double, double)} or one of its + * overloads is also set.

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param vx The target point x velocity to set + * @param vy The target point y velocity to set + * @param vz The target point z velocity to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder tvel(double vx, double vy, double vz){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.tvx = vx; + this.tvy = vy; + this.tvz = vz; + return this; + } + + /** + * Sets the target point velocity of the particle being built. This is a vector-based alternative to + * {@link ParticleBuilder#tvel(double, double, double)}, allowing for even more concise code when a vector is + * available. + *

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param vel A vector representing the target point velocity of the particle to be built. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder tvel(Vec3d vel){ + return tvel(vel.x, vel.y, vel.z); + } + + /** + * Sets the target and target velocity of the particle being built. This method takes an origin entity and a + * position and estimates the position of the target point based on the given entity's rotational velocities and its + * distance from the given position. + *

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param length The length of the particle being built. + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder length(double length){ + this.length = length; + return this; + } + + /** + * Sets the target of the particle being built. This will cause the particle to stretch to touch the given entity. + *

    + * Affects: Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE} + * @param target The entity to set + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is not yet building. + */ + public ParticleBuilder target(Entity target){ + if(!building) throw new IllegalStateException("Not building yet!"); + this.target = target; + return this; + } + + /** + * Spawns the particle that has been built and resets the particle builder. + * @param world The world in which to spawn the particle + * @throws IllegalStateException if the particle builder is not yet building. + */ + public void spawn(World world){ + + if(!building) throw new IllegalStateException("Not building yet!"); + + if(y < 0 && entity == null) Wizardry.logger.warn("Spawning particle below y = 0 - are you sure the position/entity " + + "has been set correctly?"); + + if(!world.isRemote){ + Wizardry.logger.warn("ParticleBuilder.spawn(...) called on the server side! ParticleBuilder has prevented a " + + "server crash, but calling it on the server will do nothing. Consider adding a world.isRemote check."); + // Must stop here because the line after this if statement would crash the server! + reset(); + return; + } + + electroblob.wizardry.client.particle.ParticleWizardry particle = Wizardry.proxy.createParticle(type, world, x, y, z); + + if(particle == null){ + // No need to display a warning here, we already did it in the client proxy + reset(); + return; + } + + // Anything with an if statement here allows default values to be set in particle constructors + if(!Double.isNaN(vx) && !Double.isNaN(vy) && !Double.isNaN(vz)) particle.setVelocity(vx, vy, vz); + if(r >= 0 && g >= 0 && b >= 0) particle.setRBGColorF(r, g, b); + if(fr >= 0 && fg >= 0 && fb >= 0) particle.setFadeColour(fr, fg, fb); + if(lifetime >= 0) particle.setMaxAge(lifetime); + if(radius > 0) particle.setSpin(radius, rpt); + if(!Float.isNaN(yaw) && !Float.isNaN(pitch)) particle.setFacing(yaw, pitch); + if(seed != 0) particle.setSeed(seed); + if(!Double.isNaN(tvx) && !Double.isNaN(tvy) && !Double.isNaN(tvz)) particle.setTargetVelocity(tvx, tvy, tvz); + if(length > 0) particle.setLength(length); + + particle.multipleParticleScaleBy(scale); + particle.setGravity(gravity); + particle.setShaded(shaded); + particle.setCollisions(collide); + particle.setEntity(entity); + particle.setTargetPosition(tx, ty, tz); + particle.setTargetEntity(target); + + net.minecraft.client.Minecraft.getMinecraft().effectRenderer.addEffect(particle); + + reset(); + } + + /** Resets the state of the particle builder and resets all the builder variables to their default values. */ + private void reset(){ + building = false; + type = null; + x = 0; + y = 0; + z = 0; + // NaN indicates the velocity was not set (can't use -1 since it could very reasonably be -1) + // For all other values -1 indicates the value was not set + vx = Double.NaN; + vy = Double.NaN; + vz = Double.NaN; + r = -1; + g = -1; + b = -1; + fr = -1; + fg = -1; + fb = -1; + radius = 0; + rpt = 0; + lifetime = -1; + gravity = false; + shaded = false; + collide = false; + scale = 1; + entity = null; + yaw = Float.NaN; + pitch = Float.NaN; + tx = Double.NaN; + ty = Double.NaN; + tz = Double.NaN; + tvx = Double.NaN; + tvy = Double.NaN; + tvz = Double.NaN; + target = null; + seed = 0; + length = -1; + } + + // ============================================== Convenience methods ============================================== + + // These may seem to go against the whole point of this class, but of course they return the ParticleBuilder instance + // so anything else can still be chained onto them - centralising commonly-used particle spawning patterns without + // losing any of the flexibility of the particle builder. In addition, callers of these methods are still free to + // change any of the parameters that were set within them afterwards. + + /** + * Starts building a particle of the given type and positions it randomly within the given entity's bounding box. + * Equivalent to calling {@code ParticleBuilder.create(type).pos(...)}; users should chain any additional builder + * methods onto this one and finish with {@code .spawn(world)} as normal. + * Used extensively with summoned creatures; makes code much neater and more concise. + *

    + * N.B. this does not cause the particle to move with the given entity. + * @param type The type of particle to build + * @param entity The entity to position the particle at + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is already building. + */ + public static ParticleBuilder create(ResourceLocation type, Entity entity){ + + double x = entity.posX + (entity.world.rand.nextDouble() - 0.5D) * (double)entity.width; + double y = entity.posY + entity.world.rand.nextDouble() * (double)entity.height; + double z = entity.posZ + (entity.world.rand.nextDouble() - 0.5D) * (double)entity.width; + + return ParticleBuilder.instance.particle(type).pos(x, y, z); + } + + /** + * Starts building a particle of the given type and positions it randomly within the given radius of the given position, + * with velocity proportional to distance from the given position if move is true. Good for making explosion-type effects. + * Equivalent to calling {@code ParticleBuilder.create(type).pos(...).vel(...)}; users should chain any additional builder + * methods onto this one and finish with {@code .spawn(world)} as normal. + * @param type The type of particle to build + * @param random An RNG instance + * @param x The x coordinate of the centre of the region in which to position the particle + * @param y The y coordinate of the centre of the region in which to position the particle + * @param z The z coordinate of the centre of the region in which to position the particle + * @param radius The radius of the region in which to position the particle + * @param move Whether the particle should move outwards from the centre (note that if this is false, the particle's + * default velocity will apply) + * @return The particle builder instance, allowing other methods to be chained onto this one + * @throws IllegalStateException if the particle builder is already building. + */ + public static ParticleBuilder create(ResourceLocation type, Random random, double x, double y, double z, double radius, boolean move){ + + double px = x + (random.nextDouble()*2 - 1) * radius; + double py = y + (random.nextDouble()*2 - 1) * radius; + double pz = z + (random.nextDouble()*2 - 1) * radius; + + if(move) return ParticleBuilder.instance.particle(type).pos(px, py, pz).vel(px-x, py-y, pz-z); + + return ParticleBuilder.instance.particle(type).pos(px, py, pz); + } + + // Methods for spawning specific effects (similar to the FX playing methods with the ids in RenderGlobal) + + /** Spawns spark and large smoke particles (8 of each) within a 1x1x1 volume centred on the given position. */ + public static void spawnShockParticles(World world, double x, double y, double z) { + + double px, py, pz; + + for(int i=0; i<8; i++){ + px = x + world.rand.nextDouble() - 0.5; + py = y + world.rand.nextDouble() - 0.5; + pz = z + world.rand.nextDouble() - 0.5; + ParticleBuilder.create(Type.SPARK).pos(px, py, pz).spawn(world); + px = x + world.rand.nextDouble() - 0.5; + py = y + world.rand.nextDouble() - 0.5; + pz = z + world.rand.nextDouble() - 0.5; + world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, px, py, pz, 0, 0, 0); + } + } + + /** Spawns golden-yellow sparkle particles around the given entity's head and a golden-yellow buff particle around + * its entire body. */ + public static void spawnHealParticles(World world, EntityLivingBase entity){ + + for(int i = 0; i < 10; i++){ + double x = entity.posX + world.rand.nextDouble() * 2 - 1; + double y = entity.getEntityBoundingBox().minY + entity.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double z = entity.posZ + world.rand.nextDouble() * 2 - 1; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(1, 1, 0.3f).spawn(world); + } + + ParticleBuilder.create(Type.BUFF).entity(entity).clr(1, 1, 0.3f).spawn(world); + } + +} diff --git a/src/main/java/electroblob/wizardry/util/RayTracer.java b/src/main/java/electroblob/wizardry/util/RayTracer.java new file mode 100644 index 00000000..6079251d --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/RayTracer.java @@ -0,0 +1,207 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.entity.ICustomHitbox; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.function.Predicate; + +/** + * Contains a number of static methods that perform raytracing and related functions. This was split off from + * {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public final class RayTracer { + + private RayTracer(){} // No instances! + + /** + * Helper method which performs a ray trace for blocks only from an entity's eye position in the direction + * they are looking, over a specified range, using {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)}. + * + * @param world The world in which to perform the ray trace. + * @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye + * position and proceed in the direction the entity is looking. + * @param range The distance over which the ray trace will be performed. + * @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were + * not there. + * @return A {@link RayTraceResult} representing the object that was hit, which may be either a block or nothing. + * Returns {@code null} only if the origin and endpoint are within the same block. + */ + @Nullable + public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids, + boolean ignoreUncollidables, boolean returnLastUncollidable){ + // This method does not apply an offset like ray spells do, since it is not desirable in most other use cases. + Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); + Vec3d endpoint = origin.add(entity.getLookVec().scale(range)); + return world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, returnLastUncollidable); + } + + /** + * Helper method which performs a ray trace for blocks only from an entity's eye position in the direction + * they are looking, over a specified range. This is a shorthand for + * {@link #standardBlockRayTrace(World, EntityLivingBase, double, boolean, boolean, boolean)}; ignoreUncollidables + * and returnLastUncollidable default to false. + */ + @Nullable + public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){ + return standardBlockRayTrace(world, entity, range, hitLiquids, false, false); + } + + /** + * Helper method which performs a ray trace for blocks and entities from an entity's eye position in the direction + * they are looking, over a specified range, using {@link RayTracer#rayTrace(World, Vec3d, Vec3d, float, boolean, boolean, boolean, Class, Predicate)}. Aim assist is zero, the entity type is simply {@code Entity} (all entities), and the + * filter removes the given entity and any dying entities and allows all others. + * + * @param world The world in which to perform the ray trace. + * @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye + * position and proceed in the direction the entity is looking. This entity will be ignored when ray tracing. + * @param range The distance over which the ray trace will be performed. + * @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were + * not there. + * @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or + * nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit. + */ + @Nullable + public static RayTraceResult standardEntityRayTrace(World world, Entity entity, double range, boolean hitLiquids){ + // This method does not apply an offset like ray spells do, since it is not desirable in most other use cases. + Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); + Vec3d endpoint = origin.add(entity.getLookVec().scale(range)); + return rayTrace(world, origin, endpoint, 0, hitLiquids, false, false, Entity.class, ignoreEntityFilter(entity)); + } + + /** + * Helper method for use with {@link RayTracer#rayTrace(World, Vec3d, Vec3d, float, boolean, boolean, boolean, Class, Predicate)} + * which returns a {@link Predicate} that returns true for the given entity, plus any entities that have zero health + * or less (i.e. are in the process of dying). This is a commonly used filter in spells. + * + * @param entity The entity that the returned predicate should return true for. + * @return A {@link Predicate} that returns true for the given entity and any entities that are in the process of + * dying, false for all other entities. + */ + public static Predicate ignoreEntityFilter(Entity entity){ + return e -> e == entity || (e instanceof EntityLivingBase && ((EntityLivingBase)e).getHealth() <= 0); + } + + /** + * Performs a ray trace for blocks and entities, starting at the given origin and finishing at the given endpoint. + * As of wizardry 4.2, the ray tracing methods have been rewritten to be more user-friendly and implement proper + * aim assist. + *

    + * N.B. It is possible to ignore entities entirely by passing in a {@code Predicate} that is always false; + * however, in this specific case it is more efficient to use + * {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)} or one of its overloads. + * + * @param world The world in which to perform the ray trace. + * @param origin A vector representing the coordinates of the start point of the ray trace. + * @param endpoint A vector representing the coordinates of the finish point of the ray trace. + * @param aimAssist In addition to direct hits, the ray trace will also hit entities that are up to this distance + * from its path. For a normal ray trace, this should be 0. Values greater than 0 will give an 'aim assist' effect. + * @param hitLiquids Whether liquids should be ignored when ray tracing blocks + * @param ignoreUncollidables Whether blocks with no collisions should be ignored + * @param returnLastUncollidable If blocks with no collisions are ignored, whether to return the last one (useful if, + * for example, you want to replace snow layers or tall grass) + * @param entityType The class of entities to include; all other entities will be ignored. + * @param filter A {@link Predicate} which filters out entities that can be ignored; often used to exclude the + * player that is performing the ray trace. + * + * @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or + * nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit. + * + * @see RayTracer#standardEntityRayTrace(World, Entity, double, boolean) + * @see RayTracer#standardBlockRayTrace(World, EntityLivingBase, double, boolean) + */ + // Interestingly enough, aimAssist can be negative, which means hits have to be in the middle of entities! + @Nullable + public static RayTraceResult rayTrace(World world, Vec3d origin, Vec3d endpoint, float aimAssist, + boolean hitLiquids, boolean ignoreUncollidables, boolean returnLastUncollidable, Class entityType, Predicate filter){ + + // 1 is the standard amount of extra search volume, and aim assist needs to increase this further as well as + // expanding the entities' bounding boxes. + float borderSize = 1 + aimAssist; + + // The AxisAlignedBB constructor accepts min/max coords in either order. + AxisAlignedBB searchVolume = new AxisAlignedBB(origin.x, origin.y, origin.z, endpoint.x, endpoint.y, endpoint.z) + .grow(borderSize, borderSize, borderSize); + + // Gets all of the entities in the bounding box that could be collided with. + List entities = world.getEntitiesWithinAABB(entityType, searchVolume); + // Applies the given filter to remove entities that should be ignored. + entities.removeIf(filter); + + // Finds the first block hit by the ray trace, if any. + RayTraceResult result = world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, returnLastUncollidable); + + // Clips the entity search range to the part of the ray trace before the block hit, if it hit a block. + if(result != null){ + endpoint = result.hitVec; + } + + // Search variables + Entity closestHitEntity = null; + Vec3d closestHitPosition = endpoint; + AxisAlignedBB entityBounds; + Vec3d intercept = null; + + // Iterates through all the entities + for(Entity entity : entities){ + + // I'd like to add the following line so we can, for example, use greater telekinesis through a + // ring of fire, but doing so will stop forcefields blocking particles + //if(!entity.canBeCollidedWith()) continue; + + float fuzziness = WizardryUtilities.isLiving(entity) ? aimAssist : 0; // Only living entities have aim assist + + if(entity instanceof ICustomHitbox){ // Custom hitboxes + intercept = ((ICustomHitbox)entity).calculateIntercept(origin, endpoint, fuzziness); + + }else{ // Normal hit detection + + entityBounds = entity.getEntityBoundingBox(); + + if(entityBounds != null){ + + // This is zero for everything except fireballs... + float entityBorderSize = entity.getCollisionBorderSize(); + // ... meaning the following line does nothing in all other cases. + // -> Added the non-zero check to prevent unnecessary AABB object creation. + if(entityBorderSize != 0) + entityBounds = entityBounds.grow(entityBorderSize, entityBorderSize, entityBorderSize); + + // Aim assist expands the bounding box to hit entities within the specified distance of the ray trace. + if(fuzziness != 0) entityBounds = entityBounds.grow(fuzziness, fuzziness, fuzziness); + + // Finds the first point at which the ray trace intercepts the entity's bounding box, if any. + RayTraceResult hit = entityBounds.calculateIntercept(origin, endpoint); + if(hit != null) intercept = hit.hitVec; + } + } + + // If the ray trace hit the entity... + if(intercept != null){ + // Decides whether the entity that was hit is the closest so far, and if so, overwrites the old one. + float currentHitDistance = (float)intercept.distanceTo(origin); + float closestHitDistance = (float)closestHitPosition.distanceTo(origin); + if(currentHitDistance < closestHitDistance){ + closestHitEntity = entity; + closestHitPosition = intercept; + } + } + } + + // If the ray trace hit an entity, return that entity; otherwise return the result of the block ray trace. + if(closestHitEntity != null){ + result = new RayTraceResult(closestHitEntity, closestHitPosition); + } + + return result; + } +} diff --git a/src/main/java/electroblob/wizardry/util/RelativeFacing.java b/src/main/java/electroblob/wizardry/util/RelativeFacing.java new file mode 100644 index 00000000..f5988df9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/RelativeFacing.java @@ -0,0 +1,42 @@ +package electroblob.wizardry.util; + +import net.minecraft.entity.Entity; +import net.minecraft.util.EnumFacing; + +/** + * Like {@link EnumFacing}, but relative! + */ +public enum RelativeFacing { + + DOWN("down", -1), + UP("up", -1), + FRONT("front", 0), + BACK("back", 2), + LEFT("left", 3), + RIGHT("right", 1); + + public final String name; + private final int horizontalIndex; + + private static final RelativeFacing[] HORIZONTALS = new RelativeFacing[4]; + + RelativeFacing(String name, int horizontalIndex){ + this.name = name; + this.horizontalIndex = horizontalIndex; + } + + static { + for(RelativeFacing facing : values()){ + if(facing.horizontalIndex > -1) HORIZONTALS[facing.horizontalIndex] = facing; + } + } + + public static RelativeFacing relativise(EnumFacing absolute, Entity relativeTo){ + if(absolute == EnumFacing.DOWN) return DOWN; + if(absolute == EnumFacing.UP) return UP; + EnumFacing look = relativeTo.getAdjustedHorizontalFacing(); + int relativeIndex = absolute.getHorizontalIndex() - look.getHorizontalIndex(); + if(relativeIndex < 0) relativeIndex += 4; + return HORIZONTALS[relativeIndex]; + } +} diff --git a/src/main/java/electroblob/wizardry/util/SpellModifiers.java b/src/main/java/electroblob/wizardry/util/SpellModifiers.java index 8e840226..93ea7e96 100644 --- a/src/main/java/electroblob/wizardry/util/SpellModifiers.java +++ b/src/main/java/electroblob/wizardry/util/SpellModifiers.java @@ -1,31 +1,30 @@ package electroblob.wizardry.util; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import electroblob.wizardry.event.SpellCastEvent; import io.netty.buffer.ByteBuf; import net.minecraft.item.Item; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fml.common.network.ByteBufUtils; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + /** + * "{@code SpellModifiers} - modify all the things!" + *

    * Object that wraps any number of spell modifiers into one, allowing for expandability within the Spell#cast methods. - * This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}. It is - * possible to calculate spell modifiers from wand NBT within the cast methods, but this is cumbersome and does not - * allow the modifiers to be sent to the client, which is sometimes necessary (for example, detonate needs to know about - * range modifiers on the client side or the particles wouldn't show outside of the base range). - *

    + * This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}. + *

    * Most external interaction with SpellModifiers objects will be in {@link SpellCastEvent.Pre}, where you can add * additional modifiers to them if desired for use with your own spells, or modify the existing ones. If you have added * a wand upgrade, this is not done automatically for you; you will have to do it yourself (for the simple reason * that not all wand upgrades affect spells). SpellModifiers objects are mutable, so you can simply change the * values they contain to modify the spell. - *

    - * To use a SpellModifiers object within the Spell.cast methods, simply retrieve the desired multiplier - * using {@link SpellModifiers#get(Item)} for wand upgrades, or {@link SpellModifiers#get(String)} if the multiplier is + *

    + * To use a SpellModifiers object within the Spell.cast methods, simply retrieve the desired modifier + * using {@link SpellModifiers#get(Item)} for wand upgrades, or {@link SpellModifiers#get(String)} if the modifier is * not from a wand upgrade. * * @author Electroblob @@ -38,21 +37,40 @@ import net.minecraftforge.fml.common.network.ByteBufUtils; // fly. public final class SpellModifiers { - /** Constant string identifier for the damage modifier. All the other modifiers in Wizardry have items. */ - public static final String DAMAGE = "damage"; + /** Constant string identifier for the potency modifier. */ + public static final String POTENCY = "potency"; + /** Constant string identifier for the mana cost modifier. */ + public static final String COST = "cost"; + /** Constant string identifier for the wand progression modifier. */ + public static final String PROGRESSION = "progression"; - private Map multiplierMap; - private Map syncedMultiplierMap; + private final Map multiplierMap; + private final Map syncedMultiplierMap; /** * Creates an empty SpellModifiers object. All calls to get(...) on an empty SpellModifiers object will * return a value of 1. */ public SpellModifiers(){ - multiplierMap = new HashMap(); - syncedMultiplierMap = new HashMap(); + multiplierMap = new HashMap<>(); + syncedMultiplierMap = new HashMap<>(); } +// /** Returns a deep copy of this {@code SpellModifiers} object. */ +// @Override +// public SpellModifiers clone(){ +// SpellModifiers clone; +// try { +// clone = (SpellModifiers)super.clone(); +// }catch(CloneNotSupportedException e){ +// Wizardry.logger.error("Whaaaaat?!", e); +// return null; +// } +// clone.multiplierMap = new HashMap<>(this.multiplierMap); +// clone.syncedMultiplierMap = new HashMap<>(this.syncedMultiplierMap); +// return clone; +// } + /** * Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand upgrade * item was registered with. @@ -107,6 +125,31 @@ public final class SpellModifiers { return value == null ? 1 : value; } + // Not sure this really makes sense with the current system, it may just be better to keep it how it is +// /** +// * Returns the level of upgrade (i.e. number of upgrades or wand tier) that would be required to +// * generate a modifier with the given key. This does not necessarily mean that was how this modifier was +// * applied; and the returned value may not be a whole number if commands were involved. +// */ +// public float level(String key){ +// return 0; +// } + + /** + * Returns an amplified version of the multiplier corresponding to the given string key. An amplified + * modifier is the original modifier scaled about 1 - for example, amplifying by 2 would produce the + * following results:
    + * 1.3 -> 1.6
    + * 2 -> 3
    + * 0.7 -> 0.4
    + * 1 -> 1
    + * (In other words, the modifier is decreased by 1, multiplied by the scalar and then increased by 1 again.)
    + * N.B. This does not change the stored modifier. + */ + public float amplified(String key, float scalar){ + return (get(key) - 1) * scalar + 1; + } + /** * Returns an unmodifiable map of the modifiers stored in this SpellModifiers object. Useful for iterating through * the modifiers. @@ -137,14 +180,17 @@ public final class SpellModifiers { buf.writeFloat(entry.getValue()); } } + + // These two don't use the Map <-> NBT methods in WizardryUtilities because it's better to use the strings as keys + // themselves rather than storing them separately. /** * Creates a new SpellModifiers object from the given NBTTagCompound. The NBTTagCompound should have 1 or more float * tags, which will be stored as modifiers under the same name as the tag. For example, the following NBT tag (in - * command syntax) will create a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2: - *

    + * command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2: + *

    * {damage:1.5, range:2} - *

    + *

    * Note that needsSyncing is set to true for all returned modifiers. */ public static SpellModifiers fromNBT(NBTTagCompound nbt){ @@ -154,5 +200,22 @@ public final class SpellModifiers { } return modifiers; } + + /** + * Creates a new NBTTagCompound for this SpellModifiers object. The NBTTagCompound will have a float tags for each + * modifier, which will be stored using the modifier names as keys. For example, the following NBT tag (in + * command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2: + *

    + * {damage:1.5, range:2} + *

    + * Note that information about syncing of modifiers is discarded. + */ + public NBTTagCompound toNBT(){ + NBTTagCompound nbt = new NBTTagCompound(); + for(Entry entry : multiplierMap.entrySet()){ + nbt.setFloat(entry.getKey(), entry.getValue()); + } + return nbt; + } } diff --git a/src/main/java/electroblob/wizardry/util/SpellProperties.java b/src/main/java/electroblob/wizardry/util/SpellProperties.java new file mode 100644 index 00000000..55e7d00b --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/SpellProperties.java @@ -0,0 +1,361 @@ +package electroblob.wizardry.util; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.constants.SpellType; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import io.netty.buffer.ByteBuf; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.crafting.CraftingHelper; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Object that stores base properties associated with spells. Each spell has a single instance of this class which + * stores its base properties and other data. This class also handles loading of the properties from JSON. + *

    + * All the fields in this class are final and are assigned during object creation. This is because the intent is that a + * new SpellProperties object is created on load and synced with each client on player login. Having final fields + * therefore guarantees that the properties are always synced whenever necessary, but cannot otherwise be fiddled + * with programmatically. + *

    + * Generally, users need not worry about this class; it is intended that the various property getters in Spell be used + * rather than querying this class directly (in fact, you can't do that without reflection anyway). + *

    + * @author Electroblob + * @since Wizardry 4.2 + */ +// There is not a particular semantic reason to separate this from the Spell class itself. However, doing so means that +// everything related to the JSON spell system is kept in one place and doesn't clutter the (already long) Spell class. +// Additionally, it allows SpellProperties objects to be passed around during loading and syncing. +public final class SpellProperties { + + private static final Gson gson = new Gson(); + + /** Set of enum constants representing contexts in which a spell can be enabled/disabled. */ + public enum Context { + + /** Disabling this context will make a spell's book unobtainable and unusable. */ BOOK("book"), + /** Disabling this context will make a spell's scroll unobtainable and unusable. */ SCROLL("scroll"), + /** Disabling this context will prevent a spell from being cast using a wand. */ WANDS("wands"), + /** Disabling this context will prevent NPCs from casting or dropping a spell. */ NPCS("npcs"), + /** Disabling this context will prevent dispensers from casting a spell. */ DISPENSERS("dispensers"), + /** Disabling this context will prevent a spell from being cast using commands. */ COMMANDS("commands"), + /** Disabling this context will prevent a spell's book or scroll generating in chests. */ TREASURE("treasure"), + /** Disabling this context will prevent a spell's book or scroll from being sold by NPCs.*/ TRADES("trades"), + /** Disabling this context will prevent a spell's book or scroll being dropped by mobs. */ LOOTING("looting"); + + /** The JSON identifier for this context. */ + public final String name; + + Context(String name){ + this.name = name; + } + } + + /** A map storing whether each context is enabled for this spell. */ + private final Map enabledContexts; + /** A map storing the base values for this spell. These values are defined by the spell class and cannot be + * changed. */ + // We're using Number here because it makes implementors think about what they convert it to. + // If we did what attributes do and just use doubles, people (myself included!) might plug them into calculations + // without thinking. However, with Number you can't just do that, you have to convert and therefore you have to + // decide how to do the conversion. Internally they're handled as floats though. + private final Map baseValues; + + /** The tier this spell belongs to. */ + public final Tier tier; + /** The element this spell belongs to. */ + public final Element element; + /** The type of spell this is classified as. */ + public final SpellType type; + /** Mana cost of the spell. If it is a continuous spell the cost is per second. */ + public final int cost; + /** The charge-up time of the spell, in ticks. */ + public final int chargeup; + /** The cooldown time of the spell, in ticks. */ + public final int cooldown; + + // Sometimes it just makes more sense to do the JSON parsing in the constructor + // It's the only way we're gonna keep the fields final! + /** + * Parses the given JSON object and constructs a new {@code SpellProperties} from it, setting all the relevant + * fields and references. + * + * @param json A JSON object representing the spell properties to be constructed. + * @param spell The spell that this {@code SpellProperties} object is for. + * @throws JsonSyntaxException if at any point the JSON object is found to be invalid. + */ + private SpellProperties(JsonObject json, Spell spell){ + + String[] baseValueNames = spell.getPropertyKeys(); + + enabledContexts = new EnumMap<>(Context.class); + baseValues = new HashMap<>(); + + JsonObject enabled = JsonUtils.getJsonObject(json, "enabled"); + + // This time we know the exact set of properties so we can iterate over them instead of the json object + // In fact, we actually want to throw an exception if any of them are missing + for(Context context : Context.values()){ + enabledContexts.put(context, JsonUtils.getBoolean(enabled, context.name)); + } + + try { + tier = Tier.fromName(JsonUtils.getString(json, "tier")); + element = Element.fromName(JsonUtils.getString(json, "element")); + type = SpellType.fromName(JsonUtils.getString(json, "type")); + }catch(IllegalArgumentException e){ + throw new JsonSyntaxException("Incorrect spell property value", e); + } + + cost = JsonUtils.getInt(json, "cost"); + chargeup = JsonUtils.getInt(json, "chargeup"); + cooldown = JsonUtils.getInt(json, "cooldown"); + + // There's not much point specifying the classes of the numbers here because the json getter methods just + // perform conversion to the requested type anyway. It therefore makes very little difference whether the + // conversion is done during JSON parsing or when we actually use the value - and at least in the latter case, + // individual subclasses have control over how it is converted. + + // My case in point: summoning 2.5 spiders is obviously nonsense, but what happens when we cast that with a + // modifier of 2? Should we round the base value down to 2 and then apply the x2 modifier to get 4 spiders? + // Should we round it up instead? Or should we apply the modifier first and then do the rounding, so with no + // modifier we still get 2 spiders but with the x2 modifier we get 5? + // The most pragmatic solution is to let the spell class decide for itself. + // (Of course, we can only hope that the users aren't jerks and don't try to summon 2 and a half spiders...) + + JsonObject baseValueObject = JsonUtils.getJsonObject(json, "base_properties"); + + // If the code requests more values than the JSON file contains, that will cause a JsonSyntaxException here anyway. + // If there are redundant values in the JSON file, chances are that a user has misunderstood the system and tried + // to add properties that aren't implemented. However, redundant values will also be found if a programmer has + // forgotten to call addProperties in their spell constructor (I know I have!), potentially causing a crash at + // some random point in the future. Since redundant values aren't a problem by themselves, we shouldn't throw an + // exception, but a warning is appropriate. + + int redundantKeys = baseValueObject.size() - baseValueNames.length; + if(redundantKeys > 0) Wizardry.logger.warn("Spell " + spell.getRegistryName() + " has " + redundantKeys + + " redundant spell property key(s) defined in its JSON file. Extra values will have no effect! (Modders:" + + " make sure you have called addProperties(...) during spell construction)"); + + if(baseValueNames.length > 0){ + + for(String baseValueName : baseValueNames){ + baseValues.put(baseValueName, JsonUtils.getFloat(baseValueObject, baseValueName)); + } + } + + } + + /** Constructs a new SpellProperties object for the given spell, reading its values from the given ByteBuf. */ + public SpellProperties(Spell spell, ByteBuf buf){ + + enabledContexts = new EnumMap<>(Context.class); + baseValues = new HashMap<>(); + + for(Context context : Context.values()){ + // Enum maps have a guaranteed iteration order so this works fine + enabledContexts.put(context, buf.readBoolean()); + } + + tier = Tier.values()[buf.readShort()]; + element = Element.values()[buf.readShort()]; + type = SpellType.values()[buf.readShort()]; + + cost = buf.readInt(); + chargeup = buf.readInt(); + cooldown = buf.readInt(); + + List keys = Arrays.asList(spell.getPropertyKeys()); + Collections.sort(keys); // Should be the same list of keys in the same order they were written to the ByteBuf + + for(String key : keys){ + baseValues.put(key, buf.readFloat()); + } + } + + /** Writes this SpellProperties object to the given ByteBuf so it can be sent via packets. */ + public void write(ByteBuf buf){ + + for(Context context : Context.values()){ + // Enum maps have a guaranteed iteration order so this works fine + buf.writeBoolean(enabledContexts.get(context)); + } + + buf.writeShort(tier.ordinal()); + buf.writeShort(element.ordinal()); + buf.writeShort(type.ordinal()); + + buf.writeInt(cost); + buf.writeInt(chargeup); + buf.writeInt(cooldown); + + List keys = new ArrayList<>(baseValues.keySet()); + Collections.sort(keys); // Sort alphabetically (as long as the order is consistent it doesn't matter) + + for(String key : keys){ + buf.writeFloat(baseValues.get(key).floatValue()); + } + } + + /** + * Returns whether the spell is enabled in any of the given contexts. + * @param contexts The context in which to check if the spell is enabled. + * @return True if the spell is enabled in any of the given contexts, false if not. + */ + public boolean isEnabled(Context... contexts){ + return enabledContexts.entrySet().stream().anyMatch(e -> e.getValue() && Arrays.asList(contexts).contains(e.getKey())); + } + + /** + * Returns the base value for this spell that corresponds to the given identifier. + * @param identifier The string identifier to fetch the base value for. + * @return The base value, as a {@code Number}. + * @throws IllegalArgumentException if no base value was defined with the given identifier. + */ + public Number getBaseValue(String identifier){ + if(!baseValues.containsKey(identifier)){ + throw new IllegalArgumentException("Base value with identifier '" + identifier + "' is not defined."); + } + return baseValues.get(identifier); + } + + /** + * Called from preInit() in the main mod class to initialise the spell property system. + */ + // For some reason I had this called from a method in CommonProxy which was overridden to do nothing in + // ClientProxy, but that method was never called and instead this one was called directly from the main mod class. + // I *think* I decided against the proxy thing and just forgot to delete the methods (they're gone now), but if + // things don't work as expected then that may be why - pretty sure it's fine though since the properties get + // wiped client-side on each login anyway. + public static void init(){ + // Collecting to a set should give us one of each mod ID + Set modIDs = Spell.getSpells(Spell.allSpells).stream().map(s -> s.getRegistryName().getNamespace()).collect(Collectors.toSet()); + + boolean flag = true; + + for(String modID : modIDs){ + flag &= loadSpellProperties(modID); // Don't short-circuit, or mods later on won't get loaded! + } + + if(!flag) Wizardry.logger.warn("Some spell property files did not load correctly; this will likely cause problems later!"); + } + + // Sooooooo I just realised that resource packs - you know, that famously client-side thing - can now define + // stuff that should be specified by the server. No wonder it all got moved to data packs in 1.13... + // Anyway, for the time being we're in 1.12 so we're gonna have to do this instead. + + // For crafting recipes, Forge does some stuff behind the scenes to load recipe JSON files from mods' namespaces. + // This leverages the same methods. + + private static boolean loadSpellProperties(String modID){ + + // Yes, I know you're not supposed to do orElse(null). But... meh. + ModContainer mod = Loader.instance().getModList().stream().filter(m -> m.getModId().equals(modID)).findFirst().orElse(null); + + if(mod == null){ + Wizardry.logger.warn("Tried to load spell properties for mod with ID '" + modID + "', but no such mod was loaded"); + return false; // Failed! + } + + // Spells will be removed from this list as their properties are set + // If everything works properly, it should be empty by the end + List spells = Spell.getSpells(s -> s.getRegistryName().getNamespace().equals(modID)); + if(modID.equals(Wizardry.MODID)) spells.add(Spells.none); // In this particular case we do need the none spell + + Wizardry.logger.info("Loading spell properties for " + spells.size() + " spells in mod " + modID); + + // This method is used by Forge to load mod recipes and advancements, so it's a fair bet it's the right one + // In the absence of Javadoc, here's what the non-obvious parameters do: + // - preprocessor is called once with just the root directory, allowing any global index files to be processed + // - processor is called once for each file in the directory so processing can be done + // - defaultUnfoundRoot is the default value to return if the root specified isn't found + // - visitAllFiles determines whether the method short-circuits; in other words, if the processor returns false + // at any point and visitAllFiles is false, the method returns immediately. + boolean success = CraftingHelper.findFiles(mod, "assets/" + modID + "/spells", null, + + (root, file) -> { + + String relative = root.relativize(file).toString(); + if(!"json".equals(FilenameUtils.getExtension(file.toString())) || relative.startsWith("_")) + return true; // True or it'll look like it failed just because it found a non-JSON file + + String name = FilenameUtils.removeExtension(relative).replaceAll("\\\\", "/"); + ResourceLocation key = new ResourceLocation(modID, name); + + Spell spell = Spell.registry.getValue(key); + + // If no spell matches a particular file, log it and just ignore the file + if(spell == null){ + Wizardry.logger.info("Spell properties file " + name + ".json does not match any registered spells; ensure the filename is spelled correctly."); + return true; + } + + BufferedReader reader = null; + + // We want to do this regardless of whether the JSON file got read properly, because that prints its + // own separate warning + if(!spells.remove(spell)) Wizardry.logger.warn("What's going on?!"); + + try{ + + reader = Files.newBufferedReader(file); + + JsonObject json = JsonUtils.fromJson(gson, reader, JsonObject.class); + SpellProperties properties = new SpellProperties(json, spell); + spell.setProperties(properties); + + }catch(JsonParseException jsonparseexception){ + Wizardry.logger.error("Parsing error loading spell property file for " + key, jsonparseexception); + return false; + }catch(IOException ioexception){ + Wizardry.logger.error("Couldn't read spell property file for " + key, ioexception); + return false; + }finally{ + IOUtils.closeQuietly(reader); + } + + return true; + + }, + true, true); + + // If a spell is missing its file, log an error + if(!spells.isEmpty()){ + if(spells.size() <= 15){ + spells.forEach(s -> Wizardry.logger.error("Spell " + s.getRegistryName() + " is missing a properties file!")); + }else{ + // If there are more than 15 don't bother logging them all, chances are they're all missing + Wizardry.logger.error("Mod " + modID + " has " + spells.size() + " spells that are missing properties files!"); + } + } + + return success; + } +} + +// We probably could have used the attribute system for all of this, but I am reluctant to do so for a number of +// reasons: +// - It's a mess. +// - Unlike entities and itemstacks, spells don't have a separate instance for each time they are cast, which might +// prove problematic. +// - I'm loading my base properties once and not touching them again, so they're more like block materials than anything +// else. +// - Some of the properties aren't numerical, and some of them can't have modifiers applied. In fact, most of them can't! +// So even if we were to use attributes, we'd still need this class. \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/util/WandHelper.java b/src/main/java/electroblob/wizardry/util/WandHelper.java index efb40332..449a807c 100644 --- a/src/main/java/electroblob/wizardry/util/WandHelper.java +++ b/src/main/java/electroblob/wizardry/util/WandHelper.java @@ -1,9 +1,6 @@ package electroblob.wizardry.util; -import java.util.Collections; -import java.util.HashMap; -import java.util.Set; - +import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Spell; @@ -11,23 +8,29 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import java.util.Collections; +import java.util.HashMap; +import java.util.Set; + /** + * "Never fear, {@code WandHelper} is here!" + *

    * Much like {@link net.minecraft.enchantment.EnchantmentHelper EnchantmentHelper}, this class has some static methods * which allow cleaner and more concise interaction with the wand NBT data, which is quite a complex structure. Such * interaction previously resulted in rather verbose and repetitive code which was hard to read and even harder to * debug! For example, this class allowed {@link electroblob.wizardry.item.ItemWand ItemWand} to be shortened by about * 80 lines. In addition, by having all the various null checks and array size checks in one place, the chance of * accidental errors due to forgetting to check these things is greatly reduced. - *

    + *

    * Note that these methods contain no game logic at all; they are purely for interacting with the NBT data. Conversely, * you should never need to access the wand's NBT data directly when using this class, but the keys are public in the * unlikely case that this is necessary. - *

    + *

    * Also note that none of the methods in this class actually check that the given ItemStack contains an ItemWand; you * can, for example, pass in a stack of snowballs without causing problems, but that is of course pointless! However, if * you have your own spell casting item (which doesn't extend ItemWand), this setup means you can still use this class * to manage its NBT structure. - *

    + *

    * All get methods in this class return some kind of default if the passed-in wand stack has no nbt data. See * individual method descriptions for more details.
    * All set methods in this class create a new nbt data for the passed-in wand if it has none, before doing @@ -43,11 +46,13 @@ public final class WandHelper { public static final String SPELL_ARRAY_KEY = "spells"; public static final String SELECTED_SPELL_KEY = "selectedSpell"; public static final String COOLDOWN_ARRAY_KEY = "cooldown"; + public static final String MAX_COOLDOWN_ARRAY_KEY = "maxCooldown"; public static final String UPGRADES_KEY = "upgrades"; + public static final String PROGRESSION_KEY = "progression"; private static final HashMap upgradeMap = new HashMap(); - static{ + static { upgradeMap.put(WizardryItems.condenser_upgrade, "condenser"); upgradeMap.put(WizardryItems.storage_upgrade, "storage"); upgradeMap.put(WizardryItems.siphon_upgrade, "siphon"); @@ -56,8 +61,11 @@ public final class WandHelper { upgradeMap.put(WizardryItems.cooldown_upgrade, "cooldown"); upgradeMap.put(WizardryItems.blast_upgrade, "blast"); upgradeMap.put(WizardryItems.attunement_upgrade, "attunement"); + upgradeMap.put(WizardryItems.melee_upgrade, "melee"); } + // =================================================== Spells =================================================== + /** * Returns an array containing the spells currently bound to the given wand. As of Wizardry 1.1, this array is not * always the same size; it can be anywhere between 5 and 8 (inclusive) in length. If the wand has no spell data, @@ -74,7 +82,7 @@ public final class WandHelper { spells = new Spell[spellIDs.length]; for(int i = 0; i < spellIDs.length; i++){ - spells[i] = Spell.get(spellIDs[i]); + spells[i] = Spell.byMetadata(spellIDs[i]); } } @@ -92,7 +100,7 @@ public final class WandHelper { int[] spellIDs = new int[spells.length]; for(int i = 0; i < spells.length; i++){ - spellIDs[i] = spells[i] != null ? spells[i].id() : Spells.none.id(); + spellIDs[i] = spells[i] != null ? spells[i].metadata() : Spells.none.metadata(); } wand.getTagCompound().setIntArray(SPELL_ARRAY_KEY, spellIDs); @@ -114,50 +122,90 @@ public final class WandHelper { return Spells.none; } + + /** Returns the spell after the currently selected spell for the given wand, or the 'none' spell if the wand has no + * spell data. */ + public static Spell getNextSpell(ItemStack wand){ + + Spell[] spells = getSpells(wand); + int index = getNextSpellIndex(wand); + + if(index >= 0 && index < spells.length){ + return spells[index]; + } + + return Spells.none; + } + + /** Returns the spell before the currently selected spell for the given wand, or the 'none' spell if the wand has no + * spell data. */ + public static Spell getPreviousSpell(ItemStack wand){ + + Spell[] spells = getSpells(wand); + int index = getPreviousSpellIndex(wand); + + if(index >= 0 && index < spells.length){ + return spells[index]; + } + + return Spells.none; + } /** Selects the next spell in this wand's list of spells. */ public static void selectNextSpell(ItemStack wand){ // 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades - if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]); + if(getSpells(wand).length < 0) setSpells(wand, new Spell[ItemWand.BASE_SPELL_SLOTS]); if(wand.getTagCompound() != null){ - - int numberOfSpells = getSpells(wand).length; - int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); - - // Greater than or equal to so that if attunement upgrades are somehow removed by NBT modification it just - // resets. - if(selectedSpell >= numberOfSpells - 1){ - selectedSpell = 0; - }else{ - selectedSpell++; - } - - wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell); - + wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, getNextSpellIndex(wand)); } } /** Selects the previous spell in this wand's list of spells. */ public static void selectPreviousSpell(ItemStack wand){ - // 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades - if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]); - // This cannot possibly be null here, and yet I am getting an NPE... + if(getSpells(wand).length < 0) setSpells(wand, new Spell[ItemWand.BASE_SPELL_SLOTS]); + if(wand.getTagCompound() != null){ - - int numberOfSpells = getSpells(wand).length; - int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); - - if(selectedSpell <= 0){ - selectedSpell = numberOfSpells - 1; - }else{ - selectedSpell--; - } - - wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell); + wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, getPreviousSpellIndex(wand)); } } + + private static int getNextSpellIndex(ItemStack wand){ + + if(wand.getTagCompound() == null) wand.setTagCompound(new NBTTagCompound()); + + int numberOfSpells = getSpells(wand).length; + int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); + + // Greater than or equal to so that if attunement upgrades are somehow removed by NBT modification it just + // resets. + if(spellIndex >= numberOfSpells - 1){ + spellIndex = 0; + }else{ + spellIndex++; + } + + return spellIndex; + } + + private static int getPreviousSpellIndex(ItemStack wand){ + + if(wand.getTagCompound() == null) wand.setTagCompound(new NBTTagCompound()); + + int numberOfSpells = getSpells(wand).length; + int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); + + if(spellIndex <= 0){ + spellIndex = numberOfSpells - 1; + }else{ + spellIndex--; + } + + return spellIndex; + } + + // ================================================== Cooldowns ================================================== /** * Returns an array of the cooldowns for each spell bound to the given wand. As of Wizardry 1.1, this array is not @@ -176,7 +224,8 @@ public final class WandHelper { return cooldowns; } - /** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length. */ + /** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length. + * Unlike {@link WandHelper#setCurrentCooldown(ItemStack, int)}, this will not set the max cooldowns. */ public static void setCooldowns(ItemStack wand, int[] cooldowns){ if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound())); @@ -208,8 +257,30 @@ public final class WandHelper { // Don't need to check if the tag compound is null since the above check is equivalent. return cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)]; } + + /** Returns the given wand's cooldown for the spell after the currently selected spell, or 0 if the wand has no + * cooldown data. */ + public static int getNextCooldown(ItemStack wand){ - /** Sets the given wand's cooldown for the currently selected spell. */ + int[] cooldowns = getCooldowns(wand); + + if(cooldowns.length == 0) return 0; + // Don't need to check if the tag compound is null since the above check is equivalent. + return cooldowns[getNextSpellIndex(wand)]; + } + + /** Returns the given wand's cooldown for the spell before the currently selected spell, or 0 if the wand has no + * cooldown data. */ + public static int getPreviousCooldown(ItemStack wand){ + + int[] cooldowns = getCooldowns(wand); + + if(cooldowns.length == 0) return 0; + // Don't need to check if the tag compound is null since the above check is equivalent. + return cooldowns[getPreviousSpellIndex(wand)]; + } + + /** Sets the given wand's cooldown for the currently selected spell. Will also set the maximum cooldown. */ public static void setCurrentCooldown(ItemStack wand, int cooldown){ if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound())); @@ -223,8 +294,52 @@ public final class WandHelper { cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)] = cooldown; setCooldowns(wand, cooldowns); + + int[] maxCooldowns = getMaxCooldowns(wand); + + if(maxCooldowns.length == 0) maxCooldowns = new int[getSpells(wand).length]; + + maxCooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)] = cooldown; + + setMaxCooldowns(wand, maxCooldowns); } + /** + * Returns an array of the max cooldowns for each spell bound to the given wand. If the wand has no cooldown data, + * returns an array of length 0. + */ + public static int[] getMaxCooldowns(ItemStack wand){ + + int[] cooldowns = new int[0]; + + if(wand.getTagCompound() != null){ + + return wand.getTagCompound().getIntArray(MAX_COOLDOWN_ARRAY_KEY); + } + + return cooldowns; + } + + /** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length. */ + public static void setMaxCooldowns(ItemStack wand, int[] cooldowns){ + + if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound())); + + wand.getTagCompound().setIntArray(MAX_COOLDOWN_ARRAY_KEY, cooldowns); + } + + /** Returns the given wand's max cooldown for the currently selected spell, or 0 if the wand has no cooldown data. */ + public static int getCurrentMaxCooldown(ItemStack wand){ + + int[] cooldowns = getMaxCooldowns(wand); + + if(cooldowns.length == 0) return 0; + // Don't need to check if the tag compound is null since the above check is equivalent. + return cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)]; + } + + // ================================================== Upgrades ================================================== + /** * Returns the number of upgrades of the given type that have been applied to the given wand, or 0 if the wand has * no upgrade data or the given item is not a valid wand upgrade. @@ -258,7 +373,8 @@ public final class WandHelper { /** * Applies the given upgrade to the given wand, or in other words increases the level for that upgrade by 1. This - * does not account for the individual or total upgrade stack limits. + * does not account for the individual or total upgrade stack limits or any special behaviour; it only deals + * with the NBT data. */ public static void applyUpgrade(ItemStack wand, Item upgrade){ @@ -313,4 +429,28 @@ public final class WandHelper { throw new IllegalArgumentException("Duplicate wand upgrade identifier: " + identifier); upgradeMap.put(upgrade, identifier); } + + // ================================================= Progression ================================================= + + /** Sets the given wand's progression to the given value. */ + public static void setProgression(ItemStack wand, int progression){ + + if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound())); + + wand.getTagCompound().setInteger(PROGRESSION_KEY, progression); + } + + /** Returns the progression value for the given wand, or 0 if the wand has no data. */ + public static int getProgression(ItemStack wand){ + + if(wand.getTagCompound() == null) return 0; + + return wand.getTagCompound().getInteger(PROGRESSION_KEY); + } + + /** Adds the given amount of progression to this wand's progression value. */ + public static void addProgression(ItemStack wand, int progression){ + setProgression(wand, getProgression(wand) + progression); + } + } diff --git a/src/main/java/electroblob/wizardry/util/WizardryParticleType.java b/src/main/java/electroblob/wizardry/util/WizardryParticleType.java deleted file mode 100644 index 2be30685..00000000 --- a/src/main/java/electroblob/wizardry/util/WizardryParticleType.java +++ /dev/null @@ -1,9 +0,0 @@ -package electroblob.wizardry.util; - -/** - * Enum constants representing the different types of particle added by wizardry. This was renamed from the previous - * EnumParticleType in the 1.10.2 port to avoid confusion with the vanilla version, EnumParticleTypes. - */ -public enum WizardryParticleType { - BLIZZARD, BRIGHT_DUST, DARK_MAGIC, DUST, ICE, LEAF, MAGIC_BUBBLE, MAGIC_FIRE, PATH, SNOW, SPARK, SPARKLE, SPARKLE_ROTATING -} diff --git a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java b/src/main/java/electroblob/wizardry/util/WizardryUtilities.java index 01f36882..8cbdcb58 100644 --- a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java +++ b/src/main/java/electroblob/wizardry/util/WizardryUtilities.java @@ -1,70 +1,51 @@ package electroblob.wizardry.util; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.UUID; -import java.util.function.Function; - -import javax.annotation.Nullable; - -import org.apache.commons.lang3.tuple.ImmutablePair; - import electroblob.wizardry.CommonProxy; -import electroblob.wizardry.WizardData; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Element; -import electroblob.wizardry.constants.Tier; -import electroblob.wizardry.entity.living.ISummonedCreature; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.spell.MindControl; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.entity.living.ISpellCaster; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.spell.Spell; +import net.minecraft.block.Block; +import net.minecraft.block.BlockLog; +import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; -import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.effect.EntityLightningBolt; import net.minecraft.entity.item.EntityArmorStand; import net.minecraft.entity.monster.EntityCreeper; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.entity.projectile.EntityThrowable; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.inventory.EntityEquipmentSlot.Type; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTBase; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; import net.minecraft.network.datasync.DataParameter; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.DamageSource; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.NonNullList; -import net.minecraft.util.SoundCategory; -import net.minecraft.util.SoundEvent; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.*; +import net.minecraft.util.EnumFacing.Axis; +import net.minecraft.util.math.*; +import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; -import net.minecraftforge.fml.relauncher.ReflectionHelper; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiPredicate; +import java.util.function.Predicate; /** + * "Where do you put random but useful bits and pieces? {@code WizardryUtilities} of course - the 'stuff that doesn't + * fit anywhere else' class!" + *

    * This class contains some useful static methods for use anywhere - items, entities, spells, events, blocks, etc. * Broadly speaking, these fall into the following categories: - *

    + *

    * - In-world utilities (position calculating, retrieving entities, etc.)
    * - Raytracing
    - * - Drawing utilities (client-only)
    * - NBT and data storage utilities
    * - Interaction with the ally designation system
    * - Loot and weighting utilities @@ -76,26 +57,48 @@ import net.minecraftforge.fml.relauncher.SideOnly; */ public final class WizardryUtilities { - /** - * Constant which is simply an array of the four armour slots. (Could've sworn this exists somewhere in vanilla, but - * I can't find it anywhere...) - */ + /** Constant which is simply an array of the four armour slots. (Could've sworn this exists somewhere in vanilla, + * but I can't find it anywhere...) */ public static final EntityEquipmentSlot[] ARMOUR_SLOTS; /** Changed to a constant in wizardry 2.1, since this is a lot more efficient. */ private static final DataParameter POWERED; +// /** Pointing item action used in various spells. */ +// public static final EnumAction POINT; - static{ + static { // The list of slots needs to be mutable. - List slots = new ArrayList( + List slots = new ArrayList<>( Arrays.asList(EntityEquipmentSlot.values())); slots.removeIf(slot -> slot.getSlotType() != Type.ARMOR); ARMOUR_SLOTS = slots.toArray(new EntityEquipmentSlot[0]); // Null is passed in deliberately since POWERED is a static field. - POWERED = ReflectionHelper.getPrivateValue(EntityCreeper.class, null, "POWERED", "field_184714_b"); + POWERED = ObfuscationReflectionHelper.getPrivateValue(EntityCreeper.class, null, "field_184714_b"); + + //POINT = EnumHelper.addAction("POINT"); + } + + /** A global offset used for placing/rendering flat things so that they appear to sit flush with the face of blocks + * but do not cause z-fighting at a distance where it is noticeable. */ + // This value is a compromise between flushness and minimum view distance for z-fighting to occur. 0.005 seems to + // be immune to z-fighting until over a hundred blocks away, which is pretty good, and the distance from flat + // surfaces is still indistinguishable. + public static final double ANTI_Z_FIGHTING_OFFSET = 0.005; + + // I'm fed up with remembering these... + /** Stores constant values for attribute modifier operations (and javadoc for what they actually do!) */ + public static final class Operations { + /** Adds the attribute modifier amount to the base value. */ + public static final int ADD = 0; + /** Multiplies the base value by 1 plus the attribute modifier amount. Multiple modifiers are processed in + * parallel, i.e. the calculation is based on the base value and does not depend on previous modifiers. */ + public static final int MULTIPLY_FLAT = 1; + /** Multiplies the base value by 1 plus the attribute modifier amount. Multiple modifiers are processed in + * series, i.e. the calculation is based on the value after previous modifiers are applied, in the order added. */ + public static final int MULTIPLY_CUMULATIVE = 2; } - // SECTION Block/Entity/World Utilities + // World, Blocks and Coordinates // =============================================================================================================== /** @@ -117,133 +120,256 @@ public final class WizardryUtilities { return i; } - + /** - * Returns whether the block at the given coordinates can be replaced by another one (works as if a block is being - * placed by a player). True for air, liquids, vines, tall grass and snow layers but not for flowers, signs etc. + * Returns whether the block at the given position can be replaced by another one (works as if a block is being + * placed by a player). True for air, vines, tall grass and snow layers but not for flowers, signs etc. * This is a shortcut for world.getBlockState(pos).getMaterial().isReplaceable(). - * - * @see WizardryUtilities#canBlockBeReplacedB(World, BlockPos) + * + * @param world The world the block is in. + * @param pos The position of the block. + * @param excludeLiquids True to treat liquids as non-replaceable, false to treat liquids as replaceable. + * + * @see WizardryUtilities#canBlockBeReplaced(World, BlockPos) */ - public static boolean canBlockBeReplaced(World world, BlockPos pos){ - return world.isAirBlock(new BlockPos(pos)) || world.getBlockState(pos).getMaterial().isReplaceable(); + public static boolean canBlockBeReplaced(World world, BlockPos pos, boolean excludeLiquids){ + return (world.isAirBlock(new BlockPos(pos)) || world.getBlockState(pos).getMaterial().isReplaceable()) + && (!excludeLiquids || !world.getBlockState(pos).getMaterial().isLiquid()); } /** - * Returns whether the block at the given coordinates can be replaced by another one (works as if a block is being - * placed by a player) and is not a liquid. True for air, vines, tall grass and snow layers but not for flowers, - * signs etc. or any liquids. - * - * @see WizardryUtilities#canBlockBeReplaced(World, BlockPos) + * Returns whether the block at the given position can be replaced by another one (works as if a block is being + * placed by a player). True for air, liquids, vines, tall grass and snow layers but not for flowers, signs etc. + * This is a shorthand version of {@link WizardryUtilities#canBlockBeReplaced(World, BlockPos, boolean)}; + * excludeLiquids defaults to false. + * + * @param world The world the block is in. + * @param pos The position of the block. */ - public static boolean canBlockBeReplacedB(World world, BlockPos pos){ - return canBlockBeReplaced(world, pos) && !world.getBlockState(pos).getMaterial().isLiquid(); + public static boolean canBlockBeReplaced(World world, BlockPos pos){ + return canBlockBeReplaced(world, pos, false); } /** * Returns whether the block at the given coordinates is unbreakable in survival mode. In vanilla this is true for - * bedrock and end portal frame, for example. This is a shortcut for - * world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f. Not much of a shortcut any more, since block ids - * have been phased out. + * bedrock and end portal frame, for example. This is a shortcut for:

    + * {@code world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f} */ public static boolean isBlockUnbreakable(World world, BlockPos pos){ - return world.isAirBlock(new BlockPos(pos)) ? false - : world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f; + return !world.isAirBlock(new BlockPos(pos)) && world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f; + } + + /** + * Returns a block state for the given block, with all properties set to the values from the given source state. + * The source block state must have exactly the same properties as the destination block will allow or an + * {@link IllegalArgumentException} will be thrown. + *

    + * This method allows, for example, an oak door block to be replaced with a spruce one whilst keeping the same + * orientation, door half, open/closed state, etc. + * @param block The new block type + * @param source The block state to copy property values from + * @return The resulting block state + */ + @SuppressWarnings("unchecked") // Don't complain to me about Mojang's code design... + public static IBlockState copyState(Block block, IBlockState source){ + + IBlockState state = block.getDefaultState(); + + for(IProperty property : source.getPropertyKeys()){ + // It ain't pretty but it works + state = state.withProperty(property, (Comparable)source.getProperties().get(property)); + } + + return state; + } + + /** + * Finds the nearest surface (according to the given criteria) in the given direction from the given position, + * within the range specified. This is a generalised replacement for the old {@code getNearestFloorLevel} methods; + * overloads and predefined surface criteria that replicate the old behaviours are available. + * + * @param world The world to search in + * @param pos The position to search from + * @param direction The direction to search in; also defines the direction the surface must face. + * @param range The maximum distance from the given y coordinate to search. + * @param doubleSided True to also search in the opposite direction, false to only search in the given direction. + * @param criteria A {@link SurfaceCriteria} representing the criteria defining a surface. See that class for + * available predefined criteria. + * @return The x, y, or z coordinate of the closest surface, or null if no surface was found. This represents the + * exact position of the block boundary that forms the surface (and therefore it may correspond to the coordinate + * of the inside or outside block of the surface depending on the direction). + */ + @Nullable + public static Integer getNearestSurface(World world, BlockPos pos, EnumFacing direction, int range, + boolean doubleSided, SurfaceCriteria criteria){ + + // This is a neat trick that allows a default 'not found' return value for integers where all possible integer + // values could, in theory, be returned. The alternative is to use a double and have NaN as the default, but + // that would introduce extra casting, and since NaN can be calculated with, it could produce strange results + // when unaccounted for. Using an Integer means it'll immediately throw an NPE instead. + Integer surface = null; + int currentBest = Integer.MAX_VALUE; + + for(int i = doubleSided ? -range : 0; i <= range && i < currentBest; i++){ // Now short-circuits for efficiency + + BlockPos testPos = pos.offset(direction, i); + + if(criteria.test(world, testPos, direction)){ + // Because the loop now short-circuits, this must be closer than the previous surface found + surface = (int)component(getFaceCentre(testPos, direction), direction.getAxis()); + currentBest = Math.abs(i); + } + } + + return surface; + } + + /** + * A {@code SurfaceCriteria} object is used to define a 'surface', a boundary between two blocks which differ in + * some way, for use in {@link WizardryUtilities#getNearestSurface(World, BlockPos, EnumFacing, int, boolean, SurfaceCriteria)}. + * This provides a more flexible replacement for the (now deprecated) {@code getNearestFloorLevel} methods.
    + *
    + * In the context of this class, 'outside' refers to the side of the surface that is in the supplied direction, + * and 'inside' refers to the side which is in the opposite direction. For example, if the direction is {@code UP}, + * the inside of the surface is defined as below it, and the outside is defined as above it. + */ + @FunctionalInterface + public interface SurfaceCriteria { + + /** + * Tests whether the inputs define a valid surface according to this set of criteria. + * @param world The world in which the surface is to be tested. + * @param pos The block coordinates of the inside ('solid' part) of the surface. + * @param side The direction in which the surface must face. + * @return True if the side {@code side} of the block at {@code pos} in {@code world} is a valid surface + * according to this set of criteria, false otherwise. + */ + boolean test(World world, BlockPos pos, EnumFacing side); + + /** Returns a {@code SurfaceCriteria} with the opposite arrangement to this one. */ + default SurfaceCriteria flip(){ + return (world, pos, side) -> this.test(world, pos.offset(side), side.getOpposite()); + } + + /** Returns a {@code SurfaceCriteria} based on the given condition, where the inside of the surface satisfies + * the condition and the outside does not. */ + static SurfaceCriteria basedOn(BiPredicate condition){ + return (world, pos, side) -> condition.test(world, pos) && !condition.test(world, pos.offset(side)); + } + + /** Returns a {@code SurfaceCriteria} based on the given condition, where the inside of the surface satisfies + * the condition and the outside does not. */ + static SurfaceCriteria basedOn(Predicate condition){ + return (world, pos, side) -> condition.test(world.getBlockState(pos)) && !condition.test(world.getBlockState(pos.offset(side))); + } + + /** Surface criterion which defines a surface as the boundary between a block that cannot be moved through and + * a block that can be moved through. This means the surface can be stood on. */ + SurfaceCriteria COLLIDABLE = basedOn(b -> b.getMaterial().blocksMovement()); + + /** Surface criterion which defines a surface as the boundary between a block that is solid on the required side and + * a block that is replaceable. This means the surface can be built on. */ + SurfaceCriteria BUILDABLE = (world, pos, side) -> world.isSideSolid(pos, side) && world.getBlockState(pos.offset(side)).getBlock().isReplaceable(world, pos); + + /** Surface criterion which defines a surface as the boundary between a block that is solid on the required side + * or a liquid, and an air block. Used for freezing water and placing snow. */ + // Was getNearestFloorLevelB + SurfaceCriteria SOLID_LIQUID_TO_AIR = (world, pos, side) -> (world.getBlockState(pos).getMaterial().isLiquid() + || world.isSideSolid(pos, side) && world.isAirBlock(pos.offset(side))); + + /** Surface criterion which defines a surface as the boundary between any non-air block and an air block. + * Used for particles. */ + // Was getNearestFloorLevelC + SurfaceCriteria NOT_AIR_TO_AIR = basedOn(World::isAirBlock).flip(); + + /** Surface criterion which defines a surface as the boundary between a block that cannot be moved through, and + * a block that can be moved through or a tree block (log or leaves). Used for structure generation. */ + SurfaceCriteria COLLIDABLE_IGNORING_TREES = basedOn((world, pos) -> + world.getBlockState(pos).getMaterial().blocksMovement() + && !(world.getBlockState(pos).getBlock() instanceof BlockLog) + && !(world.getBlockState(pos).getBlock().isLeaves(world.getBlockState(pos), world, pos) + && !(world.getBlockState(pos).getBlock().isFoliage(world, pos)))); + + } + + /** + * Finds the nearest floor level in the given direction from the given position, + * within the range specified. This is a shorthand for + * {@link WizardryUtilities#getNearestSurface(World, BlockPos, EnumFacing, int, boolean, SurfaceCriteria)}; + * {@code doubleSided} defaults to true, {@code direction} defaults to {@code EnumFacing.UP}, and + * {@code criteria} defaults to {@link SurfaceCriteria#COLLIDABLE}. + */ + @Nullable + public static Integer getNearestFloor(World world, BlockPos pos, int range){ + return getNearestSurface(world, pos, EnumFacing.UP, range, true, SurfaceCriteria.COLLIDABLE); } /** * Finds the nearest floor level to the given y coord within the range specified at the given x and z coords. - * Liquids and other blocks that cannot be built on top of do not count, but stuff like signs does. (Technically any - * block is allowed to be the floor according to the code, but seeing as it searches upwards and non-solid blocks - * usually need a supporting block, the floor is likely to always be solid). - * - * @param world - * @param x The x coordinate to search in - * @param y The y coordinate to search from - * @param z The z coordinate to search in + * As of Wizardry 4.2, this is now a wrapper for {@link WizardryUtilities#getNearestFloor(World, BlockPos, int)} + * which retains the old functionality (i.e. returning an {@code int}, with -1 as 'not found') for compatibility. + * + * @param world The world to search in + * @param pos The coordinates to search from * @param range The maximum distance from the given y coordinate to search. * @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the * floor as would be seen in the debug screen when the player is standing on it. - * @see WizardryUtilities#getNearestFloorLevelB(World, BlockPos, int) + * @deprecated Use {@link WizardryUtilities#getNearestFloor(World, BlockPos, int)}; this method may be removed in + * future. */ + // Since this is always a y-coordinate, the 'not found' value can just be any negative number. + @Deprecated public static int getNearestFloorLevel(World world, BlockPos pos, int range){ - - int yCoord = -2; - for(int i = -range; i <= range; i++){ - // The last bit determines whether the block found to be a suitable floor is closer than the previous one - // found. - if(world.isSideSolid(pos.up(i), EnumFacing.UP) - && (world.isAirBlock(pos.up(i + 1)) || !world.isSideSolid(pos.up(i + 1), EnumFacing.UP)) - && (i < yCoord - pos.getY() || yCoord == -2)){ - yCoord = pos.getY() + i; - } - } - return yCoord + 1; + Integer floor = getNearestFloor(world, pos, range); + return floor == null ? -1 : floor; } /** * Finds the nearest floor level to the given y coord within the range specified at the given x and z coords. Only * works if the block above the floor is actually air and the floor is solid or a liquid. * - * @param world - * @param x The x coordinate to search in - * @param y The y coordinate to search from - * @param z The z coordinate to search in + * @param world The world to search in + * @param pos The coordinates to search from * @param range The maximum distance from the given y coordinate to search. * @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the * floor as would be seen in the debug screen when the player is standing on it. - * @see WizardryUtilities#getNearestFloorLevel(World, BlockPos, int) + * @deprecated Use {@link WizardryUtilities#getNearestSurface(World, BlockPos, EnumFacing, int, boolean, SurfaceCriteria)}; + * this method may be removed in future. */ + @Deprecated public static int getNearestFloorLevelB(World world, BlockPos pos, int range){ - int yCoord = -2; - for(int i = -range; i <= range; i++){ - if(world.isAirBlock(new BlockPos(pos.up(i + 1))) - && (world.getBlockState(pos.up(i)).getMaterial().isLiquid() - || world.isSideSolid(pos.up(i), EnumFacing.UP)) - && (i < yCoord - pos.getY() || yCoord == -2)){ - // The last bit determines whether the block found to be a suitable floor is closer than the previous - // one found. - yCoord = pos.getY() + i; - } - } - return yCoord + 1; + Integer floor = getNearestSurface(world, pos, EnumFacing.UP, range, true, SurfaceCriteria.SOLID_LIQUID_TO_AIR); + return floor == null ? -1 : floor; } /** * Finds the nearest floor level to the given y coord within the range specified at the given x and z coords. * Everything that is not air is treated as floor, even stuff that can't be walked on. - * - * @param world - * @param x The x coordinate to search in - * @param y The y coordinate to search from - * @param z The z coordinate to search in + * + * @param world The world to search in + * @param pos The coordinates to search from * @param range The maximum distance from the given y coordinate to search. * @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the * floor as would be seen in the debug screen when the player is standing on it. - * @see WizardryUtilities#getNearestFloorLevel(World, BlockPos, int) + * @deprecated Use {@link WizardryUtilities#getNearestSurface(World, BlockPos, EnumFacing, int, boolean, SurfaceCriteria)}; + * this method may be removed in future. */ + @Deprecated public static int getNearestFloorLevelC(World world, BlockPos pos, int range){ - int yCoord = -2; - for(int i = -range; i <= range; i++){ - if(world.isAirBlock(new BlockPos(pos.up(i + 1))) && (i < yCoord - pos.getY() || yCoord == -2)){ - // The last bit determines whether the block found to be a suitable floor is closer than the previous - // one found. - yCoord = pos.getY() + i; - } - } - return yCoord + 1; + Integer floor = getNearestSurface(world, pos, EnumFacing.UP, range, true, SurfaceCriteria.NOT_AIR_TO_AIR); + return floor == null ? -1 : floor; } /** - * Gets a random position on the ground near the player within the specified horizontal and vertical ranges. Used to - * find a position to spawn entities in summoning spells. + * Gets a random position on the ground near the given entity within the specified horizontal and vertical ranges. + * Used to find a position to spawn entities in summoning spells. * - * @param entity The entity around which to search + * @param entity The entity around which to search. * @param horizontalRange The maximum number of blocks on the x or z axis the returned position can be from the * given entity. The number of operations performed by this method is proportional to the square of this * parameter, so for performance reasons it is recommended that it does not exceed around 10. * @param verticalRange The maximum number of blocks on the y axis the returned position can be from the given - * entity + * entity. * @return A BlockPos with the coordinates of the block directly above the ground at the position found, or null if * none were found within range. Importantly, since this method checks all possible positions within * range (i.e. randomness only occurs when deciding between the possible positions), if it returns null once @@ -254,13 +380,36 @@ public final class WizardryUtilities { public static BlockPos findNearbyFloorSpace(Entity entity, int horizontalRange, int verticalRange){ World world = entity.world; - List possibleLocations = new ArrayList(); BlockPos origin = new BlockPos(entity); + return findNearbyFloorSpace(world, origin, horizontalRange, verticalRange); + } + + /** + * Gets a random position on the ground near the given BlockPos within the specified horizontal and vertical ranges. + * Used to find a position to spawn entities in summoning spells. + * + * @param world The world in which to search. + * @param origin The BlockPos around which to search. + * @param horizontalRange The maximum number of blocks on the x or z axis the returned position can be from the + * given position. The number of operations performed by this method is proportional to the square of this + * parameter, so for performance reasons it is recommended that it does not exceed around 10. + * @param verticalRange The maximum number of blocks on the y axis the returned position can be from the given + * position. + * @return A BlockPos with the coordinates of the block directly above the ground at the position found, or null if + * none were found within range. Importantly, since this method checks all possible positions within + * range (i.e. randomness only occurs when deciding between the possible positions), if it returns null once + * then it will always return null given the same circumstances and parameters. What this means is that you + * can (and should) immediately stop trying to cast a summoning spell if this returns null. + */ + @Nullable + public static BlockPos findNearbyFloorSpace(World world, BlockPos origin, int horizontalRange, int verticalRange){ + + List possibleLocations = new ArrayList(); for(int x = -horizontalRange; x <= horizontalRange; x++){ for(int z = -horizontalRange; z <= horizontalRange; z++){ - int y = WizardryUtilities.getNearestFloorLevel(world, origin.add(x, 0, z), verticalRange); - if(y > -1) possibleLocations.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z)); + Integer y = WizardryUtilities.getNearestFloor(world, origin.add(x, 0, z), verticalRange); + if(y != null) possibleLocations.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z)); } } @@ -269,12 +418,11 @@ public final class WizardryUtilities { }else{ return possibleLocations.get(world.rand.nextInt(possibleLocations.size())); } - } /** * Gets the blockstate of the block the specified entity is standing on. Uses - * {@link MathHelper#floor_double(double)} because casting to int will not return the correct coordinate when x or z + * {@link MathHelper#floor(double)} because casting to int will not return the correct coordinate when x or z * is negative. */ public static IBlockState getBlockEntityIsStandingOn(Entity entity){ @@ -283,10 +431,43 @@ public final class WizardryUtilities { return entity.world.getBlockState(pos); } + /** + * Generates a sphere of block positions centred on the given position, with the given radius. This is an efficient + * implementation - rather than simply generating a cube and cutting the rest out, it works as follows: + *

    + * 1. Step through all x offsets within the specified radius
    + * 2. For each x offset, check the maximum y offset within the radius using Pythagoras
    + * 3. Step through the resulting valid y offsets
    + * 4. For each y offset, check the maximum z offset within the radius using Pythagoras
    + * 5. Step through the resulting valid z offsets + * @return A list of BlockPos objects in a sphere. This list will be ordered negative to positive, with axes + * nested in order (i.e. blocks in a line on the z axis will be consecutive in the list). + */ + public static List getBlockSphere(BlockPos centre, double radius){ + + // Extra efficiency by assigning enough capacity for a cube of side length r + List sphere = new ArrayList<>((int)Math.pow(radius, 3)); + + for(int i=-(int)radius; i<=radius; i++){ + + float r1 = MathHelper.sqrt(radius*radius - i*i); + + for(int j=-(int)r1; j<=r1; j++){ + + float r2 = MathHelper.sqrt(radius*radius - i*i - j*j); + + for(int k=-(int)r2; k<=r2; k++){ + sphere.add(centre.add(i, j, k)); + } + } + } + + return sphere; + } + /** * Shorthand for {@link WizardryUtilities#getEntitiesWithinRadius(double, double, double, double, World, Class)} - * with EntityLivingBase as the entity type. This is by far the most common use for that method, which is why this - * shorthand exists. + * with EntityLivingBase as the entity type. This is by far the most common use for that method. * * @param radius The search radius * @param x The x coordinate to search around @@ -305,7 +486,7 @@ public final class WizardryUtilities { * this does not exclude any entities; if any specific entities are to be excluded this must be checked when * iterating through the list. * - * @see {@link WizardryUtilities#getEntitiesWithinRadius(double, double, double, double, World)} + * @see WizardryUtilities#getEntitiesWithinRadius(double, double, double, double, World) * @param radius The search radius * @param x The x coordinate to search around * @param y The y coordinate to search around @@ -320,21 +501,121 @@ public final class WizardryUtilities { for(int i = 0; i < entityList.size(); i++){ if(entityList.get(i).getDistance(x, y, z) > radius){ entityList.remove(i); + break; } } return entityList; } + // Why is there a distanceSqToCentre method in Vec3i but not a getCentre method? + /** - * Gets an entity from its UUID. Note that you should check this isn't null. If the UUID is known to belong to an - * EntityPlayer, use the more efficient {@link World#getPlayerEntityByUUID(UUID)} instead. + * Returns a {@link Vec3d} of the coordinates at the centre of the given block position (i.e. the block coordinates + * plus 0.5 in x, y, and z). + */ + public static Vec3d getCentre(BlockPos pos){ + return new Vec3d(pos).add(0.5, 0.5, 0.5); + } + + /** + * Returns a {@link Vec3d} of the coordinates at the centre of the given bounding box (The one in {@code AxisAlignedBB} + * itself is client-side only). + */ + public static Vec3d getCentre(AxisAlignedBB box){ + return new Vec3d(box.minX + (box.maxX - box.minX) * 0.5, box.minY + (box.maxY - box.minY) * 0.5, box.minZ + (box.maxZ - box.minZ) * 0.5); + } + + /** + * Returns a {@link Vec3d} of the coordinates at the centre of the given face of the given block position (i.e. the + * centre of the block plus 0.5 in the given direction). + */ + public static Vec3d getFaceCentre(BlockPos pos, EnumFacing face){ + return getCentre(pos).add(new Vec3d(face.getDirectionVec()).scale(0.5)); + } + + /** + * Returns the component of the given {@link Vec3d} corresponding to the given {@link Axis Axis}. + */ + public static double component(Vec3d vec, Axis axis){ + return new double[]{vec.x, vec.y, vec.z}[axis.ordinal()]; // Damn, that's compact. + } + + /** + * Returns the component of the given {@link Vec3i} corresponding to the given {@link Axis Axis}. + */ + public static int component(Vec3i vec, Axis axis){ + return new int[]{vec.getX(), vec.getY(), vec.getZ()}[axis.ordinal()]; + } + + /** + * Returns a new {@link Vec3d} with the component corresponding to the given {@link Axis Axis} replaced by the + * given value. + */ + public static Vec3d replaceComponent(Vec3d vec, Axis axis, double newValue){ + double[] components = {vec.x, vec.y, vec.z}; + components[axis.ordinal()] = newValue; + return new Vec3d(components[0], components[1], components[2]); + } + + /** + * Returns a new {@link Vec3i} with the component corresponding to the given {@link Axis Axis} replaced by the + * given value. + */ + public static Vec3i replaceComponent(Vec3i vec, Axis axis, int newValue){ + int[] components = {vec.getX(), vec.getY(), vec.getZ()}; + components[axis.ordinal()] = newValue; + return new Vec3i(components[0], components[1], components[2]); + } + + /** + * Returns an array of {@code Vec3d} objects representing the vertices of the given bounding box. + * @param box The bounding box whose vertices are to be returned. + * @return The list of vertices, which will contain 8 elements. Using EnumFacing initials, the order is: + * DNW, DNE, DSE, DSW, UNW, UNE, USE, USW. The returned coordinates are absolute (i.e. measured from the world origin). + */ + public static Vec3d[] getVertices(AxisAlignedBB box){ + return new Vec3d[]{ + new Vec3d(box.minX, box.minY, box.minZ), + new Vec3d(box.maxX, box.minY, box.minZ), + new Vec3d(box.maxX, box.minY, box.maxZ), + new Vec3d(box.minX, box.minY, box.maxZ), + new Vec3d(box.minX, box.maxY, box.minZ), + new Vec3d(box.maxX, box.maxY, box.minZ), + new Vec3d(box.maxX, box.maxY, box.maxZ), + new Vec3d(box.minX, box.maxY, box.maxZ) + }; + } + + /** + * Returns an array of {@code Vec3d} objects representing the vertices of the block at the given position. + * @param pos The position of the block whose vertices are to be returned. + * @return The list of vertices, which will contain 8 elements. Using EnumFacing initials, the order is: + * DNW, DNE, DSE, DSW, UNW, UNE, USE, USW. The returned coordinates are absolute (i.e. measured from the world origin). + */ + public static Vec3d[] getVertices(World world, BlockPos pos){ + return getVertices(world.getBlockState(pos).getBoundingBox(world, pos).offset(pos.getX(), pos.getY(), pos.getZ())); + } + + /** + * Returns the pitch angle in degrees of the given {@link EnumFacing}. For some reason {@code EnumFacing} has a get + * yaw method ({@link EnumFacing#getHorizontalAngle()}) but not a get pitch method. + */ + public static float getPitch(EnumFacing facing){ + return facing == EnumFacing.UP ? 90 : facing == EnumFacing.DOWN ? -90 : 0; + } + + /** + * Gets an entity from its UUID. If the UUID is known to belong to an {@code EntityPlayer}, use the more efficient + * {@link World#getPlayerEntityByUUID(UUID)} instead. * * @param world The world the entity is in * @param id The entity's UUID * @return The Entity that has the given UUID, or null if no such entity exists in the specified world. */ @Nullable - public static Entity getEntityByUUID(World world, UUID id){ + public static Entity getEntityByUUID(World world, @Nullable UUID id){ + + if(id == null) return null; // It would return null eventually but there's no point even looking for(Entity entity : world.loadedEntityList){ // This is a perfect example of where you need to use .equals() and not ==. For most applications, @@ -368,14 +649,16 @@ public final class WizardryUtilities { player.world.playSound(null, player.posX, player.posY, player.posZ, sound, SoundCategory.PLAYERS, volume, pitch); } + // Players and Mobs + // =============================================================================================================== + /** * Returns the entity riding the given entity, or null if there is none. Allows for neater code now that entities - * have a list of passengers, because it is necessary to check that the list is not null or empty first. + * have a list of passengers, because it is necessary to check that the list is not empty first. */ @Nullable public static Entity getRider(Entity entity){ - return entity.getPassengers() != null && !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) - : null; + return !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) : null; } /** @@ -417,25 +700,25 @@ public final class WizardryUtilities { * 0.01D){ dx = (Math.random() - Math.random()) * 0.01D; } - // The first argument is never used. - target.knockBack(null, 0.4f, dx, dz); + target.knockBack(attacker, 0.4f, dx, dz); } - - // Just what benefit does having posY be the eye position on the first person client actually give? - + /** - * Gets the y coordinate of the given player's eyes. This is to cover an inconsistency between the value of - * EntityPlayer.posY on the first person client and everywhere else; in first person (i.e. when - * Minecraft.getMinecraft().player == player) player.posY is the eye position, but everywhere else it is the feet - * position. This is intended for use when spawning particles, since this is the only situation where the - * discrepancy is likely to matter. - *

    - * As of Wizardry 1.2, this is just a shorthand for: - *

    - *

    player.getEntityBoundingBox().minY + player.getEyeHeight()
    + * Undoes 1 tick's worth of velocity change due to gravity for the given entity. If the entity has no gravity, + * this method does nothing. This method is intended to be used in situations where entity gravity needs to be + * turned on and off and it is not practical to use {@link Entity#setNoGravity(boolean)}, usually if there is no + * easy way to get a reference to the entity to turn gravity back on. + * + * @param entity The entity to undo gravity for. */ - public static double getPlayerEyesPos(EntityPlayer player){ - return player.getEntityBoundingBox().minY + player.getEyeHeight(); + public static void undoGravity(Entity entity){ + if(!entity.hasNoGravity()){ + double gravity = 0.04; + if(entity instanceof EntityThrowable) gravity = 0.03; + else if(entity instanceof EntityArrow) gravity = 0.05; + else if(entity instanceof EntityLivingBase) gravity = 0.08; + entity.motionY += gravity; + } } /** @@ -455,7 +738,7 @@ public final class WizardryUtilities { /** * Returns a list of the itemstacks in the given player's hotbar and offhand, sorted into the following order: main - * hand, offhand, rest of hotbar left-to-right. The returned list is a modifiable copy of part of the player's + * hand, offhand, rest of hotbar left-to-right. The returned list is a modifiable shallow copy of part of the player's * inventory stack list; as such, changes to the list are not written through to the player's inventory. * However, the ItemStack instances themselves are not copied, so changes to any of their fields (size, metadata...) * will change those in the player's inventory. @@ -506,6 +789,24 @@ public final class WizardryUtilities { public static boolean isPlayerOp(EntityPlayer player, MinecraftServer server){ return server.getPlayerList().getOppedPlayers().getEntry(player.getGameProfile()) != null; } + + /** Checks that the given entity is allowed to damage blocks in the given world. If the entity is a player, this + * checks the player block damage config setting, otherwise it posts a mob griefing event and returns the result. */ + public static boolean canDamageBlocks(EntityLivingBase entity, World world){ + if(entity instanceof EntityPlayer) return Wizardry.settings.playerBlockDamage; + return ForgeEventFactory.getMobGriefingEvent(world, entity); + } + + /** Returns the default aiming arror used by skeletons for the given difficulty. For reference, these are: Easy - 10, + * Normal - 6, Hard - 2, Peaceful - 10 (rarely used). */ + public static int getDefaultAimingError(EnumDifficulty difficulty){ + switch(difficulty){ + case EASY: return 10; + case NORMAL: return 6; + case HARD: return 2; + default: return 10; // Peaceful counts as easy; the only time this is used is when a player attacks a (good) wizard. + } + } /** * Returns true if the given entity is an EntityLivingBase and not an armour stand; makes the code a bit neater. @@ -522,205 +823,86 @@ public final class WizardryUtilities { /** * Turns the given creeper into a charged creeper. In 1.10, this requires reflection since the DataManager keys are - * private. (You could call {@link EntityCreeper#onStruckByLightning(...)} and then heal it and extinguish - * it, but that's a bit awkward.) + * private. (You could call {@link EntityCreeper#onStruckByLightning(EntityLightningBolt)} and then heal it + * and extinguish it, but that's a bit awkward, and it'll trigger events and stuff...) */ + // The reflection here only gets done once to initialise the POWERED field, so it's not a performance issue at all. public static void chargeCreeper(EntityCreeper creeper){ creeper.getDataManager().set(POWERED, true); } - - // SECTION Raytracing - // =============================================================================================================== - + /** - * Does a block ray trace (NOT entities) from an entity's eyes (i.e. properly...) + * Returns true if the given caster is currently casting the given spell by any means. This method is intended to + * eliminate the long and cumbersome wand use checking in event handlers, which often missed out spells cast by + * means other than wands. + * @param caster The potential spell caster, which may be a player or an {@link ISpellCaster}. Any other entity will + * cause this method to always return false. + * @param spell The spell to check for. The spell must be continuous or this method will always return false. + * @return True if the caster is currently casting the given spell through any means, false otherwise. */ - @Nullable - public static RayTraceResult rayTrace(double range, World world, EntityLivingBase entity, boolean hitLiquids){ + // The reason this is a boolean check is that actually returning a spell presents a problem: players can cast two + // continuous spells at once, one via commands and one via an item, so which do you choose? Since the main point was + // to check for specific spells, it seems more useful to do it this way. + public static boolean isCasting(EntityLivingBase caster, Spell spell){ + + if(!spell.isContinuous) return false; + + if(caster instanceof EntityPlayer){ + + WizardData data = WizardData.get((EntityPlayer)caster); + + if(data != null && data.currentlyCasting() == spell) return true; - Vec3d start = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); - Vec3d look = entity.getLookVec(); - Vec3d end = start.add(look.x * range, look.y * range, look.z * range); - return world.rayTraceBlocks(start, end, hitLiquids); - } + if(caster.isHandActive()){ - /** - * Helper method which does a rayTrace for entities from an entity's eye level in the direction they are looking - * with a specified range, using the tracePath method. Tidies up the code a bit. Border size defaults to 1. - * - * @param world - * @param entity - * @param range - * @return - */ - @Nullable - public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range){ - double dx = entity.getLookVec().x * range; - double dy = entity.getLookVec().y * range; - double dz = entity.getLookVec().z * range; - HashSet hashset = new HashSet(1); - hashset.add(entity); - return WizardryUtilities.tracePath(world, (float)entity.posX, - (float)(entity.getEntityBoundingBox().minY + entity.getEyeHeight()), (float)entity.posZ, - (float)(entity.posX + dx), (float)(entity.posY + entity.getEyeHeight() + dy), (float)(entity.posZ + dz), - 1.0f, hashset, false); - } + ItemStack stack = caster.getHeldItem(caster.getActiveHand()); - /** - * Helper method which does a rayTrace for entities from a entity's eye level in the direction they are looking with - * a specified range and radius, using the tracePath method. Tidies up the code a bit. - * - * @param world - * @param entity - * @param range - * @param borderSize - * @return - */ - @Nullable - public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, - float borderSize){ - double dx = entity.getLookVec().x * range; - double dy = entity.getLookVec().y * range; - double dz = entity.getLookVec().z * range; - HashSet hashset = new HashSet(1); - hashset.add(entity); - return WizardryUtilities.tracePath(world, (float)entity.posX, - (float)(entity.getEntityBoundingBox().minY + entity.getEyeHeight()), (float)entity.posZ, - (float)(entity.posX + dx), (float)(entity.posY + entity.getEyeHeight() + dy), (float)(entity.posZ + dz), - borderSize, hashset, false); - } - - /** - * Method for ray tracing entities (the useless default method doesn't work, despite EnumHitType having an ENTITY - * field...) You can also use this for seeking. - * - * @param world - * @param x startX - * @param y startY - * @param z startZ - * @param tx endX - * @param ty endY - * @param tz endZ - * @param borderSize extra area to examine around line for entities - * @param excluded any excluded entities (the player, etc) - * @return a RayTraceResult of either the block hit (no entity hit), the entity hit (hit an entity), or null for - * nothing hit - */ - @Nullable - public static RayTraceResult tracePath(World world, float x, float y, float z, float tx, float ty, float tz, - float borderSize, HashSet excluded, boolean collideablesOnly){ - - Vec3d startVec = new Vec3d(x, y, z); - // Vec3d lookVec = new Vec3d(tx-x, ty-y, tz-z); - Vec3d endVec = new Vec3d(tx, ty, tz); - float minX = x < tx ? x : tx; - float minY = y < ty ? y : ty; - float minZ = z < tz ? z : tz; - float maxX = x > tx ? x : tx; - float maxY = y > ty ? y : ty; - float maxZ = z > tz ? z : tz; - AxisAlignedBB bb = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ).grow(borderSize, borderSize, - borderSize); - List allEntities = world.getEntitiesWithinAABBExcludingEntity(null, bb); - RayTraceResult blockHit = world.rayTraceBlocks(startVec, endVec); - startVec = new Vec3d(x, y, z); - endVec = new Vec3d(tx, ty, tz); - float maxDistance = (float)endVec.distanceTo(startVec); - if(blockHit != null){ - maxDistance = (float)blockHit.hitVec.distanceTo(startVec); - } - Entity closestHitEntity = null; - float closestHit = maxDistance; - float currentHit = 0.f; - AxisAlignedBB entityBb;// = ent.getBoundingBox(); - RayTraceResult intercept; - for(Entity ent : allEntities){ - if((ent.canBeCollidedWith() || !collideablesOnly) - && ((excluded != null && !excluded.contains(ent)) || excluded == null)){ - float entBorder = ent.getCollisionBorderSize(); - entityBb = ent.getEntityBoundingBox(); - if(entityBb != null){ - entityBb = entityBb.grow(entBorder, entBorder, entBorder); - intercept = entityBb.calculateIntercept(startVec, endVec); - if(intercept != null){ - currentHit = (float)intercept.hitVec.distanceTo(startVec); - if(currentHit < closestHit || currentHit == 0){ - closestHit = currentHit; - closestHitEntity = ent; - } - } + if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) == spell + && ((ISpellCastingItem)stack.getItem()).canCast(stack, spell, (EntityPlayer)caster, + EnumHand.MAIN_HAND, 0, new SpellModifiers())){ + return true; } } + + }else if(caster instanceof ISpellCaster){ + if(((ISpellCaster)caster).getContinuousSpell() == spell) return true; } - if(closestHitEntity != null){ - blockHit = new RayTraceResult(closestHitEntity); - } - return blockHit; - } - - // SECTION Rendering and GUIs - // =============================================================================================================== - - // Doesn't seem right to put this in the proxies since it should only ever be called from client-side code, and I'm - // not about to make a whole separate utilities class just for one method. Fully qualified names it is! - /** - * [Client-side only] Draws a textured rectangle, taking the size of the image and the bit needed into - * account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int) - * Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also handy - * for custom potion icons. - * - * @param x The x position of the rectangle - * @param y The y position of the rectangle - * @param u The x position of the top left corner of the section of the image wanted - * @param v The y position of the top left corner of the section of the image wanted - * @param width The width of the section - * @param height The height of the section - * @param textureWidth The width of the actual image. - * @param textureHeight The height of the actual image. - */ - @SideOnly(Side.CLIENT) - public static void drawTexturedRect(int x, int y, int u, int v, int width, int height, int textureWidth, - int textureHeight){ - - float f = 1F / (float)textureWidth; - float f1 = 1F / (float)textureHeight; - - // Essentially the same as getting the tessellator. For most code, you'll want the tessellator AND the - // vertexbuffer - // stored in local variables. - BufferBuilder buffer = net.minecraft.client.renderer.Tessellator.getInstance() - .getBuffer(); - // Equivalent of tessellator.startDrawingQuads() - buffer.begin(org.lwjgl.opengl.GL11.GL_QUADS, - net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX); - // Equivalent of tessellator.addVertex() - buffer.pos((double)(x), (double)(y + height), 0) - .tex((double)((float)(u) * f), (double)((float)(v + height) * f1)).endVertex(); - buffer.pos((double)(x + width), (double)(y + height), 0) - .tex((double)((float)(u + width) * f), (double)((float)(v + height) * f1)).endVertex(); - buffer.pos((double)(x + width), (double)(y), 0).tex((double)((float)(u + width) * f), (double)((float)(v) * f1)) - .endVertex(); - buffer.pos((double)(x), (double)(y), 0).tex((double)((float)(u) * f), (double)((float)(v) * f1)).endVertex(); - // Exactly the same as before. - net.minecraft.client.renderer.Tessellator.getInstance().draw(); + + return false; } /** - * Shorthand for {@link WizardryUtilities#drawTexturedRect(int, int, int, int, int, int, int, int)} which draws the - * entire texture (u and v are set to 0 and textureWidth and textureHeight are the same as width and height). + * Returns whether the given {@link DamageSource} is melee damage. This method makes a best guess as to whether + * the damage was from a melee attack; there is no way of testing this properly. + * @param source The damage source to be tested. + * @return True if the given damage source is melee damage, false otherwise. */ - @SideOnly(Side.CLIENT) - public static void drawTexturedRect(int x, int y, int width, int height){ - drawTexturedRect(x, y, 0, 0, width, height, width, height); + public static boolean isMeleeDamage(DamageSource source){ + + // With the exception of minions, melee damage always has the same entity for immediate/true source + if(!(source instanceof MinionDamage) && source.getImmediateSource() != source.getTrueSource()) return false; + if(source.isProjectile()) return false; // Projectile damage obviously isn't melee damage + if(source.isUnblockable()) return false; // Melee damage should always be blockable + if(!(source instanceof MinionDamage) && source instanceof IElementalDamage) return false; + if(!(source.getTrueSource() instanceof EntityLivingBase)) return false; // Only living things can melee! + + if(source.getTrueSource() instanceof EntityPlayer && source.getDamageLocation() != null + && source.getDamageLocation().distanceTo(source.getTrueSource().getPositionVector()) > ((EntityLivingBase)source + .getTrueSource()).getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue()){ + return false; // Out of melee reach for players + } + + // If it got through all that, chances are it's melee damage + return true; } - // SECTION NBT and Data Storage + // Miscellaneous // =============================================================================================================== /** * Verifies that the given string is a valid string representation of a UUID. More specifically, returns true if and * only if the given string is not null and matches the regular expression: - *

    + *

    *
    /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/

    * which is the regex equivalent of the standard string representation of a UUID as described in * {@link UUID#toString()}. This method is intended to be used as a check to prevent an @@ -737,394 +919,24 @@ public final class WizardryUtilities { } /** - * Generic method that stores any Map to an NBTTagList, given two functions that convert the key and value types in - * that map to subclasses of NBTBase. For what it's worth, there is very little point in using this unless you can - * use something more concise than an anonymous class to do the conversion. A lambda expression, or better, a method - * reference, would fit nicely. For example, take ExtendedPlayer's use of this to store conjured item durations: - *

    - * properties.setTag("conjuredItems", WizardryUtilities.mapToNBT(this.conjuredItemDurations, - * item -> new NBTTagInt(Item.getIdFromItem((Item)item)), NBTTagInt::new)); - *

    - * This is a lot nicer than simply iterating through the map, because for that you need to use the entry list, which - * introduces local variables that aren't really necessary. Notice that, since the values V in the map are simply - * Integer objects, a simple constructor reference to NBTTagInt::new can be used instead of a lambda expression (the - * Integer is auto-unboxed to int). - * - * @param The type of key stored in the given Map. - * @param The type of value stored in the given Map. - * @param The subtype of NBTBase that the keys (of type K) will be converted to. - * @param The subtype of NBTBase that the values (of type V) will be converted to. - * @param map The Map to be stored. - * @param keyFunction A Function that converts the keys in the map to NBT objects that can be stored. - * @param valueFunction A Function that converts the values in the map to NBT objects that can be stored. - * @param keyTagName The tag name to use for the key tags. - * @param valueTagName The tag name to use for the value tags. - * @return An NBTTagList that represents the given Map. + * Flattens the given nested collection. The returned collection is an unmodifiable collection of all the elements + * contained within all of the sub-collections of the given nested collection. + * @param collection A nested collection to flatten + * @param The type of elements in the given nested collection + * @return The resulting flattened collection. */ - public static NBTTagList mapToNBT(Map map, - Function keyFunction, Function valueFunction, String keyTagName, String valueTagName){ - - NBTTagList tagList = new NBTTagList(); - - for(Entry entry : map.entrySet()){ - NBTTagCompound mapping = new NBTTagCompound(); - mapping.setTag(keyTagName, keyFunction.apply(entry.getKey())); - mapping.setTag(valueTagName, valueFunction.apply(entry.getValue())); - tagList.appendTag(mapping); - } - - return tagList; + public static Collection flatten(Collection> collection){ + Collection result = new ArrayList<>(); + collection.forEach(result::addAll); + return Collections.unmodifiableCollection(result); } - /** - * See {@link WizardryUtilities#mapToNBT(Map, Function, Function, String, String)}; this version is for when the - * names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). - */ - public static NBTTagList mapToNBT(Map map, - Function keyFunction, Function valueFunction){ - return mapToNBT(map, keyFunction, valueFunction, "key", "value"); - } + // Neat way of getting a random element from a set, wasn't needed in the end but kept here for future reference +// public static E randomElement(Collection collection, Random random){ +// if(collection.isEmpty()) throw new IndexOutOfBoundsException("The given collection must not be empty"); +// Iterator iterator = collection.iterator(); +// for(int n = random.nextInt(collection.size()); n > 0; n--) iterator.next(); +// return iterator.next(); +// } - /** - * Generic method that reads a Map from an NBTTagList, given two functions that convert the key and value tag types - * into the key and value types in the returned map. The given NBTTagList remains unchanged after calling this - * method. - * - * @param The type of key stored in the returned Map. - * @param The type of value stored in the returned Map. - * @param The subtype of NBTBase that the keys are stored as. - * @param The subtype of NBTBase that the values are stored as. - * @param tagList The NBTTagList to be converted. This must be a list of compound tags. - * @param keyFunction A Function that converts the generic NBTBase tags in the list to keys of type K for the map. - * @param valueFunction A Function that converts the generic NBTBase tags in the list to values of type V for the - * map. - * @param keyTagName The tag name used for the key tags. - * @param valueTagName The tag name used for the value tags. - * @return A Map containing the keys and values stored in the given NBTTagList. Can be empty, but not null. - * @throws ClassCastException If the tags are not of the expected type. - * @see WizardryUtilities#mapToNBT(Map, Function, Function, String, String) - */ - @SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here. - public static Map NBTToMap(NBTTagList tagList, - Function keyFunction, Function valueFunction, String keyTagName, String valueTagName){ - - Map map = new HashMap(); - - for(int i = 0; i < tagList.tagCount(); i++){ - NBTTagCompound mapping = tagList.getCompoundTagAt(i); - NBTBase keyTag = mapping.getTag(keyTagName); - NBTBase valueTag = mapping.getTag(valueTagName); - K key = null; - try{ - key = keyFunction.apply((L)keyTag); - }catch (ClassCastException e){ - Wizardry.logger.error( - "Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[keyTag.getId()], e); - } - V value = null; - try{ - value = valueFunction.apply((W)valueTag); - }catch (ClassCastException e){ - Wizardry.logger.error( - "Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[valueTag.getId()], - e); - } - map.put(key, value); - } - - return map; - } - - /** - * See {@link WizardryUtilities#NBTToMap(NBTTagList, Function, Function, String, String)}; this version is for when - * the names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). - */ - public static Map NBTToMap(NBTTagList tagList, - Function keyFunction, Function valueFunction){ - return NBTToMap(tagList, keyFunction, valueFunction, "key", "value"); - } - - /** - * Generic method that stores any Collection to an NBTTagList, given a function that converts the elements in that - * collection to subclasses of NBTBase. For what it's worth, there is very little point in using this unless you can - * use something more concise than an anonymous class to do the conversion. A lambda expression, or better, a method - * reference, would fit nicely. - * - * @param The type of element stored in the given Collection. - * @param The NBT tag type that the elements will be converted to. - * @param list The Collection to be stored. - * @param function A Function that converts the elements in the collection to NBT objects that can be stored. - * @return An NBTTagList that represents the given Collection. - */ - public static NBTTagList listToNBT(Collection list, Function function){ - - NBTTagList tagList = new NBTTagList(); - // If the collection is ordered, it will preserve the order, even though we don't know what type it is yet. - for(E element : list){ - tagList.appendTag(function.apply(element)); - } - - return tagList; - } - - /** - * Generic method that reads a Collection from an NBTTagList, given a function that converts the element tag types - * to the element types in the returned collection. The given NBTTagList remains unchanged after calling this - * method. Unless the target variable for this method is of type Collection, you will need to create a new - * collection containing the elements in the returned collection via that collection's constructor (e.g. {@code new - * HashSet(collection)}). Although this method returns a Collection rather than any of its subtypes, it uses - * an ArrayList internally to guarantee the order of the elements in the returned collection is the same as the - * order in which they were stored. As such, you may safely cast to List should you wish. - * - * @param The type of element stored in the returned Collection. - * @param The subtype of NBTBase that the elements are stored as. - * @param tagList The NBTTagList to be converted. - * @param function A Function that converts the generic NBTBase tags in the list to elements for the collection. - * Chances are you will need to cast the NBTBase tag to whichever NBT tag type you are expecting in order to - * access the appropriate getter method. - * @return A Collection containing the elements stored in the given NBTTagList. Can be empty, but not null. - * @throws ClassCastException If the tags are not of the expected type. - */ - @SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here. - public static Collection NBTToList(NBTTagList tagList, Function function){ - // Uses an ArrayList to guarantee iteration order, and also to permit duplicate elements (which are - // perfectly reasonable in this context). - Collection list = new ArrayList(); - // The original tag list should remain unchanged, hence the copy. - NBTTagList tagList2 = (NBTTagList)tagList.copy(); - - while(!tagList2.isEmpty()){ - NBTBase tag = tagList2.removeTag(0); - // Why oh why is NBTTagList not parametrised? It even has a tagType field, so it must know! - try{ - list.add(function.apply((T)tag)); - }catch (ClassCastException e){ - Wizardry.logger.error( - "Error when reading list from NBT: unexpected tag type " + NBTBase.NBT_TYPES[tag.getId()], e); - } - } - - return list; - - } - - /** Removes the UUID with the given key from the given NBT tag, if any. Why this doesn't exist in vanilla I have - * no idea. */ - public static void removeUniqueId(NBTTagCompound tag, String key){ - tag.removeTag(key + "Most"); - tag.removeTag(key + "Least"); - } - - // TODO: Backport: It has recently become apparent that storing UUIDs as strings is not good practice, so backport - // these two - // methods to 1.7.10 and replace tag.setUniqueId and tag.getUniqueId with their respective contents from 1.10.2. - - /** - * Returns an NBTTagCompound which contains only the given UUID, stored using - * {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists. - */ - public static NBTTagCompound UUIDtoTagCompound(UUID id){ - NBTTagCompound tag = new NBTTagCompound(); - tag.setUniqueId("uuid", id); - return tag; - } - - /** - * Wrapper for {@link NBTTagCompound#getUniqueId(String)} which converts an NBTTagCompound directly to a UUID. - * Intended to be used as the inverse of {@link WizardryUtilities#UUIDtoTagCompound(UUID)}. - */ - public static UUID tagCompoundToUUID(NBTTagCompound tag){ - return tag.getUniqueId("uuid"); - } - - // SECTION Ally Designation System - // =============================================================================================================== - - /** - * Returns whether the given target can be attacked by the given attacker. It is up to the caller of this method to - * work out what this means; it doesn't necessarily mean the target is completely immune (for example, revenge - * targeting might reasonably bypass this). This method is intended for use where the damage is indirect and/or - * unavoidable; direct attacks should not check this method. Currently this means the following situations check - * this method: - *

    - * - AI targeting for summoned creatures
    - * - AI targeting for mind-controlled creatures
    - * - Constructs with an area of effect
    - * - Instantaneous spells with an area of effect around the caster (e.g. forest's curse, thunderstorm)
    - * - Any lightning chaining effects
    - * - Any projectiles which seek targets - *

    - * Also note that the friendly fire option is dealt with in the event handler. This method acts as a sort of wrapper - * for all the ADS stuff in {@link WizardData}; more details about the ally designation system can be found there. - * - * @param attacker The entity that cast the spell originally - * @param target The entity being attacked - * - * @return False under any of the following circumstances, true otherwise: - *

    - * - The target is null - *

    - * - The target is the attacker (this isn't as stupid as it sounds - anything with an AoE might cause this - * to be true, as can summoned creatures) - *

    - * - The target and the attacker are both players and the target is an ally of the attacker (but the - * attacker need not be an ally of the target) - *

    - * - The target is a creature that was summoned/controlled by the attacker or by an ally of the attacker. - *

    - * As of wizardry 4.1.2, this method now returns true instead of false if the attacker is null. This - * is because in the vast majority of cases, it makes more sense this way: if a construct has no caster, it - * should affect all entities; if a minion has no caster is should target all entities; etc. - */ - public static boolean isValidTarget(Entity attacker, Entity target){ - - // Always return true if the attacker is null - if(attacker == null) return true; - - // Always return false if the target is null - if(target == null) return false; - - // Tests whether the target is the attacker - if(target == attacker) return false; - - // Tests whether the target is a creature that was summoned by the attacker - if(target instanceof ISummonedCreature && ((ISummonedCreature)target).getCaster() == attacker){ - return false; - } - - // Tests whether the target is a creature that was mind controlled by the attacker - if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ - - NBTTagCompound entityNBT = target.getEntityData(); - - if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ - if(attacker == WizardryUtilities.getEntityByUUID(target.world, - entityNBT.getUniqueId(MindControl.NBT_KEY))){ - return false; - } - } - } - - // Ally section - if(attacker instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) != null){ - - if(target instanceof EntityPlayer){ - // Tests whether the target is an ally of the attacker - if(WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)target)){ - return false; - } - - }else if(target instanceof ISummonedCreature){ - // Tests whether the target is a creature that was summoned by an ally of the attacker - if(((ISummonedCreature)target).getCaster() instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) - .isPlayerAlly((EntityPlayer)((ISummonedCreature)target).getCaster())){ - return false; - } - - }else if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){ - // Tests whether the target is a creature that was mind controlled by an ally of the attacker - NBTTagCompound entityNBT = target.getEntityData(); - - if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){ - - Entity controller = WizardryUtilities.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); - - if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){ - return false; - } - } - } - } - - return true; - } - - /** Helper method for testing if the second player is an ally of the first player. Makes the code neater. */ - public static boolean isPlayerAlly(EntityPlayer allyOf, EntityPlayer possibleAlly){ - - WizardData properties = WizardData.get(allyOf); - - if(properties != null && properties.isPlayerAlly(possibleAlly)) return true; - - return false; - } - - // SECTION Loot and Weighting - // =============================================================================================================== - - /** - * See {@link WizardryUtilities#getStandardWeightedRandomSpellId(Random, boolean)}. nonContinuous defaults to false. - */ - public static int getStandardWeightedRandomSpellId(Random random){ - return getStandardWeightedRandomSpellId(random, false); - } - - /** - * Helper method which gets a spell id according to the standard weighting. The tier is a weighted random value; the - * actual spell within that tier is completely random. Will not return the id of a spell which has been disabled in - * the config. This is for simple stuff like chests and drops; more complex generators like wizard trades don't use - * this method. - *

    - * For reference, the standard weighting is as follows: Basic: 60%, Apprentice: 25%, Advanced: 10%, Master: 5% - * - * @param random An instance of {@link Random} to use for RNG - * @param nonContinuous Whether the spells must be non-continuous (used for scrolls) - * @return A random spell id number - */ - public static int getStandardWeightedRandomSpellId(Random random, boolean nonContinuous){ - - Tier tier = Tier.getWeightedRandomTier(random); - - List spells = Spell.getSpells(new Spell.TierElementFilter(tier, null)); - if(nonContinuous) spells.retainAll(Spell.getSpells(Spell.nonContinuousSpells)); - - // Ensures the tier chosen actually has spells in it, and if not uses BASIC instead. - if(spells.isEmpty()){ - spells = Spell.getSpells(new Spell.TierElementFilter(Tier.BASIC, null)); - if(nonContinuous) spells.retainAll(Spell.getSpells(Spell.nonContinuousSpells)); - } - - // Finds a random spell in the list and returns its id. - return spells.get(random.nextInt(spells.size())).id(); - } - - // TODO: These methods need a rethink. What are we trying to achieve with them? Should each use case look in the - // same pool of items? For example, might we (or someone else) want to have a wand which can generate in chests, but - // is not used by wizards? - - // I reckon this should be strictly for cases where we only ever want the standard wand set, i.e. wizards' gear, - // etc. - - /** - * Helper method to return the appropriate armour item based on element and slot. As of Wizardry 2.1, this uses the - * immutable map stored in {@link WizardryItems#ARMOUR_MAP}. Currently used to iterate through armour for - * registering charging recipes and for chest generation. - * - * @param element The EnumElement of the armour required. Null will be converted to {@link Element#MAGIC}. - * @param slot EntityEquipmentSlot of the armour piece required - * @return The armour item which corresponds to the given element and slot, or null if no such item exists. - * @throws IllegalArgumentException if the given slot is not an armour slot. - */ - public static Item getArmour(Element element, EntityEquipmentSlot slot){ - if(slot == null || slot.getSlotType() != Type.ARMOR) - throw new IllegalArgumentException("Must be a valid armour slot"); - if(element == null) element = Element.MAGIC; - return WizardryItems.ARMOUR_MAP.get(ImmutablePair.of(slot, element)); - } - - /** - * Helper method to return the appropriate wand based on tier and element.As of Wizardry 2.1, this uses the - * immutable map stored in {@link WizardryItems#WAND_MAP}. Currently used in the packet handler for upgrading wands, - * for chest generation and to iterate through wands for charging recipes. - * - * @param tier The tier of the wand required. - * @param element The element of the wand required. Null will be converted to {@link Element#MAGIC}. - * @return The wand item which corresponds to the given element and slot, or null if no such item exists. - * @throws NullPointerException if the given tier is null. - */ - public static Item getWand(Tier tier, Element element){ - if(tier == null) throw new NullPointerException("The given tier cannot be null."); - if(element == null) element = Element.MAGIC; - return WizardryItems.WAND_MAP.get(ImmutablePair.of(tier, element)); - } } diff --git a/src/main/java/electroblob/wizardry/worldgen/MossifierTemplateProcessor.java b/src/main/java/electroblob/wizardry/worldgen/MossifierTemplateProcessor.java new file mode 100644 index 00000000..fab35b0d --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/MossifierTemplateProcessor.java @@ -0,0 +1,51 @@ +package electroblob.wizardry.worldgen; + +import net.minecraft.block.BlockStoneBrick; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.Template; + +import javax.annotation.Nullable; + +/** Structure template processor that randomly 'mossifies' cobblestone and stone bricks in the structure. This is done + * using weighting so there is more moss at the bottom, making it look more natural. */ +// Behold, the ACME Mossifier 3000! (Patent pending) +public class MossifierTemplateProcessor implements ITemplateProcessor { + + private final float mossiness; + private final float heightWeight; + private final int groundLevel; + + /** + * Creates a new {@code MossifierTemplateProcessor} with the given parameters. + * @param mossiness The chance for each block in a given layer to be mossified. + * @param heightWeight The amount by which mossiness reduces for each subsequent level upwards. + * @param groundLevel The ground level for the structure, at which height is taken to be zero for the purposes of + * calculating mossiness. + */ + public MossifierTemplateProcessor(float mossiness, float heightWeight, int groundLevel){ + this.mossiness = mossiness; + this.heightWeight = heightWeight; + this.groundLevel = groundLevel; + } + + @Nullable + @Override + public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){ + + float chance = mossiness - heightWeight * (pos.getY() - groundLevel); + + if(world.rand.nextFloat() < chance){ + if(info.blockState.getBlock() == Blocks.COBBLESTONE){ + return new Template.BlockInfo(info.pos, Blocks.MOSSY_COBBLESTONE.getDefaultState(), info.tileentityData); + }else if(info.blockState.getBlock() == Blocks.STONEBRICK){ + return new Template.BlockInfo(info.pos, Blocks.STONEBRICK.getDefaultState() + .withProperty(BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.MOSSY), info.tileentityData); + } + } + + return info; + } +} diff --git a/src/main/java/electroblob/wizardry/worldgen/MultiTemplateProcessor.java b/src/main/java/electroblob/wizardry/worldgen/MultiTemplateProcessor.java new file mode 100644 index 00000000..791ba318 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/MultiTemplateProcessor.java @@ -0,0 +1,39 @@ +package electroblob.wizardry.worldgen; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.Template; + +import javax.annotation.Nullable; + +/** Structure template processor that allows multiple processors to be run in order. */ +public class MultiTemplateProcessor implements ITemplateProcessor { + + private final ITemplateProcessor[] processors; + private final boolean stopWhenNull; + + /** + * Creates a new {@code MultiTemplateProcessor} which applies the given processors in order. + * @param stopWhenNull True to skip any remaining processors in the sequence if one of them returns null, false to + * process them all regardless. If this is false, you should ensure all the given processors + * accept null {@link net.minecraft.world.gen.structure.template.Template.BlockInfo} arguments. + * @param processors The processors to be run, in order (i.e. the first one given will be applied first). + */ + public MultiTemplateProcessor(boolean stopWhenNull, ITemplateProcessor... processors){ + this.processors = processors; + this.stopWhenNull = stopWhenNull; + } + + @Nullable + @Override + public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){ + + for(ITemplateProcessor processor : processors){ + info = processor.processBlock(world, pos, info); + if(stopWhenNull && info == null) break; + } + + return info; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java b/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java new file mode 100644 index 00000000..a900c929 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java @@ -0,0 +1,90 @@ +package electroblob.wizardry.worldgen; + +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.block.Block; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.BlockWoodSlab; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.Template; + +import javax.annotation.Nullable; +import java.util.EnumMap; + +/** Structure template processor that switches all wood in the structure to a certain given wood type. */ +public class WoodTypeTemplateProcessor implements ITemplateProcessor { + + private final BlockPlanks.EnumType woodType; + + private final EnumMap DOORS; + private final EnumMap STAIRS; + private final EnumMap FENCES; + private final EnumMap FENCE_GATES; + + /** + * Creates a new {@code WoodTypeTemplateProcessor} of the given type. + * @param woodType The wood type to be used. + */ + public WoodTypeTemplateProcessor(BlockPlanks.EnumType woodType){ + + this.woodType = woodType; + + DOORS = new EnumMap<>(BlockPlanks.EnumType.class); + DOORS.put(BlockPlanks.EnumType.OAK, Blocks.OAK_DOOR); + DOORS.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_DOOR); + DOORS.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_DOOR); + DOORS.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_DOOR); + DOORS.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_DOOR); + DOORS.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_DOOR); + + STAIRS = new EnumMap<>(BlockPlanks.EnumType.class); + STAIRS.put(BlockPlanks.EnumType.OAK, Blocks.OAK_STAIRS); + STAIRS.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_STAIRS); + STAIRS.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_STAIRS); + STAIRS.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_STAIRS); + STAIRS.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_STAIRS); + STAIRS.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_STAIRS); + + FENCES = new EnumMap<>(BlockPlanks.EnumType.class); + FENCES.put(BlockPlanks.EnumType.OAK, Blocks.OAK_FENCE); + FENCES.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_FENCE); + FENCES.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_FENCE); + FENCES.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_FENCE); + FENCES.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_FENCE); + FENCES.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_FENCE); + + FENCE_GATES = new EnumMap<>(BlockPlanks.EnumType.class); + FENCE_GATES.put(BlockPlanks.EnumType.OAK, Blocks.OAK_FENCE_GATE); + FENCE_GATES.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_FENCE_GATE); + FENCE_GATES.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_FENCE_GATE); + FENCE_GATES.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_FENCE_GATE); + FENCE_GATES.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_FENCE_GATE); + FENCE_GATES.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_FENCE_GATE); + } + + @Nullable + @Override + public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){ + + // Why do these each have their own property key? + if(info.blockState.getBlock() instanceof BlockPlanks){ + return new Template.BlockInfo(info.pos, info.blockState.withProperty(BlockPlanks.VARIANT, woodType), info.tileentityData); + }else if(info.blockState.getBlock() instanceof BlockWoodSlab){ + return new Template.BlockInfo(info.pos, info.blockState.withProperty(BlockWoodSlab.VARIANT, woodType), info.tileentityData); + // This is a mess, no wonder the flattening happened + }else if(DOORS.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(DOORS.get(woodType), info.blockState), info.tileentityData); + }else if(STAIRS.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(STAIRS.get(woodType), info.blockState), info.tileentityData); + }else if(FENCES.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(FENCES.get(woodType), info.blockState), info.tileentityData); + }else if(FENCE_GATES.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(FENCE_GATES.get(woodType), info.blockState), info.tileentityData); + } + + return info; + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalFlower.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalFlower.java new file mode 100644 index 00000000..8de076b8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalFlower.java @@ -0,0 +1,61 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.primitives.Ints; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryBlocks; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.gen.IChunkGenerator; +import net.minecraftforge.fml.common.IWorldGenerator; + +import java.util.Random; + +public class WorldGenCrystalFlower implements IWorldGenerator { + + @Override + public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){ + + if(Ints.contains(Wizardry.settings.flowerDimensions, world.provider.getDimension())){ + this.generatePlant(WizardryBlocks.crystal_flower.getDefaultState(), world, random, 8 + chunkX * 16, 8 + chunkZ * 16, 2, 20); + } + } + + /** + * Generates the specified plant randomly throughout the world. + * + * @param state The plant block + * @param world The world + * @param random A instance of {@code Random} to use + * @param x The x coordinate of the first block in the chunk + * @param z The y coordinate 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); + } + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalOre.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalOre.java new file mode 100644 index 00000000..151b3776 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenCrystalOre.java @@ -0,0 +1,61 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.primitives.Ints; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryBlocks; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.gen.IChunkGenerator; +import net.minecraft.world.gen.feature.WorldGenMinable; +import net.minecraftforge.fml.common.IWorldGenerator; + +import java.util.Random; + +public class WorldGenCrystalOre implements IWorldGenerator { + + @Override + public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){ + + if(Ints.contains(Wizardry.settings.oreDimensions, world.provider.getDimension())){ + this.addOreSpawn(WizardryBlocks.crystal_ore.getDefaultState(), world, random, chunkX * 16, chunkZ * 16, 16, 16, 5, 7, 5, 30); + } + } + + /** + * 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 state The Block to spawn + * @param world The World to spawn in + * @param random A Random object for retrieving random positions within the world to spawn the Block + * @param blockXPos An int for passing the X-Coordinate for the Generation method + * @param blockZPos An int for passing the Z-Coordinate for the Generation method + * @param maxX An int for setting the maximum X-Coordinate values for spawning on the X-Axis on a Per-Chunk basis + * @param maxZ An int for setting the maximum Z-Coordinate values for spawning on the Z-Axis on a Per-Chunk basis + * @param maxVeinSize An int for setting the maximum size of a vein + * @param chancesToSpawn An int for the Number of chances available for the Block to spawn per-chunk + * @param minY An int for the minimum Y-Coordinate height at which this block may spawn + * @param maxY 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); + // N.B. This method applies the anti-cascading-lag offset itself + (new WorldGenMinable(state, maxVeinSize)).generate(world, random, new BlockPos(posX, posY, posZ)); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java new file mode 100644 index 00000000..e8d6d1d2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java @@ -0,0 +1,101 @@ +package electroblob.wizardry.worldgen; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockRunestone; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; +import net.minecraft.init.Blocks; +import net.minecraft.tileentity.MobSpawnerBaseLogic; +import net.minecraft.tileentity.TileEntityMobSpawner; +import net.minecraft.util.Mirror; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.PlacementSettings; +import net.minecraft.world.gen.structure.template.Template; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Random; + +public class WorldGenObelisk extends WorldGenSurfaceStructure { + + private static final String SPAWNER_DATA_BLOCK_TAG = "spawner"; + + private static final EnumMap MOB_TYPES = new EnumMap<>(Element.class); + + static { + MOB_TYPES.put(Element.FIRE, new ResourceLocation(Wizardry.MODID, "blaze_minion")); + MOB_TYPES.put(Element.ICE, new ResourceLocation(Wizardry.MODID, "ice_wraith")); + MOB_TYPES.put(Element.LIGHTNING, new ResourceLocation(Wizardry.MODID, "lightning_wraith")); + MOB_TYPES.put(Element.NECROMANCY, new ResourceLocation(Wizardry.MODID, "wither_skeleton_minion")); + MOB_TYPES.put(Element.EARTH, new ResourceLocation(Wizardry.MODID, "spider_minion")); + MOB_TYPES.put(Element.SORCERY, new ResourceLocation(Wizardry.MODID, "vex_minion")); + MOB_TYPES.put(Element.HEALING, new ResourceLocation(Wizardry.MODID, "husk_minion")); + } + + @Override + public String getStructureName(){ + return "obelisk"; + } + + @Override + public long getRandomSeedModifier(){ + return 19348242L; + } + + @Override + public Mirror[] getValidMirrors(){ + return new Mirror[]{Mirror.NONE}; // It's symmetrical so there's no point mirroring it + } + + @Override + public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){ + return ArrayUtils.contains(Wizardry.settings.obeliskDimensions, world.provider.getDimension()) + && Wizardry.settings.obeliskRarity > 0 && random.nextInt(Wizardry.settings.obeliskRarity) == 0; + } + + @Override + public ResourceLocation getStructureFile(Random random){ + return Wizardry.settings.obeliskFiles[random.nextInt(Wizardry.settings.obeliskFiles.length)]; + } + + @Override + public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){ + + final Element element = Element.values()[1 + random.nextInt(Element.values().length-1)]; + + ITemplateProcessor processor = (w, p, i) -> i.blockState.getBlock() instanceof BlockRunestone ? new Template.BlockInfo( + i.pos, i.blockState.withProperty(BlockRunestone.ELEMENT, element), i.tileentityData) : i; + + template.addBlocksToWorld(world, origin, processor, settings, 2); + + WizardryAntiqueAtlasIntegration.markObelisk(world, origin.getX(), origin.getZ()); + + // Mob spawner + Map dataBlocks = template.getDataBlocks(origin, settings); + + for(Map.Entry entry : dataBlocks.entrySet()){ + + if(entry.getValue().equals(SPAWNER_DATA_BLOCK_TAG)){ + + world.setBlockState(entry.getKey(), Blocks.MOB_SPAWNER.getDefaultState()); + + if(world.getTileEntity(entry.getKey()) instanceof TileEntityMobSpawner){ + + MobSpawnerBaseLogic spawnerLogic = ((TileEntityMobSpawner)world.getTileEntity(entry.getKey())).getSpawnerBaseLogic(); + spawnerLogic.setEntityId(MOB_TYPES.get(element)); + + }else{ + Wizardry.logger.info("Tried to set the mob spawned by an obelisk, but the expected TileEntityMobSpawner was not present"); + } + + }else{ + // This probably shouldn't happen... + Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile); + } + } + } +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java new file mode 100644 index 00000000..076a811d --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java @@ -0,0 +1,95 @@ +package electroblob.wizardry.worldgen; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockPedestal; +import electroblob.wizardry.block.BlockRunestone; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.spell.ArcaneLock; +import electroblob.wizardry.tileentity.TileEntityShrineCore; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.PlacementSettings; +import net.minecraft.world.gen.structure.template.Template; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +public class WorldGenShrine extends WorldGenSurfaceStructure { + + private static final String CORE_DATA_BLOCK_TAG = "core"; + + @Override + public String getStructureName(){ + return "shrine"; + } + + @Override + public long getRandomSeedModifier(){ + return 17502749L; + } + + @Override + public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){ + return ArrayUtils.contains(Wizardry.settings.shrineDimensions, world.provider.getDimension()) + && Wizardry.settings.shrineRarity > 0 && random.nextInt(Wizardry.settings.shrineRarity) == 0; + } + + @Override + public ResourceLocation getStructureFile(Random random){ + return Wizardry.settings.shrineFiles[random.nextInt(Wizardry.settings.shrineFiles.length)]; + } + + @Override + public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){ + + final Element element = Element.values()[1 + random.nextInt(Element.values().length-1)]; + + ITemplateProcessor processor = (w, p, i) -> i.blockState.getBlock() instanceof BlockRunestone ? new Template.BlockInfo( + i.pos, i.blockState.withProperty(BlockRunestone.ELEMENT, element), i.tileentityData) : i; + + template.addBlocksToWorld(world, origin, processor, settings, 2); + + WizardryAntiqueAtlasIntegration.markShrine(world, origin.getX(), origin.getZ()); + + // Shrine core + Map dataBlocks = template.getDataBlocks(origin, settings); + + for(Map.Entry entry : dataBlocks.entrySet()){ + + if(entry.getValue().equals(CORE_DATA_BLOCK_TAG)){ + // This bit could have been done with a template processor, but we also need to link the chest and lock it + world.setBlockState(entry.getKey(), WizardryBlocks.runestone_pedestal.getDefaultState() + .withProperty(BlockPedestal.ELEMENT, element).withProperty(BlockPedestal.NATURAL, true)); + + TileEntity core = world.getTileEntity(entry.getKey()); + TileEntity container = world.getTileEntity(entry.getKey().up()); + + if(container != null){ + + container.getTileData().setUniqueId(ArcaneLock.NBT_KEY, new UUID(0, 0)); // Nil UUID + + if(core instanceof TileEntityShrineCore){ + ((TileEntityShrineCore)core).linkContainer(container); + }else{ + Wizardry.logger.info("What?!"); + } + + }else{ + Wizardry.logger.info("Expected chest or other container at {} in structure {}, found no tile entity", entry.getKey(), structureFile); + } + + }else{ + // This probably shouldn't happen... + Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java new file mode 100644 index 00000000..a2b58c31 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java @@ -0,0 +1,450 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.math.Quantiles; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.util.WizardryUtilities; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.block.Block; +import net.minecraft.block.BlockLeaves; +import net.minecraft.block.BlockLog; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.Mirror; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.*; +import net.minecraft.world.World; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.gen.IChunkGenerator; +import net.minecraft.world.gen.structure.MapGenStructureData; +import net.minecraft.world.gen.structure.StructureBoundingBox; +import net.minecraft.world.gen.structure.template.PlacementSettings; +import net.minecraft.world.gen.structure.template.Template; +import net.minecraftforge.common.BiomeDictionary; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fml.common.IWorldGenerator; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import javax.annotation.Nullable; +import java.util.*; + +/** Base structure generation class which handles code common to all wizardry's above-ground structures, such as + * calculating the median ground level. */ +@Mod.EventBusSubscriber +public abstract class WorldGenSurfaceStructure implements IWorldGenerator { + + /** The maximum fraction of the area where a structure is to be spawned that may be covered by liquid. */ + private static final float MAX_LIQUID_FRACTION = 0.4f; + + /** Static map used to store all structure generators for the purpose of advancements. */ + private static final Map generators = new HashMap<>(); + + /** A random instance used solely for the purpose of emulating the world generation to predict locations. */ + private final Random random; + + private World world; + + private MapGenStructureData structureData; + + /** Stores the bounding boxes of all structures of this type that have been generated so far. */ + protected final Long2ObjectMap structureMap = new Long2ObjectOpenHashMap<>(1024); + + public WorldGenSurfaceStructure(){ + random = new Random(); // Seed will be set later + generators.put(this.getStructureName(), this); + } + + /** Returns a constant (but unique) long value used to change the random seed so that each generator produces a + * different sequence of numbers. Without this, all wizardry's generators attempt to generate in the same chunks + * when set to the same rarity. */ + public abstract long getRandomSeedModifier(); + + /** Pre-check for whether the structure can generate. Usually this is just used for randomisation so that + * calculations are only performed for chunks that will generate a structure; most placement-specific stuff + * can just be done using a check inside {@link WorldGenSurfaceStructure#spawnStructure(Random, World, BlockPos, Template, PlacementSettings, ResourceLocation)} */ + public abstract boolean canGenerate(Random random, World world, int chunkX, int chunkZ); + + /** Called each time the structure is generated to get a structure file to use. */ + public abstract ResourceLocation getStructureFile(Random random); + + /** Returns the name of this structure type, which is used as an identifier in the world save file and for + * advancement JSON files. */ + public abstract String getStructureName(); + + /** + * Spawns the structure at the given origin with the given placement settings. + * @param random A {@code Random} instance to use for any further parameters that need randomising. + * @param world The world to spawn the structure in. + * @param origin The origin coordinates of the structure in the world, pre-adjusted for floor height and rotation + * to avoid floating structures and minimise cascading worldgen lag. + * @param template The template to be generated. + * @param settings The placement settings for the structure. + * @param structureFile The location of the chosen structure file, for logging purposes. + */ + public abstract void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile); + + /** Specifies valid rotation values for the structure. By default this returns all rotations. */ + public Rotation[] getValidRotations(){ + return Rotation.values(); + } + + /** Specifies valid rotation values for the structure. By default this returns an array of {@code Mirror.LEFT_RIGHT} + * and {@code Mirror.NONE}. */ + public Mirror[] getValidMirrors(){ + return new Mirror[]{Mirror.NONE, Mirror.LEFT_RIGHT}; + } + + /** + * Finds a random position within the given chunk at which the given template may be generated. + * In an effort to make structure rarity more uniform, they now get a number of tries to spawn in each + * randomly-selected chunk so they have a better chance of avoiding stuff that might be in the way (cliffs, + * villages, lakes, etc.). + *

    + * This method calculates the median floor height to ensure that sudden changes in level are ignored and the + * structure is always spawned at the same level as the majority of the underlying floor. Trees are also ignored + * when determining floor level, so that forests don't impede structure spawning. + * + * @param template The template to be generated + * @param settings The placement settings for the structure template + * @param random A random instance to use. This should have had its seed set according to the world seed and chunk + * coordinates. + * @param world The world in which to spawn the structure + * @param chunkX The x-coordinate of the chunk being populated + * @param chunkZ The z-coordinate of the chunk being populated + * @return The coordinates of the position found, or null if no suitable position was found. The returned + * {@code BlockPos} is always the northwest corner of the structure, and the y-coordinate is that of the + * uppermost block at those (x, z) coordinates. If the structure is being rotated this needs to be altered using + * {@link Template#getZeroPositionWithTransform(BlockPos, Mirror, Rotation)} before it can be fed into the template + * spawning methods. + */ + @Nullable + protected BlockPos findValidPosition(Template template, PlacementSettings settings, Random random, World world, + int chunkX, int chunkZ){ + + // Offset by (8, 8) to minimise cascading worldgen lag + // See https://www.reddit.com/r/feedthebeast/cowmments/5x0twz/investigating_extreme_worldgen_lag/?ref=share&ref_source=embed&utm_content=title&utm_medium=post_embed&utm_name=c07cbb545f74487793783012794733d8&utm_source=embedly&utm_term=5x0twz + // Multiplying and left-shifting are identical but it's good practice to bitshift here I guess + BlockPos origin = new BlockPos(8 + (chunkX << 4) + random.nextInt(16), 0, 8 + (chunkZ << 4) + random.nextInt(16)); + + BlockPos size = template.transformedSize(settings.getRotation()); + // Estimate a starting height for searching for the floor + BlockPos centre = world.getTopSolidOrLiquidBlock(new BlockPos(origin.add(size.getX()/2, 0, size.getZ()/2))); + Integer startingHeight = WizardryUtilities.getNearestSurface(world, centre, EnumFacing.UP, 32, true, + WizardryUtilities.SurfaceCriteria.COLLIDABLE_IGNORING_TREES); + + if(startingHeight == null) return null; + + if(Wizardry.settings.fastWorldgen){ + BlockPos result = origin.up(startingHeight); + // Fast worldgen doesn't check for water, instead it checks the biome like vanilla, which is crude but fast + return BiomeDictionary.hasType(world.getBiome(result), BiomeDictionary.Type.WATER) ? null : result; + } + + int[] floorHeights = new int[size.getX() * size.getZ()]; + + int liquidCount = 0; + + for(int i = 0; i < floorHeights.length; i++){ + // 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. + BlockPos pos = origin.add(i / size.getZ(), 0, i % size.getZ()); + Integer floor = WizardryUtilities.getNearestSurface(world, pos.up(startingHeight), EnumFacing.UP, 32, true, + WizardryUtilities.SurfaceCriteria.COLLIDABLE_IGNORING_TREES); + floorHeights[i] = floor == null ? 0 : floor; // Very unlikely that floor is null + // ^ That method gets the top solid block. Most non-solid blocks are ok to have around the structure, + // with the exception of liquids, so if there are too many the position is deemed unsuitable. + if(world.getBlockState(pos.up(floorHeights[i])).getMaterial().isLiquid()) liquidCount++; + if(liquidCount > floorHeights.length * MAX_LIQUID_FRACTION) return null; + } + + // Get the median floor height (rather than the mean, that way cliffs should have no effect) + int medianFloorHeight = MathHelper.floor(Quantiles.median().compute(floorHeights)); + + // Now we know the y level of the base of the structure, we can check for stuff in the way + // A structure is deemed to have stuff in the way if the floor level at any of the (x, z) positions it + // occupies differs from the base y level by more than its distance from the centre plus a constant. + // In practical terms, this means structures can't spawn on steep slopes or inside cave mouths or buildings. + + for(int i = 0; i < floorHeights.length; i++){ + int orthogonalDist = Math.max(Math.abs(i / size.getZ() - size.getX()/2), Math.abs(i % size.getZ() - size.getZ()/2)); + if(Math.abs(floorHeights[i] - medianFloorHeight) > Math.max(2, orthogonalDist)) return null; // Something is in the way + } + + return origin.up(medianFloorHeight - 1); + } + + @Override + public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){ + + if(!world.getWorldInfo().isMapFeaturesEnabled()) return; + + // Don't need to worry about overflows because they'll just wrap around, which is fine for this purpose + random.setSeed(random.nextLong() + getRandomSeedModifier()); + + initializeStructureData(world); // Load the data from the save file if it isn't already loaded + + if(canGenerate(random, world, chunkX, chunkZ)){ + + ResourceLocation structureFile = getStructureFile(random); + + Template template = world.getSaveHandler().getStructureTemplateManager().getTemplate( + world.getMinecraftServer(), structureFile); + + Rotation[] rotations = getValidRotations(); + Mirror[] mirrors = getValidMirrors(); + + PlacementSettings settings = new PlacementSettings() + .setRotation(rotations[random.nextInt(rotations.length)]) + .setMirror(mirrors[random.nextInt(mirrors.length)]); + + int triesLeft = 10; + + BlockPos origin; + + do { + origin = findValidPosition(template, settings, random, world, chunkX, chunkZ); + triesLeft--; + }while(triesLeft > 0 && origin != null); + + if(origin == null) return; + + // Need to subtract 1 from each coordinate since both corners are inclusive + StructureBoundingBox box = new StructureBoundingBox(origin, origin.add(template.transformedSize(settings.getRotation())).add(-1, -1, -1)); + + // DEBUG +// world.setBlockState(new BlockPos(box.minX, box.minY, box.minZ), Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, EnumDyeColor.MAGENTA)); +// world.setBlockState(new BlockPos(box.maxX, box.maxY, box.maxZ), Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, EnumDyeColor.MAGENTA)); + + if(!Wizardry.settings.fastWorldgen){ + for(WorldGenSurfaceStructure generator : generators.values()){ + StructureBoundingBox otherbox = generator.structureMap.get(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4)); + if(otherbox != null && otherbox.intersectsWith(box)) return; + } + } + + settings.setBoundingBox(box); + + // PlacementSettings rotates and mirrors the structure around the origin, keeping the origin in the same + // place in the world. This means the structure can be rotated/mirrored into the 8 block border, undoing all + // our hard work to try and prevent cascading worldgen lag! + + // To properly minimise cascading worldgen lag, the method below returns the position where the corner needs + // to be such that the original structure's NW (-X, -Z) corner is at the origin. + origin = template.getZeroPositionWithTransform(origin, settings.getMirror(), settings.getRotation()); + + spawnStructure(random, world, origin, template, settings, structureFile); + + if(!Wizardry.settings.fastWorldgen) removeFloatingTrees(world, box); + + structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox()); + + NBTTagCompound tag = new NBTTagCompound(); + tag.setInteger("ChunkX", chunkX); + tag.setInteger("ChunkZ", chunkZ); + tag.setTag("BB", settings.getBoundingBox().toNBTTagIntArray()); + structureData.writeInstance(tag, chunkX, chunkZ); + structureData.markDirty(); + } + } + + /** Copied from MapGenStructure. Unlike most NBT loading, this is lazy - it only gets read from NBT when requested. */ + protected void initializeStructureData(World world){ + + // If the world that was last generated is not this world, load the data for the new world + // This is a bit of a dirty hack, it would be better if we had separate instances per-world but... effort... + // For now it works, maybe one day I'll improve it + if(world != this.world){ + + this.world = world; + + this.structureData = (MapGenStructureData)world.getPerWorldStorage().getOrLoadData(MapGenStructureData.class, this.getStructureName()); + + // This has to be cleared or worlds will interfere with each other! + // Vanilla doesn't have to do this because each world has a separate ChunkGenerator which stores MapGenBase + // instances + this.structureMap.clear(); + + if(this.structureData == null){ + + this.structureData = new MapGenStructureData(this.getStructureName()); + world.getPerWorldStorage().setData(this.getStructureName(), this.structureData); + + }else{ + + NBTTagCompound nbt = this.structureData.getTagCompound(); + + for(String s : nbt.getKeySet()){ + + NBTBase nbtbase = nbt.getTag(s); + + if(nbtbase.getId() == Constants.NBT.TAG_COMPOUND){ + + NBTTagCompound entry = (NBTTagCompound)nbtbase; + + if(entry.hasKey("ChunkX") && entry.hasKey("ChunkZ") && entry.hasKey("BB")){ + + int i = entry.getInteger("ChunkX"); + int j = entry.getInteger("ChunkZ"); + int[] coords = entry.getIntArray("BB"); + + this.structureMap.put(ChunkPos.asLong(i, j), new StructureBoundingBox(coords)); + } + } + } + } + } + } + + /** Finds and removes any floating bits of tree in and above the given structure bounding box. */ + protected static void removeFloatingTrees(World world, StructureBoundingBox boundingBox){ + + boolean changed = true; + int y = boundingBox.minY; + + // Remove all the logs + + while(changed && y < world.getHeight()){ // I do hope the trees don't reach the world height... + + // Always checks at least the first layer above the bounding box in case the structure cut the rest off + if(y > boundingBox.maxY + 1) changed = false; + + for(int x = boundingBox.minX; x <= boundingBox.maxX; x++){ + for(int z = boundingBox.minZ; z <= boundingBox.maxZ; z++){ + + BlockPos pos = new BlockPos(x, y, z); + + Block block = world.getBlockState(pos).getBlock(); + Block below = world.getBlockState(pos.down()).getBlock(); + + if(block instanceof BlockLog){ + if(below != Blocks.GRASS && below != Blocks.DIRT && !(below instanceof BlockLog) && + !below.isLeaves(world.getBlockState(pos.down()), world, pos.down())){ + world.setBlockToAir(pos); + changed = true; + } + } + } + } + + y++; + } + + // Now update all leaves in the area 16 times to make them decay + + int border = 8; + + List leaves = new ArrayList<>(); + + for(int x = boundingBox.minX - border; x <= boundingBox.maxX + border; x++){ + for(int y1 = boundingBox.minY - border; y1 <= y + border; y1++){ + for(int z = boundingBox.minZ - border; z <= boundingBox.maxZ + border; z++){ + BlockPos pos = new BlockPos(x, y1, z); + if(world.getBlockState(pos).getBlock() instanceof BlockLeaves) leaves.add(pos); + } + } + } + + for(int i=0; i<16; i++){ + leaves.forEach(p -> world.getBlockState(p).getBlock().updateTick(world, p, world.getBlockState(p), null)); + } + + // Finally, remove all the items that were dropped as a result of leaf decay + + AxisAlignedBB box = new AxisAlignedBB(boundingBox.minX, boundingBox.minY, boundingBox.minZ, boundingBox.maxX, y, boundingBox.maxZ).grow(border); + + world.getEntitiesWithinAABB(EntityItem.class, box).forEach(Entity::setDead); + + } + + /** Returns true if the given position is within a structure of this type in the given world, false + * otherwise. This will not work on chunks that are yet to be generated; attempting to do so will print a + * warning to the console. */ + public boolean isInsideStructure(World world, double x, double y, double z){ + + initializeStructureData(world); // Load the data from the save file if it isn't already loaded + + int chunkX = (int)x >> 4; + int chunkZ = (int)z >> 4; + + if(!world.isChunkGeneratedAt(chunkX, chunkZ)){ + Wizardry.logger.warn("Testing whether position ({}, {}, {}) is inside a structure, but that chunk hasn't been generated yet", x, y, z); + return false; + } + + // Vanilla just iterates through the entire structure map, but we can be a little more intelligent + // about it by only testing the chunks near the player (since all the structures are smaller than 32x32) + long[] chunks = {ChunkPos.asLong(chunkX - 1, chunkZ - 1), ChunkPos.asLong(chunkX - 1, chunkZ), ChunkPos.asLong(chunkX - 1, chunkZ + 1), + ChunkPos.asLong(chunkX, chunkZ - 1), ChunkPos.asLong(chunkX, chunkZ), ChunkPos.asLong(chunkX, chunkZ + 1), + ChunkPos.asLong(chunkX + 1, chunkZ - 1), ChunkPos.asLong(chunkX + 1, chunkZ), ChunkPos.asLong(chunkX + 1, chunkZ + 1)}; + + for(long chunkPos : chunks){ + if(structureMap.containsKey(chunkPos) && structureMap.get(chunkPos).isVecInside(new Vec3i(x, y, z))) + return true; + } + + return false; + } + + /** Copied from MapGenMineshaft. The general idea (it seems) is to emulate the world generator's randomisation + * without actually placing any blocks. Presumably mineshafts don't need sub-chunk randomisation? */ + public BlockPos getNearestStructurePos(World world, BlockPos pos, boolean findUnexplored){ + + // TODO: We have a problem here, in that the 'pragmatic' placement algorithm (good as it is) requires + // the chunk to have already been generated, so we can't be sure if a structure actually exists until + // the chunk is actually generated. + + int j = pos.getX() >> 4; + int k = pos.getZ() >> 4; + + for (int l = 0; l <= 1000; ++l) + { + for (int i1 = -l; i1 <= l; ++i1) + { + boolean flag = i1 == -l || i1 == l; + + for (int j1 = -l; j1 <= l; ++j1) + { + boolean flag1 = j1 == -l || j1 == l; + + if (flag || flag1) + { + // TESTME: Is this the same as Forge's per-chunk seeds? (see caller of generate()) + int k1 = j + i1; + int l1 = k + j1; + this.random.setSeed((long)(k1 ^ l1) ^ world.getSeed()); + this.random.nextInt(); + + if(this.canGenerate(this.random, world, k1, l1) && (!findUnexplored || !world.isChunkGeneratedAt(k1, l1))){ + return new BlockPos((k1 << 4) + 8, 64, (l1 << 4) + 8); + } + } + } + } + } + + return null; + } + + /** Returns the world generator with the given name. */ + public static WorldGenSurfaceStructure byName(String name){ + return generators.get(name); + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event){ + if(event.player instanceof EntityPlayerMP && event.player.ticksExisted % 20 == 0){ + WizardryAdvancementTriggers.visit_structure.trigger((EntityPlayerMP)event.player); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java new file mode 100644 index 00000000..64882da9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java @@ -0,0 +1,165 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.collect.ImmutableMap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.living.EntityEvilWizard; +import electroblob.wizardry.entity.living.EntityWizard; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.BlockStainedHardenedClay; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Biomes; +import net.minecraft.init.Blocks; +import net.minecraft.item.EnumDyeColor; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.gen.structure.template.ITemplateProcessor; +import net.minecraft.world.gen.structure.template.PlacementSettings; +import net.minecraft.world.gen.structure.template.Template; +import net.minecraftforge.common.BiomeDictionary; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +public class WorldGenWizardTower extends WorldGenSurfaceStructure { + + // TODO: Add wizard towers to the /locate command + // This requires some careful manipulation of Random objects to replicate the positions exactly for the current + // world. See the end of ChunkGeneratorOverworld for the relevant methods. + + private static final String WIZARD_DATA_BLOCK_TAG = "wizard"; + private static final String EVIL_WIZARD_DATA_BLOCK_TAG = "evil_wizard"; + + private final Map SPECIAL_WALL_BLOCKS; + + public WorldGenWizardTower(){ + // These are initialised here because it's a convenient point after the blocks are registered + SPECIAL_WALL_BLOCKS = ImmutableMap.of( + BiomeDictionary.Type.MESA, Blocks.RED_SANDSTONE.getDefaultState(), + BiomeDictionary.Type.MOUNTAIN, Blocks.STONEBRICK.getDefaultState(), + BiomeDictionary.Type.NETHER, Blocks.NETHER_BRICK.getDefaultState(), + BiomeDictionary.Type.SANDY, Blocks.SANDSTONE.getDefaultState() + ); + } + + @Override + public String getStructureName(){ + return "wizard_tower"; + } + + @Override + public long getRandomSeedModifier(){ + return 10473957L; // Yep, I literally typed 8 digits at random + } + + @Override + public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){ + return ArrayUtils.contains(Wizardry.settings.towerDimensions, world.provider.getDimension()) + && Wizardry.settings.towerRarity > 0 && random.nextInt(Wizardry.settings.towerRarity) == 0; + } + + @Override + public ResourceLocation getStructureFile(Random random){ + return random.nextDouble() < Wizardry.settings.evilWizardChance ? + Wizardry.settings.towerWithChestFiles[random.nextInt(Wizardry.settings.towerWithChestFiles.length)] : + Wizardry.settings.towerFiles[random.nextInt(Wizardry.settings.towerFiles.length)]; + } + + @Override + public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){ + + final EnumDyeColor colour = EnumDyeColor.values()[random.nextInt(EnumDyeColor.values().length)]; + final Biome biome = world.getBiome(origin); + + final IBlockState wallMaterial = SPECIAL_WALL_BLOCKS.keySet().stream().filter(t -> BiomeDictionary.hasType(biome, t)) + .findFirst().map(SPECIAL_WALL_BLOCKS::get).orElse(Blocks.COBBLESTONE.getDefaultState()); + + final float mossiness = getBiomeMossiness(biome); + final BlockPlanks.EnumType woodType = getBiomeWoodVariant(biome); + + final Set blocksPlaced = new HashSet<>(); + + ITemplateProcessor processor = new MultiTemplateProcessor(true, + // Roof colour + (w, p, i) -> i.blockState.getBlock() instanceof BlockStainedHardenedClay ? new Template.BlockInfo( + i.pos, i.blockState.withProperty(BlockStainedHardenedClay.COLOR, colour), i.tileentityData) : i, + // Wall material + (w, p, i) -> i.blockState.getBlock() == Blocks.COBBLESTONE ? new Template.BlockInfo(i.pos, + wallMaterial, i.tileentityData) : i, + // Wood type + new WoodTypeTemplateProcessor(woodType), + // Mossifier + new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1), + // Block recording (the process() method doesn't get called for structure voids) + (w, p, i) -> {if(i.blockState.getBlock() != Blocks.AIR) blocksPlaced.add(p); return i;} + ); + + template.addBlocksToWorld(world, origin, processor, settings, 2); + + WizardryAntiqueAtlasIntegration.markTower(world, origin.getX(), origin.getZ()); + + // Wizard spawning + Map dataBlocks = template.getDataBlocks(origin, settings); + + for(Map.Entry entry : dataBlocks.entrySet()){ + + Vec3d vec = WizardryUtilities.getCentre(entry.getKey()); + + if(entry.getValue().equals(WIZARD_DATA_BLOCK_TAG)){ + + EntityWizard wizard = new EntityWizard(world); + wizard.setLocationAndAngles(vec.x, vec.y, vec.z, 0, 0); + wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null); + wizard.setTowerBlocks(blocksPlaced); + world.spawnEntity(wizard); + + }else if(entry.getValue().equals(EVIL_WIZARD_DATA_BLOCK_TAG)){ + + EntityEvilWizard wizard = new EntityEvilWizard(world); + wizard.setLocationAndAngles(vec.x, vec.y, vec.z, 0, 0); + wizard.hasStructure = true; // Stops it despawning + wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null); + world.spawnEntity(wizard); + + }else{ + // This probably shouldn't happen... + Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile); + } + } + } + + private static float getBiomeMossiness(Biome biome){ + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DENSE)) return 0.7f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) return 0.7f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.WET)) return 0.5f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SWAMP)) return 0.5f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.FOREST)) return 0.3f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.LUSH)) return 0.3f; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DRY)) return 0; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.COLD)) return 0; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DEAD)) return 0; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.WASTELAND)) return 0; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.NETHER)) return 0; + return 0.1f; // Everything else (plains, etc.) has a small amount of moss + } + + private static BlockPlanks.EnumType getBiomeWoodVariant(Biome biome){ + // Unfortunately, I can't check all the wood types with the biome dictionary + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS)) return BlockPlanks.EnumType.SPRUCE; + if(biome == Biomes.BIRCH_FOREST || biome == Biomes.BIRCH_FOREST_HILLS) return BlockPlanks.EnumType.BIRCH; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) return BlockPlanks.EnumType.JUNGLE; + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SAVANNA)) return BlockPlanks.EnumType.ACACIA; + // Not technically a tree type, but I think it fits quite well anyway + if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SPOOKY)) return BlockPlanks.EnumType.DARK_OAK; + // Everything else is oak + return BlockPlanks.EnumType.OAK; + } + +} diff --git a/src/main/resources/assets/ebwizardry/advancements/advanced.json b/src/main/resources/assets/ebwizardry/advancements/advanced.json new file mode 100644 index 00000000..7bc58eb8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/advanced.json @@ -0,0 +1,108 @@ +{ + "display": { + "icon": { + "item": "ebwizardry:advanced_wand" + }, + "title": { + "translate": "advancement.ebwizardry:advanced" + }, + "description": { + "translate": "advancement.ebwizardry:advanced.desc" + } + }, + "parent": "ebwizardry:apprentice", + "criteria": { + "magic": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_wand" + } + ] + } + }, + "fire": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_fire_wand" + } + ] + } + }, + "ice": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_ice_wand" + } + ] + } + }, + "lightning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_lightning_wand" + } + ] + } + }, + "necromancy": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_necromancy_wand" + } + ] + } + }, + "earth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_earth_wand" + } + ] + } + }, + "sorcery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_sorcery_wand" + } + ] + } + }, + "healing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:advanced_healing_wand" + } + ] + } + } + }, + "requirements": [ + [ + "magic", + "fire", + "ice", + "lightning", + "necromancy", + "earth", + "sorcery", + "healing" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json b/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json new file mode 100644 index 00000000..1ad658b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json @@ -0,0 +1,647 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:all_artefacts" + }, + "description": { + "translate": "advancement.ebwizardry:all_artefacts.desc" + }, + "icon": { + "item": "ebwizardry:amulet_resurrection" + }, + "frame": "challenge" + }, + "parent": "ebwizardry:artefact", + "criteria": { + "ring_condensing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_condensing" + } + ] + } + }, + "ring_siphoning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_siphoning" + } + ] + } + }, + "ring_battlemage": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_battlemage" + } + ] + } + }, + "ring_combustion": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_combustion" + } + ] + } + }, + "ring_fire_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_fire_melee" + } + ] + } + }, + "ring_fire_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_fire_biome" + } + ] + } + }, + "ring_disintegration": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_disintegration" + } + ] + } + }, + "ring_ice_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_ice_melee" + } + ] + } + }, + "ring_ice_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_ice_biome" + } + ] + } + }, + "ring_arcane_frost": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_arcane_frost" + } + ] + } + }, + "ring_shattering": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_shattering" + } + ] + } + }, + "ring_lightning_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_lightning_melee" + } + ] + } + }, + "ring_storm": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_storm" + } + ] + } + }, + "ring_seeking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_seeking" + } + ] + } + }, + "ring_hammer": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_hammer" + } + ] + } + }, + "ring_soulbinding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_soulbinding" + } + ] + } + }, + "ring_leeching": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_leeching" + } + ] + } + }, + "ring_necromancy_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_necromancy_melee" + } + ] + } + }, + "ring_mind_control": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_mind_control" + } + ] + } + }, + "ring_poison": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_poison" + } + ] + } + }, + "ring_earth_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_earth_melee" + } + ] + } + }, + "ring_earth_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_earth_biome" + } + ] + } + }, + "ring_full_moon": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_full_moon" + } + ] + } + }, + "ring_extraction": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_extraction" + } + ] + } + }, + "ring_mana_return": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_mana_return" + } + ] + } + }, + "ring_blockwrangler": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_blockwrangler" + } + ] + } + }, + "ring_conjurer": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_conjurer" + } + ] + } + }, + "ring_defender": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_defender" + } + ] + } + }, + "ring_paladin": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_paladin" + } + ] + } + }, + "ring_interdiction": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_interdiction" + } + ] + } + }, + "amulet_arcane_defence": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_arcane_defence" + } + ] + } + }, + "amulet_warding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_warding" + } + ] + } + }, + "amulet_wisdom": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_wisdom" + } + ] + } + }, + "amulet_fire_protection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_fire_protection" + } + ] + } + }, + "amulet_fire_cloaking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_fire_cloaking" + } + ] + } + }, + "amulet_ice_immunity": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_ice_immunity" + } + ] + } + }, + "amulet_ice_protection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_ice_protection" + } + ] + } + }, + "amulet_potential": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_potential" + } + ] + } + }, + "amulet_channeling": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_channeling" + } + ] + } + }, + "amulet_lich": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_lich" + } + ] + } + }, + "amulet_wither_immunity": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_wither_immunity" + } + ] + } + }, + "amulet_glide": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_glide" + } + ] + } + }, + "amulet_banishing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_banishing" + } + ] + } + }, + "amulet_anchoring": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_anchoring" + } + ] + } + }, + "amulet_recovery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_recovery" + } + ] + } + }, + "amulet_transience": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_transience" + } + ] + } + }, + "amulet_resurrection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_resurrection" + } + ] + } + }, + "amulet_auto_shield": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_auto_shield" + } + ] + } + }, + "charm_haggler": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_haggler" + } + ] + } + }, + "charm_experience_tome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_experience_tome" + } + ] + } + }, + "charm_auto_smelt": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_auto_smelt" + } + ] + } + }, + "charm_lava_walking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_lava_walking" + } + ] + } + }, + "charm_storm": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_storm" + } + ] + } + }, + "charm_minion_health": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_minion_health" + } + ] + } + }, + "charm_minion_variants": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_minion_variants" + } + ] + } + }, + "charm_flight": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_flight" + } + ] + } + }, + "charm_growth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_growth" + } + ] + } + }, + "charm_abseiling": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_abseiling" + } + ] + } + }, + "charm_silk_touch": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_silk_touch" + } + ] + } + }, + "charm_stop_time": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_stop_time" + } + ] + } + }, + "charm_light": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_light" + } + ] + } + }, + "charm_transportation": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_transportation" + } + ] + } + }, + "charm_feeding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_feeding" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/all_spells.json b/src/main/resources/assets/ebwizardry/advancements/all_spells.json index ce7c75c1..16a35949 100644 --- a/src/main/resources/assets/ebwizardry/advancements/all_spells.json +++ b/src/main/resources/assets/ebwizardry/advancements/all_spells.json @@ -1,20 +1,1393 @@ { "display": { "icon": { - "item": "ebwizardry:wizard_handbook" + "item": "ebwizardry:spell_book" }, "title": { - "translate": "advancement.wizardry:all_spells" + "translate": "advancement.ebwizardry:all_spells" }, "description": { - "translate": "advancement.wizardry:all_spells.desc" + "translate": "advancement.ebwizardry:all_spells.desc" }, - "frame": "challenge" + "frame": "challenge" }, "parent": "ebwizardry:master", "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_all_spells" + "agility": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "agility" + } + } + }, + "arc": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "arc" + } + } + }, + "arcane_jammer": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "arcane_jammer" + } + } + }, + "arcane_lock": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "arcane_lock" + } + } + }, + "arrow_rain": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "arrow_rain" + } + } + }, + "banish": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "banish" + } + } + }, + "black_hole": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "black_hole" + } + } + }, + "blink": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "blink" + } + } + }, + "blizzard": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "blizzard" + } + } + }, + "bubble": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "bubble" + } + } + }, + "chain_lightning": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "chain_lightning" + } + } + }, + "charge": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "charge" + } + } + }, + "clairvoyance": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "clairvoyance" + } + } + }, + "cobwebs": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "cobwebs" + } + } + }, + "combustion_rune": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "combustion_rune" + } + } + }, + "conjure_armour": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "conjure_armour" + } + } + }, + "conjure_block": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "conjure_block" + } + } + }, + "conjure_bow": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "conjure_bow" + } + } + }, + "conjure_pickaxe": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "conjure_pickaxe" + } + } + }, + "conjure_sword": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "conjure_sword" + } + } + }, + "containment": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "containment" + } + } + }, + "cure_effects": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "cure_effects" + } + } + }, + "curse_of_enfeeblement": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "curse_of_enfeeblement" + } + } + }, + "curse_of_soulbinding": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "curse_of_soulbinding" + } + } + }, + "curse_of_undeath": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "curse_of_undeath" + } + } + }, + "darkness_orb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "darkness_orb" + } + } + }, + "darkvision": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "darkvision" + } + } + }, + "dart": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "dart" + } + } + }, + "decay": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "decay" + } + } + }, + "decoy": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "decoy" + } + } + }, + "detonate": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "detonate" + } + } + }, + "diamondflesh": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "diamondflesh" + } + } + }, + "disintegration": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "disintegration" + } + } + }, + "divination": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "divination" + } + } + }, + "dragon_fireball": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "dragon_fireball" + } + } + }, + "earthquake": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "earthquake" + } + } + }, + "empowering_presence": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "empowering_presence" + } + } + }, + "entrapment": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "entrapment" + } + } + }, + "evade": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "evade" + } + } + }, + "fireball": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fireball" + } + } + }, + "firebolt": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "firebolt" + } + } + }, + "firebomb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "firebomb" + } + } + }, + "fire_resistance": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fire_resistance" + } + } + }, + "fire_sigil": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fire_sigil" + } + } + }, + "fireskin": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fireskin" + } + } + }, + "fire_breath": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fire_breath" + } + } + }, + "flame_ray": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "flame_ray" + } + } + }, + "flaming_axe": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "flaming_axe" + } + } + }, + "flaming_weapon": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "flaming_weapon" + } + } + }, + "flight": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "flight" + } + } + }, + "font_of_mana": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "font_of_mana" + } + } + }, + "font_of_vitality": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "font_of_vitality" + } + } + }, + "force_arrow": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "force_arrow" + } + } + }, + "forcefield": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "forcefield" + } + } + }, + "force_orb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "force_orb" + } + } + }, + "forests_curse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "forests_curse" + } + } + }, + "forest_of_thorns": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "forest_of_thorns" + } + } + }, + "freeze": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "freeze" + } + } + }, + "freezing_weapon": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "freezing_weapon" + } + } + }, + "frost_axe": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "frost_axe" + } + } + }, + "frost_ray": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "frost_ray" + } + } + }, + "frost_sigil": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "frost_sigil" + } + } + }, + "frost_step": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "frost_step" + } + } + }, + "glide": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "glide" + } + } + }, + "grapple": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "grapple" + } + } + }, + "greater_fireball": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "greater_fireball" + } + } + }, + "greater_heal": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "greater_heal" + } + } + }, + "greater_telekinesis": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "greater_telekinesis" + } + } + }, + "greater_ward": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "greater_ward" + } + } + }, + "group_heal": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "group_heal" + } + } + }, + "growth_aura": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "growth_aura" + } + } + }, + "hailstorm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "hailstorm" + } + } + }, + "heal": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "heal" + } + } + }, + "heal_ally": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "heal_ally" + } + } + }, + "healing_aura": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "healing_aura" + } + } + }, + "homing_spark": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "homing_spark" + } + } + }, + "iceball": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "iceball" + } + } + }, + "ice_age": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_age" + } + } + }, + "ice_charge": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_charge" + } + } + }, + "ice_lance": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_lance" + } + } + }, + "ice_shard": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_shard" + } + } + }, + "ice_shroud": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_shroud" + } + } + }, + "ice_spikes": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_spikes" + } + } + }, + "ice_statue": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ice_statue" + } + } + }, + "ignite": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ignite" + } + } + }, + "imbue_weapon": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "imbue_weapon" + } + } + }, + "intimidate": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "intimidate" + } + } + }, + "invigorating_presence": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "invigorating_presence" + } + } + }, + "invisibility": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "invisibility" + } + } + }, + "invoke_weather": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "invoke_weather" + } + } + }, + "ironflesh": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ironflesh" + } + } + }, + "leap": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "leap" + } + } + }, + "levitation": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "levitation" + } + } + }, + "life_drain": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "life_drain" + } + } + }, + "light": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "light" + } + } + }, + "lightning_arrow": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_arrow" + } + } + }, + "lightning_bolt": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_bolt" + } + } + }, + "lightning_disc": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_disc" + } + } + }, + "lightning_hammer": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_hammer" + } + } + }, + "lightning_pulse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_pulse" + } + } + }, + "lightning_ray": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_ray" + } + } + }, + "lightning_sigil": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_sigil" + } + } + }, + "lightning_web": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "lightning_web" + } + } + }, + "magic_missile": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "magic_missile" + } + } + }, + "metamorphosis": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "metamorphosis" + } + } + }, + "meteor": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "meteor" + } + } + }, + "mind_control": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "mind_control" + } + } + }, + "mind_trick": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "mind_trick" + } + } + }, + "mine": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "mine" + } + } + }, + "muffle": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "muffle" + } + } + }, + "oakflesh": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "oakflesh" + } + } + }, + "paralysis": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "paralysis" + } + } + }, + "petrify": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "petrify" + } + } + }, + "phase_step": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "phase_step" + } + } + }, + "plague_of_darkness": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "plague_of_darkness" + } + } + }, + "pocket_furnace": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "pocket_furnace" + } + } + }, + "pocket_workbench": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "pocket_workbench" + } + } + }, + "poison": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "poison" + } + } + }, + "poison_bomb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "poison_bomb" + } + } + }, + "possession": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "possession" + } + } + }, + "ray_of_purification": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ray_of_purification" + } + } + }, + "remove_curse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "remove_curse" + } + } + }, + "replenish_hunger": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "replenish_hunger" + } + } + }, + "resurrection": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "resurrection" + } + } + }, + "reversal": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "reversal" + } + } + }, + "ring_of_fire": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ring_of_fire" + } + } + }, + "satiety": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "satiety" + } + } + }, + "shadow_ward": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "shadow_ward" + } + } + }, + "shield": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "shield" + } + } + }, + "shockwave": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "shockwave" + } + } + }, + "shulker_bullet": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "shulker_bullet" + } + } + }, + "silverfish_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "silverfish_swarm" + } + } + }, + "sixth_sense": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "sixth_sense" + } + } + }, + "slime": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "slime" + } + } + }, + "slow_time": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "slow_time" + } + } + }, + "smoke_bomb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "smoke_bomb" + } + } + }, + "snare": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "snare" + } + } + }, + "snowball": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "snowball" + } + } + }, + "spark_bomb": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "spark_bomb" + } + } + }, + "spectral_pathway": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "spectral_pathway" + } + } + }, + "speed_time": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "speed_time" + } + } + }, + "spider_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "spider_swarm" + } + } + }, + "static_aura": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "static_aura" + } + } + }, + "summon_blaze": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_blaze" + } + } + }, + "summon_ice_giant": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_ice_giant" + } + } + }, + "summon_ice_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_ice_wraith" + } + } + }, + "summon_iron_golem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_iron_golem" + } + } + }, + "summon_lightning_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_lightning_wraith" + } + } + }, + "summon_phoenix": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_phoenix" + } + } + }, + "summon_shadow_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_shadow_wraith" + } + } + }, + "summon_skeleton": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_skeleton" + } + } + }, + "summon_skeleton_legion": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_skeleton_legion" + } + } + }, + "summon_snow_golem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_snow_golem" + } + } + }, + "summon_spirit_horse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_spirit_horse" + } + } + }, + "summon_spirit_wolf": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_spirit_wolf" + } + } + }, + "summon_storm_elemental": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_storm_elemental" + } + } + }, + "summon_wither_skeleton": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_wither_skeleton" + } + } + }, + "summon_zombie": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_zombie" + } + } + }, + "telekinesis": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "telekinesis" + } + } + }, + "thunderbolt": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "thunderbolt" + } + } + }, + "thunderstorm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "thunderstorm" + } + } + }, + "tornado": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "tornado" + } + } + }, + "transience": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "transience" + } + } + }, + "transportation": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "transportation" + } + } + }, + "vanishing_box": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "vanishing_box" + } + } + }, + "vex_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "vex_swarm" + } + } + }, + "wall_of_frost": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "wall_of_frost" + } + } + }, + "ward": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "ward" + } + } + }, + "water_breathing": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "water_breathing" + } + } + }, + "whirlwind": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "whirlwind" + } + } + }, + "wither": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "wither" + } + } + }, + "wither_skull": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "wither_skull" + } + } } } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/anger_wizard.json b/src/main/resources/assets/ebwizardry/advancements/anger_wizard.json index ea1b5070..b9d5dee3 100644 --- a/src/main/resources/assets/ebwizardry/advancements/anger_wizard.json +++ b/src/main/resources/assets/ebwizardry/advancements/anger_wizard.json @@ -4,10 +4,10 @@ "item": "minecraft:iron_sword" }, "title": { - "translate": "advancement.wizardry:anger_wizard" + "translate": "advancement.ebwizardry:anger_wizard" }, "description": { - "translate": "advancement.wizardry:anger_wizard.desc" + "translate": "advancement.ebwizardry:anger_wizard.desc" } }, "parent": "ebwizardry:wizard_trade", diff --git a/src/main/resources/assets/ebwizardry/advancements/apprentice.json b/src/main/resources/assets/ebwizardry/advancements/apprentice.json index 493f7963..12596e68 100644 --- a/src/main/resources/assets/ebwizardry/advancements/apprentice.json +++ b/src/main/resources/assets/ebwizardry/advancements/apprentice.json @@ -4,16 +4,105 @@ "item": "ebwizardry:apprentice_wand" }, "title": { - "translate": "advancement.wizardry:apprentice" + "translate": "advancement.ebwizardry:apprentice" }, "description": { - "translate": "advancement.wizardry:apprentice.desc" + "translate": "advancement.ebwizardry:apprentice.desc" } }, "parent": "ebwizardry:arcane_initiate", "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_apprentice" + "magic": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_wand" + } + ] + } + }, + "fire": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_fire_wand" + } + ] + } + }, + "ice": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_ice_wand" + } + ] + } + }, + "lightning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_lightning_wand" + } + ] + } + }, + "necromancy": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_necromancy_wand" + } + ] + } + }, + "earth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_earth_wand" + } + ] + } + }, + "sorcery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_sorcery_wand" + } + ] + } + }, + "healing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:apprentice_healing_wand" + } + ] + } } - } + }, + "requirements": [ + [ + "magic", + "fire", + "ice", + "lightning", + "necromancy", + "earth", + "sorcery", + "healing" + ] + ] } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/arcane_initiate.json b/src/main/resources/assets/ebwizardry/advancements/arcane_initiate.json index 927f6fe4..6d4bcc33 100644 --- a/src/main/resources/assets/ebwizardry/advancements/arcane_initiate.json +++ b/src/main/resources/assets/ebwizardry/advancements/arcane_initiate.json @@ -1,10 +1,10 @@ { "display": { "title": { - "translate": "advancement.wizardry:arcane_initiate" + "translate": "advancement.ebwizardry:arcane_initiate" }, "description": { - "translate": "advancement.wizardry:arcane_initiate.desc" + "translate": "advancement.ebwizardry:arcane_initiate.desc" }, "icon": { "item": "ebwizardry:magic_wand" diff --git a/src/main/resources/assets/ebwizardry/advancements/armour_set.json b/src/main/resources/assets/ebwizardry/advancements/armour_set.json index 7e36d47c..c69d5c65 100644 --- a/src/main/resources/assets/ebwizardry/advancements/armour_set.json +++ b/src/main/resources/assets/ebwizardry/advancements/armour_set.json @@ -1,10 +1,10 @@ { "display": { "title": { - "translate": "advancement.wizardry:armour_set" + "translate": "advancement.ebwizardry:armour_set" }, "description": { - "translate": "advancement.wizardry:armour_set.desc" + "translate": "advancement.ebwizardry:armour_set.desc" }, "icon": { "item": "ebwizardry:wizard_hat" @@ -12,8 +12,24 @@ }, "parent": "ebwizardry:arcane_initiate", "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_armour_set" + "wizard_hat": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:wizard_hat" + }, + { + "item": "ebwizardry:wizard_robe" + }, + { + "item": "ebwizardry:wizard_leggings" + }, + { + "item": "ebwizardry:wizard_boots" + } + ] + } } } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/artefact.json b/src/main/resources/assets/ebwizardry/advancements/artefact.json new file mode 100644 index 00000000..a5287409 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/artefact.json @@ -0,0 +1,713 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:artefact" + }, + "description": { + "translate": "advancement.ebwizardry:artefact.desc" + }, + "icon": { + "item": "ebwizardry:ring_condensing" + } + }, + "parent": "ebwizardry:visit_shrine", + "criteria": { + "ring_condensing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_condensing" + } + ] + } + }, + "ring_siphoning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_siphoning" + } + ] + } + }, + "ring_battlemage": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_battlemage" + } + ] + } + }, + "ring_combustion": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_combustion" + } + ] + } + }, + "ring_fire_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_fire_melee" + } + ] + } + }, + "ring_fire_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_fire_biome" + } + ] + } + }, + "ring_disintegration": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_disintegration" + } + ] + } + }, + "ring_ice_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_ice_melee" + } + ] + } + }, + "ring_ice_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_ice_biome" + } + ] + } + }, + "ring_arcane_frost": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_arcane_frost" + } + ] + } + }, + "ring_shattering": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_shattering" + } + ] + } + }, + "ring_lightning_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_lightning_melee" + } + ] + } + }, + "ring_storm": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_storm" + } + ] + } + }, + "ring_seeking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_seeking" + } + ] + } + }, + "ring_hammer": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_hammer" + } + ] + } + }, + "ring_soulbinding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_soulbinding" + } + ] + } + }, + "ring_leeching": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_leeching" + } + ] + } + }, + "ring_necromancy_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_necromancy_melee" + } + ] + } + }, + "ring_mind_control": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_mind_control" + } + ] + } + }, + "ring_poison": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_poison" + } + ] + } + }, + "ring_earth_melee": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_earth_melee" + } + ] + } + }, + "ring_earth_biome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_earth_biome" + } + ] + } + }, + "ring_full_moon": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_full_moon" + } + ] + } + }, + "ring_extraction": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_extraction" + } + ] + } + }, + "ring_mana_return": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_mana_return" + } + ] + } + }, + "ring_blockwrangler": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_blockwrangler" + } + ] + } + }, + "ring_conjurer": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_conjurer" + } + ] + } + }, + "ring_defender": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_defender" + } + ] + } + }, + "ring_paladin": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_paladin" + } + ] + } + }, + "ring_interdiction": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_interdiction" + } + ] + } + }, + "amulet_arcane_defence": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_arcane_defence" + } + ] + } + }, + "amulet_warding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_warding" + } + ] + } + }, + "amulet_wisdom": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_wisdom" + } + ] + } + }, + "amulet_fire_protection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_fire_protection" + } + ] + } + }, + "amulet_fire_cloaking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_fire_cloaking" + } + ] + } + }, + "amulet_ice_immunity": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_ice_immunity" + } + ] + } + }, + "amulet_ice_protection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_ice_protection" + } + ] + } + }, + "amulet_potential": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_potential" + } + ] + } + }, + "amulet_channeling": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_channeling" + } + ] + } + }, + "amulet_lich": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_lich" + } + ] + } + }, + "amulet_wither_immunity": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_wither_immunity" + } + ] + } + }, + "amulet_glide": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_glide" + } + ] + } + }, + "amulet_banishing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_banishing" + } + ] + } + }, + "amulet_anchoring": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_anchoring" + } + ] + } + }, + "amulet_recovery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_recovery" + } + ] + } + }, + "amulet_transience": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_transience" + } + ] + } + }, + "amulet_resurrection": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_resurrection" + } + ] + } + }, + "amulet_auto_shield": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_auto_shield" + } + ] + } + }, + "charm_haggler": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_haggler" + } + ] + } + }, + "charm_experience_tome": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_experience_tome" + } + ] + } + }, + "charm_auto_smelt": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_auto_smelt" + } + ] + } + }, + "charm_lava_walking": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_lava_walking" + } + ] + } + }, + "charm_storm": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_storm" + } + ] + } + }, + "charm_minion_health": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_minion_health" + } + ] + } + }, + "charm_minion_variants": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_minion_variants" + } + ] + } + }, + "charm_flight": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_flight" + } + ] + } + }, + "charm_growth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_growth" + } + ] + } + }, + "charm_abseiling": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_abseiling" + } + ] + } + }, + "charm_silk_touch": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_silk_touch" + } + ] + } + }, + "charm_stop_time": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_stop_time" + } + ] + } + }, + "charm_light": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_light" + } + ] + } + }, + "charm_transportation": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_transportation" + } + ] + } + }, + "charm_feeding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_feeding" + } + ] + } + } + }, + "requirements": [ + [ + "ring_condensing", + "ring_siphoning", + "ring_battlemage", + "ring_combustion", + "ring_fire_melee", + "ring_fire_biome", + "ring_disintegration", + "ring_ice_melee", + "ring_ice_biome", + "ring_arcane_frost", + "ring_shattering", + "ring_lightning_melee", + "ring_storm", + "ring_seeking", + "ring_hammer", + "ring_soulbinding", + "ring_leeching", + "ring_necromancy_melee", + "ring_mind_control", + "ring_poison", + "ring_earth_melee", + "ring_earth_biome", + "ring_full_moon", + "ring_extraction", + "ring_mana_return", + "ring_blockwrangler", + "ring_conjurer", + "ring_defender", + "ring_paladin", + "ring_interdiction", + "amulet_arcane_defence", + "amulet_warding", + "amulet_wisdom", + "amulet_fire_protection", + "amulet_fire_cloaking", + "amulet_ice_immunity", + "amulet_ice_protection", + "amulet_potential", + "amulet_channeling", + "amulet_lich", + "amulet_wither_immunity", + "amulet_glide", + "amulet_banishing", + "amulet_anchoring", + "amulet_recovery", + "amulet_transience", + "amulet_resurrection", + "amulet_auto_shield", + "charm_haggler", + "charm_experience_tome", + "charm_auto_smelt", + "charm_lava_walking", + "charm_storm", + "charm_minion_health", + "charm_minion_variants", + "charm_flight", + "charm_growth", + "charm_abseiling", + "charm_silk_touch", + "charm_stop_time", + "charm_light", + "charm_transportation", + "charm_feeding" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/buy_master_spell.json b/src/main/resources/assets/ebwizardry/advancements/buy_master_spell.json index 1f00f8b4..3cf455d4 100644 --- a/src/main/resources/assets/ebwizardry/advancements/buy_master_spell.json +++ b/src/main/resources/assets/ebwizardry/advancements/buy_master_spell.json @@ -1,13 +1,13 @@ { "display": { "title": { - "translate": "advancement.wizardry:buy_master_spell" + "translate": "advancement.ebwizardry:buy_master_spell" }, "description": { - "translate": "advancement.wizardry:buy_master_spell.desc" + "translate": "advancement.ebwizardry:buy_master_spell.desc" }, "icon": { - "item": "ebwizardry:spell_book" + "item": "ebwizardry:astral_diamond" }, "frame": "goal" }, diff --git a/src/main/resources/assets/ebwizardry/advancements/charge_creeper.json b/src/main/resources/assets/ebwizardry/advancements/charge_creeper.json deleted file mode 100644 index 8fad22e1..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/charge_creeper.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:charge_creeper" - }, - "description": { - "translate": "advancement.wizardry:charge_creeper.desc" - }, - "icon": { - "item": "minecraft:gunpowder" - } - }, - "parent": "ebwizardry:arcane_initiate", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_charge_creeper" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/craft_flask.json b/src/main/resources/assets/ebwizardry/advancements/craft_flask.json deleted file mode 100644 index 7ae4cba2..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/craft_flask.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:craft_flask" - }, - "description": { - "translate": "advancement.wizardry:craft_flask.desc" - }, - "icon": { - "item": "ebwizardry:mana_flask" - } - }, - "parent": "ebwizardry:arcane_initiate", - "criteria": { - "criteria_0": { - "trigger": "minecraft:inventory_changed", - "conditions": { - "items": [ - { - "item": "ebwizardry:mana_flask" - } - ] - } - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/crystal.json b/src/main/resources/assets/ebwizardry/advancements/crystal.json index ddbbf5f4..9dcc0773 100644 --- a/src/main/resources/assets/ebwizardry/advancements/crystal.json +++ b/src/main/resources/assets/ebwizardry/advancements/crystal.json @@ -1,13 +1,14 @@ { "display": { "title": { - "translate": "advancement.wizardry:crystal" + "translate": "advancement.ebwizardry:crystal" }, "description": { - "translate": "advancement.wizardry:crystal.desc" + "translate": "advancement.ebwizardry:crystal.desc" }, "icon": { - "item": "ebwizardry:magic_crystal" + "item": "ebwizardry:magic_crystal", + "data": 0 } }, "parent": "ebwizardry:root", @@ -17,7 +18,8 @@ "conditions": { "items": [ { - "item": "ebwizardry:magic_crystal" + "item": "ebwizardry:magic_crystal", + "data": 0 } ] } diff --git a/src/main/resources/assets/ebwizardry/advancements/defeat_evil_wizard.json b/src/main/resources/assets/ebwizardry/advancements/defeat_evil_wizard.json index 7faa2467..810f1074 100644 --- a/src/main/resources/assets/ebwizardry/advancements/defeat_evil_wizard.json +++ b/src/main/resources/assets/ebwizardry/advancements/defeat_evil_wizard.json @@ -1,16 +1,16 @@ { "display": { "title": { - "translate": "advancement.wizardry:defeat_evil_wizard" + "translate": "advancement.ebwizardry:defeat_evil_wizard" }, "description": { - "translate": "advancement.wizardry:defeat_evil_wizard.desc" + "translate": "advancement.ebwizardry:defeat_evil_wizard.desc" }, "icon": { "item": "ebwizardry:wizard_boots_necromancy" } }, - "parent": "ebwizardry:wizard_trade", + "parent": "ebwizardry:arcane_initiate", "criteria": { "criteria_0": { "trigger": "minecraft:player_killed_entity", diff --git a/src/main/resources/assets/ebwizardry/advancements/discover_master_spell.json b/src/main/resources/assets/ebwizardry/advancements/discover_master_spell.json new file mode 100644 index 00000000..35b38fd0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/discover_master_spell.json @@ -0,0 +1,28 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:discover_master_spell" + }, + "description": { + "translate": "advancement.ebwizardry:discover_master_spell.desc" + }, + "icon": { + "item": "minecraft:blaze_powder" + }, + "frame": "challenge" + }, + "parent": "ebwizardry:spell_failure", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:discover_spell", + "conditions": { + "spell": { + "tiers": [ + "master" + ] + }, + "source": "casting" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/discover_spell.json b/src/main/resources/assets/ebwizardry/advancements/discover_spell.json new file mode 100644 index 00000000..ba0b2d72 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/discover_spell.json @@ -0,0 +1,22 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:discover_spell" + }, + "description": { + "translate": "advancement.ebwizardry:discover_spell.desc" + }, + "icon": { + "item": "minecraft:paper" + } + }, + "parent": "ebwizardry:arcane_initiate", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:discover_spell", + "conditions": { + "source": "casting" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/element_master.json b/src/main/resources/assets/ebwizardry/advancements/element_master.json deleted file mode 100644 index bf8d38f4..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/element_master.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:element_master" - }, - "description": { - "translate": "advancement.wizardry:element_master.desc" - }, - "icon": { - "item": "ebwizardry:master_ice_wand" - }, - "frame": "challenge" - }, - "parent": "ebwizardry:elemental", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_element_master" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/elemental.json b/src/main/resources/assets/ebwizardry/advancements/elemental.json deleted file mode 100644 index 43eb4c16..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/elemental.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:elemental" - }, - "description": { - "translate": "advancement.wizardry:elemental.desc" - }, - "icon": { - "item": "ebwizardry:basic_fire_wand" - } - }, - "parent": "ebwizardry:arcane_initiate", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_elemental" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/enchant_scroll.json b/src/main/resources/assets/ebwizardry/advancements/enchant_scroll.json new file mode 100644 index 00000000..abf5782b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/enchant_scroll.json @@ -0,0 +1,24 @@ +{ + "display": { + "icon": { + "item": "ebwizardry:scroll" + }, + "title": { + "translate": "advancement.ebwizardry:enchant_scroll" + }, + "description": { + "translate": "advancement.ebwizardry:enchant_scroll.desc" + } + }, + "parent": "ebwizardry:arcane_initiate", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:arcane_workbench", + "conditions": { + "item": { + "item": "ebwizardry:scroll" + } + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/frankenstein.json b/src/main/resources/assets/ebwizardry/advancements/frankenstein.json deleted file mode 100644 index d197636c..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/frankenstein.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:frankenstein" - }, - "description": { - "translate": "advancement.wizardry:frankenstein.desc" - }, - "icon": { - "item": "ebwizardry:advanced_lightning_wand" - }, - "frame": "challenge" - }, - "parent": "ebwizardry:charge_creeper", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_frankenstein" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/freeze_blaze.json b/src/main/resources/assets/ebwizardry/advancements/freeze_blaze.json deleted file mode 100644 index 595fdabe..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/freeze_blaze.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:freeze_blaze" - }, - "description": { - "translate": "advancement.wizardry:freeze_blaze.desc" - }, - "icon": { - "item": "minecraft:ice" - } - }, - "parent": "ebwizardry:apprentice", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_freeze_blaze" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/arcane_workbench.json b/src/main/resources/assets/ebwizardry/advancements/handbook/arcane_workbench.json new file mode 100644 index 00000000..0822d951 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/arcane_workbench.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:arcane_workbench" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/crystal_flowers.json b/src/main/resources/assets/ebwizardry/advancements/handbook/crystal_flowers.json new file mode 100644 index 00000000..b336c1d3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/crystal_flowers.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:crystal_flower" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/elements.json b/src/main/resources/assets/ebwizardry/advancements/handbook/elements.json new file mode 100644 index 00000000..de48f0b3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/elements.json @@ -0,0 +1,92 @@ +{ + "criteria": { + "fire": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 1 + } + ] + } + }, + "ice": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 2 + } + ] + } + }, + "lightning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 3 + } + ] + } + }, + "necromancy": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 4 + } + ] + } + }, + "earth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 5 + } + ] + } + }, + "sorcery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 6 + } + ] + } + }, + "healing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ] + } + } + }, + "requirements": [ + [ + "fire", + "ice", + "lightning", + "necromancy", + "earth", + "sorcery", + "healing" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/magical_creatures.json b/src/main/resources/assets/ebwizardry/advancements/handbook/magical_creatures.json new file mode 100644 index 00000000..4bcdae10 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/magical_creatures.json @@ -0,0 +1,242 @@ +{ + "criteria": { + "ice_wraith": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:ice_wraith" + } + } + }, + "lightning_wraith": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:lightning_wraith" + } + } + }, + "spirit_wolf": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:spirit_wolf" + } + } + }, + "spirit_horse": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:spirit_horse" + } + } + }, + "phoenix": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:phoenix" + } + } + }, + "ice_giant": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:ice_giant" + } + } + }, + "shadow_wraith": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:shadow_wraith" + } + } + }, + "storm_elemental": { + "trigger": "minecraft:player_killed_entity", + "conditions": { + "entity": { + "type": "ebwizardry:storm_elemental" + } + } + }, + "silverfish_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "silverfish_swarm" + } + } + }, + "spider_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "spider_swarm" + } + } + }, + "vex_swarm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "vex_swarm" + } + } + }, + "summon_blaze": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_blaze" + } + } + }, + "summon_ice_giant": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_ice_giant" + } + } + }, + "summon_ice_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_ice_wraith" + } + } + }, + "summon_iron_golem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_iron_golem" + } + } + }, + "summon_lightning_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_lightning_wraith" + } + } + }, + "summon_phoenix": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_phoenix" + } + } + }, + "summon_shadow_wraith": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_shadow_wraith" + } + } + }, + "summon_skeleton": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_skeleton" + } + } + }, + "summon_skeleton_legion": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_skeleton_legion" + } + } + }, + "summon_snow_golem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_snow_golem" + } + } + }, + "summon_spirit_horse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_spirit_horse" + } + } + }, + "summon_spirit_wolf": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_spirit_wolf" + } + } + }, + "summon_storm_elemental": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_storm_elemental" + } + } + }, + "summon_wither_skeleton": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_wither_skeleton" + } + } + }, + "summon_zombie": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "summon_zombie" + } + } + } + }, + "requirements": [ + [ + "ice_wraith", + "lightning_wraith", + "spirit_wolf", + "spirit_horse", + "phoenix", + "ice_giant", + "shadow_wraith", + "storm_elemental", + "silverfish_swarm", + "spider_swarm", + "vex_swarm", + "summon_blaze", + "summon_ice_giant", + "summon_ice_wraith", + "summon_iron_golem", + "summon_lightning_wraith", + "summon_phoenix", + "summon_shadow_wraith", + "summon_skeleton", + "summon_skeleton_legion", + "summon_snow_golem", + "summon_spirit_horse", + "summon_spirit_wolf", + "summon_storm_elemental", + "summon_wither_skeleton", + "summon_zombie" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/mana_flasks.json b/src/main/resources/assets/ebwizardry/advancements/handbook/mana_flasks.json new file mode 100644 index 00000000..05c91520 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/mana_flasks.json @@ -0,0 +1,10 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "ebwizardry:medium_mana_flask" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/obelisks.json b/src/main/resources/assets/ebwizardry/advancements/handbook/obelisks.json new file mode 100644 index 00000000..07f802c8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/obelisks.json @@ -0,0 +1,10 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:visit_structure", + "conditions": { + "structure_type": "obelisk" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/on_subsection_unlock.json b/src/main/resources/assets/ebwizardry/advancements/handbook/on_subsection_unlock.json new file mode 100644 index 00000000..52e2393c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/on_subsection_unlock.json @@ -0,0 +1,7 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:impossible" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/scrolls.json b/src/main/resources/assets/ebwizardry/advancements/handbook/scrolls.json new file mode 100644 index 00000000..22dfe99a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/scrolls.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:scroll" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/spells.json b/src/main/resources/assets/ebwizardry/advancements/handbook/spells.json new file mode 100644 index 00000000..1bc8d817 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/spells.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spell_book" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/throwables.json b/src/main/resources/assets/ebwizardry/advancements/handbook/throwables.json new file mode 100644 index 00000000..43b8e1e9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/throwables.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:firebomb" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/handbook/tome_of_arcana.json b/src/main/resources/assets/ebwizardry/advancements/handbook/tome_of_arcana.json new file mode 100644 index 00000000..40ee0c46 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/tome_of_arcana.json @@ -0,0 +1,14 @@ +{ + "criteria": { + "criteria_0": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:arcane_tome" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/identify_spell.json b/src/main/resources/assets/ebwizardry/advancements/identify_spell.json index 3f58890b..b8c1aaae 100644 --- a/src/main/resources/assets/ebwizardry/advancements/identify_spell.json +++ b/src/main/resources/assets/ebwizardry/advancements/identify_spell.json @@ -1,19 +1,22 @@ { "display": { "title": { - "translate": "advancement.wizardry:identify_spell" + "translate": "advancement.ebwizardry:identify_spell" }, "description": { - "translate": "advancement.wizardry:identify_spell.desc" + "translate": "advancement.ebwizardry:identify_spell.desc" }, "icon": { "item": "ebwizardry:identification_scroll" } }, - "parent": "ebwizardry:arcane_initiate", + "parent": "ebwizardry:discover_spell", "criteria": { "criteria_0": { - "trigger": "ebwizardry:trigger_identify_spell" + "trigger": "ebwizardry:discover_spell", + "conditions": { + "source": "identification_scroll" + } } } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/jam_wizard.json b/src/main/resources/assets/ebwizardry/advancements/jam_wizard.json deleted file mode 100644 index f860835e..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/jam_wizard.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:jam_wizard" - }, - "description": { - "translate": "advancement.wizardry:jam_wizard.desc" - }, - "icon": { - "item": "minecraft:web" - } - }, - "parent": "ebwizardry:apprentice", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_jam_wizard" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/legendary.json b/src/main/resources/assets/ebwizardry/advancements/legendary.json index f1473e47..691e7a2b 100644 --- a/src/main/resources/assets/ebwizardry/advancements/legendary.json +++ b/src/main/resources/assets/ebwizardry/advancements/legendary.json @@ -1,10 +1,10 @@ { "display": { "title": { - "translate": "advancement.wizardry:legendary" + "translate": "advancement.ebwizardry:legendary" }, "description": { - "translate": "advancement.wizardry:legendary.desc" + "translate": "advancement.ebwizardry:legendary.desc" }, "icon": { "item": "ebwizardry:armour_upgrade" diff --git a/src/main/resources/assets/ebwizardry/advancements/master.json b/src/main/resources/assets/ebwizardry/advancements/master.json index bd6b24fe..12236cbf 100644 --- a/src/main/resources/assets/ebwizardry/advancements/master.json +++ b/src/main/resources/assets/ebwizardry/advancements/master.json @@ -1,19 +1,108 @@ { "display": { "title": { - "translate": "advancement.wizardry:master" + "translate": "advancement.ebwizardry:master" }, "description": { - "translate": "advancement.wizardry:master.desc" + "translate": "advancement.ebwizardry:master.desc" }, "icon": { "item": "ebwizardry:master_wand" } }, - "parent": "ebwizardry:apprentice", + "parent": "ebwizardry:advanced", "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_master" + "magic": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_wand" + } + ] + } + }, + "fire": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_fire_wand" + } + ] + } + }, + "ice": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_ice_wand" + } + ] + } + }, + "lightning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_lightning_wand" + } + ] + } + }, + "necromancy": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_necromancy_wand" + } + ] + } + }, + "earth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_earth_wand" + } + ] + } + }, + "sorcery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_sorcery_wand" + } + ] + } + }, + "healing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:master_healing_wand" + } + ] + } } - } + }, + "requirements": [ + [ + "magic", + "fire", + "ice", + "lightning", + "necromancy", + "earth", + "sorcery", + "healing" + ] + ] } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/max_out_wand.json b/src/main/resources/assets/ebwizardry/advancements/max_out_wand.json index 2a779459..7a571967 100644 --- a/src/main/resources/assets/ebwizardry/advancements/max_out_wand.json +++ b/src/main/resources/assets/ebwizardry/advancements/max_out_wand.json @@ -1,17 +1,17 @@ { "display": { "title": { - "translate": "advancement.wizardry:max_out_wand" + "translate": "advancement.ebwizardry:max_out_wand" }, "description": { - "translate": "advancement.wizardry:max_out_wand.desc" + "translate": "advancement.ebwizardry:max_out_wand.desc" }, "icon": { "item": "ebwizardry:arcane_tome" }, "frame": "goal" }, - "parent": "ebwizardry:special_upgrade", + "parent": "ebwizardry:master", "criteria": { "criteria_0": { "trigger": "ebwizardry:trigger_max_out_wand" diff --git a/src/main/resources/assets/ebwizardry/advancements/pig_tornado.json b/src/main/resources/assets/ebwizardry/advancements/pig_tornado.json deleted file mode 100644 index 04770269..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/pig_tornado.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:pig_tornado" - }, - "description": { - "translate": "advancement.wizardry:pig_tornado.desc" - }, - "icon": { - "item": "minecraft:saddle" - }, - "frame": "challenge" - }, - "parent": "ebwizardry:apprentice", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_pig_tornado" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/root.json b/src/main/resources/assets/ebwizardry/advancements/root.json index 63212c97..6c80c1fb 100644 --- a/src/main/resources/assets/ebwizardry/advancements/root.json +++ b/src/main/resources/assets/ebwizardry/advancements/root.json @@ -1,10 +1,10 @@ { "display": { "title": { - "translate": "advancement.wizardry:root" + "translate": "advancement.ebwizardry:root" }, "description": { - "translate": "advancement.wizardry:root.desc" + "translate": "advancement.ebwizardry:root.desc" }, "show_toast": false, "announce_to_chat": false, diff --git a/src/main/resources/assets/ebwizardry/advancements/self_destruct.json b/src/main/resources/assets/ebwizardry/advancements/self_destruct.json deleted file mode 100644 index d0253a73..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/self_destruct.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:self_destruct" - }, - "description": { - "translate": "advancement.wizardry:self_destruct.desc" - }, - "icon": { - "item": "minecraft:pumpkin" - }, - "frame": "challenge" - }, - "parent": "ebwizardry:arcane_initiate", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_self_destruct" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/slime_skeleton.json b/src/main/resources/assets/ebwizardry/advancements/slime_skeleton.json deleted file mode 100644 index 90a982bb..00000000 --- a/src/main/resources/assets/ebwizardry/advancements/slime_skeleton.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "display": { - "title": { - "translate": "advancement.wizardry:slime_skeleton" - }, - "description": { - "translate": "advancement.wizardry:slime_skeleton.desc" - }, - "icon": { - "item": "minecraft:slime_ball" - } - }, - "parent": "ebwizardry:apprentice", - "criteria": { - "criteria_0": { - "trigger": "ebwizardry:trigger_slime_skeleton" - } - } -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/special_upgrade.json b/src/main/resources/assets/ebwizardry/advancements/special_upgrade.json index 41b48fd4..324c4973 100644 --- a/src/main/resources/assets/ebwizardry/advancements/special_upgrade.json +++ b/src/main/resources/assets/ebwizardry/advancements/special_upgrade.json @@ -1,16 +1,16 @@ { "display": { "title": { - "translate": "advancement.wizardry:special_upgrade" + "translate": "advancement.ebwizardry:special_upgrade" }, "description": { - "translate": "advancement.wizardry:special_upgrade.desc" + "translate": "advancement.ebwizardry:special_upgrade.desc" }, "icon": { "item": "ebwizardry:condenser_upgrade" } }, - "parent": "ebwizardry:arcane_initiate", + "parent": "ebwizardry:apprentice", "criteria": { "criteria_0": { "trigger": "ebwizardry:trigger_special_upgrade" diff --git a/src/main/resources/assets/ebwizardry/advancements/spell_failure.json b/src/main/resources/assets/ebwizardry/advancements/spell_failure.json new file mode 100644 index 00000000..eed72bdf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/spell_failure.json @@ -0,0 +1,19 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:spell_failure" + }, + "description": { + "translate": "advancement.ebwizardry:spell_failure.desc" + }, + "icon": { + "item": "minecraft:pumpkin" + } + }, + "parent": "ebwizardry:discover_spell", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:trigger_spell_failure" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/visit_shrine.json b/src/main/resources/assets/ebwizardry/advancements/visit_shrine.json new file mode 100644 index 00000000..01060fb9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/visit_shrine.json @@ -0,0 +1,23 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:visit_shrine" + }, + "description": { + "translate": "advancement.ebwizardry:visit_shrine.desc" + }, + "icon": { + "item": "ebwizardry:runestone", + "data": 5 + } + }, + "parent": "ebwizardry:defeat_evil_wizard", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:visit_structure", + "conditions": { + "structure_type": "shrine" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/wizard_tower.json b/src/main/resources/assets/ebwizardry/advancements/wizard_tower.json new file mode 100644 index 00000000..2bdd8c0e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/wizard_tower.json @@ -0,0 +1,22 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:wizard_tower" + }, + "description": { + "translate": "advancement.ebwizardry:wizard_tower.desc" + }, + "icon": { + "item": "minecraft:bookshelf" + } + }, + "parent": "ebwizardry:arcane_initiate", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:visit_structure", + "conditions": { + "structure_type": "wizard_tower" + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/wizard_trade.json b/src/main/resources/assets/ebwizardry/advancements/wizard_trade.json index b5e6312d..f86668ab 100644 --- a/src/main/resources/assets/ebwizardry/advancements/wizard_trade.json +++ b/src/main/resources/assets/ebwizardry/advancements/wizard_trade.json @@ -1,16 +1,16 @@ { "display": { "title": { - "translate": "advancement.wizardry:wizard_trade" + "translate": "advancement.ebwizardry:wizard_trade" }, "description": { - "translate": "advancement.wizardry:wizard_trade.desc" + "translate": "advancement.ebwizardry:wizard_trade.desc" }, "icon": { "item": "minecraft:emerald" } }, - "parent": "ebwizardry:arcane_initiate", + "parent": "ebwizardry:wizard_tower", "criteria": { "criteria_0": { "trigger": "ebwizardry:trigger_wizard_trade" diff --git a/src/main/resources/assets/ebwizardry/blockstates/crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/crystal_block.json deleted file mode 100644 index e86e78b6..00000000 --- a/src/main/resources/assets/ebwizardry/blockstates/crystal_block.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "forge_marker": 1, - "variants": { - "normal": { "model": "ebwizardry:crystal_block" } - } -} diff --git a/src/main/resources/assets/ebwizardry/blockstates/dry_frosted_ice.json b/src/main/resources/assets/ebwizardry/blockstates/dry_frosted_ice.json new file mode 100644 index 00000000..6ed17a19 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/dry_frosted_ice.json @@ -0,0 +1,8 @@ +{ + "variants": { + "age=0": { "model": "minecraft:frosted_ice_0" }, + "age=1": { "model": "minecraft:frosted_ice_1" }, + "age=2": { "model": "minecraft:frosted_ice_2" }, + "age=3": { "model": "minecraft:frosted_ice_3" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/earth_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/earth_crystal_block.json new file mode 100644 index 00000000..b2e41d3a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/earth_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:earth_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/earth_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/earth_runestone.json new file mode 100644 index 00000000..235ad22c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/earth_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:earth_runestone_1", "uvlock": true }, + { "model": "ebwizardry:earth_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:earth_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:earth_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:earth_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:earth_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:earth_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:earth_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:earth_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/earth_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/earth_runestone_pedestal.json new file mode 100644 index 00000000..1eda064c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/earth_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:earth_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/fire_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/fire_crystal_block.json new file mode 100644 index 00000000..05979ee4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/fire_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:fire_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/fire_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/fire_runestone.json new file mode 100644 index 00000000..8fbbdbc1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/fire_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:fire_runestone_1", "uvlock": true }, + { "model": "ebwizardry:fire_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:fire_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:fire_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:fire_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:fire_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:fire_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:fire_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:fire_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/fire_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/fire_runestone_pedestal.json new file mode 100644 index 00000000..d7a37acc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/fire_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:fire_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/healing_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/healing_crystal_block.json new file mode 100644 index 00000000..37b3f3a6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/healing_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:healing_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/healing_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/healing_runestone.json new file mode 100644 index 00000000..c2af1a54 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/healing_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:healing_runestone_1", "uvlock": true }, + { "model": "ebwizardry:healing_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:healing_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:healing_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:healing_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:healing_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:healing_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:healing_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:healing_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/healing_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/healing_runestone_pedestal.json new file mode 100644 index 00000000..c1017a01 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/healing_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:healing_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/ice_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/ice_crystal_block.json new file mode 100644 index 00000000..711ad5d9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/ice_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:ice_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/ice_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/ice_runestone.json new file mode 100644 index 00000000..b50a395f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/ice_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:ice_runestone_1", "uvlock": true }, + { "model": "ebwizardry:ice_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:ice_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:ice_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:ice_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:ice_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:ice_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:ice_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:ice_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/ice_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/ice_runestone_pedestal.json new file mode 100644 index 00000000..d11cb9a1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/ice_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:ice_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/lightning_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/lightning_crystal_block.json new file mode 100644 index 00000000..5b8deac4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/lightning_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:lightning_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone.json new file mode 100644 index 00000000..9905b6ef --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true }, + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:lightning_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:lightning_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:lightning_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:lightning_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone_pedestal.json new file mode 100644 index 00000000..42616710 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/lightning_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:lightning_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/magic_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/magic_crystal_block.json new file mode 100644 index 00000000..f3f2353c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/magic_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:magic_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/necromancy_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/necromancy_crystal_block.json new file mode 100644 index 00000000..bfe3888a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/necromancy_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:necromancy_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone.json new file mode 100644 index 00000000..3541a5fd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true }, + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:necromancy_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:necromancy_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:necromancy_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:necromancy_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone_pedestal.json new file mode 100644 index 00000000..50c7ce28 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/necromancy_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:necromancy_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/obsidian_crust.json b/src/main/resources/assets/ebwizardry/blockstates/obsidian_crust.json new file mode 100644 index 00000000..1d21f4ff --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/obsidian_crust.json @@ -0,0 +1,8 @@ +{ + "variants": { + "age=0": { "model": "ebwizardry:obsidian_crust_0" }, + "age=1": { "model": "ebwizardry:obsidian_crust_1" }, + "age=2": { "model": "ebwizardry:obsidian_crust_2" }, + "age=3": { "model": "ebwizardry:obsidian_crust_3" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/sorcery_crystal_block.json b/src/main/resources/assets/ebwizardry/blockstates/sorcery_crystal_block.json new file mode 100644 index 00000000..77fbcb9a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/sorcery_crystal_block.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:sorcery_crystal_block" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone.json b/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone.json new file mode 100644 index 00000000..4e626b9d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone.json @@ -0,0 +1,31 @@ +{ + "forge_marker": 1, + "variants": { + "normal": [ + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true }, + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:sorcery_runestone_1", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:sorcery_runestone_2", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:sorcery_runestone_3", "uvlock": true, "x": 270 }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true, "y": 90 }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true, "y": 180 }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true, "y": 270 }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true, "x": 90 }, + { "model": "ebwizardry:sorcery_runestone_4", "uvlock": true, "x": 270 } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone_pedestal.json new file mode 100644 index 00000000..02e1d413 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/sorcery_runestone_pedestal.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:sorcery_runestone_pedestal" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/thorns.json b/src/main/resources/assets/ebwizardry/blockstates/thorns.json new file mode 100644 index 00000000..c60cd8cb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/thorns.json @@ -0,0 +1,20 @@ +{ + "variants": { + "age=0,half=lower": { "model": "ebwizardry:thorns_lower_0" }, + "age=1,half=lower": { "model": "ebwizardry:thorns_lower_1" }, + "age=2,half=lower": { "model": "ebwizardry:thorns_lower_2" }, + "age=3,half=lower": { "model": "ebwizardry:thorns_lower_3" }, + "age=4,half=lower": { "model": "ebwizardry:thorns_lower_4" }, + "age=5,half=lower": { "model": "ebwizardry:thorns_lower_5" }, + "age=6,half=lower": { "model": "ebwizardry:thorns_lower_6" }, + "age=7,half=lower": { "model": "ebwizardry:thorns_lower_7" }, + "age=0,half=upper": { "model": "ebwizardry:thorns_upper_0" }, + "age=1,half=upper": { "model": "ebwizardry:thorns_upper_1" }, + "age=2,half=upper": { "model": "ebwizardry:thorns_upper_2" }, + "age=3,half=upper": { "model": "ebwizardry:thorns_upper_3" }, + "age=4,half=upper": { "model": "ebwizardry:thorns_upper_4" }, + "age=5,half=upper": { "model": "ebwizardry:thorns_upper_5" }, + "age=6,half=upper": { "model": "ebwizardry:thorns_upper_6" }, + "age=7,half=upper": { "model": "ebwizardry:thorns_upper_7" } + } +} diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang index 720a3b1f..7e733c22 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang @@ -1,283 +1,515 @@ -tile.ebwizardry:arcane_workbench.name=Arcane Workbench -tile.ebwizardry:crystal_ore.name=Crystal Ore -tile.ebwizardry:petrified_stone.name=Petrified Stone -tile.ebwizardry:ice_statue.name=Ice Statue -tile.ebwizardry:crystal_flower.name=Crystal Flower -tile.ebwizardry:snare.name=Snare -tile.ebwizardry:transportation_stone.name=Stone of Transportation -tile.ebwizardry:spectral_block.name=Spectral Block -tile.ebwizardry:crystal_block.name=Block of Crystal +#PARSE_ESCAPES +tile.ebwizardry\:arcane_workbench.name=Arcane Workbench +tile.ebwizardry\:crystal_ore.name=Crystal Ore +tile.ebwizardry\:petrified_stone.name=Petrified Stone +tile.ebwizardry\:ice_statue.name=Ice Statue +tile.ebwizardry\:crystal_flower.name=Crystal Flower +tile.ebwizardry\:snare.name=Snare +tile.ebwizardry\:transportation_stone.name=Stone of Transportation +tile.ebwizardry\:transportation_stone.confirm=You will now be returned here upon casting %1$s +tile.ebwizardry\:transportation_stone.remember=Remembered the location %s, %s, %s in dimension %s +tile.ebwizardry\:transportation_stone.forget=Forgot the location %s, %s, %s in dimension %s +tile.ebwizardry\:transportation_stone.invalid=You must make a circle with 8 stones of transportation first! +tile.ebwizardry\:spectral_block.name=Spectral Block +tile.ebwizardry\:runestone.name=Runestone +tile.ebwizardry\:runestone_pedestal.name=Runestone Pedestal +tile.ebwizardry\:thorns.name=Thorns +tile.ebwizardry\:obsidian_crust.name=Obsidian Crust +tile.ebwizardry\:dry_frosted_ice.name=Dry Frosted Ice -item.ebwizardry:magic_crystal.name=Magic Crystal -item.ebwizardry:magic_wand.name=Magic Wand -item.ebwizardry:apprentice_wand.name=Apprentice Wand -item.ebwizardry:advanced_wand.name=Advanced Wand -item.ebwizardry:master_wand.name=Master Wand -item.ebwizardry:spell_book.name=Spell Book +tile.ebwizardry\:magic_crystal_block.name=Block of Crystal +tile.ebwizardry\:fire_crystal_block.name=Block of Fiery Crystal +tile.ebwizardry\:ice_crystal_block.name=Block of Icy Crystal +tile.ebwizardry\:lightning_crystal_block.name=Block of Stormy Crystal +tile.ebwizardry\:necromancy_crystal_block.name=Block of Dark Crystal +tile.ebwizardry\:earth_crystal_block.name=Block of Verdant Crystal +tile.ebwizardry\:sorcery_crystal_block.name=Block of Mystical Crystal +tile.ebwizardry\:healing_crystal_block.name=Block of Radiant Crystal -item.ebwizardry:arcane_tome.name=Tome of Arcana -item.ebwizardry:arcane_tome.desc1=Upgrades any %1$s -item.ebwizardry:arcane_tome.desc2=wand to %1$s tier +item.ebwizardry\:crystal_magic.name=Magic Crystal +item.ebwizardry\:crystal_fire.name=Fiery Crystal +item.ebwizardry\:crystal_ice.name=Icy Crystal +item.ebwizardry\:crystal_lightning.name=Stormy Crystal +item.ebwizardry\:crystal_necromancy.name=Dark Crystal +item.ebwizardry\:crystal_earth.name=Verdant Crystal +item.ebwizardry\:crystal_sorcery.name=Mystical Crystal +item.ebwizardry\:crystal_healing.name=Radiant Crystal -item.ebwizardry:wizard_handbook.name=The Wizard's Handbook -item.ebwizardry:wizard_handbook.desc=by %1$s +item.ebwizardry\:magic_wand.name=Magic Wand +item.ebwizardry\:apprentice_wand.name=Apprentice Wand +item.ebwizardry\:advanced_wand.name=Advanced Wand +item.ebwizardry\:master_wand.name=Master Wand +item.ebwizardry\:spell_book.name=Spell Book -item.ebwizardry:wand.buff=+%1$s %2$s potency -item.ebwizardry:wand.spell=Current Spell: %1$s -item.ebwizardry:wand.mana=Mana: %1$s/%2$s +item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s -item.ebwizardry:wand.addally=%1$s has been added to your list of allies -item.ebwizardry:wand.removeally=%1$s has been removed from your list of allies +item.ebwizardry\:arcane_tome.name=Tome of Arcana +item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s +item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier -item.ebwizardry:basic_fire_wand.name=Wand of Embers -item.ebwizardry:basic_ice_wand.name=Wand of Frost -item.ebwizardry:basic_lightning_wand.name=Wand of Sparks -item.ebwizardry:basic_necromancy_wand.name=Wand of Shadows -item.ebwizardry:basic_earth_wand.name=Wand of the Forest -item.ebwizardry:basic_sorcery_wand.name=Wand of Mystery -item.ebwizardry:basic_healing_wand.name=Wand of Healing +item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook +item.ebwizardry\:wizard_handbook.desc=by %1$s -item.ebwizardry:apprentice_fire_wand.name=Apprentice Pyromancer Wand -item.ebwizardry:apprentice_ice_wand.name=Apprentice Ice Mage Wand -item.ebwizardry:apprentice_lightning_wand.name=Apprentice Storm Mage Wand -item.ebwizardry:apprentice_necromancy_wand.name=Apprentice Necromancer Wand -item.ebwizardry:apprentice_earth_wand.name=Apprentice Earth Mage Wand -item.ebwizardry:apprentice_sorcery_wand.name=Apprentice Sorcerer Wand -item.ebwizardry:apprentice_healing_wand.name=Apprentice Healer Wand +item.ebwizardry\:wand.generic=wand -item.ebwizardry:advanced_fire_wand.name=Wand of the Pyromancer -item.ebwizardry:advanced_ice_wand.name=Wand of the Ice Mage -item.ebwizardry:advanced_lightning_wand.name=Wand of the Storm Mage -item.ebwizardry:advanced_necromancy_wand.name=Wand of the Necromancer -item.ebwizardry:advanced_earth_wand.name=Wand of the Earth Mage -item.ebwizardry:advanced_sorcery_wand.name=Wand of the Sorcerer -item.ebwizardry:advanced_healing_wand.name=Wand of the Healer +item.ebwizardry\:wand.buff=+%1$s %2$s potency +item.ebwizardry\:wand.spell=Current Spell\: %1$s +item.ebwizardry\:wand.mana=Mana\: %1$s/%2$s +item.ebwizardry\:wand.progression=Progression\: %1$s/%2$s -item.ebwizardry:master_fire_wand.name=Master Pyromancer Wand -item.ebwizardry:master_ice_wand.name=Master Ice Mage Wand -item.ebwizardry:master_lightning_wand.name=Master Storm Mage Wand -item.ebwizardry:master_necromancy_wand.name=Master Necromancer Wand -item.ebwizardry:master_earth_wand.name=Master Earth Mage Wand -item.ebwizardry:master_sorcery_wand.name=Master Sorcerer Wand -item.ebwizardry:master_healing_wand.name=Master Healer Wand +item.ebwizardry\:wand.levelup=%1$s is ready to upgrade to %2$s tier -item.ebwizardry:spectral_sword.name=Spectral Sword -item.ebwizardry:spectral_pickaxe.name=Spectral Pickaxe -item.ebwizardry:spectral_bow.name=Spectral Bow +item.ebwizardry\:wand.addally=%1$s has been added to your list of allies +item.ebwizardry\:wand.removeally=%1$s has been removed from your list of allies -item.ebwizardry:mana_flask.name=Mana Flask -item.ebwizardry:storage_upgrade.name=Wand Storage Upgrade -item.ebwizardry:siphon_upgrade.name=Wand Siphon Upgrade -item.ebwizardry:condenser_upgrade.name=Wand Condenser Upgrade -item.ebwizardry:range_upgrade.name=Wand Range Upgrade -item.ebwizardry:duration_upgrade.name=Wand Duration Upgrade -item.ebwizardry:cooldown_upgrade.name=Wand Cooldown Upgrade -item.ebwizardry:blast_upgrade.name=Wand Blast Upgrade -item.ebwizardry:attunement_upgrade.name=Wand Attunement Upgrade +item.ebwizardry\:novice_fire_wand.name=Wand of Embers +item.ebwizardry\:novice_ice_wand.name=Wand of Frost +item.ebwizardry\:novice_lightning_wand.name=Wand of Sparks +item.ebwizardry\:novice_necromancy_wand.name=Wand of Shadows +item.ebwizardry\:novice_earth_wand.name=Wand of the Forest +item.ebwizardry\:novice_sorcery_wand.name=Wand of Mystery +item.ebwizardry\:novice_healing_wand.name=Wand of Healing -item.ebwizardry:flaming_axe.name=Flaming Axe -item.ebwizardry:frost_axe.name=Frost Axe +item.ebwizardry\:apprentice_fire_wand.name=Apprentice Pyromancer Wand +item.ebwizardry\:apprentice_ice_wand.name=Apprentice Ice Mage Wand +item.ebwizardry\:apprentice_lightning_wand.name=Apprentice Storm Mage Wand +item.ebwizardry\:apprentice_necromancy_wand.name=Apprentice Necromancer Wand +item.ebwizardry\:apprentice_earth_wand.name=Apprentice Earth Mage Wand +item.ebwizardry\:apprentice_sorcery_wand.name=Apprentice Sorcerer Wand +item.ebwizardry\:apprentice_healing_wand.name=Apprentice Healer Wand -item.ebwizardry:firebomb.name=Firebomb -item.ebwizardry:poison_bomb.name=Poison Bomb -item.ebwizardry:smoke_bomb.name=Smoke Bomb +item.ebwizardry\:advanced_fire_wand.name=Wand of the Pyromancer +item.ebwizardry\:advanced_ice_wand.name=Wand of the Ice Mage +item.ebwizardry\:advanced_lightning_wand.name=Wand of the Storm Mage +item.ebwizardry\:advanced_necromancy_wand.name=Wand of the Necromancer +item.ebwizardry\:advanced_earth_wand.name=Wand of the Earth Mage +item.ebwizardry\:advanced_sorcery_wand.name=Wand of the Sorcerer +item.ebwizardry\:advanced_healing_wand.name=Wand of the Healer -item.ebwizardry:blank_scroll.name=Blank Scroll -item.ebwizardry:scroll.name=Scroll of %1$s -item.ebwizardry:scroll.undiscovered.name=Scroll "%1$s" -item.ebwizardry:identification_scroll.name=Scroll of Identification -item.ebwizardry:identification_scroll.desc1=%1$sIdentifies an unknown -item.ebwizardry:identification_scroll.desc2=%1$sspell book or scroll -item.ebwizardry:identification_scroll.nothing_to_identify=Nothing to identify! +item.ebwizardry\:master_fire_wand.name=Master Pyromancer Wand +item.ebwizardry\:master_ice_wand.name=Master Ice Mage Wand +item.ebwizardry\:master_lightning_wand.name=Master Storm Mage Wand +item.ebwizardry\:master_necromancy_wand.name=Master Necromancer Wand +item.ebwizardry\:master_earth_wand.name=Master Earth Mage Wand +item.ebwizardry\:master_sorcery_wand.name=Master Sorcerer Wand +item.ebwizardry\:master_healing_wand.name=Master Healer Wand -item.ebwizardry:armour_upgrade.name=Arcane Seal of Protection -item.ebwizardry:armour_upgrade.desc1=%1$sUpgrades any wizard armour -item.ebwizardry:armour_upgrade.desc2=%1$sto make it %2$slegendary +item.ebwizardry\:spectral_sword.name=Spectral Sword +item.ebwizardry\:spectral_pickaxe.name=Spectral Pickaxe +item.ebwizardry\:spectral_bow.name=Spectral Bow -item.ebwizardry:magic_silk.name=Magical Silk +item.ebwizardry\:spectral_sword_upgraded.name=Spectral Sword +item.ebwizardry\:spectral_pickaxe_upgraded.name=Spectral Pickaxe -item.ebwizardry:wizard_armour.legendary=Legendary -item.ebwizardry:wizard_armour.buff=-%1$s %2$s cost -item.ebwizardry:wizard_armour.mana=Mana: %1$s/%2$s +item.ebwizardry\:small_mana_flask.name=Small Mana Flask +item.ebwizardry\:medium_mana_flask.name=Medium Mana Flask +item.ebwizardry\:large_mana_flask.name=Large Mana Flask -item.ebwizardry:wizard_hat.name=Wizard Hat -item.ebwizardry:wizard_robe.name=Wizard Robes -item.ebwizardry:wizard_leggings.name=Wizard Leggings -item.ebwizardry:wizard_boots.name=Wizard Boots +item.ebwizardry\:grand_crystal.name=Grand Magic Crystal +item.ebwizardry\:crystal_shard.name=Magic Crystal Shard -item.ebwizardry:wizard_hat_fire.name=Pyromancer Hat -item.ebwizardry:wizard_robe_fire.name=Pyromancer Robes -item.ebwizardry:wizard_leggings_fire.name=Pyromancer Leggings -item.ebwizardry:wizard_boots_fire.name=Pyromancer Boots +item.ebwizardry\:astral_diamond.name=Astral Diamond -item.ebwizardry:wizard_hat_ice.name=Ice Mage Hat -item.ebwizardry:wizard_robe_ice.name=Ice Mage Robes -item.ebwizardry:wizard_leggings_ice.name=Ice Mage Leggings -item.ebwizardry:wizard_boots_ice.name=Ice Mage Boots +item.ebwizardry\:purifying_elixir.name=Purifying Elixir +item.ebwizardry\:purifying_elixir.desc=Removes curses when consumed -item.ebwizardry:wizard_hat_lightning.name=Storm Mage Hat -item.ebwizardry:wizard_robe_lightning.name=Storm Mage Robes -item.ebwizardry:wizard_leggings_lightning.name=Storm Mage Leggings -item.ebwizardry:wizard_boots_lightning.name=Storm Mage Boots +item.ebwizardry\:storage_upgrade.name=Wand Storage Upgrade +item.ebwizardry\:siphon_upgrade.name=Wand Siphon Upgrade +item.ebwizardry\:condenser_upgrade.name=Wand Condenser Upgrade +item.ebwizardry\:range_upgrade.name=Wand Range Upgrade +item.ebwizardry\:duration_upgrade.name=Wand Duration Upgrade +item.ebwizardry\:cooldown_upgrade.name=Wand Cooldown Upgrade +item.ebwizardry\:blast_upgrade.name=Wand Blast Upgrade +item.ebwizardry\:attunement_upgrade.name=Wand Attunement Upgrade +item.ebwizardry\:melee_upgrade.name=Wand Melee Upgrade -item.ebwizardry:wizard_hat_necromancy.name=Necromancer Hat -item.ebwizardry:wizard_robe_necromancy.name=Necromancer Robes -item.ebwizardry:wizard_leggings_necromancy.name=Necromancer Leggings -item.ebwizardry:wizard_boots_necromancy.name=Necromancer Boots +item.ebwizardry\:storage_upgrade.desc=Upgrades the mana capacity of a wand +item.ebwizardry\:siphon_upgrade.desc=Upgrades a wand to extract mana from mobs when killed +item.ebwizardry\:condenser_upgrade.desc=Upgrades a wand to slowly regenerate mana over time +item.ebwizardry\:range_upgrade.desc=Upgrades the effective range of spells cast by a wand +item.ebwizardry\:duration_upgrade.desc=Upgrades the duration of effects cast by a wand +item.ebwizardry\:cooldown_upgrade.desc=Upgrades the spell cooldown speed of a wand +item.ebwizardry\:blast_upgrade.desc=Upgrades the area of effect of spells cast by a wand +item.ebwizardry\:attunement_upgrade.desc=Upgrades the number of spells that can be bound to a wand +item.ebwizardry\:melee_upgrade.desc=Upgrades a wand to use mana to deal more damage to mobs when hit -item.ebwizardry:wizard_hat_earth.name=Earth Mage Hat -item.ebwizardry:wizard_robe_earth.name=Earth Mage Robes -item.ebwizardry:wizard_leggings_earth.name=Earth Mage Leggings -item.ebwizardry:wizard_boots_earth.name=Earth Mage Boots +item.ebwizardry\:flaming_axe.name=Flaming Axe +item.ebwizardry\:frost_axe.name=Frost Axe -item.ebwizardry:wizard_hat_sorcery.name=Sorcerer Hat -item.ebwizardry:wizard_robe_sorcery.name=Sorcerer Robes -item.ebwizardry:wizard_leggings_sorcery.name=Sorcerer Leggings -item.ebwizardry:wizard_boots_sorcery.name=Sorcerer Boots +item.ebwizardry\:flaming_axe_upgraded.name=Flaming Axe +item.ebwizardry\:frost_axe_upgraded.name=Frost Axe -item.ebwizardry:wizard_hat_healing.name=Healer Hat -item.ebwizardry:wizard_robe_healing.name=Healer Robes -item.ebwizardry:wizard_leggings_healing.name=Healer Leggings -item.ebwizardry:wizard_boots_healing.name=Healer Boots +item.ebwizardry\:firebomb.name=Firebomb +item.ebwizardry\:poison_bomb.name=Poison Bomb +item.ebwizardry\:smoke_bomb.name=Smoke Bomb +item.ebwizardry\:spark_bomb.name=Spark Bomb -item.ebwizardry:spawn_wizard.name=Spawn Wizard -item.ebwizardry:spawn_evil_wizard.name=Spawn Evil Wizard +item.ebwizardry\:blank_scroll.name=Blank Scroll +item.ebwizardry\:scroll.generic=scroll +item.ebwizardry\:scroll.name=Scroll of %1$s +item.ebwizardry\:scroll.undiscovered.name=Scroll "%1$s" +item.ebwizardry\:identification_scroll.name=Scroll of Identification +item.ebwizardry\:identification_scroll.desc=Identifies an unknown spell book or scroll +item.ebwizardry\:identification_scroll.nothing_to_identify=Nothing to identify! -item.ebwizardry:spectral_helmet.name=Spectral Helmet -item.ebwizardry:spectral_chestplate.name=Spectral Chestplate -item.ebwizardry:spectral_leggings.name=Spectral Leggings -item.ebwizardry:spectral_boots.name=Spectral Boots +item.ebwizardry\:armour_upgrade.name=Arcane Seal of Protection +item.ebwizardry\:armour_upgrade.desc1=%1$sUpgrades any wizard armour +item.ebwizardry\:armour_upgrade.desc2=%1$sto make it %2$slegendary -entity.ebwizardry:summonedcreature.nameplate=%1$s's %2$s -entity.ebwizardry:summonedcreature.nameplate_fallback=Someone's %1$s +item.ebwizardry\:magic_silk.name=Magical Silk -entity.ebwizardry:zombie_minion.name=Zombie -entity.ebwizardry:skeleton_minion.name=Skeleton -entity.ebwizardry:spider_minion.name=Spider -entity.ebwizardry:blaze_minion.name=Blaze -entity.ebwizardry:wither_skeleton_minion.name=Wither Skeleton -entity.ebwizardry:ice_wraith.name=Ice Wraith -entity.ebwizardry:lightning_wraith.name=Lightning Wraith -entity.ebwizardry:shadow_wraith.name=Shadow Wraith -entity.ebwizardry:spirit_wolf.name=Spirit Wolf -entity.ebwizardry:spirit_horse.name=Spirit Horse -entity.ebwizardry:ice_giant.name=Ice Giant -entity.ebwizardry:phoenix.name=Phoenix -entity.ebwizardry:wizard.name=Wizard -entity.ebwizardry:magic_slime.name=Magical Slime -entity.ebwizardry:silverfish_minion.name=Silverfish -entity.ebwizardry:storm_elemental.name=Storm Elemental -entity.ebwizardry:evil_wizard.name=Wizard -entity.ebwizardry:decoy.name=Decoy +item.ebwizardry\:wizard_armour.legendary=Legendary +item.ebwizardry\:wizard_armour.buff=-%1$s %2$s cost +item.ebwizardry\:wizard_armour.mana=Mana\: %1$s/%2$s -entity.ebwizardry:magic_missile.name=Magic -entity.ebwizardry:arc.name=Magic -entity.ebwizardry:spark_bomb.name=Magic -entity.ebwizardry:ice_shard.name=Magic -entity.ebwizardry:firebomb.name=Magic -entity.ebwizardry:poison_bomb.name=Magic -entity.ebwizardry:force_orb.name=Magic -entity.ebwizardry:spark.name=Magic -entity.ebwizardry:darkness_orb.name=Magic -entity.ebwizardry:fire_sigil.name=Magic -entity.ebwizardry:frost_sigil.name=Magic -entity.ebwizardry:lightning_sigil.name=Magic -entity.ebwizardry:lightning_arrow.name=Magic -entity.ebwizardry:firebolt.name=Magic -entity.ebwizardry:ice_charge.name=Magic -entity.ebwizardry:force_arrow.name=Magic -entity.ebwizardry:dart.name=Magic -entity.ebwizardry:lightning_disc.name=Magic -entity.ebwizardry:thunderbolt.name=Magic -entity.ebwizardry:decay.name=Magic -entity.ebwizardry:ice_lance.name=Magic -entity.ebwizardry:smoke_bomb.name=Magic -entity.ebwizardry:ice_spike.name=Magic +item.ebwizardry\:wizard_hat.name=Wizard Hat +item.ebwizardry\:wizard_robe.name=Wizard Robes +item.ebwizardry\:wizard_leggings.name=Wizard Leggings +item.ebwizardry\:wizard_boots.name=Wizard Boots -entity.ebwizardry:black_hole.name=Black Hole -entity.ebwizardry:shield.name=Shield -entity.ebwizardry:meteor.name=Meteor -entity.ebwizardry:blizzard.name=Blizzard -entity.ebwizardry:bubble.name=Bubble -entity.ebwizardry:tornado.name=Tornado -entity.ebwizardry:lightning_hammer.name=Lightning Hammer -entity.ebwizardry:arrow_rain.name=Arrow Rain -entity.ebwizardry:healing_aura.name=Healing Aura -entity.ebwizardry:forcefield.name=Forcefield -entity.ebwizardry:ring_of_fire.name=Ring of Fire -entity.ebwizardry:earthquake.name=Earthquake -entity.ebwizardry:falling_grass.name=Falling Grass -entity.ebwizardry:hailstorm.name=Hailstorm -entity.ebwizardry:lightning_pulse.name=Lightning Pulse +item.ebwizardry\:wizard_hat_fire.name=Pyromancer Hat +item.ebwizardry\:wizard_robe_fire.name=Pyromancer Robes +item.ebwizardry\:wizard_leggings_fire.name=Pyromancer Leggings +item.ebwizardry\:wizard_boots_fire.name=Pyromancer Boots -item_group.ebwizardry=Wizardry -item_group.wizardryspells=Spells +item.ebwizardry\:wizard_hat_ice.name=Ice Mage Hat +item.ebwizardry\:wizard_robe_ice.name=Ice Mage Robes +item.ebwizardry\:wizard_leggings_ice.name=Ice Mage Leggings +item.ebwizardry\:wizard_boots_ice.name=Ice Mage Boots -advancement.wizardry:root=Wizardry -advancement.wizardry:root.desc=A wizard's journey to mastering the arcane -advancement.wizardry:crystal=A Curious Crystal... -advancement.wizardry:crystal.desc=Mine a magic crystal -advancement.wizardry:arcane_initiate=Arcane Initiate -advancement.wizardry:arcane_initiate.desc=Craft a magic wand with a gold nugget, a stick and a magic crystal -advancement.wizardry:apprentice=Wizard's Apprentice -advancement.wizardry:apprentice.desc=Use a tome of arcana to upgrade your wand -advancement.wizardry:master=Arcane Master -advancement.wizardry:master.desc=Obtain a master wand -advancement.wizardry:all_spells=Mage of All Trades -advancement.wizardry:all_spells.desc=Cast every single spell in the game -advancement.wizardry:wizard_trade=Magic Dealing -advancement.wizardry:wizard_trade.desc=Purchase an item from a wizard -advancement.wizardry:buy_master_spell=Knowledge is Power -advancement.wizardry:buy_master_spell.desc=Purchase a master spell from a wizard -advancement.wizardry:freeze_blaze=Not So Hot Now -advancement.wizardry:freeze_blaze.desc=Freeze a blaze solid -advancement.wizardry:charge_creeper=It's Gonna Blow -advancement.wizardry:charge_creeper.desc='Accidentally' charge a creeper -advancement.wizardry:frankenstein=Frankenstein -advancement.wizardry:frankenstein.desc=Turn a pig into a zombie pigman using the lightning bolt spell -advancement.wizardry:special_upgrade=Arcane Tinkering -advancement.wizardry:special_upgrade.desc=Apply a special upgrade to a wand -advancement.wizardry:craft_flask=It's Magic, Bottled! -advancement.wizardry:craft_flask.desc=Craft a mana flask -advancement.wizardry:elemental=Elemental -advancement.wizardry:elemental.desc=Obtain an elemental wand -advancement.wizardry:armour_set=Now You're a Proper Wizard -advancement.wizardry:armour_set.desc=Craft and equip a full set of wizard armour -advancement.wizardry:legendary=Legendary -advancement.wizardry:legendary.desc=Obtain a piece of legendary wizard armour -advancement.wizardry:self_destruct=That Backfired -advancement.wizardry:self_destruct.desc=Get killed by your own magic -advancement.wizardry:pig_tornado=Not Again... -advancement.wizardry:pig_tornado.desc=Ride a pig into a tornado -advancement.wizardry:jam_wizard=Jamming Session -advancement.wizardry:jam_wizard.desc=Use the arcane jammer spell on a wizard -advancement.wizardry:slime_skeleton=Sticky Situation -advancement.wizardry:slime_skeleton.desc=Engulf a skeleton in slime -advancement.wizardry:anger_wizard=You'll Regret That -advancement.wizardry:anger_wizard.desc=Make a wizard angry -advancement.wizardry:defeat_evil_wizard=Righteousness -advancement.wizardry:defeat_evil_wizard.desc=Defeat an evil wizard -advancement.wizardry:max_out_wand=Fully Equipped -advancement.wizardry:max_out_wand.desc=Apply the maximum number of upgrades to a master wand -advancement.wizardry:element_master=Element Mastery -advancement.wizardry:element_master.desc=Cast all the spells of any element -advancement.wizardry:identify_spell=Arcane Appraisal -advancement.wizardry:identify_spell.desc=Use a scroll of identification to identify a spell book or scroll +item.ebwizardry\:wizard_hat_lightning.name=Storm Mage Hat +item.ebwizardry\:wizard_robe_lightning.name=Storm Mage Robes +item.ebwizardry\:wizard_leggings_lightning.name=Storm Mage Leggings +item.ebwizardry\:wizard_boots_lightning.name=Storm Mage Boots -tile.ebwizardry:transportation_stone.confirm=You will now be returned here upon casting %1$s -tile.ebwizardry:transportation_stone.invalid=You must make a circle with 8 stones of transportation first! +item.ebwizardry\:wizard_hat_necromancy.name=Necromancer Hat +item.ebwizardry\:wizard_robe_necromancy.name=Necromancer Robes +item.ebwizardry\:wizard_leggings_necromancy.name=Necromancer Leggings +item.ebwizardry\:wizard_boots_necromancy.name=Necromancer Boots -container.ebwizardry:arcane_workbench=Arcane Workbench -container.ebwizardry:arcane_workbench.apply=Apply -container.ebwizardry:arcane_workbench.mana=Mana: -container.ebwizardry:arcane_workbench.upgrades=Applied Upgrades: +item.ebwizardry\:wizard_hat_earth.name=Earth Mage Hat +item.ebwizardry\:wizard_robe_earth.name=Earth Mage Robes +item.ebwizardry\:wizard_leggings_earth.name=Earth Mage Leggings +item.ebwizardry\:wizard_boots_earth.name=Earth Mage Boots -tier.basic=Novice +item.ebwizardry\:wizard_hat_sorcery.name=Sorcerer Hat +item.ebwizardry\:wizard_robe_sorcery.name=Sorcerer Robes +item.ebwizardry\:wizard_leggings_sorcery.name=Sorcerer Leggings +item.ebwizardry\:wizard_boots_sorcery.name=Sorcerer Boots + +item.ebwizardry\:wizard_hat_healing.name=Healer Hat +item.ebwizardry\:wizard_robe_healing.name=Healer Robes +item.ebwizardry\:wizard_leggings_healing.name=Healer Leggings +item.ebwizardry\:wizard_boots_healing.name=Healer Boots + +item.ebwizardry\:spawn_wizard.name=Spawn Wizard +item.ebwizardry\:spawn_evil_wizard.name=Spawn Evil Wizard + +item.ebwizardry\:spectral_helmet.name=Spectral Helmet +item.ebwizardry\:spectral_chestplate.name=Spectral Chestplate +item.ebwizardry\:spectral_leggings.name=Spectral Leggings +item.ebwizardry\:spectral_boots.name=Spectral Boots + +item.ebwizardry\:lightning_hammer.name=Lightning Hammer + +item.ebwizardry\:ring_condensing.name=Ring of Condensing +item.ebwizardry\:ring_condensing.desc=Slowly regenerates mana for all wands on your hotbar +item.ebwizardry\:ring_siphoning.name=Ring of Siphoning +item.ebwizardry\:ring_siphoning.desc=Increases siphoned mana by 30%% +item.ebwizardry\:ring_battlemage.name=Ring of the Battlemage +item.ebwizardry\:ring_battlemage.desc=Holding a wand in your offhand and a sword in your main hand grants 10%% extra magic damage +item.ebwizardry\:ring_combustion.name=Ring of Combustion +item.ebwizardry\:ring_combustion.desc=Creatures killed by fire spells explode +item.ebwizardry\:ring_fire_melee.name=Ring of Fiery Touch +item.ebwizardry\:ring_fire_melee.desc=Hitting a creature with a fire wand sets it on fire +item.ebwizardry\:ring_fire_biome.name=Ring of the Desert Sun +item.ebwizardry\:ring_fire_biome.desc=Fire spells are 30%% more potent in hot biomes +item.ebwizardry\:ring_disintegration.name=Ring of Searing Embers +item.ebwizardry\:ring_disintegration.desc=All fire attack spells cause their victims to disintegrate +item.ebwizardry\:ring_ice_melee.name=Ring of Icy Touch +item.ebwizardry\:ring_ice_melee.desc=Hitting a creature with an ice wand gives it the frostbite effect +item.ebwizardry\:ring_ice_biome.name=Ring of Glaciation +item.ebwizardry\:ring_ice_biome.desc=Ice spells are 30%% more potent in snowy biomes +item.ebwizardry\:ring_arcane_frost.name=Ring of Arcane Frost +item.ebwizardry\:ring_arcane_frost.desc=Creatures killed by ice spells release ice shards in all directions +item.ebwizardry\:ring_shattering.name=Ring of Shattering +item.ebwizardry\:ring_shattering.desc=Melee attacks on mobs with the frostbite effect have a chance to shatter them into ice shards +item.ebwizardry\:ring_lightning_melee.name=Ring of Chaining +item.ebwizardry\:ring_lightning_melee.desc=Hitting a creature with a lightning wand shoots lightning at another nearby creature +item.ebwizardry\:ring_storm.name=Ring of the Gathering Storm +item.ebwizardry\:ring_storm.desc=During thunderstorms, lightning spells have dramatically reduced cooldowns +item.ebwizardry\:ring_seeking.name=Ring of Attraction +item.ebwizardry\:ring_seeking.desc=All projectile spells seek their targets +item.ebwizardry\:ring_hammer.name=Ring of Thalek the Almighty +item.ebwizardry\:ring_hammer.desc=Lightning hammers can be picked up and thrown +item.ebwizardry\:ring_soulbinding.name=Soulwalker's Ring +item.ebwizardry\:ring_soulbinding.desc=Creatures damaged by necromancy spells become soulbound to you +item.ebwizardry\:ring_leeching.name=Ring of Leeching +item.ebwizardry\:ring_leeching.desc=All necromancy attacks have a 30%% chance to trigger a life drain effect +item.ebwizardry\:ring_necromancy_melee.name=Ring of Necrotic Touch +item.ebwizardry\:ring_necromancy_melee.desc=Hitting a creature with a necromancy wand gives it the wither effect +item.ebwizardry\:ring_mind_control.name=Ring of the Psychic +item.ebwizardry\:ring_mind_control.desc=Mind-controlled creatures have a chance to mind control other nearby creatures +item.ebwizardry\:ring_poison.name=Serpentine Ring +item.ebwizardry\:ring_poison.desc=All earth spells poison their target +item.ebwizardry\:ring_earth_melee.name=Ring of Venomous Touch +item.ebwizardry\:ring_earth_melee.desc=Hitting a creature with an earth wand poisons it +item.ebwizardry\:ring_earth_biome.name=Dryad's Ring +item.ebwizardry\:ring_earth_biome.desc=Earth spells are 30%% more potent in forests and roofed forests +item.ebwizardry\:ring_full_moon.name=Ring of the Howling Wolf +item.ebwizardry\:ring_full_moon.desc=Earth spells have dramatically reduced cooldowns under a full moon +item.ebwizardry\:ring_extraction.name=Ring of Extraction +item.ebwizardry\:ring_extraction.desc=Kills with sorcery spells grant bonus mana +item.ebwizardry\:ring_mana_return.name=Ring of the Perfectionist +item.ebwizardry\:ring_mana_return.desc=Force arrows that miss their target return the mana they used to your wand +item.ebwizardry\:ring_blockwrangler.name=Blockwrangler's Ring +item.ebwizardry\:ring_blockwrangler.desc=Thrown blocks deal twice their normal damage +item.ebwizardry\:ring_conjurer.name=Conjurer's Ring +item.ebwizardry\:ring_conjurer.desc=Conjured items last twice as long +item.ebwizardry\:ring_defender.name=Ring of the Defender +item.ebwizardry\:ring_defender.desc=Your projectiles pass through forcefields belonging to you or an ally +item.ebwizardry\:ring_paladin.name=Paladin's Ring +item.ebwizardry\:ring_paladin.desc=When you heal yourself or an ally, nearby allies will also gain some health +item.ebwizardry\:ring_interdiction.name=Ring of Interdiction +item.ebwizardry\:ring_interdiction.desc=Your forcefield damages creatures that touch it + +item.ebwizardry\:amulet_arcane_defence.name=Amulet of Arcane Defence +item.ebwizardry\:amulet_arcane_defence.desc=Slowly regenerates mana for all worn wizard armour +item.ebwizardry\:amulet_warding.name=Amulet of Warding +item.ebwizardry\:amulet_warding.desc=Reduces all incoming magic damage by 10%% +item.ebwizardry\:amulet_wisdom.name=Amulet of Wisdom +item.ebwizardry\:amulet_wisdom.desc=Greatly reduces the chance of negative effects when using undiscovered spells +item.ebwizardry\:amulet_fire_protection.name=Flamefast Amulet +item.ebwizardry\:amulet_fire_protection.desc=Reduces fire damage by 30%% +item.ebwizardry\:amulet_fire_cloaking.name=Amulet of Cloaking Flame +item.ebwizardry\:amulet_fire_cloaking.desc=Reduces incoming damage by 75%% whilst standing within a ring of fire belonging to you or an ally +item.ebwizardry\:amulet_ice_immunity.name=Permafrost Amulet +item.ebwizardry\:amulet_ice_immunity.desc=Grants total immunity to frostbite effects +item.ebwizardry\:amulet_ice_protection.name=Frostbound Amulet +item.ebwizardry\:amulet_ice_protection.desc=Reduces frost damage by 30%% +item.ebwizardry\:amulet_potential.name=Amulet of Potential +item.ebwizardry\:amulet_potential.desc=Grants a 15%% chance to shoot lightning at creatures that melee attack you +item.ebwizardry\:amulet_channeling.name=Amulet of Channeling +item.ebwizardry\:amulet_channeling.desc=Grants a 30%% chance to negate incoming shock damage +item.ebwizardry\:amulet_lich.name=Amulet of the Lich +item.ebwizardry\:amulet_lich.desc=Grants a 15%% chance for incoming damage to be dealt to a nearby minion instead +item.ebwizardry\:amulet_wither_immunity.name=Wither Pearl Amulet +item.ebwizardry\:amulet_wither_immunity.desc=Grants total immunity to wither effects +item.ebwizardry\:amulet_glide.name=Windfeather Amulet +item.ebwizardry\:amulet_glide.desc=Falling more than 3 blocks has a 50%% chance to activate the glide effect +item.ebwizardry\:amulet_banishing.name=Enderbound Amulet +item.ebwizardry\:amulet_banishing.desc=Creatures that melee attack you have a 15%% chance to be teleported to a random nearby location +item.ebwizardry\:amulet_anchoring.name=Amulet of Anchoring +item.ebwizardry\:amulet_anchoring.desc=Grants immunity to being moved by magic +item.ebwizardry\:amulet_recovery.name=Amulet of Recovery +item.ebwizardry\:amulet_recovery.desc=When on less than 50%% health, mana from your armour will be used to heal you over time +item.ebwizardry\:amulet_transience.name=Amulet of Transience +item.ebwizardry\:amulet_transience.desc=Grants a 25%% chance to activate a transience effect when critically wounded +item.ebwizardry\:amulet_resurrection.name=Amulet of the Immortal +item.ebwizardry\:amulet_resurrection.desc=The resurrection spell can be used on yourself from beyond the grave +item.ebwizardry\:amulet_auto_shield.name=Hyeleth's Amulet +item.ebwizardry\:amulet_auto_shield.desc=Grants a 25%% chance to trigger the shield spell for a few seconds when bound to a wand on your hotbar + +item.ebwizardry\:charm_haggler.name=Haggler's Sign +item.ebwizardry\:charm_haggler.desc=Wizards are guaranteed to offer a new trade every time you trade with them +item.ebwizardry\:charm_experience_tome.name=Tome of the Diligent +item.ebwizardry\:charm_experience_tome.desc=Increases the rate at which wands gain progression by 40%% +item.ebwizardry\:charm_auto_smelt.name=Metallurgist's Mark +item.ebwizardry\:charm_auto_smelt.desc=Pocket furnace triggers automatically when bound to a wand on your hotbar +item.ebwizardry\:charm_lava_walking.name=Nether Ice Core +item.ebwizardry\:charm_lava_walking.desc=Frost step freezes lava to form an obsidian crust +item.ebwizardry\:charm_storm.name=Bottled Thundercloud +item.ebwizardry\:charm_storm.desc=Invoke weather is guaranteed to summon storms +item.ebwizardry\:charm_minion_health.name=Obsidian Zombie Head +item.ebwizardry\:charm_minion_health.desc=Summoned creatures are 25%% stronger +item.ebwizardry\:charm_minion_variants.name=Talisman of Transformation +item.ebwizardry\:charm_minion_variants.desc=Summoned zombies are husks; summoned skeletons are strays +item.ebwizardry\:charm_flight.name=Emerald Beetle Wing +item.ebwizardry\:charm_flight.desc=Flight and Glide are 50%% faster +item.ebwizardry\:charm_growth.name=Crystal Flower Charm +item.ebwizardry\:charm_growth.desc=Growth aura has a 35%% chance to instantly grow crops to fully-grown +item.ebwizardry\:charm_abseiling.name=Enchanted Twine +item.ebwizardry\:charm_abseiling.desc=Holding sneak whilst grappling slowly pays out the line +item.ebwizardry\:charm_silk_touch.name=Moonstone Orb +item.ebwizardry\:charm_silk_touch.desc=Blocks broken with the mine spell always drop themselves +item.ebwizardry\:charm_stop_time.name=Peculiar Pocketwatch +item.ebwizardry\:charm_stop_time.desc=The slow time spell makes time stand still +item.ebwizardry\:charm_light.name=Elevinia's Everburning Lantern +item.ebwizardry\:charm_light.desc=Conjured light sources last forever and may be dispelled by right-clicking with a wand +item.ebwizardry\:charm_transportation.name=Ancient Compass +item.ebwizardry\:charm_transportation.desc=Up to four stone circles may be remembered and selected from when using transportation +item.ebwizardry\:charm_feeding.name=Bottomless Provisions +item.ebwizardry\:charm_feeding.desc=Replenish hunger triggers automatically when bound to a wand on your hotbar + +item.charge_status.full=Full +item.charge_status.almost_full=Almost full +item.charge_status.mostly_full=Mostly full +item.charge_status.half_full=Half full +item.charge_status.mostly_empty=Mostly empty +item.charge_status.almost_empty=Almost empty +item.charge_status.empty=Empty + +entity.ebwizardry\:summonedcreature.nameplate=%1$s's %2$s +entity.ebwizardry\:summonedcreature.nameplate_fallback=Someone's %1$s + +entity.ebwizardry\:wizard.greeting_0=Good day, fellow wanderer. +entity.ebwizardry\:wizard.greeting_1=Greetings, traveller. What brings you here? +entity.ebwizardry\:wizard.greeting_2=Ah, nice to see another person around here. +entity.ebwizardry\:wizard.speech_0=Might I interest you in any spells, perhaps? +entity.ebwizardry\:wizard.speech_1=Magic is everywhere, if you know where to look. +entity.ebwizardry\:wizard.speech_2=There is still much to be learned about the arcane arts, adventurer. +entity.ebwizardry\:wizard.speech_3=Perhaps you have learned something yourself that you wish to share? +entity.ebwizardry\:wizard.speech_4=Studying the arcane is most fascinating, don't you think? +entity.ebwizardry\:wizard.farewell_0=Good luck in your quest, adventurer. +entity.ebwizardry\:wizard.farewell_1=I trust that we will meet again soon, friend. +entity.ebwizardry\:wizard.farewell_2=Goodbye then, traveller. +entity.ebwizardry\:wizard.combat_0=Be gone, foul creatures! +entity.ebwizardry\:wizard.combat_1=Leave me alone, pests! +entity.ebwizardry\:wizard.combat_2=Undead beings are not welcome here, shoo! +entity.ebwizardry\:wizard.combat_3=Away with you, creatures of darkness! +entity.ebwizardry\:wizard.combat_4=This is all I need! Get out of here, monsters! +entity.ebwizardry\:wizard.combat_5=Return to the caves from whence you came, evil creatures! +entity.ebwizardry\:wizard.player_combat_0=You will regret that decision, traveller! +entity.ebwizardry\:wizard.player_combat_1=What do you think you are doing?! +entity.ebwizardry\:wizard.player_combat_2=Only a fool dares to anger a wizard! +entity.ebwizardry\:wizard.player_combat_3=You will pay for your carelessness, adventurer! +entity.ebwizardry\:wizard.player_combat_4=Be ready to defend yourself, villain! +entity.ebwizardry\:wizard.player_combat_5=Prepare to feel my wrath! + +entity.ebwizardry\:zombie_minion.name=Zombie +entity.ebwizardry\:husk_minion.name=Husk +entity.ebwizardry\:skeleton_minion.name=Skeleton +entity.ebwizardry\:stray_minion.name=Stray +entity.ebwizardry\:spider_minion.name=Spider +entity.ebwizardry\:blaze_minion.name=Blaze +entity.ebwizardry\:wither_skeleton_minion.name=Wither Skeleton +entity.ebwizardry\:ice_wraith.name=Ice Wraith +entity.ebwizardry\:lightning_wraith.name=Lightning Wraith +entity.ebwizardry\:shadow_wraith.name=Shadow Wraith +entity.ebwizardry\:spirit_wolf.name=Spirit Wolf +entity.ebwizardry\:spirit_horse.name=Spirit Horse +entity.ebwizardry\:ice_giant.name=Ice Giant +entity.ebwizardry\:phoenix.name=Phoenix +entity.ebwizardry\:wizard.name=Wizard +entity.ebwizardry\:magic_slime.name=Magical Slime +entity.ebwizardry\:silverfish_minion.name=Silverfish +entity.ebwizardry\:storm_elemental.name=Storm Elemental +entity.ebwizardry\:evil_wizard.name=Wizard +entity.ebwizardry\:decoy.name=Decoy +entity.ebwizardry\:vex_minion.name=Vex + +entity.ebwizardry\:magic_missile.name=Magic +entity.ebwizardry\:arc.name=Magic +entity.ebwizardry\:spark_bomb.name=Magic +entity.ebwizardry\:ice_shard.name=Magic +entity.ebwizardry\:firebomb.name=Magic +entity.ebwizardry\:poison_bomb.name=Magic +entity.ebwizardry\:force_orb.name=Magic +entity.ebwizardry\:spark.name=Magic +entity.ebwizardry\:darkness_orb.name=Magic +entity.ebwizardry\:fire_sigil.name=Magic +entity.ebwizardry\:frost_sigil.name=Magic +entity.ebwizardry\:lightning_sigil.name=Magic +entity.ebwizardry\:lightning_arrow.name=Magic +entity.ebwizardry\:firebolt.name=Magic +entity.ebwizardry\:ice_charge.name=Magic +entity.ebwizardry\:force_arrow.name=Magic +entity.ebwizardry\:dart.name=Magic +entity.ebwizardry\:lightning_disc.name=Magic +entity.ebwizardry\:thunderbolt.name=Magic +entity.ebwizardry\:decay.name=Magic +entity.ebwizardry\:ice_lance.name=Magic +entity.ebwizardry\:smoke_bomb.name=Magic +entity.ebwizardry\:ice_spike.name=Magic +entity.ebwizardry\:combustion_rune.name=Magic + +entity.ebwizardry\:black_hole.name=Black Hole +entity.ebwizardry\:shield.name=Shield +entity.ebwizardry\:meteor.name=Meteor +entity.ebwizardry\:blizzard.name=Blizzard +entity.ebwizardry\:bubble.name=Bubble +entity.ebwizardry\:tornado.name=Tornado +entity.ebwizardry\:lightning_hammer.name=Lightning Hammer +entity.ebwizardry\:arrow_rain.name=Arrow Rain +entity.ebwizardry\:healing_aura.name=Healing Aura +entity.ebwizardry\:forcefield.name=Forcefield +entity.ebwizardry\:ring_of_fire.name=Ring of Fire +entity.ebwizardry\:earthquake.name=Earthquake +entity.ebwizardry\:hailstorm.name=Hailstorm +entity.ebwizardry\:lightning_pulse.name=Lightning Pulse + +itemGroup.ebwizardry=Wizardry +itemGroup.ebwizardryspells=Spells +itemGroup.ebwizardrygear=Wizard Gear + +advancement.ebwizardry\:root=Wizardry +advancement.ebwizardry\:root.desc=A wizard's journey to mastering the arcane +advancement.ebwizardry\:crystal=A Curious Crystal... +advancement.ebwizardry\:crystal.desc=Mine a magic crystal +advancement.ebwizardry\:arcane_initiate=Arcane Initiate +advancement.ebwizardry\:arcane_initiate.desc=Craft a magic wand with a gold nugget, a stick and a magic crystal + +advancement.ebwizardry\:apprentice=Wizard's Apprentice +advancement.ebwizardry\:apprentice.desc=Use a tome of arcana to upgrade your wand +advancement.ebwizardry\:special_upgrade=Arcane Tinkering +advancement.ebwizardry\:special_upgrade.desc=Apply a special upgrade to a wand +advancement.ebwizardry\:advanced=Advancing Rapidly +advancement.ebwizardry\:advanced.desc=Upgrade your wand to advanced tier +advancement.ebwizardry\:master=Arcane Master +advancement.ebwizardry\:master.desc=Obtain a master wand +advancement.ebwizardry\:max_out_wand=Fully Equipped +advancement.ebwizardry\:max_out_wand.desc=Apply the maximum number of upgrades to a master wand +advancement.ebwizardry\:all_spells=Mage of All Trades +advancement.ebwizardry\:all_spells.desc=Cast every single spell in the game + +advancement.ebwizardry\:wizard_tower=Who Lives Here? +advancement.ebwizardry\:wizard_tower.desc=Visit a wizard's tower +advancement.ebwizardry\:wizard_trade=Magic Dealing +advancement.ebwizardry\:wizard_trade.desc=Purchase an item from a wizard +advancement.ebwizardry\:anger_wizard=You'll Regret That +advancement.ebwizardry\:anger_wizard.desc=Make a wizard angry +advancement.ebwizardry\:defeat_evil_wizard=Righteousness +advancement.ebwizardry\:defeat_evil_wizard.desc=Defeat an evil wizard +advancement.ebwizardry\:buy_master_spell=Knowledge is Power +advancement.ebwizardry\:buy_master_spell.desc=Purchase a master spell from a wizard + +advancement.ebwizardry\:discover_spell=Trial And Error +advancement.ebwizardry\:discover_spell.desc=Identify an unknown spell by casting it and seeing what happens +advancement.ebwizardry\:identify_spell=Arcane Appraisal +advancement.ebwizardry\:identify_spell.desc=Find a more reliable way of identifying spells +advancement.ebwizardry\:spell_failure=That Backfired +advancement.ebwizardry\:spell_failure.desc=Find out the hard way how spells can go wrong +advancement.ebwizardry\:discover_master_spell=Dicing With Danger +advancement.ebwizardry\:discover_master_spell.desc=Successfully identify a master spell by trial and error + +advancement.ebwizardry\:visit_shrine=Enshrined +advancement.ebwizardry\:visit_shrine.desc=Enter a shrine and awaken its ancient magic +advancement.ebwizardry\:artefact=A Forgotten Relic +advancement.ebwizardry\:artefact.desc=Lift the protective enchantments from a shrine and claim the treasures within +advancement.ebwizardry\:all_artefacts=A Veritable Museum +advancement.ebwizardry\:all_artefacts.desc=Collect all of the rings, amulets and charms + +advancement.ebwizardry\:armour_set=Now You're a Proper Wizard +advancement.ebwizardry\:armour_set.desc=Craft and equip a full set of wizard armour +advancement.ebwizardry\:legendary=Legendary +advancement.ebwizardry\:legendary.desc=Obtain a piece of legendary wizard armour + +advancement.ebwizardry\:enchant_scroll=Scroll Up! +advancement.ebwizardry\:enchant_scroll.desc=Craft a blank scroll and enchant it with a spell at an arcane workbench + +handbook.toast.title=New Handbook Section Unlocked! + +container.ebwizardry\:arcane_workbench=Arcane Workbench +container.ebwizardry\:arcane_workbench.apply=Apply +container.ebwizardry\:arcane_workbench.mana=Mana\: +container.ebwizardry\:arcane_workbench.upgrades=Applied Upgrades\: + +tier.novice=Novice tier.apprentice=Apprentice tier.advanced=Advanced tier.master=Master -element.simple=None +element.magic=None element.fire=Fire element.ice=Ice element.lightning=Lightning @@ -286,7 +518,7 @@ element.earth=Earth element.sorcery=Sorcery element.healing=Healing -element.simple.wizard=Wizard +element.magic.wizard=Wizard element.fire.wizard=Pyromancer element.ice.wizard=Ice Mage element.lightning.wizard=Storm Mage @@ -299,323 +531,477 @@ spelltype.attack=Attack spelltype.defence=Defence spelltype.utility=Utility spelltype.minion=Minion +spelltype.buff=Buff +spelltype.construct=Construct +spelltype.projectile=Projectile +spelltype.alteration=Alteration spell.disabled=%1$s has been disabled in the config spell.resist=%1$s resisted %2$s spell.discover=Discovered the spell %1$s! -spell.ebwizardry:agility=Agility -spell.ebwizardry:arc=Arc -spell.ebwizardry:arcane_jammer=Arcane Jammer -spell.ebwizardry:arrow_rain=Arrow Rain -spell.ebwizardry:banish=Banish -spell.ebwizardry:black_hole=Black Hole -spell.ebwizardry:blink=Blink -spell.ebwizardry:blizzard=Blizzard -spell.ebwizardry:bubble=Bubble -spell.ebwizardry:chain_lightning=Chain Lightning -spell.ebwizardry:clairvoyance=Clairvoyance -spell.ebwizardry:cobwebs=Cobwebs -spell.ebwizardry:conjure_armour=Conjure Armour -spell.ebwizardry:conjure_bow=Conjure Bow -spell.ebwizardry:conjure_pickaxe=Conjure Pickaxe -spell.ebwizardry:conjure_sword=Conjure Sword -spell.ebwizardry:cure_effects=Cure Effects -spell.ebwizardry:curse_of_soulbinding=Curse of Soulbinding -spell.ebwizardry:darkness_orb=Darkness Orb -spell.ebwizardry:darkvision=Darkvision -spell.ebwizardry:dart=Dart -spell.ebwizardry:decay=Decay -spell.ebwizardry:decoy=Decoy -spell.ebwizardry:detonate=Detonate -spell.ebwizardry:diamondflesh=Diamondflesh -spell.ebwizardry:earthquake=Earthquake -spell.ebwizardry:entrapment=Entrapment -spell.ebwizardry:fireball=Fireball -spell.ebwizardry:firebolt=Firebolt -spell.ebwizardry:firebomb=Firebomb -spell.ebwizardry:fire_resistance=Fire Resistance -spell.ebwizardry:fire_sigil=Fire Sigil -spell.ebwizardry:fireskin=Fireskin -spell.ebwizardry:firestorm=Firestorm -spell.ebwizardry:flame_ray=Flame Ray -spell.ebwizardry:flaming_axe=Flaming Axe -spell.ebwizardry:flaming_weapon=Flaming Weapon -spell.ebwizardry:flight=Flight -spell.ebwizardry:font_of_mana=Font of Mana -spell.ebwizardry:font_of_vitality=Font of Vitality -spell.ebwizardry:force_arrow=Force Arrow -spell.ebwizardry:forcefield=Forcefield -spell.ebwizardry:force_orb=Force Orb -spell.ebwizardry:forests_curse=Forest's Curse -spell.ebwizardry:freeze=Freeze -spell.ebwizardry:freezing_weapon=Freezing Weapon -spell.ebwizardry:frost_axe=Frost Axe -spell.ebwizardry:frost_ray=Frost Ray -spell.ebwizardry:frost_sigil=Frost Sigil -spell.ebwizardry:glide=Glide -spell.ebwizardry:greater_fireball=Greater Fireball -spell.ebwizardry:greater_heal=Greater Heal -spell.ebwizardry:group_heal=Group Heal -spell.ebwizardry:growth_aura=Growth Aura -spell.ebwizardry:hailstorm=Hailstorm -spell.ebwizardry:heal=Heal -spell.ebwizardry:heal_ally=Heal Ally -spell.ebwizardry:healing_aura=Healing Aura -spell.ebwizardry:homing_spark=Homing Spark -spell.ebwizardry:ice_age=Ice Age -spell.ebwizardry:ice_charge=Ice Charge -spell.ebwizardry:ice_lance=Ice Lance -spell.ebwizardry:ice_shard=Ice Shard -spell.ebwizardry:ice_shroud=Ice Shroud -spell.ebwizardry:ice_spikes=Ice Spikes -spell.ebwizardry:ice_statue=Ice Statue -spell.ebwizardry:ignite=Ignite -spell.ebwizardry:imbue_weapon=Imbue Weapon -spell.ebwizardry:intimidate=Intimidate -spell.ebwizardry:invigorating_presence=Invigorating Presence -spell.ebwizardry:invisibility=Invisibility -spell.ebwizardry:invoke_weather=Invoke Weather -spell.ebwizardry:ironflesh=Ironflesh -spell.ebwizardry:leap=Leap -spell.ebwizardry:levitation=Levitation -spell.ebwizardry:life_drain=Life Drain -spell.ebwizardry:light=Light -spell.ebwizardry:lightning_arrow=Lightning Arrow -spell.ebwizardry:lightning_bolt=Lightning Bolt -spell.ebwizardry:lightning_disc=Lightning Disc -spell.ebwizardry:lightning_hammer=Lightning Hammer -spell.ebwizardry:lightning_pulse=Lightning Pulse -spell.ebwizardry:lightning_ray=Lightning Ray -spell.ebwizardry:lightning_sigil=Lightning Sigil -spell.ebwizardry:lightning_web=Lightning Web -spell.ebwizardry:magic_missile=Magic Missile -spell.ebwizardry:metamorphosis=Metamorphosis -spell.ebwizardry:meteor=Meteor -spell.ebwizardry:mind_control=Mind Control -spell.ebwizardry:mind_trick=Mind Trick -spell.ebwizardry:none=[Empty Slot] -spell.ebwizardry:oakflesh=Oakflesh -spell.ebwizardry:petrify=Petrify -spell.ebwizardry:phase_step=Phase Step -spell.ebwizardry:plague_of_darkness=Plague of Darkness -spell.ebwizardry:pocket_furnace=Pocket Furnace -spell.ebwizardry:pocket_workbench=Pocket Workbench -spell.ebwizardry:poison=Poison -spell.ebwizardry:poison_bomb=Poison Bomb -spell.ebwizardry:replenish_hunger=Replenish Hunger -spell.ebwizardry:ring_of_fire=Ring of Fire -spell.ebwizardry:shadow_ward=Shadow Ward -spell.ebwizardry:shield=Shield -spell.ebwizardry:shockwave=Shockwave -spell.ebwizardry:silverfish_swarm=Silverfish Swarm -spell.ebwizardry:sixth_sense=Sixth Sense -spell.ebwizardry:slime=Slime -spell.ebwizardry:smoke_bomb=Smoke Bomb -spell.ebwizardry:snare=Snare -spell.ebwizardry:snowball=Snowball -spell.ebwizardry:spark_bomb=Spark Bomb -spell.ebwizardry:spectral_pathway=Spectral Pathway -spell.ebwizardry:spider_swarm=Spider Swarm -spell.ebwizardry:static_aura=Static Aura -spell.ebwizardry:summon_blaze=Summon Blaze -spell.ebwizardry:summon_ice_giant=Summon Ice Giant -spell.ebwizardry:summon_ice_wraith=Summon Ice Wraith -spell.ebwizardry:summon_iron_golem=Summon Iron Golem -spell.ebwizardry:summon_lightning_wraith=Summon Lightning Wraith -spell.ebwizardry:summon_phoenix=Summon Phoenix -spell.ebwizardry:summon_shadow_wraith=Summon Shadow Wraith -spell.ebwizardry:summon_skeleton=Summon Skeleton -spell.ebwizardry:summon_skeleton_legion=Summon Skeleton Legion -spell.ebwizardry:summon_snow_golem=Summon Snow Golem -spell.ebwizardry:summon_spirit_horse=Summon Spirit Horse -spell.ebwizardry:summon_spirit_wolf=Summon Spirit Wolf -spell.ebwizardry:summon_storm_elemental=Summon Storm Elemental -spell.ebwizardry:summon_wither_skeleton=Summon Wither Skeleton -spell.ebwizardry:summon_zombie=Summon Zombie -spell.ebwizardry:telekinesis=Telekinesis -spell.ebwizardry:thunderbolt=Thunderbolt -spell.ebwizardry:thunderstorm=Thunderstorm -spell.ebwizardry:tornado=Tornado -spell.ebwizardry:transience=Transience -spell.ebwizardry:transportation=Transportation -spell.ebwizardry:vanishing_box=Vanishing Box -spell.ebwizardry:wall_of_frost=Wall of Frost -spell.ebwizardry:water_breathing=Water Breathing -spell.ebwizardry:whirlwind=Whirlwind -spell.ebwizardry:wither=Wither -spell.ebwizardry:wither_skull=Wither Skull +spell.ebwizardry\:agility=Agility +spell.ebwizardry\:arc=Arc +spell.ebwizardry\:arcane_jammer=Arcane Jammer +spell.ebwizardry\:arcane_lock=Arcane Lock +spell.ebwizardry\:arrow_rain=Arrow Rain +spell.ebwizardry\:banish=Banish +spell.ebwizardry\:black_hole=Black Hole +spell.ebwizardry\:blink=Blink +spell.ebwizardry\:blizzard=Blizzard +spell.ebwizardry\:bubble=Bubble +spell.ebwizardry\:chain_lightning=Chain Lightning +spell.ebwizardry\:charge=Charge +spell.ebwizardry\:clairvoyance=Clairvoyance +spell.ebwizardry\:cobwebs=Cobwebs +spell.ebwizardry\:combustion_rune=Combustion Rune +spell.ebwizardry\:conjure_armour=Conjure Armour +spell.ebwizardry\:conjure_block=Conjure Block +spell.ebwizardry\:conjure_bow=Conjure Bow +spell.ebwizardry\:conjure_pickaxe=Conjure Pickaxe +spell.ebwizardry\:conjure_sword=Conjure Sword +spell.ebwizardry\:containment=Containment +spell.ebwizardry\:cure_effects=Cure Effects +spell.ebwizardry\:curse_of_enfeeblement=Curse of Enfeeblement +spell.ebwizardry\:curse_of_soulbinding=Curse of Soulbinding +spell.ebwizardry\:curse_of_undeath=Curse of Undeath +spell.ebwizardry\:darkness_orb=Darkness Orb +spell.ebwizardry\:darkvision=Darkvision +spell.ebwizardry\:dart=Dart +spell.ebwizardry\:decay=Decay +spell.ebwizardry\:decoy=Decoy +spell.ebwizardry\:detonate=Detonate +spell.ebwizardry\:diamondflesh=Diamondflesh +spell.ebwizardry\:disintegration=Disintegration +spell.ebwizardry\:divination=Divination +spell.ebwizardry\:dragon_fireball=Dragon Fireball +spell.ebwizardry\:earthquake=Earthquake +spell.ebwizardry\:empowering_presence=Empowering Presence +spell.ebwizardry\:entrapment=Entrapment +spell.ebwizardry\:evade=Evade +spell.ebwizardry\:fireball=Fireball +spell.ebwizardry\:firebolt=Firebolt +spell.ebwizardry\:firebomb=Firebomb +spell.ebwizardry\:fire_resistance=Fire Resistance +spell.ebwizardry\:fire_sigil=Fire Sigil +spell.ebwizardry\:fireskin=Fireskin +spell.ebwizardry\:fire_breath=Fire Breath +spell.ebwizardry\:flame_ray=Flame Ray +spell.ebwizardry\:flaming_axe=Flaming Axe +spell.ebwizardry\:flaming_weapon=Flaming Weapon +spell.ebwizardry\:flight=Flight +spell.ebwizardry\:font_of_mana=Font of Mana +spell.ebwizardry\:font_of_vitality=Font of Vitality +spell.ebwizardry\:force_arrow=Force Arrow +spell.ebwizardry\:forcefield=Forcefield +spell.ebwizardry\:force_orb=Force Orb +spell.ebwizardry\:forests_curse=Forest's Curse +spell.ebwizardry\:forest_of_thorns=Forest of Thorns +spell.ebwizardry\:freeze=Freeze +spell.ebwizardry\:freezing_weapon=Freezing Weapon +spell.ebwizardry\:frost_axe=Frost Axe +spell.ebwizardry\:frost_ray=Frost Ray +spell.ebwizardry\:frost_sigil=Frost Sigil +spell.ebwizardry\:frost_step=Frost Step +spell.ebwizardry\:glide=Glide +spell.ebwizardry\:grapple=Grapple +spell.ebwizardry\:greater_fireball=Greater Fireball +spell.ebwizardry\:greater_heal=Greater Heal +spell.ebwizardry\:greater_telekinesis=Greater Telekinesis +spell.ebwizardry\:greater_ward=Greater Ward +spell.ebwizardry\:group_heal=Group Heal +spell.ebwizardry\:growth_aura=Growth Aura +spell.ebwizardry\:hailstorm=Hailstorm +spell.ebwizardry\:heal=Heal +spell.ebwizardry\:heal_ally=Heal Ally +spell.ebwizardry\:healing_aura=Healing Aura +spell.ebwizardry\:homing_spark=Homing Spark +spell.ebwizardry\:iceball=Iceball +spell.ebwizardry\:ice_age=Ice Age +spell.ebwizardry\:ice_charge=Ice Charge +spell.ebwizardry\:ice_lance=Ice Lance +spell.ebwizardry\:ice_shard=Ice Shard +spell.ebwizardry\:ice_shroud=Ice Shroud +spell.ebwizardry\:ice_spikes=Ice Spikes +spell.ebwizardry\:ice_statue=Ice Statue +spell.ebwizardry\:ignite=Ignite +spell.ebwizardry\:imbue_weapon=Imbue Weapon +spell.ebwizardry\:intimidate=Intimidate +spell.ebwizardry\:invigorating_presence=Invigorating Presence +spell.ebwizardry\:invisibility=Invisibility +spell.ebwizardry\:invoke_weather=Invoke Weather +spell.ebwizardry\:ironflesh=Ironflesh +spell.ebwizardry\:leap=Leap +spell.ebwizardry\:levitation=Levitation +spell.ebwizardry\:life_drain=Life Drain +spell.ebwizardry\:light=Light +spell.ebwizardry\:lightning_arrow=Lightning Arrow +spell.ebwizardry\:lightning_bolt=Lightning Bolt +spell.ebwizardry\:lightning_disc=Lightning Disc +spell.ebwizardry\:lightning_hammer=Lightning Hammer +spell.ebwizardry\:lightning_pulse=Lightning Pulse +spell.ebwizardry\:lightning_ray=Lightning Ray +spell.ebwizardry\:lightning_sigil=Lightning Sigil +spell.ebwizardry\:lightning_web=Lightning Web +spell.ebwizardry\:magic_missile=Magic Missile +spell.ebwizardry\:metamorphosis=Metamorphosis +spell.ebwizardry\:meteor=Meteor +spell.ebwizardry\:mind_control=Mind Control +spell.ebwizardry\:mind_trick=Mind Trick +spell.ebwizardry\:mine=Mine +spell.ebwizardry\:muffle=Muffle +spell.ebwizardry\:none=[Empty Slot] +spell.ebwizardry\:oakflesh=Oakflesh +spell.ebwizardry\:paralysis=Paralysis +spell.ebwizardry\:petrify=Petrify +spell.ebwizardry\:phase_step=Phase Step +spell.ebwizardry\:plague_of_darkness=Plague of Darkness +spell.ebwizardry\:pocket_furnace=Pocket Furnace +spell.ebwizardry\:pocket_workbench=Pocket Workbench +spell.ebwizardry\:poison=Poison +spell.ebwizardry\:poison_bomb=Poison Bomb +spell.ebwizardry\:possession=Possession +spell.ebwizardry\:ray_of_purification=Ray of Purification +spell.ebwizardry\:remove_curse=Remove Curse +spell.ebwizardry\:replenish_hunger=Replenish Hunger +spell.ebwizardry\:resurrection=Resurrection +spell.ebwizardry\:reversal=Reversal +spell.ebwizardry\:ring_of_fire=Ring of Fire +spell.ebwizardry\:satiety=Satiety +spell.ebwizardry\:shadow_ward=Shadow Ward +spell.ebwizardry\:shield=Shield +spell.ebwizardry\:shockwave=Shockwave +spell.ebwizardry\:shulker_bullet=Shulker Bullet +spell.ebwizardry\:silverfish_swarm=Silverfish Swarm +spell.ebwizardry\:sixth_sense=Sixth Sense +spell.ebwizardry\:slime=Slime +spell.ebwizardry\:slow_time=Slow Time +spell.ebwizardry\:smoke_bomb=Smoke Bomb +spell.ebwizardry\:snare=Snare +spell.ebwizardry\:snowball=Snowball +spell.ebwizardry\:spark_bomb=Spark Bomb +spell.ebwizardry\:spectral_pathway=Spectral Pathway +spell.ebwizardry\:speed_time=Speed Time +spell.ebwizardry\:spider_swarm=Spider Swarm +spell.ebwizardry\:static_aura=Static Aura +spell.ebwizardry\:summon_blaze=Summon Blaze +spell.ebwizardry\:summon_ice_giant=Summon Ice Giant +spell.ebwizardry\:summon_ice_wraith=Summon Ice Wraith +spell.ebwizardry\:summon_iron_golem=Summon Iron Golem +spell.ebwizardry\:summon_lightning_wraith=Summon Lightning Wraith +spell.ebwizardry\:summon_phoenix=Summon Phoenix +spell.ebwizardry\:summon_shadow_wraith=Summon Shadow Wraith +spell.ebwizardry\:summon_skeleton=Summon Skeleton +spell.ebwizardry\:summon_skeleton_legion=Summon Skeleton Legion +spell.ebwizardry\:summon_snow_golem=Summon Snow Golem +spell.ebwizardry\:summon_spirit_horse=Summon Spirit Horse +spell.ebwizardry\:summon_spirit_wolf=Summon Spirit Wolf +spell.ebwizardry\:summon_storm_elemental=Summon Storm Elemental +spell.ebwizardry\:summon_wither_skeleton=Summon Wither Skeleton +spell.ebwizardry\:summon_zombie=Summon Zombie +spell.ebwizardry\:telekinesis=Telekinesis +spell.ebwizardry\:thunderbolt=Thunderbolt +spell.ebwizardry\:thunderstorm=Thunderstorm +spell.ebwizardry\:tornado=Tornado +spell.ebwizardry\:transience=Transience +spell.ebwizardry\:transportation=Transportation +spell.ebwizardry\:vanishing_box=Vanishing Box +spell.ebwizardry\:vex_swarm=Vex Swarm +spell.ebwizardry\:wall_of_frost=Wall of Frost +spell.ebwizardry\:ward=Ward +spell.ebwizardry\:water_breathing=Water Breathing +spell.ebwizardry\:whirlwind=Whirlwind +spell.ebwizardry\:wither=Wither +spell.ebwizardry\:wither_skull=Wither Skull -spell.ebwizardry:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. -spell.ebwizardry:arc.desc=Fires a spark of lightning at the target. -spell.ebwizardry:arcane_jammer.desc=Prevents the target from using magic for 15 seconds. -spell.ebwizardry:arrow_rain.desc="Archers, fire!" -spell.ebwizardry:banish.desc=Teleports the target against its will to a random location within a certain range. -spell.ebwizardry:black_hole.desc=Tear reality asunder. -spell.ebwizardry:blink.desc=Teleports the caster over a short distance to where they are pointing. -spell.ebwizardry:blizzard.desc=Creates a zone of swirling icy wind which slows and continually damages anything trapped inside. The caster is immune to the damage but is still slowed. -spell.ebwizardry:bubble.desc=Fires a jet of bubbles which causes anything it hits to float upwards helplessly. The target will fall after a certain time or if it is damaged. -spell.ebwizardry:chain_lightning.desc=Fires a spark of lightning at the target, which then chains to additional targets up to twice. -spell.ebwizardry:clairvoyance.desc=Reveals the path to a remembered location. With this spell selected, sneak-right-click on a block to set the location. Cast this spell normally to reveal the path. The path will fade after 90 seconds. -spell.ebwizardry:cobwebs.desc=Creates cobwebs where you are pointing, which greatly hamper the movement of any creatures caught amongst them. The cobwebs will vanish after 20 seconds or if broken. -spell.ebwizardry:conjure_armour.desc=Creates spectral armour around the caster which offers protection equal to that of iron armour. The armour lasts for 60 seconds. The caster must have an empty armour slot. -spell.ebwizardry:conjure_bow.desc=Creates a spectral bow with unlimited arrows that lasts for 30 seconds. -spell.ebwizardry:conjure_pickaxe.desc=Creates a spectral pickaxe of equal strength to an iron pickaxe that lasts for 30 seconds. -spell.ebwizardry:conjure_sword.desc=Creates a spectral sword of equal strength to an iron sword that lasts for 30 seconds. -spell.ebwizardry:cure_effects.desc=Removes all potion effects currently affecting the caster, good or bad. -spell.ebwizardry:curse_of_soulbinding.desc=Causes the target's soul to be inextricably bound to that of the caster, meaning all damage dealt to the caster is also dealt to the victim. Lasts until either the victim or the caster dies. -spell.ebwizardry:darkness_orb.desc=Fires a slow moving bolt of dark energy in the direction you are pointing, which withers whatever it hits. -spell.ebwizardry:darkvision.desc=Grants the caster night vision for 45 seconds. -spell.ebwizardry:dart.desc=Fires a dart in the direction you are pointing which damages and weakens its target. -spell.ebwizardry:decay.desc=Creates a patch of decay on the ground which infects any creature that touches it, causing lingering damage over time and spreading more decay wherever it walks. -spell.ebwizardry:decoy.desc=Creates an illusory clone of the caster which tricks mobs into attacking it instead. The decoy will vanish after 30 seconds. -spell.ebwizardry:detonate.desc=Causes an explosion where you are pointing, damaging all nearby creatures - including the caster, if they are too close. -spell.ebwizardry:diamondflesh.desc="Your arrows are no match for me!" -spell.ebwizardry:earthquake.desc=A true master of earth magic can move mountains. -spell.ebwizardry:entrapment.desc=Traps the target in a sphere of darkness which pulls it helplessly upwards and continually damages it. -spell.ebwizardry:fireball.desc=Launches a fireball in the direction you are pointing. -spell.ebwizardry:firebolt.desc=Shoots a jet of fire a short distance in front of you. -spell.ebwizardry:firebomb.desc=Lanches a firebomb in the direction you are pointing which explodes on impact, setting targets on fire. -spell.ebwizardry:fire_resistance.desc=Grants the caster fire resistance for 30 seconds. -spell.ebwizardry:fire_sigil.desc=Places a magical fire trap on the ground which damages and sets on fire the creature that triggers it. -spell.ebwizardry:fireskin.desc=Cloaks the caster in flames for 30 seconds, causing anything that attacks them to catch fire. -spell.ebwizardry:firestorm.desc="I am the dragon." -spell.ebwizardry:flame_ray.desc=Creates a stream of flames in the direction you are pointing which sets on fire and continually damages targets. -spell.ebwizardry:flaming_axe.desc=Creates a flaming axe which sets enemies on fire when hit. Lasts for 30 seconds. -spell.ebwizardry:flaming_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of flame, causing it to set fire to its victims. The magic wears off after 45 seconds. -spell.ebwizardry:flight.desc=Soar like an eagle. -spell.ebwizardry:font_of_mana.desc="We were filled with an intense magical energy appearing to emanate from the centre of the..." - Extract from the journal of a forgotten mage; the rest of the page has been burnt away. -spell.ebwizardry:font_of_vitality.desc=It feels amazing. -spell.ebwizardry:force_arrow.desc=Shoots an arrow of force in the direction you are pointing. -spell.ebwizardry:forcefield.desc=Creates a forcefield around the caster which repels creatures and deflects projectiles. -spell.ebwizardry:force_orb.desc=Launches a sphere of force which damages and repels nearby creatures on impact. -spell.ebwizardry:forests_curse.desc="How dare you enter my forest!" -spell.ebwizardry:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. -spell.ebwizardry:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. -spell.ebwizardry:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. -spell.ebwizardry:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. -spell.ebwizardry:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. -spell.ebwizardry:glide.desc=Allows the caster to glide downwards while in the air and holding the use item button. -spell.ebwizardry:greater_fireball.desc=Launches a large fireball in the direction you are pointing which explodes on impact. -spell.ebwizardry:greater_heal.desc=Heals the caster by 4 hearts. -spell.ebwizardry:group_heal.desc=Heals the caster and all nearby allies and summoned creatures by 3 hearts. -spell.ebwizardry:growth_aura.desc=Grows all crops near the caster. Also grows tall grass and flowers on grass. -spell.ebwizardry:hailstorm.desc=It was during the great winter of the third age that the ice mages discovered their true power. -spell.ebwizardry:heal.desc=Heals the caster by 2 hearts. -spell.ebwizardry:heal_ally.desc=Heals the target by 2 and a half hearts. -spell.ebwizardry:healing_aura.desc=Creates a zone of healing energy which regenerates the health of any ally inside it. Any undead inside the healing aura will slowly take damage. -spell.ebwizardry:homing_spark.desc=Creates a floating spark which moves towards enemies. -spell.ebwizardry:ice_age.desc="You shall be frozen for an eternity!" -spell.ebwizardry:ice_charge.desc=Launches an ice charge which explodes on impact, freezing nearby creatures and releasing shards in all directions. -spell.ebwizardry:ice_lance.desc=Fires a great spear of ice in the direction you are pointing which overpenetrates targets, damaging and freezing them in the process. -spell.ebwizardry:ice_shard.desc=Fires a shard of ice in the direction you are pointing which damages and slows targets when hit. -spell.ebwizardry:ice_shroud.desc=Creates a shroud of cold around the caster for 30 seconds, causing anything that attacks them to be frozen. -spell.ebwizardry:ice_spikes.desc=Causes razor-sharp ice spikes to rise from the ground where you are pointing, skewering any creatures caught amongst them. -spell.ebwizardry:ice_statue.desc=Freezes the target solid for 20 seconds or until broken out. The target cannot move or do anything while frozen but is also impervious to all damage. -spell.ebwizardry:ignite.desc=Sets the target on fire for 10 seconds. Also works like a flint and steel. -spell.ebwizardry:imbue_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with magic, rendering it more effective. The magic wears off after 45 seconds. -spell.ebwizardry:intimidate.desc=Emits an intimidating growl which causes nearby creatures to run away in fear. Fear stricken creatures will recover after 30 seconds. -spell.ebwizardry:invigorating_presence.desc=Grants the caster and all nearby allies increased strength for 45 seconds. -spell.ebwizardry:invisibility.desc=Makes the caster invisible for 30 seconds. -spell.ebwizardry:invoke_weather.desc=Changes the weather in the world. -spell.ebwizardry:ironflesh.desc=Greatly improves the caster's damage resistance for 30 seconds. -spell.ebwizardry:leap.desc=Causes the caster to jump upwards several blocks and slightly forward. -spell.ebwizardry:levitation.desc=Raises the caster upwards while the use item button is held. Will also negate fall damage if used before hitting the ground. -spell.ebwizardry:life_drain.desc=Creates a stream of withering energy in the direction you are pointing which drains the life of the target and uses it to gradually regenerate your health. -spell.ebwizardry:light.desc=Creates a magical point of light which illuminates the surrounding area. Lasts for 30 seconds. -spell.ebwizardry:lightning_arrow.desc=Shoots an arrow of lightning in the direction you are pointing. -spell.ebwizardry:lightning_bolt.desc=Causes lightning to strike where you are pointing. -spell.ebwizardry:lightning_disc.desc=Sends a disc of lightning flying off in the direction you are pointing, which seeks targets. -spell.ebwizardry:lightning_hammer.desc="I smite you by the wrath of the heavens!" -spell.ebwizardry:lightning_pulse.desc=Charges the ground around the caster with lightning, damaging and repelling nearby creatures. -spell.ebwizardry:lightning_ray.desc=Creates a stream of lightning in the direction you are pointing which continually damages targets. -spell.ebwizardry:lightning_sigil.desc=Places a magical lightning trap on the ground which damages the creature that triggers it and chains lightning to other nearby creatures. -spell.ebwizardry:lightning_web.desc="Focus. Channel the storm in your mind through your wand and unleash its fury." -spell.ebwizardry:magic_missile.desc=Fires a bolt of magical energy in the direction you are pointing. -spell.ebwizardry:metamorphosis.desc=Changes the target into another form. Only works on some creatures. -spell.ebwizardry:meteor.desc=Some wizards just want to see the world burn... -spell.ebwizardry:mind_control.desc=Takes control of the target's mind for 30 seconds, causing it switch sides and fight for the caster instead. Will not work on creatures that are too strong-willed. -spell.ebwizardry:mind_trick.desc=Confuses and disorients the target for 15 seconds, rendering it unable to attack effectively. The effect will be dispelled if the target takes damage. -spell.ebwizardry:none.desc=To get a spell book with the /give command, use metadata: /give [player] ebwizardry:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). -spell.ebwizardry:oakflesh.desc=Improves the caster's damage resistance for 30 seconds. -spell.ebwizardry:petrify.desc=Turns the target to stone until broken out, with a chance for it to break out when it is dark. The target cannot move or do anything while petrified but is also impervious to all damage. -spell.ebwizardry:phase_step.desc=Teleports the caster through a 1 block thick wall in front of them. Range upgrades will increase the thickness you can teleport through. -spell.ebwizardry:plague_of_darkness.desc=The darkness will consume them all... -spell.ebwizardry:pocket_furnace.desc=Smelts up to 5 smeltable items in the caster's inventory. Items on the hotbar will be smelted first. -spell.ebwizardry:pocket_workbench.desc=Allows the caster to craft items as if they were at a crafting table. -spell.ebwizardry:poison.desc=Fires poison in the direction you are pointing. -spell.ebwizardry:poison_bomb.desc=Lanches a poison bomb in the direction you are pointing which explodes on impact, poisoning nearby creatures. -spell.ebwizardry:replenish_hunger.desc=Replenishes the caster's food level by 6 hunger points. -spell.ebwizardry:ring_of_fire.desc=Creates a ring of fire around the caster, damaging all nearby enemies and setting them on fire. -spell.ebwizardry:shadow_ward.desc=Creates a wall of darkness in front of the caster which causes half of all incoming damage to be inflicted upon the attacker instead. -spell.ebwizardry:shield.desc=Creates a protective barrier of force that blocks projectiles and magic. Also grants the caster a weak resistance effect. -spell.ebwizardry:shockwave.desc=Boom. -spell.ebwizardry:silverfish_swarm.desc="Ahhhh! They're MULTIPLYING!" -spell.ebwizardry:sixth_sense.desc=Allows the caster to sense the locations of nearby creatures, even through walls, for 20 seconds. -spell.ebwizardry:slime.desc=Engulfs the target in slime which slows and continually damages it. The slime bursts after 10 seconds. -spell.ebwizardry:smoke_bomb.desc=Launches a smoke bomb in the direction you are pointing which explodes on impact, releasing smoke and blinding nearby creatures for a short time. -spell.ebwizardry:snare.desc=Sets a trap on the ground which damages and briefly slows the creature that triggers it. -spell.ebwizardry:snowball.desc=Launches a snowball in the direction you are pointing. -spell.ebwizardry:spark_bomb.desc=Launches a shock charge in the direction you are pointing which releases sparks at nearby enemies on impact. -spell.ebwizardry:spectral_pathway.desc=Creates an indestructible magical bridge in front of you which extends for 15 blocks. The bridge vanishes after 60 seconds. -spell.ebwizardry:spider_swarm.desc=Summons a swarm of venomous spiders to fight for you. The spiders will disappear after 30 seconds or if they are killed. -spell.ebwizardry:static_aura.desc=Surrounds the caster with lightning for 30 seconds, firing a spark of lightning at anything that hits them. -spell.ebwizardry:summon_blaze.desc=Summons a blaze to fight for you. The blaze will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_ice_giant.desc="Smash them!" -spell.ebwizardry:summon_ice_wraith.desc=Summons an ice wraith to fight for you. The ice wraith will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_iron_golem.desc=Automatic automated autonomous automaton. -spell.ebwizardry:summon_lightning_wraith.desc=Summons a lightning wraith to fight for you. The lightning wraith will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_phoenix.desc=From the ashes... -spell.ebwizardry:summon_shadow_wraith.desc=Summons a shadow wraith to fight for you. -spell.ebwizardry:summon_skeleton.desc=Summons a skeleton to fight for you. The skeleton will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_skeleton_legion.desc="Rise, undead army!" -spell.ebwizardry:summon_snow_golem.desc=Creates a snow golem to fight for you. Lasts until the snow golem dies. -spell.ebwizardry:summon_spirit_horse.desc=Summons a spirit horse for you to ride. The spirit horse will vanish a short while after it is dismounted, or you can dismiss it by shift-right-clicking on it with any wand. -spell.ebwizardry:summon_spirit_wolf.desc=Summons a spirit wolf companion to fight for you. The spirit wolf will only disappear if it is killed, or you can dismiss it by shift-right-clicking on it with any wand. -spell.ebwizardry:summon_storm_elemental.desc="Storm Elemental: An ancient manifestation of the elements, it can hardly contain the raw power churning within it." - The Wizard's Guide to Arcane Beings, Volume I_i -spell.ebwizardry:summon_wither_skeleton.desc=Summons a wither skeleton to fight for you. The wither skeleton will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_zombie.desc=Summons a zombie to fight for you. The zombie will disappear after 30 seconds or if it is killed. -spell.ebwizardry:telekinesis.desc=Moves an item or other small object towards you, or right-clicks the block you are looking at. Can also be used to disarm players. -spell.ebwizardry:thunderbolt.desc=Shoots a bolt of thunder which knocks back targets. -spell.ebwizardry:thunderstorm.desc="Mwahahahahahaha!" -spell.ebwizardry:tornado.desc=Unleashes a tornado in the direction you are pointing which hurls anything in its path skywards. -spell.ebwizardry:transience.desc=Makes the caster transient for 20 seconds. The caster is immune to all damage while transient but cannot break or place blocks or cause any damage. -spell.ebwizardry:transportation.desc=Transports the caster to their remembered stone circle. To use this spell, make a circle of stones of transportation, then right click it with a wand. -spell.ebwizardry:vanishing_box.desc=Grants the caster access to their ender chest storage. -spell.ebwizardry:wall_of_frost.desc=Winter at your fingertips. -spell.ebwizardry:water_breathing.desc=Allows the caster to breathe underwater for 60 seconds. -spell.ebwizardry:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. -spell.ebwizardry:wither.desc=Fires a ray of darkness which withers anything it touches. -spell.ebwizardry:wither_skull.desc=Launches a wither skull in the direction you are pointing. +spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. +spell.ebwizardry\:arc.desc=Fires a spark of lightning at the target. +spell.ebwizardry\:arcane_jammer.desc=Prevents the target from using magic for 15 seconds. +spell.ebwizardry\:arcane_lock.desc=Creates an impenetrable barrier of force around a container, protecting it from being opened or destroyed. The caster and their allies may still open it, however. +spell.ebwizardry\:arrow_rain.desc="Archers, fire!" +spell.ebwizardry\:banish.desc=Teleports the target against its will to a random locations within a certain range. +spell.ebwizardry\:black_hole.desc=Tear reality asunder. +spell.ebwizardry\:blink.desc=Teleports the caster over a short distance to where they are pointing. +spell.ebwizardry\:blizzard.desc=Creates a zone of swirling icy wind which slows and continually damages anything trapped inside. The caster is immune to the damage but is still slowed. +spell.ebwizardry\:bubble.desc=Fires a jet of bubbles which causes anything it hits to float upwards helplessly. The target will fall after a certain time or if it is damaged. +spell.ebwizardry\:chain_lightning.desc=Fires a spark of lightning at the target, which then chains to additional targets up to twice. +spell.ebwizardry\:charge.desc=Causes the caster to charge rapidly in the direction they are looking, damaging and knocking back anything in their path. +spell.ebwizardry\:clairvoyance.desc=Reveals the path to a remembered locations. With this spell selected, sneak-right-click on a block to set the locations. Cast this spell normally to reveal the path. The path will fade after 90 seconds. +spell.ebwizardry\:cobwebs.desc=Creates cobwebs where you are pointing, which greatly hamper the movement of any creatures caught amongst them. The cobwebs will vanish after 20 seconds or if broken. +spell.ebwizardry\:combustion_rune.desc=Places a magical landmine on the ground where you are pointing, which explodes when stepped upon. +spell.ebwizardry\:conjure_armour.desc=Creates spectral armour around the caster which offers protection equal to that of iron armour. The armour lasts for 60 seconds. The caster must have an empty armour slot. +spell.ebwizardry\:conjure_block.desc=Conjures a spectral block where you are pointing. The spectral block disappears after 45 seconds, or you can dispel it by casting this spell at it again. +spell.ebwizardry\:conjure_bow.desc=Creates a spectral bow with unlimited arrows that lasts for 30 seconds. +spell.ebwizardry\:conjure_pickaxe.desc=Creates a spectral pickaxe of equal strength to an iron pickaxe that lasts for 30 seconds. +spell.ebwizardry\:conjure_sword.desc=Creates a spectral sword of equal strength to an iron sword that lasts for 30 seconds. +spell.ebwizardry\:containment.desc=Contains the target to within a short distance of its current position for 20 seconds. +spell.ebwizardry\:cure_effects.desc=Removes all potion effects currently affecting the caster, good or bad. +spell.ebwizardry\:curse_of_enfeeblement.desc="He was suddenly weakened, as if the life had been wrenched from within him." - Testimony of the only person known to have encountered a member of the soulwalker cult and survived. +spell.ebwizardry\:curse_of_soulbinding.desc=Causes the target's soul to be inextricably bound to that of the caster, meaning all damage dealt to the caster is also dealt to the victim. Lasts until either the victim or the caster dies. +spell.ebwizardry\:curse_of_undeath.desc=Curses the target with undeath, causing it to burn in sunlight, like zombies and skeletons. Lasts until the victim dies. Has no effect on undead creatures. +spell.ebwizardry\:darkness_orb.desc=Fires a slow moving bolt of dark energy in the direction you are pointing, which withers whatever it hits. +spell.ebwizardry\:darkvision.desc=Grants the caster night vision for 45 seconds. +spell.ebwizardry\:dart.desc=Fires a dart in the direction you are pointing which damages and weakens its target. +spell.ebwizardry\:decay.desc=Creates a patch of decay on the ground which infects any creature that touches it, causing lingering damage over time and spreading more decay wherever it walks. +spell.ebwizardry\:decoy.desc=Creates an illusory clone of the caster which tricks mobs into attacking it instead. The decoy will vanish after 30 seconds. +spell.ebwizardry\:detonate.desc=Causes an explosion where you are pointing, damaging all nearby creatures - including the caster, if they are too close. +spell.ebwizardry\:diamondflesh.desc="Your arrows are no match for me!" +spell.ebwizardry\:disintegration.desc=Shoots a powerful bolt of flame a short distance in front of you which causes targets to explode into burning embers when killed. Creatures that step on the embers will be set on fire. +spell.ebwizardry\:divination.desc=Guides the caster to nearby ores and resources. Potency will increase the chance of finding more valuable ores. +spell.ebwizardry\:dragon_fireball.desc=Launches an enderdragon fireball in the direction you are pointing, which releases lingering poisonous clouds on impact. +spell.ebwizardry\:earthquake.desc=A true master of earth magic can move mountains. +spell.ebwizardry\:empowering_presence.desc=Grants the caster and nearby allies increased magic damage for 30 seconds. +spell.ebwizardry\:entrapment.desc=Traps the target in a sphere of darkness which pulls it helplessly upwards and continually damages it. +spell.ebwizardry\:evade.desc=Causes the caster to quickly jump sideways to dodge incoming attacks. +spell.ebwizardry\:fireball.desc=Launches a fireball in the direction you are pointing. +spell.ebwizardry\:firebolt.desc=Shoots a jet of fire a short distance in front of you. +spell.ebwizardry\:firebomb.desc=Lanches a firebomb in the direction you are pointing which explodes on impact, setting targets on fire. +spell.ebwizardry\:fire_resistance.desc=Grants the caster fire resistance for 30 seconds. +spell.ebwizardry\:fire_sigil.desc=Places a magical fire trap on the ground which damages and sets on fire the creature that triggers it. +spell.ebwizardry\:fireskin.desc=Cloaks the caster in flames for 30 seconds, causing anything that attacks them to catch fire. +spell.ebwizardry\:fire_breath.desc="I am the dragon." +spell.ebwizardry\:flame_ray.desc=Creates a stream of flames in the direction you are pointing which sets on fire and continually damages targets. +spell.ebwizardry\:flaming_axe.desc=Creates a flaming axe which sets enemies on fire when hit. Lasts for 30 seconds. +spell.ebwizardry\:flaming_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of flame, causing it to set fire to its victims. The magic wears off after 45 seconds. +spell.ebwizardry\:flight.desc=Soar like an eagle. +spell.ebwizardry\:font_of_mana.desc="We were filled with an intense magical energy appearing to emanate from the centre of the..." - Extract from the journal of a forgotten mage; the rest of the page has been burnt away. +spell.ebwizardry\:font_of_vitality.desc=It feels amazing. +spell.ebwizardry\:force_arrow.desc=Shoots an arrow of force in the direction you are pointing. +spell.ebwizardry\:forcefield.desc=Creates a forcefield around the caster which repels creatures and deflects projectiles. +spell.ebwizardry\:force_orb.desc=Launches a sphere of force which damages and repels nearby creatures on impact. +spell.ebwizardry\:forests_curse.desc="How dare you enter my forest!" +spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn the glade a wizard performs,\nMagical arts of an order untold,\nDeep within the forest of thorns. +spell.ebwizardry\:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. +spell.ebwizardry\:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. +spell.ebwizardry\:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. +spell.ebwizardry\:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. +spell.ebwizardry\:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. +spell.ebwizardry\:frost_step.desc=Allows the caster to freeze water as they walk for 30 seconds. +spell.ebwizardry\:grapple.desc=Shoots a magical vine which allows the caster to grapple towards blocks or reel in entities. Release the use item button to let go of the block or entity. +spell.ebwizardry\:glide.desc=Allows the caster to glide downwards while in the air and holding the use item button. +spell.ebwizardry\:greater_fireball.desc=Launches a large fireball in the direction you are pointing which explodes on impact. +spell.ebwizardry\:greater_heal.desc=Heals the caster by 4 hearts. +spell.ebwizardry\:greater_telekinesis.desc=Allows the caster to pick up a block or creature and hold it in the air for as long as the use item button is pressed. Sneaking whilst holding a block or creature will throw it a short distance. +spell.ebwizardry\:greater_ward.desc=Grants the caster a strong shielding effect that greatly reduces incoming magic damage for 30 seconds. +spell.ebwizardry\:group_heal.desc=Heals the caster and all nearby allies and summoned creatures by 3 hearts. +spell.ebwizardry\:growth_aura.desc=Grows all crops near the caster. Also grows tall grass and flowers on grass. +spell.ebwizardry\:hailstorm.desc=It was during the great winter of the third age that the ice mages discovered their true power. +spell.ebwizardry\:heal.desc=Heals the caster by 2 hearts. +spell.ebwizardry\:heal_ally.desc=Heals the target by 2 and a half hearts. +spell.ebwizardry\:healing_aura.desc=Creates a zone of healing energy which regenerates the health of any ally inside it. Any undead inside the healing aura will slowly take damage. +spell.ebwizardry\:homing_spark.desc=Creates a floating spark which moves towards enemies. +spell.ebwizardry\:iceball.desc=Launches an iceball in the direction you are pointing. +spell.ebwizardry\:ice_age.desc="You shall be frozen for an eternity!" +spell.ebwizardry\:ice_charge.desc=Launches an ice charge which explodes on impact, freezing nearby creatures and releasing shards in all directions. +spell.ebwizardry\:ice_lance.desc=Fires a great spear of ice in the direction you are pointing which overpenetrates targets, damaging and freezing them in the process. +spell.ebwizardry\:ice_shard.desc=Fires a shard of ice in the direction you are pointing which damages and slows targets when hit. +spell.ebwizardry\:ice_shroud.desc=Creates a shroud of cold around the caster for 30 seconds, causing anything that attacks them to be frozen. +spell.ebwizardry\:ice_spikes.desc=Causes razor-sharp ice spikes to rise from the ground where you are pointing, skewering any creatures caught amongst them. +spell.ebwizardry\:ice_statue.desc=Freezes the target solid for 20 seconds or until broken out. The target cannot move or do anything while frozen but is also impervious to all damage. +spell.ebwizardry\:ignite.desc=Sets the target on fire for 10 seconds. Also works like a flint and steel. +spell.ebwizardry\:imbue_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with magic, rendering it more effective. The magic wears off after 45 seconds. +spell.ebwizardry\:intimidate.desc=Emits an intimidating growl which causes nearby creatures to run away in fear. Fear stricken creatures will recover after 30 seconds. +spell.ebwizardry\:invigorating_presence.desc=Grants the caster and all nearby allies increased strength for 45 seconds. +spell.ebwizardry\:invisibility.desc=Makes the caster invisible for 30 seconds. +spell.ebwizardry\:invoke_weather.desc=Changes the weather in the world. +spell.ebwizardry\:ironflesh.desc=Greatly improves the caster's damage resistance for 30 seconds. +spell.ebwizardry\:leap.desc=Causes the caster to jump upwards several blocks and slightly forward. +spell.ebwizardry\:levitation.desc=Raises the caster upwards while the use item button is held. Will also negate fall damage if used before hitting the ground. +spell.ebwizardry\:life_drain.desc=Creates a stream of withering energy in the direction you are pointing which drains the life of the target and uses it to gradually regenerate your health. +spell.ebwizardry\:light.desc=Creates a magical point of light which illuminates the surrounding area. Lasts for 30 seconds. +spell.ebwizardry\:lightning_arrow.desc=Shoots an arrow of lightning in the direction you are pointing. +spell.ebwizardry\:lightning_bolt.desc=Causes lightning to strike where you are pointing. +spell.ebwizardry\:lightning_disc.desc=Sends a disc of lightning flying off in the direction you are pointing, which seeks targets. +spell.ebwizardry\:lightning_hammer.desc="I smite you by the wrath of the heavens!" +spell.ebwizardry\:lightning_pulse.desc=Charges the ground around the caster with lightning, damaging and repelling nearby creatures. +spell.ebwizardry\:lightning_ray.desc=Creates a stream of lightning in the direction you are pointing which continually damages targets. +spell.ebwizardry\:lightning_sigil.desc=Places a magical lightning trap on the ground which damages the creature that triggers it and chains lightning to other nearby creatures. +spell.ebwizardry\:lightning_web.desc="Focus. Channel the storm in your mind through your wand and unleash its fury." +spell.ebwizardry\:magic_missile.desc=Fires a bolt of magical energy in the direction you are pointing. +spell.ebwizardry\:metamorphosis.desc=Changes the target into another form. Only works on some creatures. +spell.ebwizardry\:meteor.desc=Some wizards just want to see the world burn... +spell.ebwizardry\:mind_control.desc=Takes control of the target's mind for 30 seconds, causing it switch sides and fight for the caster instead. Will not work on creatures that are too strong-willed. +spell.ebwizardry\:mind_trick.desc=Confuses and disorients the target for 15 seconds, rendering it unable to attack effectively. The effect will be dispelled if the target takes damage. +spell.ebwizardry\:mine.desc=Breaks the block the caster is looking at. Potency will allow harder blocks to be broken. +spell.ebwizardry\:muffle.desc=Silences any sounds made by the caster for 30 seconds. When muffled, mobs can only detect you when looking towards you. +spell.ebwizardry\:none.desc=To get a spell book with the /give command, use id\: /give [player] ebwizardry\:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). +spell.ebwizardry\:oakflesh.desc=Improves the caster's damage resistance for 30 seconds. +spell.ebwizardry\:paralysis.desc=Delivers a powerful lightning bolt that paralyses targets for 5 seconds. Paralysed creatures cannot move and will still take damage - but too much will snap the creature out of paralysis. +spell.ebwizardry\:petrify.desc=Turns the target to stone until broken out, with a chance for it to break out when it is dark. The target cannot move or do anything while petrified but is also impervious to all damage. +spell.ebwizardry\:phase_step.desc=Teleports the caster a short distance in front of them, including through walls. Range upgrades will increase the wall thickness you can teleport through. +spell.ebwizardry\:plague_of_darkness.desc=The darkness will consume them all... +spell.ebwizardry\:pocket_furnace.desc=Smelts up to 5 smeltable items in the caster's inventory. Items on the hotbar will be smelted first. +spell.ebwizardry\:pocket_workbench.desc=Allows the caster to craft items as if they were at a crafting table. +spell.ebwizardry\:poison.desc=Fires poison in the direction you are pointing. +spell.ebwizardry\:poison_bomb.desc=Lanches a poison bomb in the direction you are pointing which explodes on impact, poisoning nearby creatures. +spell.ebwizardry\:possession.desc="Become thy enemy." +spell.ebwizardry\:ray_of_purification.desc=Emits a ray of blinding light that damages and blinds its targets. Undead creatures take double damage and are set on fire. +spell.ebwizardry\:remove_curse.desc=Removes any curse currently affecting the caster. +spell.ebwizardry\:replenish_hunger.desc=Replenishes the caster's food level by 4 hunger points. +spell.ebwizardry\:resurrection.desc=With a master healer by your side, being dead is... optional. +spell.ebwizardry\:reversal.desc=Removes a random negative potion effect and inflicts it upon the target, which will suffer that effect for the remaining duration. Potency increases the number of effects that are reversed. +spell.ebwizardry\:ring_of_fire.desc=Creates a ring of fire around the caster, damaging all nearby enemies and setting them on fire. +spell.ebwizardry\:satiety.desc=Replenishes the caster's food level by 8 hunger points. +spell.ebwizardry\:shadow_ward.desc=Creates a wall of darkness in front of the caster which causes half of all incoming damage to be inflicted upon the attacker instead. +spell.ebwizardry\:shield.desc=Creates a protective barrier of force that blocks projectiles and magic. Also grants the caster a weak resistance effect. +spell.ebwizardry\:shockwave.desc=Boom. +spell.ebwizardry\:shulker_bullet.desc=Shoots a shulker bullet which seeks targets and causes them to levitate when hit. +spell.ebwizardry\:silverfish_swarm.desc="Ahhhh! They're MULTIPLYING!" +spell.ebwizardry\:sixth_sense.desc=Allows the caster to sense the locations of nearby creatures, even through walls, for 20 seconds. +spell.ebwizardry\:slime.desc=Engulfs the target in slime which slows and continually damages it. The slime bursts after 10 seconds. +spell.ebwizardry\:slow_time.desc=Chronomancy is the art of manipulating time itself to fit one's needs. It was widely believed to be lost in the past... until now. +spell.ebwizardry\:smoke_bomb.desc=Launches a smoke bomb in the direction you are pointing which explodes on impact, releasing smoke and blinding nearby creatures for a short time. +spell.ebwizardry\:snare.desc=Sets a trap on the ground which damages and briefly slows the creature that triggers it. +spell.ebwizardry\:snowball.desc=Launches a snowball in the direction you are pointing. +spell.ebwizardry\:spark_bomb.desc=Launches a shock charge in the direction you are pointing which releases sparks at nearby enemies on impact. +spell.ebwizardry\:spectral_pathway.desc=Creates an indestructible magical bridge in front of you which extends for 15 blocks. The bridge vanishes after 60 seconds. +spell.ebwizardry\:speed_time.desc=...day, night, dawn, dusk, sunrise and sunset\: thus is the passage of time, which traps us in an endless cycle of... +spell.ebwizardry\:spider_swarm.desc=Summons a swarm of venomous spiders to fight for you. The spiders will disappear after 30 seconds or if they are killed. +spell.ebwizardry\:static_aura.desc=Surrounds the caster with lightning for 30 seconds, firing a spark of lightning at anything that hits them. +spell.ebwizardry\:summon_blaze.desc=Summons a blaze to fight for you. The blaze will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_ice_giant.desc="Smash them!" +spell.ebwizardry\:summon_ice_wraith.desc=Summons an ice wraith to fight for you. The ice wraith will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_iron_golem.desc=Automatic automated autonomous automaton. +spell.ebwizardry\:summon_lightning_wraith.desc=Summons a lightning wraith to fight for you. The lightning wraith will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_phoenix.desc=From the ashes... +spell.ebwizardry\:summon_shadow_wraith.desc=Summons a shadow wraith to fight for you. +spell.ebwizardry\:summon_skeleton.desc=Summons a skeleton to fight for you. The skeleton will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_skeleton_legion.desc="Rise, undead army!" +spell.ebwizardry\:summon_snow_golem.desc=Creates a snow golem to fight for you. Lasts until the snow golem dies. +spell.ebwizardry\:summon_spirit_horse.desc=Summons a spirit horse for you to ride. The spirit horse will vanish a short while after it is dismounted, or you can dismiss it by shift-right-clicking on it with any wand. +spell.ebwizardry\:summon_spirit_wolf.desc=Summons a spirit wolf companion to fight for you. The spirit wolf will only disappear if it is killed, or you can dismiss it by shift-right-clicking on it with any wand. +spell.ebwizardry\:summon_storm_elemental.desc="Storm Elemental\: An ancient manifestation of the elements, it can hardly contain the raw power churning within it." - The Wizard's Guide to Arcane Beings, Volume II +spell.ebwizardry\:summon_wither_skeleton.desc=Summons a wither skeleton to fight for you. The wither skeleton will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_zombie.desc=Summons a zombie to fight for you. The zombie will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:telekinesis.desc=Moves an item or other small object towards you, or right-clicks the block you are looking at. Can also be used to disarm players. +spell.ebwizardry\:thunderbolt.desc=Shoots a bolt of thunder which knocks back targets. +spell.ebwizardry\:thunderstorm.desc="Mwahahahahahaha!" +spell.ebwizardry\:tornado.desc=Unleashes a tornado in the direction you are pointing which hurls anything in its path skywards. +spell.ebwizardry\:transience.desc=Makes the caster transient for 20 seconds. The caster is immune to all damage while transient but cannot break or place blocks or cause any damage. +spell.ebwizardry\:transportation.desc=Transports the caster to their remembered stone circle. To use this spell, make a circle of stones of transportation, then right click it with a wand. +spell.ebwizardry\:vanishing_box.desc=Grants the caster access to their ender chest storage. +spell.ebwizardry\:vex_swarm.desc=Summons a swarm of flying vexes to fight for you. The vexes will disappear after 30 seconds or if they are killed. +spell.ebwizardry\:wall_of_frost.desc=Winter at your fingertips. +spell.ebwizardry\:ward.desc=Grants the caster a shielding effect that reduces incoming magic damage for 30 seconds. +spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater for 60 seconds. +spell.ebwizardry\:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. +spell.ebwizardry\:wither.desc=Fires a ray of darkness which withers anything it touches. +spell.ebwizardry\:wither_skull.desc=Launches a wither skull in the direction you are pointing. -spell.ebwizardry:invoke_weather.sun=The rain begins to stop... -spell.ebwizardry:invoke_weather.rain=The heavens open... -spell.ebwizardry:transportation.missing=Your remembered stone circle is missing or obstructed... -spell.ebwizardry:transportation.undefined=You must remember the location of a stone circle first! -spell.ebwizardry:transportation.wrongdimension=Your remembered stone circle is in another dimension... -spell.ebwizardry:clairvoyance.searching=Searching... -spell.ebwizardry:clairvoyance.confirm=The path revealed upon casting %1$s will now lead back to this point -spell.ebwizardry:clairvoyance.outofrange=Your remembered location is too far away or inaccessible... -spell.ebwizardry:clairvoyance.undefined=You must remember a location first! -spell.ebwizardry:clairvoyance.wrongdimension=Your remembered location is in another dimension... +spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... +spell.ebwizardry\:invoke_weather.rain=The heavens open... +spell.ebwizardry\:transportation.missing=The stone circle is missing or obstructed... +spell.ebwizardry\:transportation.undefined=You must remember the location of a stone circle first! +spell.ebwizardry\:transportation.wrongdimension=No remembered stone circle in this dimension... +spell.ebwizardry\:clairvoyance.searching=Searching... +spell.ebwizardry\:clairvoyance.confirm=The path revealed upon casting %1$s will now lead back to this point +spell.ebwizardry\:clairvoyance.outofrange=Your remembered locations is too far away or inaccessible... +spell.ebwizardry\:clairvoyance.undefined=You must remember a locations first! +spell.ebwizardry\:clairvoyance.wrongdimension=Your remembered locations is in another dimension... +spell.ebwizardry\:possession.insufficienthealth=You don't have enough health to possess %1$s! +spell.ebwizardry\:possession.success=Press %s to stop possessing +spell.ebwizardry\:divination.nothing=Nothing happened. +spell.ebwizardry\:divination.weak=You can just feel your wand twitching %s. +spell.ebwizardry\:divination.moderate=You feel your wand being tugged %s. +spell.ebwizardry\:divination.strong=Your wand pulls %s strongly. +spell.ebwizardry\:divination.very_strong=Your wand lurches %s sharply. +spell.ebwizardry\:divination.down=downwards +spell.ebwizardry\:divination.up=upwards +spell.ebwizardry\:divination.front=forwards +spell.ebwizardry\:divination.back=backwards +spell.ebwizardry\:divination.left=to the left +spell.ebwizardry\:divination.right=to the right +spell.ebwizardry\:resurrection.resurrect_ally=%s was resurrected by %s +spell.ebwizardry\:resurrection.resurrect_self=%s came back to life +spell.ebwizardry\:resurrection.button_wait=Resurrect (%ss) +spell.ebwizardry\:resurrection.button_ready=Resurrect -potion.ebwizardry:frost=Frostbite -potion.ebwizardry:fireskin=Fireskin -potion.ebwizardry:ice_shroud=Ice Shroud -potion.ebwizardry:static_aura=Static Aura -potion.ebwizardry:transience=Transience -potion.ebwizardry:decay=Decay -potion.ebwizardry:sixth_sense=Sixth Sense -potion.ebwizardry:arcane_jammer=Arcane Jammer -potion.ebwizardry:mind_trick=Mind Trick -potion.ebwizardry:mind_control=Mind Control -potion.ebwizardry:font_of_mana=Font of Mana -potion.ebwizardry:fear=Fear +forfeit.ebwizardry\:do_nothing=Nothing happened. -enchantment.ebwizardry:magic_sword=Imbuement -enchantment.ebwizardry:magic_bow=Imbuement -enchantment.ebwizardry:flaming_weapon=Fire Imbuement -enchantment.ebwizardry:freezing_weapon=Frost Imbuement +forfeit.ebwizardry\:burn_self=The %s bursts into flames in your hand! +forfeit.ebwizardry\:fireball=A fireball materialises in front of you! +forfeit.ebwizardry\:firebomb=A firebomb appears right above you! +forfeit.ebwizardry\:explode=A sudden explosion knocks you to the ground! +forfeit.ebwizardry\:blazes=Suddenly, hostile blazes materialise around you! +forfeit.ebwizardry\:burn_surroundings=The area around you is set ablaze! +forfeit.ebwizardry\:meteors=Fiery armageddon rains from the sky! + +forfeit.ebwizardry\:freeze_self=You feel an icy chill rush through you! +forfeit.ebwizardry\:freeze_self_2=From the %s emanates a cold so intense that it roots you to the spot! +forfeit.ebwizardry\:ice_spikes=Ice spikes rise from the ground beneath your feet! +forfeit.ebwizardry\:blizzard=You are suddenly surrounded by a raging blizzard! +forfeit.ebwizardry\:ice_wraiths=Suddenly, hostile ice wraiths materialise around you! +forfeit.ebwizardry\:hailstorm=Razor-sharp ice starts raining down upon you! +forfeit.ebwizardry\:ice_giant=A hostile ice giant materialises in front of you! + +forfeit.ebwizardry\:thunder=The scroll emits a deafening noise, knocking you to the ground! +forfeit.ebwizardry\:storm=Looks like a storm is brewing! +forfeit.ebwizardry\:lightning_sigils=Watch your step! +forfeit.ebwizardry\:lightning=You are struck by lightning! +forfeit.ebwizardry\:paralyse_self=You are paralysed! +forfeit.ebwizardry\:lightning_wraiths=Suddenly, hostile lightning wraiths materialise around you! +forfeit.ebwizardry\:storm_elementals=Hostile storm elementals materialise on all sides! + +forfeit.ebwizardry\:nausea=...huh? What just happened? Where am I? +forfeit.ebwizardry\:zombie_horde=A horde of zombies rises from the ground around you! +forfeit.ebwizardry\:wither_self=Darkness withers your soul! +forfeit.ebwizardry\:cripple_self=The %s emits a deathly howl, and you are crippled to within an inch of your life! +forfeit.ebwizardry\:shadow_wraiths=Hostile shadow wraiths materialise on all sides! + +forfeit.ebwizardry\:snares=It's a trap! +forfeit.ebwizardry\:squid=Squid! +forfeit.ebwizardry\:uproot_plants=All nearby plants are suddenly uprooted! +forfeit.ebwizardry\:poison_self=You are poisoned! +forfeit.ebwizardry\:flood=Water materialises around you! +forfeit.ebwizardry\:bury_self=The ground collapses beneath you! + +forfeit.ebwizardry\:spill_inventory=Your items spill themselves everywhere! +forfeit.ebwizardry\:teleport_self=You are instantly teleported somewhere! +forfeit.ebwizardry\:levitate_self=You begin to float upwards helplessly! +forfeit.ebwizardry\:vex_horde=A horde of vexes materialises around you! +forfeit.ebwizardry\:black_hole=A swirling vortex appears in front of you! +forfeit.ebwizardry\:arrow_rain=A barrage of arrows starts raining down upon you! + +forfeit.ebwizardry\:damage_self=Ouch! +forfeit.ebwizardry\:spill_armour=All your armour falls off! +forfeit.ebwizardry\:hunger=You suddenly feel very hungry! +forfeit.ebwizardry\:blind_self=You are blinded! +forfeit.ebwizardry\:weaken_self=You are severely weakened! +forfeit.ebwizardry\:jam_self=Your magic is rendered useless! +forfeit.ebwizardry\:curse_self=You are cursed with undeath! + +potion.ebwizardry\:frost=Frostbite +potion.ebwizardry\:fireskin=Fireskin +potion.ebwizardry\:ice_shroud=Ice Shroud +potion.ebwizardry\:static_aura=Static Aura +potion.ebwizardry\:transience=Transience +potion.ebwizardry\:decay=Decay +potion.ebwizardry\:sixth_sense=Sixth Sense +potion.ebwizardry\:arcane_jammer=Arcane Jammer +potion.ebwizardry\:mind_trick=Mind Trick +potion.ebwizardry\:mind_control=Mind Control +potion.ebwizardry\:font_of_mana=Font of Mana +potion.ebwizardry\:fear=Fear +potion.ebwizardry\:curse_of_soulbinding=Curse of Soulbinding +potion.ebwizardry\:paralysis=Paralysis +potion.ebwizardry\:muffle=Muffle +potion.ebwizardry\:ward=Ward +potion.ebwizardry\:slow_time=Slow Time +potion.ebwizardry\:empowerment=Empowerment +potion.ebwizardry\:curse_of_enfeeblement=Curse of Enfeeblement +potion.ebwizardry\:curse_of_undeath=Curse of Undeath +potion.ebwizardry\:containment=Containment +potion.ebwizardry\:frost_step=Frost Step + +enchantment.ebwizardry\:magic_sword=Imbuement +enchantment.ebwizardry\:magic_bow=Imbuement +enchantment.ebwizardry\:flaming_weapon=Fire Imbuement +enchantment.ebwizardry\:freezing_weapon=Frost Imbuement +enchantment.ebwizardry\:shocking_weapon=Lightning Imbuement + +enchantment.ebwizardry\:magic_protection=Magic Protection +enchantment.ebwizardry\:frost_protection=Frost Protection +enchantment.ebwizardry\:shock_protection=Shock Protection key.categories.ebwizardry=Wizardry @@ -625,136 +1011,268 @@ key.ebwizardry.previous_spell=Previous Spell death.attack.wizardry_magic=%1$s was killed by %2$s using magic death.attack.indirect_wizardry_magic=%1$s was killed by %2$s using magic -commands.ebwizardry:cast.usage=/%1$s [player] [damage multiplier] [range multiplier] [duration multiplier] [blast multiplier] -commands.ebwizardry:cast.success=Successfully cast %1$s -commands.ebwizardry:cast.success_continuous=Successfully cast %1$s; repeat the command to stop -commands.ebwizardry:cast.success_remote=Successfully cast %1$s as %2$s -commands.ebwizardry:cast.success_remote_continuous=Successfully cast %1$s as %2$s; repeat the command to stop -commands.ebwizardry:cast.fail=Unable to cast %1$s -commands.ebwizardry:cast.not_found=There is no such spell with ID %1$s -commands.ebwizardry:cast.tag_error=Data tag parsing failed: %s +soundCategory.ebwizardry\:spells=Spells -commands.ebwizardry:ally.usage=/%1$s [player] -commands.ebwizardry:ally.addally=%1$s has been added to %2$s's list of allies -commands.ebwizardry:ally.removeally=%1$s has been removed from %2$s's list of allies -commands.ebwizardry:ally.self=Players cannot be an ally of themselves! -commands.ebwizardry:ally.permission=You do not have permission to change other players' allies +commands.ebwizardry\:cast.usage=/%1$s [player | x y z direction] [duration] [modifiers] +commands.ebwizardry\:cast.success=Successfully cast %s +commands.ebwizardry\:cast.success_continuous=Successfully cast %s for %s seconds +commands.ebwizardry\:cast.success_remote=Successfully cast %s as %s +commands.ebwizardry\:cast.success_remote_continuous=Successfully cast %s as %s for %s seconds +commands.ebwizardry\:cast.success_position=Successfully cast %s at %s, %s, %s +commands.ebwizardry\:cast.success_position_continuous=Successfully cast %s at %s, %s, %s for %s seconds +commands.ebwizardry\:cast.fail=Unable to cast %1$s +commands.ebwizardry\:cast.not_found=There is no such spell with ID %1$s +commands.ebwizardry\:cast.invalid_direction=Invalid direction: %1$s (valid directions are: up, down, north, south, east, west) +commands.ebwizardry\:cast.tag_error=Data tag parsing failed\: %s +commands.ebwizardry\:cast.origin_not_specified=You must specify a player or location to cast the spell from +commands.ebwizardry\:cast.duration_not_specified=You must specify a duration for this spell -commands.ebwizardry:allies.usage=/%1$s [player] -commands.ebwizardry:allies.list=Players allied to you: %1$s -commands.ebwizardry:allies.list_other=Players allied to %1$s: %2$s -commands.ebwizardry:allies.permission=You do not have permission to view other players' allies -commands.ebwizardry:allies.none=None +commands.ebwizardry\:ally.usage=/%1$s [player] +commands.ebwizardry\:ally.addally=%1$s has been added to %2$s's list of allies +commands.ebwizardry\:ally.removeally=%1$s has been removed from %2$s's list of allies +commands.ebwizardry\:ally.self=Players cannot be an ally of themselves! +commands.ebwizardry\:ally.permission=You do not have permission to change other players' allies -commands.ebwizardry:discoverspell.usage=/%1$s [player] -commands.ebwizardry:discoverspell.not_found=There is no such spell with ID %1$s -commands.ebwizardry:discoverspell.clear=Cleared all spell discovery data for %1$s -commands.ebwizardry:discoverspell.all=Added all spells to %1$s's spell discovery data -commands.ebwizardry:discoverspell.addspell=Added %1$s to %2$s's spell discovery data -commands.ebwizardry:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data +commands.ebwizardry\:allies.usage=/%1$s [player] +commands.ebwizardry\:allies.list=Players allied to you\: %1$s +commands.ebwizardry\:allies.list_other=Players allied to %1$s\: %2$s +commands.ebwizardry\:allies.permission=You do not have permission to view other players' allies +commands.ebwizardry\:allies.none=None + +commands.ebwizardry\:discoverspell.usage=/%1$s [player] +commands.ebwizardry\:discoverspell.not_found=There is no such spell with ID %1$s +commands.ebwizardry\:discoverspell.clear=Cleared all spell discovery data for %1$s +commands.ebwizardry\:discoverspell.all=Added all spells to %1$s's spell discovery data +commands.ebwizardry\:discoverspell.addspell=Added %1$s to %2$s's spell discovery data +commands.ebwizardry\:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data config.ebwizardry.title.general=Mod Options +config.ebwizardry.generic.true=Enabled +config.ebwizardry.generic.false=Disabled + +config.ebwizardry.category.spells=Spell Configuration +config.ebwizardry.category.spells.tooltip=Select which spells are enabled +config.ebwizardry.title.spells=Spell Configuration +config.ebwizardry.subtitle.spells=Disabling a spell globally here will override the finer controls in the spell JSON file. + config.ebwizardry.category.gameplay=Gameplay Settings config.ebwizardry.category.gameplay.tooltip=Configure wizardry's general gameplay config.ebwizardry.title.gameplay=Gameplay Settings config.ebwizardry.subtitle.gameplay=Global settings that affect game mechanics. +config.ebwizardry.discovery_mode=Discovery Mode +config.ebwizardry.discovery_mode.tooltip=For those who like a sense of mystery! When enabled, 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 disabled. +config.ebwizardry.legacy_wand_levelling=Legacy Wand Levelling +config.ebwizardry.legacy_wand_levelling.tooltip=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 silently when this is enabled, so if you go back to the new system you won't lose any progress. +config.ebwizardry.legacy_wand_levelling.true=Yes - I liked it how it was! +config.ebwizardry.legacy_wand_levelling.false=No - Level me up! +config.ebwizardry.friendly_fire=Friendly Fire +config.ebwizardry.friendly_fire.tooltip=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 makes them completely immune if disabled. +config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting +config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge-attack players or creatures that they would not otherwise be able to attack. +config.ebwizardry.minion_revenge_targeting.true=Yes - I'd never hurt them! +config.ebwizardry.minion_revenge_targeting.false=No - they must always obey me! +config.ebwizardry.players_move_each_other=Players Move Each Other +config.ebwizardry.players_move_each_other.tooltip=Whether to allow players to move other players around using magic. +config.ebwizardry.players_move_each_other.true=Yes - let the games begin! +config.ebwizardry.players_move_each_other.false=No - I won't be pushed around +config.ebwizardry.player_block_damage=Player Block Damage +config.ebwizardry.player_block_damage.tooltip=Whether spells cast by players can destroy blocks in the world. Disable this to prevent griefing. To prevent non-players from destroying blocks with magic, use the mobGriefing gamerule. +config.ebwizardry.player_block_damage.true=Yes - kaboom! +config.ebwizardry.player_block_damage.false=No - activate anti-grief (TM) +config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament +config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Disable to prevent stealing of items. +config.ebwizardry.telekinetic_disarmament.true=Yes - let people steal things +config.ebwizardry.telekinetic_disarmament.false=No - that's cheating! +config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks +config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. +config.ebwizardry.teleport_through_unbreakable_blocks.true=Yes - I wish to see the void! +config.ebwizardry.teleport_through_unbreakable_blocks.false=No - bedrock is impenetrable +config.ebwizardry.world_time_manipulation=World Time Manipulation +config.ebwizardry.world_time_manipulation.tooltip=Whether players are allowed to change the world time with the speed time spell. If disabled, the speed time spell will not change the world time but will still speed up nearby block, entity and tile entity ticks. +config.ebwizardry.world_time_manipulation.true=Yes - daytime, nighttime... +config.ebwizardry.world_time_manipulation.false=No - I need my sleep! +config.ebwizardry.replace_vanilla_fireballs=Replace Vanilla Fireballs +config.ebwizardry.replace_vanilla_fireballs.tooltip=Whether to replace Minecraft's own fireballs with wizardry fireballs. If this is disabled, only wizardry spells will use the custom fireballs. +config.ebwizardry.replace_vanilla_fireballs.true=Yes - give me FIRE! +config.ebwizardry.replace_vanilla_fireballs.false=No - blazes are mean enough +config.ebwizardry.replace_vanilla_fall_damage=Replace Vanilla Fall Damage +config.ebwizardry.replace_vanilla_fall_damage.tooltip=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 revert to a more simplistic method of resetting the player's fall damage in certain cases. +config.ebwizardry.replace_vanilla_fall_damage.true=Yes - it's parkour time +config.ebwizardry.replace_vanilla_fall_damage.false=No - break my legs normally +config.ebwizardry.creative_bypasses_arcane_lock=Bypass Arcane Lock +config.ebwizardry.creative_bypasses_arcane_lock.tooltip=Determines which players can bypass arcane locks. +config.ebwizardry.creative_bypasses_arcane_lock.true=Anyone in creative mode +config.ebwizardry.creative_bypasses_arcane_lock.false=Only ops in creative mode +config.ebwizardry.slow_time_affects_players=Slow Time Affects Players +config.ebwizardry.slow_time_affects_players.tooltip=Whether players are slowed when another nearby player uses the slow time spell. If this is disabled, mobs and projectiles will still be affected but players will move at normal speed. +config.ebwizardry.mob_loot_table_whitelist=Mob Loot Table Whitelist +config.ebwizardry.mob_loot_table_whitelist.tooltip=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. +config.ebwizardry.mob_loot_table_blacklist=Mob Loot Table Blacklist +config.ebwizardry.mob_loot_table_blacklist.tooltip=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. +config.ebwizardry.mob_spawn_dimensions=Mob Spawning Dimensions +config.ebwizardry.mob_spawn_dimensions.tooltip=List of ids of dimensions in which wizardry's hostile mobs can spawn. +config.ebwizardry.mob_spawn_biome_blacklist=Mob Spawning Biome Blacklist +config.ebwizardry.mob_spawn_biome_blaclist.tooltip=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). +config.ebwizardry.evil_wizard_spawn_rate=Evil Wizard Spawn Rate +config.ebwizardry.evil_wizard_spawn_rate.tooltip=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. +config.ebwizardry.ice_wraith_spawn_rate=Ice Wraith Spawn Rate +config.ebwizardry.ice_wraith_spawn_rate.tooltip=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. +config.ebwizardry.lightning_wraith_spawn_rate=Lightning Wraith Spawn Rate +config.ebwizardry.lightning_wraith_spawn_rate.tooltip=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. +config.ebwizardry.forfeit_chance=Forfeit Chance +config.ebwizardry.forfeit_chance.tooltip=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. +config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor +config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. +config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor +config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. +config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist +config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist +config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist +config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.pocket_furnace_item_blacklist=Pocket Furnace Item Blacklist +config.ebwizardry.pocket_furnace_item_blacklist.tooltip=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. ebwizardry\:crystal_ore). +config.ebwizardry.divination_ore_whitelist=Divination Ore Whitelist +config.ebwizardry.divination_ore_whitelist.tooltip=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. ebwizardry\:crystal_ore). +config.ebwizardry.sword_item_whitelist=Sword Item Whitelist +config.ebwizardry.sword_item_whitelist.tooltip=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). +config.ebwizardry.bow_item_whitelist=Bow Item Whitelist +config.ebwizardry.bow_item_whitelist.tooltip=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). +config.ebwizardry.currency_items=Currency Items +config.ebwizardry.currency_items.tooltip=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. + config.ebwizardry.category.worldgen=World Generation Settings config.ebwizardry.category.worldgen.tooltip=Configure wizardry's world generation features config.ebwizardry.title.worldgen=World Generation Settings config.ebwizardry.subtitle.worldgen=Settings that affect world generation. -config.ebwizardry.category.commands=Command Settings -config.ebwizardry.category.commands.tooltip=Configure wizardry's commands -config.ebwizardry.title.commands=Command Settings -config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. +config.ebwizardry.fast_worldgen=Structure Generation +config.ebwizardry.fast_worldgen.tooltip=Controls which algorithm wizardry uses for structure generation. Fancy worldgen prevents structures on cliffs, in caves and intersecting other structures, and cleans up floating trees after generating. Fast worldgen sacrifices these improvements, potentially resulting in faster world generation. 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. +config.ebwizardry.fast_worldgen.true=Fast +config.ebwizardry.fast_worldgen.false=Fancy +config.ebwizardry.tower_dimensions=Tower Dimensions +config.ebwizardry.tower_dimensions.tooltip=List of ids of dimensions in which wizard towers will generate. +config.ebwizardry.tower_rarity=Tower Rarity +config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. 1 in this many chunks will contain a wizard tower, meaning higher numbers are rarer. +config.ebwizardry.evil_wizard_chance=Evil Wizard Chance +config.ebwizardry.evil_wizard_chance.tooltip=The chance for wizard towers to generate with an evil wizard and chest inside, instead of a friendly wizard. +config.ebwizardry.tower_files=Tower Structure Files +config.ebwizardry.tower_files.tooltip=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. +config.ebwizardry.tower_with_chest_files=Tower With Chest Structure Files +config.ebwizardry.tower_with_chest_files.tooltip=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. +config.ebwizardry.obelisk_dimensions=Obelisk Dimensions +config.ebwizardry.obelisk_dimensions.tooltip=List of ids of dimensions in which obelisks will generate. +config.ebwizardry.obelisk_rarity=Obelisk Rarity +config.ebwizardry.obelisk_rarity.tooltip=Rarity of obelisks. 1 in this many chunks will contain an obelisk, meaning higher numbers are rarer. +config.ebwizardry.obelisk_files=Obelisk Structure Files +config.ebwizardry.obelisk_files.tooltip=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. +config.ebwizardry.shrine_dimensions=Shrine Dimensions +config.ebwizardry.shrine_dimensions.tooltip=List of ids of dimensions in which shrines will generate. +config.ebwizardry.shrine_rarity=Shrine Rarity +config.ebwizardry.shrine_rarity.tooltip=Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer. +config.ebwizardry.shrine_files=Shrine Structure Files +config.ebwizardry.shrine_files.tooltip=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. +config.ebwizardry.ore_dimensions=Ore Dimensions +config.ebwizardry.ore_dimensions.tooltip=List of ids of dimensions in which crystal ore will generate. Note that removing the overworld (id 0) from this list will make the mod VERY difficult to play! +config.ebwizardry.flower_dimensions=Flower Dimensions +config.ebwizardry.flower_dimensions.tooltip=List of ids of dimensions in which crystal flowers will generate. +config.ebwizardry.loot_injection_locations=Loot Injection Locations +config.ebwizardry.loot_injection_locations.tooltip=List of loot tables to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into. config.ebwizardry.category.client=Client Settings config.ebwizardry.category.client.tooltip=Configure wizardry's display and controls config.ebwizardry.title.client=Client Settings config.ebwizardry.subtitle.client=Client-side settings that only affect the local minecraft game. -config.ebwizardry.category.spells=Spell Configuration -config.ebwizardry.category.spells.tooltip=Select which spells are enabled -config.ebwizardry.title.spells=Spell Configuration -config.ebwizardry.subtitle.spells=Set a spell to false to disable it. +config.ebwizardry.shift_scrolling=Shift-scrolling +config.ebwizardry.shift_scrolling.tooltip=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. +config.ebwizardry.reverse_scroll_direction=Scroll Direction +config.ebwizardry.reverse_scroll_direction.tooltip=The scroll direction used to switch between spells on a wand while sneaking. +config.ebwizardry.reverse_scroll_direction.true=Reversed +config.ebwizardry.reverse_scroll_direction.false=Normal +config.ebwizardry.spell_hud_position=Spell HUD Position +config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. +config.ebwizardry.spell_hud_skin=Spell HUD Skin +config.ebwizardry.spell_hud_skin.tooltip=Change the look of the spell HUD... +config.ebwizardry.spell_hud_skin.preview=Preview +config.ebwizardry.handbook_progression=Handbook Progression +config.ebwizardry.handbook_progression.tooltip=When enabled, to help guide players through the mod, sections of The Wizard's Handbook are unlocked when a player gains the advancement that triggers them, and are hidden otherwise. When disabled, the entire handbook is readable regardless of advancement progress. The entire handbook is always readable in creative mode. +config.ebwizardry.handbook_progression.true=Yes - teach me, Sensei! +config.ebwizardry.handbook_progression.false=No - I know what I'm doing +config.ebwizardry.books_pause_game=Books Pause Game +config.ebwizardry.books_pause_game.tooltip=Whether opening any of wizardry's books pauses the game in singleplayer. Has no effect on servers or LAN worlds. +config.ebwizardry.books_pause_game.true=Yes - I don't want any distractions! +config.ebwizardry.books_pause_game.false=No - I read to pass the time +config.ebwizardry.summoned_creature_names=Summoned Creature Names +config.ebwizardry.summoned_creature_names.tooltip=Controls whether summoned creatures' names and owners are displayed above their heads. +config.ebwizardry.summoned_creature_names.true=Shown +config.ebwizardry.summoned_creature_names.false=Hidden +config.ebwizardry.use_shaders=Use Custom Shaders +config.ebwizardry.use_shaders.tooltip=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. +config.ebwizardry.use_shaders.true=Yes - gimme those sweet shaders! +config.ebwizardry.use_shaders.false=No - I'm running this on a potato + +config.ebwizardry.category.commands=Command Settings +config.ebwizardry.category.commands.tooltip=Configure wizardry's commands +config.ebwizardry.title.commands=Command Settings +config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. + +config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit +config.ebwizardry.cast_command_multiplier_limit.tooltip=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! +config.ebwizardry.cast_command_name=Cast Spell Command Name +config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. +config.ebwizardry.discoverspell_command_name=Discover Spell Command Name +config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. +config.ebwizardry.ally_command_name=Set Ally Command Name +config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. +config.ebwizardry.allies_command_name=View Allies Command Name +config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. config.ebwizardry.category.resistances=Resistance Configuration config.ebwizardry.category.resistances.tooltip=Configure which mobs are immune to different types of magic config.ebwizardry.title.resistances=Resistance Configuration config.ebwizardry.subtitle.resistances=Settings which allow entities to be made immune to certain types of magic. -config.ebwizardry.tower_rarity=Tower Rarity -config.ebwizardry.ore_dimensions=Ore Dimensions -config.ebwizardry.flower_dimensions=Flower Dimensions -config.ebwizardry.tower_dimensions=Tower Dimensions -config.ebwizardry.spell_book_drop_chance=Spell Book Drop Chance -config.ebwizardry.generate_loot=Generate Loot -config.ebwizardry.firebomb_is_craftable=Firebomb Is Craftable -config.ebwizardry.poison_bomb_is_craftable=Poison Bomb Is Craftable -config.ebwizardry.smoke_bomb_is_craftable=Smoke Bomb Is Craftable -config.ebwizardry.use_alternate_scroll_recipe=Use Alternate Scroll Recipe -config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks -config.ebwizardry.show_summoned_creature_names=Show Summoned Creature Names -config.ebwizardry.friendly_fire=Friendly Fire -config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament -config.ebwizardry.discovery_mode=Discovery Mode -config.ebwizardry.enable_shift_scrolling=Enable Shift-scrolling -config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting -config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor -config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor -config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit -config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist -config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist -config.ebwizardry.spell_hud_position=Spell HUD Position -config.ebwizardry.cast_command_name=Cast Spell Command Name -config.ebwizardry.discoverspell_command_name=Discover Spell Command Name -config.ebwizardry.ally_command_name=Set Ally Command Name -config.ebwizardry.allies_command_name=View Allies Command Name -config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist -config.ebwizardry.evil_wizard_dimensions=Evil Wizard Dimensions - config.ebwizardry.mobs_immune_to_fire=Mobs Immune To Fire +config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice +config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning +config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither +config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison +config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry\:wizard). -config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely. -config.ebwizardry.ore_dimensions.tooltip=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! -config.ebwizardry.flower_dimensions.tooltip=List of dimension ids in which crystal flowers will generate. -config.ebwizardry.tower_dimensions.tooltip=List of dimension ids in which wizard towers will generate. -config.ebwizardry.spell_book_drop_chance.tooltip=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. -config.ebwizardry.generate_loot.tooltip=Whether to generate wizardry loot in dungeon chests. -config.ebwizardry.firebomb_is_craftable.tooltip=Whether firebombs can be crafted or not. -config.ebwizardry.poison_bomb_is_craftable.tooltip=Whether poison bombs can be crafted or not. -config.ebwizardry.smoke_bomb_is_craftable.tooltip=Whether smoke bombs can be crafted or not. -config.ebwizardry.use_alternate_scroll_recipe.tooltip=Whether to require a magic crystal in the shapeless crafting recipe for blank scrolls. Set to true if another mod adds a conflicting recipe. -config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. -config.ebwizardry.show_summoned_creature_names.tooltip=Whether to show summoned creatures' names and owners above their heads. -config.ebwizardry.friendly_fire.tooltip=Whether to allow players to damage their designated allies using magic. -config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items. -config.ebwizardry.discovery_mode.tooltip=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. -config.ebwizardry.enable_shift_scrolling.tooltip=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. -config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge attack their owner if their owner attacks them. -config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. -config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. -config.ebwizardry.cast_command_multiplier_limit.tooltip=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! -config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. -config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. -config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. -config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. -config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. -config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.evil_wizard_dimensions.tooltip=List of dimension ids in which evil wizards can spawn. +config.ebwizardry.category.compatibility=Mod Compatibility Settings +config.ebwizardry.category.compatibility.tooltip=Configure how wizardry interacts with other mods +config.ebwizardry.title.compatibility=Mod Compatibility Settings +config.ebwizardry.subtitle.compatibility=Settings that affect how wizardry interacts with other mods -config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry:wizard). +config.ebwizardry.damage_source_blacklist=Damage Source Blacklist +config.ebwizardry.damage_source_blacklist.tooltip=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. +config.ebwizardry.compatibility_warnings=Compatibility Warnings +config.ebwizardry.compatibility_warnings.tooltip=Whether to print compatibility warnings to the console. Set to false if excessive messages are being printed. +config.ebwizardry.baubles_integration=Baubles Integration +config.ebwizardry.baubles_integration.tooltip=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. +config.ebwizardry.jei_integration=JEI Integration +config.ebwizardry.jei_integration.tooltip=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. +config.ebwizardry.antique_atlas_integration=Antique Atlas Integration +config.ebwizardry.antique_atlas_integration.tooltip=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. +config.ebwizardry.auto_place_tower_markers=Auto-Place Tower Markers +config.ebwizardry.auto_place_tower_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of wizard towers. +config.ebwizardry.auto_place_obelisk_markers=Auto-Place Obelisk Markers +config.ebwizardry.auto_place_obelisk_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of obelisks. +config.ebwizardry.auto_place_shrine_markers=Auto-Place Shrine Markers +config.ebwizardry.auto_place_shrine_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of shrines. + +integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench + +integration.antiqueatlas.marker.ebwizardry\:wizard_tower=Wizard Tower +integration.antiqueatlas.marker.ebwizardry\:obelisk=Obelisk +integration.antiqueatlas.marker.ebwizardry\:shrine=Shrine wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 7c14a457..bcc6666f 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -1,283 +1,515 @@ -tile.ebwizardry:arcane_workbench.name=Arcane Workbench -tile.ebwizardry:crystal_ore.name=Crystal Ore -tile.ebwizardry:petrified_stone.name=Petrified Stone -tile.ebwizardry:ice_statue.name=Ice Statue -tile.ebwizardry:crystal_flower.name=Crystal Flower -tile.ebwizardry:snare.name=Snare -tile.ebwizardry:transportation_stone.name=Stone of Transportation -tile.ebwizardry:spectral_block.name=Spectral Block -tile.ebwizardry:crystal_block.name=Block of Crystal +#PARSE_ESCAPES +tile.ebwizardry\:arcane_workbench.name=Arcane Workbench +tile.ebwizardry\:crystal_ore.name=Crystal Ore +tile.ebwizardry\:petrified_stone.name=Petrified Stone +tile.ebwizardry\:ice_statue.name=Ice Statue +tile.ebwizardry\:crystal_flower.name=Crystal Flower +tile.ebwizardry\:snare.name=Snare +tile.ebwizardry\:transportation_stone.name=Stone of Transportation +tile.ebwizardry\:transportation_stone.confirm=You will now be returned here upon casting %1$s +tile.ebwizardry\:transportation_stone.remember=Remembered the location %s, %s, %s in dimension %s +tile.ebwizardry\:transportation_stone.forget=Forgot the location %s, %s, %s in dimension %s +tile.ebwizardry\:transportation_stone.invalid=You must make a circle with 8 stones of transportation first! +tile.ebwizardry\:spectral_block.name=Spectral Block +tile.ebwizardry\:runestone.name=Runestone +tile.ebwizardry\:runestone_pedestal.name=Runestone Pedestal +tile.ebwizardry\:thorns.name=Thorns +tile.ebwizardry\:obsidian_crust.name=Obsidian Crust +tile.ebwizardry\:dry_frosted_ice.name=Dry Frosted Ice -item.ebwizardry:magic_crystal.name=Magic Crystal -item.ebwizardry:magic_wand.name=Magic Wand -item.ebwizardry:apprentice_wand.name=Apprentice Wand -item.ebwizardry:advanced_wand.name=Advanced Wand -item.ebwizardry:master_wand.name=Master Wand -item.ebwizardry:spell_book.name=Spell Book +tile.ebwizardry\:magic_crystal_block.name=Block of Crystal +tile.ebwizardry\:fire_crystal_block.name=Block of Fiery Crystal +tile.ebwizardry\:ice_crystal_block.name=Block of Icy Crystal +tile.ebwizardry\:lightning_crystal_block.name=Block of Stormy Crystal +tile.ebwizardry\:necromancy_crystal_block.name=Block of Dark Crystal +tile.ebwizardry\:earth_crystal_block.name=Block of Verdant Crystal +tile.ebwizardry\:sorcery_crystal_block.name=Block of Mystical Crystal +tile.ebwizardry\:healing_crystal_block.name=Block of Radiant Crystal -item.ebwizardry:arcane_tome.name=Tome of Arcana -item.ebwizardry:arcane_tome.desc1=Upgrades any %1$s -item.ebwizardry:arcane_tome.desc2=wand to %1$s tier +item.ebwizardry\:crystal_magic.name=Magic Crystal +item.ebwizardry\:crystal_fire.name=Fiery Crystal +item.ebwizardry\:crystal_ice.name=Icy Crystal +item.ebwizardry\:crystal_lightning.name=Stormy Crystal +item.ebwizardry\:crystal_necromancy.name=Dark Crystal +item.ebwizardry\:crystal_earth.name=Verdant Crystal +item.ebwizardry\:crystal_sorcery.name=Mystical Crystal +item.ebwizardry\:crystal_healing.name=Radiant Crystal -item.ebwizardry:wizard_handbook.name=The Wizard's Handbook -item.ebwizardry:wizard_handbook.desc=by %1$s +item.ebwizardry\:magic_wand.name=Magic Wand +item.ebwizardry\:apprentice_wand.name=Apprentice Wand +item.ebwizardry\:advanced_wand.name=Advanced Wand +item.ebwizardry\:master_wand.name=Master Wand +item.ebwizardry\:spell_book.name=Spell Book -item.ebwizardry:wand.buff=+%1$s %2$s potency -item.ebwizardry:wand.spell=Current Spell: %1$s -item.ebwizardry:wand.mana=Mana: %1$s/%2$s +item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s -item.ebwizardry:wand.addally=%1$s has been added to your list of allies -item.ebwizardry:wand.removeally=%1$s has been removed from your list of allies +item.ebwizardry\:arcane_tome.name=Tome of Arcana +item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s +item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier -item.ebwizardry:basic_fire_wand.name=Wand of Embers -item.ebwizardry:basic_ice_wand.name=Wand of Frost -item.ebwizardry:basic_lightning_wand.name=Wand of Sparks -item.ebwizardry:basic_necromancy_wand.name=Wand of Shadows -item.ebwizardry:basic_earth_wand.name=Wand of the Forest -item.ebwizardry:basic_sorcery_wand.name=Wand of Mystery -item.ebwizardry:basic_healing_wand.name=Wand of Healing +item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook +item.ebwizardry\:wizard_handbook.desc=by %1$s -item.ebwizardry:apprentice_fire_wand.name=Apprentice Pyromancer Wand -item.ebwizardry:apprentice_ice_wand.name=Apprentice Ice Mage Wand -item.ebwizardry:apprentice_lightning_wand.name=Apprentice Storm Mage Wand -item.ebwizardry:apprentice_necromancy_wand.name=Apprentice Necromancer Wand -item.ebwizardry:apprentice_earth_wand.name=Apprentice Earth Mage Wand -item.ebwizardry:apprentice_sorcery_wand.name=Apprentice Sorcerer Wand -item.ebwizardry:apprentice_healing_wand.name=Apprentice Healer Wand +item.ebwizardry\:wand.generic=wand -item.ebwizardry:advanced_fire_wand.name=Wand of the Pyromancer -item.ebwizardry:advanced_ice_wand.name=Wand of the Ice Mage -item.ebwizardry:advanced_lightning_wand.name=Wand of the Storm Mage -item.ebwizardry:advanced_necromancy_wand.name=Wand of the Necromancer -item.ebwizardry:advanced_earth_wand.name=Wand of the Earth Mage -item.ebwizardry:advanced_sorcery_wand.name=Wand of the Sorcerer -item.ebwizardry:advanced_healing_wand.name=Wand of the Healer +item.ebwizardry\:wand.buff=+%1$s %2$s potency +item.ebwizardry\:wand.spell=Current Spell\: %1$s +item.ebwizardry\:wand.mana=Mana\: %1$s/%2$s +item.ebwizardry\:wand.progression=Progression\: %1$s/%2$s -item.ebwizardry:master_fire_wand.name=Master Pyromancer Wand -item.ebwizardry:master_ice_wand.name=Master Ice Mage Wand -item.ebwizardry:master_lightning_wand.name=Master Storm Mage Wand -item.ebwizardry:master_necromancy_wand.name=Master Necromancer Wand -item.ebwizardry:master_earth_wand.name=Master Earth Mage Wand -item.ebwizardry:master_sorcery_wand.name=Master Sorcerer Wand -item.ebwizardry:master_healing_wand.name=Master Healer Wand +item.ebwizardry\:wand.levelup=%1$s is ready to upgrade to %2$s tier -item.ebwizardry:spectral_sword.name=Spectral Sword -item.ebwizardry:spectral_pickaxe.name=Spectral Pickaxe -item.ebwizardry:spectral_bow.name=Spectral Bow +item.ebwizardry\:wand.addally=%1$s has been added to your list of allies +item.ebwizardry\:wand.removeally=%1$s has been removed from your list of allies -item.ebwizardry:mana_flask.name=Mana Flask -item.ebwizardry:storage_upgrade.name=Wand Storage Upgrade -item.ebwizardry:siphon_upgrade.name=Wand Siphon Upgrade -item.ebwizardry:condenser_upgrade.name=Wand Condenser Upgrade -item.ebwizardry:range_upgrade.name=Wand Range Upgrade -item.ebwizardry:duration_upgrade.name=Wand Duration Upgrade -item.ebwizardry:cooldown_upgrade.name=Wand Cooldown Upgrade -item.ebwizardry:blast_upgrade.name=Wand Blast Upgrade -item.ebwizardry:attunement_upgrade.name=Wand Attunement Upgrade +item.ebwizardry\:novice_fire_wand.name=Wand of Embers +item.ebwizardry\:novice_ice_wand.name=Wand of Frost +item.ebwizardry\:novice_lightning_wand.name=Wand of Sparks +item.ebwizardry\:novice_necromancy_wand.name=Wand of Shadows +item.ebwizardry\:novice_earth_wand.name=Wand of the Forest +item.ebwizardry\:novice_sorcery_wand.name=Wand of Mystery +item.ebwizardry\:novice_healing_wand.name=Wand of Healing -item.ebwizardry:flaming_axe.name=Flaming Axe -item.ebwizardry:frost_axe.name=Frost Axe +item.ebwizardry\:apprentice_fire_wand.name=Apprentice Pyromancer Wand +item.ebwizardry\:apprentice_ice_wand.name=Apprentice Ice Mage Wand +item.ebwizardry\:apprentice_lightning_wand.name=Apprentice Storm Mage Wand +item.ebwizardry\:apprentice_necromancy_wand.name=Apprentice Necromancer Wand +item.ebwizardry\:apprentice_earth_wand.name=Apprentice Earth Mage Wand +item.ebwizardry\:apprentice_sorcery_wand.name=Apprentice Sorcerer Wand +item.ebwizardry\:apprentice_healing_wand.name=Apprentice Healer Wand -item.ebwizardry:firebomb.name=Firebomb -item.ebwizardry:poison_bomb.name=Poison Bomb -item.ebwizardry:smoke_bomb.name=Smoke Bomb +item.ebwizardry\:advanced_fire_wand.name=Wand of the Pyromancer +item.ebwizardry\:advanced_ice_wand.name=Wand of the Ice Mage +item.ebwizardry\:advanced_lightning_wand.name=Wand of the Storm Mage +item.ebwizardry\:advanced_necromancy_wand.name=Wand of the Necromancer +item.ebwizardry\:advanced_earth_wand.name=Wand of the Earth Mage +item.ebwizardry\:advanced_sorcery_wand.name=Wand of the Sorcerer +item.ebwizardry\:advanced_healing_wand.name=Wand of the Healer -item.ebwizardry:blank_scroll.name=Blank Scroll -item.ebwizardry:scroll.name=Scroll of %1$s -item.ebwizardry:scroll.undiscovered.name=Scroll "%1$s" -item.ebwizardry:identification_scroll.name=Scroll of Identification -item.ebwizardry:identification_scroll.desc1=%1$sIdentifies an unknown -item.ebwizardry:identification_scroll.desc2=%1$sspell book or scroll -item.ebwizardry:identification_scroll.nothing_to_identify=Nothing to identify! +item.ebwizardry\:master_fire_wand.name=Master Pyromancer Wand +item.ebwizardry\:master_ice_wand.name=Master Ice Mage Wand +item.ebwizardry\:master_lightning_wand.name=Master Storm Mage Wand +item.ebwizardry\:master_necromancy_wand.name=Master Necromancer Wand +item.ebwizardry\:master_earth_wand.name=Master Earth Mage Wand +item.ebwizardry\:master_sorcery_wand.name=Master Sorcerer Wand +item.ebwizardry\:master_healing_wand.name=Master Healer Wand -item.ebwizardry:armour_upgrade.name=Arcane Seal of Protection -item.ebwizardry:armour_upgrade.desc1=%1$sUpgrades any wizard armour -item.ebwizardry:armour_upgrade.desc2=%1$sto make it %2$slegendary +item.ebwizardry\:spectral_sword.name=Spectral Sword +item.ebwizardry\:spectral_pickaxe.name=Spectral Pickaxe +item.ebwizardry\:spectral_bow.name=Spectral Bow -item.ebwizardry:magic_silk.name=Magical Silk +item.ebwizardry\:spectral_sword_upgraded.name=Spectral Sword +item.ebwizardry\:spectral_pickaxe_upgraded.name=Spectral Pickaxe -item.ebwizardry:wizard_armour.legendary=Legendary -item.ebwizardry:wizard_armour.buff=-%1$s %2$s cost -item.ebwizardry:wizard_armour.mana=Mana: %1$s/%2$s +item.ebwizardry\:small_mana_flask.name=Small Mana Flask +item.ebwizardry\:medium_mana_flask.name=Medium Mana Flask +item.ebwizardry\:large_mana_flask.name=Large Mana Flask -item.ebwizardry:wizard_hat.name=Wizard Hat -item.ebwizardry:wizard_robe.name=Wizard Robes -item.ebwizardry:wizard_leggings.name=Wizard Leggings -item.ebwizardry:wizard_boots.name=Wizard Boots +item.ebwizardry\:grand_crystal.name=Grand Magic Crystal +item.ebwizardry\:crystal_shard.name=Magic Crystal Shard -item.ebwizardry:wizard_hat_fire.name=Pyromancer Hat -item.ebwizardry:wizard_robe_fire.name=Pyromancer Robes -item.ebwizardry:wizard_leggings_fire.name=Pyromancer Leggings -item.ebwizardry:wizard_boots_fire.name=Pyromancer Boots +item.ebwizardry\:astral_diamond.name=Astral Diamond -item.ebwizardry:wizard_hat_ice.name=Ice Mage Hat -item.ebwizardry:wizard_robe_ice.name=Ice Mage Robes -item.ebwizardry:wizard_leggings_ice.name=Ice Mage Leggings -item.ebwizardry:wizard_boots_ice.name=Ice Mage Boots +item.ebwizardry\:purifying_elixir.name=Purifying Elixir +item.ebwizardry\:purifying_elixir.desc=Removes curses when consumed -item.ebwizardry:wizard_hat_lightning.name=Storm Mage Hat -item.ebwizardry:wizard_robe_lightning.name=Storm Mage Robes -item.ebwizardry:wizard_leggings_lightning.name=Storm Mage Leggings -item.ebwizardry:wizard_boots_lightning.name=Storm Mage Boots +item.ebwizardry\:storage_upgrade.name=Wand Storage Upgrade +item.ebwizardry\:siphon_upgrade.name=Wand Siphon Upgrade +item.ebwizardry\:condenser_upgrade.name=Wand Condenser Upgrade +item.ebwizardry\:range_upgrade.name=Wand Range Upgrade +item.ebwizardry\:duration_upgrade.name=Wand Duration Upgrade +item.ebwizardry\:cooldown_upgrade.name=Wand Cooldown Upgrade +item.ebwizardry\:blast_upgrade.name=Wand Blast Upgrade +item.ebwizardry\:attunement_upgrade.name=Wand Attunement Upgrade +item.ebwizardry\:melee_upgrade.name=Wand Melee Upgrade -item.ebwizardry:wizard_hat_necromancy.name=Necromancer Hat -item.ebwizardry:wizard_robe_necromancy.name=Necromancer Robes -item.ebwizardry:wizard_leggings_necromancy.name=Necromancer Leggings -item.ebwizardry:wizard_boots_necromancy.name=Necromancer Boots +item.ebwizardry\:storage_upgrade.desc=Upgrades the mana capacity of a wand +item.ebwizardry\:siphon_upgrade.desc=Upgrades a wand to extract mana from mobs when killed +item.ebwizardry\:condenser_upgrade.desc=Upgrades a wand to slowly regenerate mana over time +item.ebwizardry\:range_upgrade.desc=Upgrades the effective range of spells cast by a wand +item.ebwizardry\:duration_upgrade.desc=Upgrades the duration of effects cast by a wand +item.ebwizardry\:cooldown_upgrade.desc=Upgrades the spell cooldown speed of a wand +item.ebwizardry\:blast_upgrade.desc=Upgrades the area of effect of spells cast by a wand +item.ebwizardry\:attunement_upgrade.desc=Upgrades the number of spells that can be bound to a wand +item.ebwizardry\:melee_upgrade.desc=Upgrades a wand to use mana to deal more damage to mobs when hit -item.ebwizardry:wizard_hat_earth.name=Earth Mage Hat -item.ebwizardry:wizard_robe_earth.name=Earth Mage Robes -item.ebwizardry:wizard_leggings_earth.name=Earth Mage Leggings -item.ebwizardry:wizard_boots_earth.name=Earth Mage Boots +item.ebwizardry\:flaming_axe.name=Flaming Axe +item.ebwizardry\:frost_axe.name=Frost Axe -item.ebwizardry:wizard_hat_sorcery.name=Sorcerer Hat -item.ebwizardry:wizard_robe_sorcery.name=Sorcerer Robes -item.ebwizardry:wizard_leggings_sorcery.name=Sorcerer Leggings -item.ebwizardry:wizard_boots_sorcery.name=Sorcerer Boots +item.ebwizardry\:flaming_axe_upgraded.name=Flaming Axe +item.ebwizardry\:frost_axe_upgraded.name=Frost Axe -item.ebwizardry:wizard_hat_healing.name=Healer Hat -item.ebwizardry:wizard_robe_healing.name=Healer Robes -item.ebwizardry:wizard_leggings_healing.name=Healer Leggings -item.ebwizardry:wizard_boots_healing.name=Healer Boots +item.ebwizardry\:firebomb.name=Firebomb +item.ebwizardry\:poison_bomb.name=Poison Bomb +item.ebwizardry\:smoke_bomb.name=Smoke Bomb +item.ebwizardry\:spark_bomb.name=Spark Bomb -item.ebwizardry:spawn_wizard.name=Spawn Wizard -item.ebwizardry:spawn_evil_wizard.name=Spawn Evil Wizard +item.ebwizardry\:blank_scroll.name=Blank Scroll +item.ebwizardry\:scroll.generic=scroll +item.ebwizardry\:scroll.name=Scroll of %1$s +item.ebwizardry\:scroll.undiscovered.name=Scroll "%1$s" +item.ebwizardry\:identification_scroll.name=Scroll of Identification +item.ebwizardry\:identification_scroll.desc=Identifies an unknown spell book or scroll +item.ebwizardry\:identification_scroll.nothing_to_identify=Nothing to identify! -item.ebwizardry:spectral_helmet.name=Spectral Helmet -item.ebwizardry:spectral_chestplate.name=Spectral Chestplate -item.ebwizardry:spectral_leggings.name=Spectral Leggings -item.ebwizardry:spectral_boots.name=Spectral Boots +item.ebwizardry\:armour_upgrade.name=Arcane Seal of Protection +item.ebwizardry\:armour_upgrade.desc1=%1$sUpgrades any wizard armour +item.ebwizardry\:armour_upgrade.desc2=%1$sto make it %2$slegendary -entity.ebwizardry:summonedcreature.nameplate=%1$s's %2$s -entity.ebwizardry:summonedcreature.nameplate_fallback=Someone's %1$s +item.ebwizardry\:magic_silk.name=Magical Silk -entity.ebwizardry:zombie_minion.name=Zombie -entity.ebwizardry:skeleton_minion.name=Skeleton -entity.ebwizardry:spider_minion.name=Spider -entity.ebwizardry:blaze_minion.name=Blaze -entity.ebwizardry:wither_skeleton_minion.name=Wither Skeleton -entity.ebwizardry:ice_wraith.name=Ice Wraith -entity.ebwizardry:lightning_wraith.name=Lightning Wraith -entity.ebwizardry:shadow_wraith.name=Shadow Wraith -entity.ebwizardry:spirit_wolf.name=Spirit Wolf -entity.ebwizardry:spirit_horse.name=Spirit Horse -entity.ebwizardry:ice_giant.name=Ice Giant -entity.ebwizardry:phoenix.name=Phoenix -entity.ebwizardry:wizard.name=Wizard -entity.ebwizardry:magic_slime.name=Magical Slime -entity.ebwizardry:silverfish_minion.name=Silverfish -entity.ebwizardry:storm_elemental.name=Storm Elemental -entity.ebwizardry:evil_wizard.name=Wizard -entity.ebwizardry:decoy.name=Decoy +item.ebwizardry\:wizard_armour.legendary=Legendary +item.ebwizardry\:wizard_armour.buff=-%1$s %2$s cost +item.ebwizardry\:wizard_armour.mana=Mana\: %1$s/%2$s -entity.ebwizardry:magic_missile.name=Magic -entity.ebwizardry:arc.name=Magic -entity.ebwizardry:spark_bomb.name=Magic -entity.ebwizardry:ice_shard.name=Magic -entity.ebwizardry:firebomb.name=Magic -entity.ebwizardry:poison_bomb.name=Magic -entity.ebwizardry:force_orb.name=Magic -entity.ebwizardry:spark.name=Magic -entity.ebwizardry:darkness_orb.name=Magic -entity.ebwizardry:fire_sigil.name=Magic -entity.ebwizardry:frost_sigil.name=Magic -entity.ebwizardry:lightning_sigil.name=Magic -entity.ebwizardry:lightning_arrow.name=Magic -entity.ebwizardry:firebolt.name=Magic -entity.ebwizardry:ice_charge.name=Magic -entity.ebwizardry:force_arrow.name=Magic -entity.ebwizardry:dart.name=Magic -entity.ebwizardry:lightning_disc.name=Magic -entity.ebwizardry:thunderbolt.name=Magic -entity.ebwizardry:decay.name=Magic -entity.ebwizardry:ice_lance.name=Magic -entity.ebwizardry:smoke_bomb.name=Magic -entity.ebwizardry:ice_spike.name=Magic +item.ebwizardry\:wizard_hat.name=Wizard Hat +item.ebwizardry\:wizard_robe.name=Wizard Robes +item.ebwizardry\:wizard_leggings.name=Wizard Leggings +item.ebwizardry\:wizard_boots.name=Wizard Boots -entity.ebwizardry:black_hole.name=Black Hole -entity.ebwizardry:shield.name=Shield -entity.ebwizardry:meteor.name=Meteor -entity.ebwizardry:blizzard.name=Blizzard -entity.ebwizardry:bubble.name=Bubble -entity.ebwizardry:tornado.name=Tornado -entity.ebwizardry:lightning_hammer.name=Lightning Hammer -entity.ebwizardry:arrow_rain.name=Arrow Rain -entity.ebwizardry:healing_aura.name=Healing Aura -entity.ebwizardry:forcefield.name=Forcefield -entity.ebwizardry:ring_of_fire.name=Ring of Fire -entity.ebwizardry:earthquake.name=Earthquake -entity.ebwizardry:falling_grass.name=Falling Grass -entity.ebwizardry:hailstorm.name=Hailstorm -entity.ebwizardry:lightning_pulse.name=Lightning Pulse +item.ebwizardry\:wizard_hat_fire.name=Pyromancer Hat +item.ebwizardry\:wizard_robe_fire.name=Pyromancer Robes +item.ebwizardry\:wizard_leggings_fire.name=Pyromancer Leggings +item.ebwizardry\:wizard_boots_fire.name=Pyromancer Boots + +item.ebwizardry\:wizard_hat_ice.name=Ice Mage Hat +item.ebwizardry\:wizard_robe_ice.name=Ice Mage Robes +item.ebwizardry\:wizard_leggings_ice.name=Ice Mage Leggings +item.ebwizardry\:wizard_boots_ice.name=Ice Mage Boots + +item.ebwizardry\:wizard_hat_lightning.name=Storm Mage Hat +item.ebwizardry\:wizard_robe_lightning.name=Storm Mage Robes +item.ebwizardry\:wizard_leggings_lightning.name=Storm Mage Leggings +item.ebwizardry\:wizard_boots_lightning.name=Storm Mage Boots + +item.ebwizardry\:wizard_hat_necromancy.name=Necromancer Hat +item.ebwizardry\:wizard_robe_necromancy.name=Necromancer Robes +item.ebwizardry\:wizard_leggings_necromancy.name=Necromancer Leggings +item.ebwizardry\:wizard_boots_necromancy.name=Necromancer Boots + +item.ebwizardry\:wizard_hat_earth.name=Earth Mage Hat +item.ebwizardry\:wizard_robe_earth.name=Earth Mage Robes +item.ebwizardry\:wizard_leggings_earth.name=Earth Mage Leggings +item.ebwizardry\:wizard_boots_earth.name=Earth Mage Boots + +item.ebwizardry\:wizard_hat_sorcery.name=Sorcerer Hat +item.ebwizardry\:wizard_robe_sorcery.name=Sorcerer Robes +item.ebwizardry\:wizard_leggings_sorcery.name=Sorcerer Leggings +item.ebwizardry\:wizard_boots_sorcery.name=Sorcerer Boots + +item.ebwizardry\:wizard_hat_healing.name=Healer Hat +item.ebwizardry\:wizard_robe_healing.name=Healer Robes +item.ebwizardry\:wizard_leggings_healing.name=Healer Leggings +item.ebwizardry\:wizard_boots_healing.name=Healer Boots + +item.ebwizardry\:spawn_wizard.name=Spawn Wizard +item.ebwizardry\:spawn_evil_wizard.name=Spawn Evil Wizard + +item.ebwizardry\:spectral_helmet.name=Spectral Helmet +item.ebwizardry\:spectral_chestplate.name=Spectral Chestplate +item.ebwizardry\:spectral_leggings.name=Spectral Leggings +item.ebwizardry\:spectral_boots.name=Spectral Boots + +item.ebwizardry\:lightning_hammer.name=Lightning Hammer + +item.ebwizardry\:ring_condensing.name=Ring of Condensing +item.ebwizardry\:ring_condensing.desc=Slowly regenerates mana for all wands on your hotbar +item.ebwizardry\:ring_siphoning.name=Ring of Siphoning +item.ebwizardry\:ring_siphoning.desc=Increases siphoned mana by 30%% +item.ebwizardry\:ring_battlemage.name=Ring of the Battlemage +item.ebwizardry\:ring_battlemage.desc=Holding a wand in your offhand and a sword in your main hand grants 10%% extra magic damage +item.ebwizardry\:ring_combustion.name=Ring of Combustion +item.ebwizardry\:ring_combustion.desc=Creatures killed by fire spells explode +item.ebwizardry\:ring_fire_melee.name=Ring of Fiery Touch +item.ebwizardry\:ring_fire_melee.desc=Hitting a creature with a fire wand sets it on fire +item.ebwizardry\:ring_fire_biome.name=Ring of the Desert Sun +item.ebwizardry\:ring_fire_biome.desc=Fire spells are 30%% more potent in hot biomes +item.ebwizardry\:ring_disintegration.name=Ring of Searing Embers +item.ebwizardry\:ring_disintegration.desc=All fire attack spells cause their victims to disintegrate +item.ebwizardry\:ring_ice_melee.name=Ring of Icy Touch +item.ebwizardry\:ring_ice_melee.desc=Hitting a creature with an ice wand gives it the frostbite effect +item.ebwizardry\:ring_ice_biome.name=Ring of Glaciation +item.ebwizardry\:ring_ice_biome.desc=Ice spells are 30%% more potent in snowy biomes +item.ebwizardry\:ring_arcane_frost.name=Ring of Arcane Frost +item.ebwizardry\:ring_arcane_frost.desc=Creatures killed by ice spells release ice shards in all directions +item.ebwizardry\:ring_shattering.name=Ring of Shattering +item.ebwizardry\:ring_shattering.desc=Melee attacks on mobs with the frostbite effect have a chance to shatter them into ice shards +item.ebwizardry\:ring_lightning_melee.name=Ring of Chaining +item.ebwizardry\:ring_lightning_melee.desc=Hitting a creature with a lightning wand shoots lightning at another nearby creature +item.ebwizardry\:ring_storm.name=Ring of the Gathering Storm +item.ebwizardry\:ring_storm.desc=During thunderstorms, lightning spells have dramatically reduced cooldowns +item.ebwizardry\:ring_seeking.name=Ring of Attraction +item.ebwizardry\:ring_seeking.desc=All projectile spells seek their targets +item.ebwizardry\:ring_hammer.name=Ring of Thalek the Almighty +item.ebwizardry\:ring_hammer.desc=Lightning hammers can be picked up and thrown +item.ebwizardry\:ring_soulbinding.name=Soulwalker's Ring +item.ebwizardry\:ring_soulbinding.desc=Creatures damaged by necromancy spells become soulbound to you +item.ebwizardry\:ring_leeching.name=Ring of Leeching +item.ebwizardry\:ring_leeching.desc=All necromancy attacks have a 30%% chance to trigger a life drain effect +item.ebwizardry\:ring_necromancy_melee.name=Ring of Necrotic Touch +item.ebwizardry\:ring_necromancy_melee.desc=Hitting a creature with a necromancy wand gives it the wither effect +item.ebwizardry\:ring_mind_control.name=Ring of the Psychic +item.ebwizardry\:ring_mind_control.desc=Mind-controlled creatures have a chance to mind control other nearby creatures +item.ebwizardry\:ring_poison.name=Serpentine Ring +item.ebwizardry\:ring_poison.desc=All earth spells poison their target +item.ebwizardry\:ring_earth_melee.name=Ring of Venomous Touch +item.ebwizardry\:ring_earth_melee.desc=Hitting a creature with an earth wand poisons it +item.ebwizardry\:ring_earth_biome.name=Dryad's Ring +item.ebwizardry\:ring_earth_biome.desc=Earth spells are 30%% more potent in forests and roofed forests +item.ebwizardry\:ring_full_moon.name=Ring of the Howling Wolf +item.ebwizardry\:ring_full_moon.desc=Earth spells have dramatically reduced cooldowns under a full moon +item.ebwizardry\:ring_extraction.name=Ring of Extraction +item.ebwizardry\:ring_extraction.desc=Kills with sorcery spells grant bonus mana +item.ebwizardry\:ring_mana_return.name=Ring of the Perfectionist +item.ebwizardry\:ring_mana_return.desc=Force arrows that miss their target return the mana they used to your wand +item.ebwizardry\:ring_blockwrangler.name=Blockwrangler's Ring +item.ebwizardry\:ring_blockwrangler.desc=Thrown blocks deal twice their normal damage +item.ebwizardry\:ring_conjurer.name=Conjurer's Ring +item.ebwizardry\:ring_conjurer.desc=Conjured items last twice as long +item.ebwizardry\:ring_defender.name=Ring of the Defender +item.ebwizardry\:ring_defender.desc=Your projectiles pass through forcefields belonging to you or an ally +item.ebwizardry\:ring_paladin.name=Paladin's Ring +item.ebwizardry\:ring_paladin.desc=When you heal yourself or an ally, nearby allies will also gain some health +item.ebwizardry\:ring_interdiction.name=Ring of Interdiction +item.ebwizardry\:ring_interdiction.desc=Your forcefield damages creatures that touch it + +item.ebwizardry\:amulet_arcane_defence.name=Amulet of Arcane Defense +item.ebwizardry\:amulet_arcane_defence.desc=Slowly regenerates mana for all worn wizard armor +item.ebwizardry\:amulet_warding.name=Amulet of Warding +item.ebwizardry\:amulet_warding.desc=Reduces all incoming magic damage by 10%% +item.ebwizardry\:amulet_wisdom.name=Amulet of Wisdom +item.ebwizardry\:amulet_wisdom.desc=Greatly reduces the chance of negative effects when using undiscovered spells +item.ebwizardry\:amulet_fire_protection.name=Flamefast Amulet +item.ebwizardry\:amulet_fire_protection.desc=Reduces fire damage by 30%% +item.ebwizardry\:amulet_fire_cloaking.name=Amulet of Cloaking Flame +item.ebwizardry\:amulet_fire_cloaking.desc=Reduces incoming damage by 75%% whilst standing within a ring of fire belonging to you or an ally +item.ebwizardry\:amulet_ice_immunity.name=Permafrost Amulet +item.ebwizardry\:amulet_ice_immunity.desc=Grants total immunity to frostbite effects +item.ebwizardry\:amulet_ice_protection.name=Frostbound Amulet +item.ebwizardry\:amulet_ice_protection.desc=Reduces frost damage by 30%% +item.ebwizardry\:amulet_potential.name=Amulet of Potential +item.ebwizardry\:amulet_potential.desc=Grants a 15%% chance to shoot lightning at creatures that melee attack you +item.ebwizardry\:amulet_channeling.name=Amulet of Channeling +item.ebwizardry\:amulet_channeling.desc=Grants a 30%% chance to negate incoming shock damage +item.ebwizardry\:amulet_lich.name=Amulet of the Lich +item.ebwizardry\:amulet_lich.desc=Grants a 15%% chance for incoming damage to be dealt to a nearby minion instead +item.ebwizardry\:amulet_wither_immunity.name=Wither Pearl Amulet +item.ebwizardry\:amulet_wither_immunity.desc=Grants total immunity to wither effects +item.ebwizardry\:amulet_glide.name=Windfeather Amulet +item.ebwizardry\:amulet_glide.desc=Falling more than 3 blocks has a 50%% chance to activate the glide effect +item.ebwizardry\:amulet_banishing.name=Enderbound Amulet +item.ebwizardry\:amulet_banishing.desc=Creatures that melee attack you have a 15%% chance to be teleported to a random nearby location +item.ebwizardry\:amulet_anchoring.name=Amulet of Anchoring +item.ebwizardry\:amulet_anchoring.desc=Grants immunity to being moved by magic +item.ebwizardry\:amulet_recovery.name=Amulet of Recovery +item.ebwizardry\:amulet_recovery.desc=When on less than 50%% health, mana from your armor will be used to heal you over time +item.ebwizardry\:amulet_transience.name=Amulet of Transience +item.ebwizardry\:amulet_transience.desc=Grants a 25%% chance to activate a transience effect when critically wounded +item.ebwizardry\:amulet_resurrection.name=Amulet of the Immortal +item.ebwizardry\:amulet_resurrection.desc=The resurrection spell can be used on yourself from beyond the grave +item.ebwizardry\:amulet_auto_shield.name=Hyeleth's Amulet +item.ebwizardry\:amulet_auto_shield.desc=Grants a 25%% chance to trigger the shield spell for a few seconds when bound to a wand on your hotbar + +item.ebwizardry\:charm_haggler.name=Haggler's Sign +item.ebwizardry\:charm_haggler.desc=Wizards are guaranteed to offer a new trade every time you trade with them +item.ebwizardry\:charm_experience_tome.name=Tome of the Diligent +item.ebwizardry\:charm_experience_tome.desc=Increases the rate at which wands gain progression by 40%% +item.ebwizardry\:charm_auto_smelt.name=Metallurgist's Mark +item.ebwizardry\:charm_auto_smelt.desc=Pocket furnace triggers automatically when bound to a wand on your hotbar +item.ebwizardry\:charm_lava_walking.name=Nether Ice Core +item.ebwizardry\:charm_lava_walking.desc=Frost step freezes lava to form an obsidian crust +item.ebwizardry\:charm_storm.name=Bottled Thundercloud +item.ebwizardry\:charm_storm.desc=Invoke weather is guaranteed to summon storms +item.ebwizardry\:charm_minion_health.name=Obsidian Zombie Head +item.ebwizardry\:charm_minion_health.desc=Summoned creatures are 25%% stronger +item.ebwizardry\:charm_minion_variants.name=Talisman of Transformation +item.ebwizardry\:charm_minion_variants.desc=Summoned zombies are husks; summoned skeletons are strays +item.ebwizardry\:charm_flight.name=Emerald Beetle Wing +item.ebwizardry\:charm_flight.desc=Flight and Glide are 50%% faster +item.ebwizardry\:charm_growth.name=Crystal Flower Charm +item.ebwizardry\:charm_growth.desc=Growth aura has a 35%% chance to instantly grow crops to fully-grown +item.ebwizardry\:charm_abseiling.name=Enchanted Twine +item.ebwizardry\:charm_abseiling.desc=Holding sneak whilst grappling slowly pays out the line +item.ebwizardry\:charm_silk_touch.name=Moonstone Orb +item.ebwizardry\:charm_silk_touch.desc=Blocks broken with the mine spell always drop themselves +item.ebwizardry\:charm_stop_time.name=Peculiar Pocketwatch +item.ebwizardry\:charm_stop_time.desc=The slow time spell makes time stand still +item.ebwizardry\:charm_light.name=Elevinia's Everburning Lantern +item.ebwizardry\:charm_light.desc=Conjured light sources last forever and may be dispelled by right-clicking with a wand +item.ebwizardry\:charm_transportation.name=Ancient Compass +item.ebwizardry\:charm_transportation.desc=Up to four stone circles may be remembered and selected from when using transportation +item.ebwizardry\:charm_feeding.name=Bottomless Provisions +item.ebwizardry\:charm_feeding.desc=Replenish hunger triggers automatically when bound to a wand on your hotbar + +item.charge_status.full=Full +item.charge_status.almost_full=Almost full +item.charge_status.mostly_full=Mostly full +item.charge_status.half_full=Half full +item.charge_status.mostly_empty=Mostly empty +item.charge_status.almost_empty=Almost empty +item.charge_status.empty=Empty + +entity.ebwizardry\:summonedcreature.nameplate=%1$s's %2$s +entity.ebwizardry\:summonedcreature.nameplate_fallback=Someone's %1$s + +entity.ebwizardry\:wizard.greeting_0=Good day, fellow wanderer. +entity.ebwizardry\:wizard.greeting_1=Greetings, traveller. What brings you here? +entity.ebwizardry\:wizard.greeting_2=Ah, nice to see another person around here. +entity.ebwizardry\:wizard.speech_0=Might I interest you in any spells, perhaps? +entity.ebwizardry\:wizard.speech_1=Magic is everywhere, if you know where to look. +entity.ebwizardry\:wizard.speech_2=There is still much to be learned about the arcane arts, adventurer. +entity.ebwizardry\:wizard.speech_3=Perhaps you have learned something yourself that you wish to share? +entity.ebwizardry\:wizard.speech_4=Studying the arcane is most fascinating, don't you think? +entity.ebwizardry\:wizard.farewell_0=Good luck in your quest, adventurer. +entity.ebwizardry\:wizard.farewell_1=I trust that we will meet again soon, friend. +entity.ebwizardry\:wizard.farewell_2=Goodbye then, traveller. +entity.ebwizardry\:wizard.combat_0=Be gone, foul creatures! +entity.ebwizardry\:wizard.combat_1=Leave me alone, pests! +entity.ebwizardry\:wizard.combat_2=Undead beings are not welcome here, shoo! +entity.ebwizardry\:wizard.combat_3=Away with you, creatures of darkness! +entity.ebwizardry\:wizard.combat_4=This is all I need! Get out of here, monsters! +entity.ebwizardry\:wizard.combat_5=Return to the caves from whence you came, evil creatures! +entity.ebwizardry\:wizard.player_combat_0=You will regret that decision, traveller! +entity.ebwizardry\:wizard.player_combat_1=What do you think you are doing?! +entity.ebwizardry\:wizard.player_combat_2=Only a fool dares to anger a wizard! +entity.ebwizardry\:wizard.player_combat_3=You will pay for your carelessness, adventurer! +entity.ebwizardry\:wizard.player_combat_4=Be ready to defend yourself, villain! +entity.ebwizardry\:wizard.player_combat_5=Prepare to feel my wrath! + +entity.ebwizardry\:zombie_minion.name=Zombie +entity.ebwizardry\:husk_minion.name=Husk +entity.ebwizardry\:skeleton_minion.name=Skeleton +entity.ebwizardry\:stray_minion.name=Stray +entity.ebwizardry\:spider_minion.name=Spider +entity.ebwizardry\:blaze_minion.name=Blaze +entity.ebwizardry\:wither_skeleton_minion.name=Wither Skeleton +entity.ebwizardry\:ice_wraith.name=Ice Wraith +entity.ebwizardry\:lightning_wraith.name=Lightning Wraith +entity.ebwizardry\:shadow_wraith.name=Shadow Wraith +entity.ebwizardry\:spirit_wolf.name=Spirit Wolf +entity.ebwizardry\:spirit_horse.name=Spirit Horse +entity.ebwizardry\:ice_giant.name=Ice Giant +entity.ebwizardry\:phoenix.name=Phoenix +entity.ebwizardry\:wizard.name=Wizard +entity.ebwizardry\:magic_slime.name=Magical Slime +entity.ebwizardry\:silverfish_minion.name=Silverfish +entity.ebwizardry\:storm_elemental.name=Storm Elemental +entity.ebwizardry\:evil_wizard.name=Wizard +entity.ebwizardry\:decoy.name=Decoy +entity.ebwizardry\:vex_minion.name=Vex + +entity.ebwizardry\:magic_missile.name=Magic +entity.ebwizardry\:arc.name=Magic +entity.ebwizardry\:spark_bomb.name=Magic +entity.ebwizardry\:ice_shard.name=Magic +entity.ebwizardry\:firebomb.name=Magic +entity.ebwizardry\:poison_bomb.name=Magic +entity.ebwizardry\:force_orb.name=Magic +entity.ebwizardry\:spark.name=Magic +entity.ebwizardry\:darkness_orb.name=Magic +entity.ebwizardry\:fire_sigil.name=Magic +entity.ebwizardry\:frost_sigil.name=Magic +entity.ebwizardry\:lightning_sigil.name=Magic +entity.ebwizardry\:lightning_arrow.name=Magic +entity.ebwizardry\:firebolt.name=Magic +entity.ebwizardry\:ice_charge.name=Magic +entity.ebwizardry\:force_arrow.name=Magic +entity.ebwizardry\:dart.name=Magic +entity.ebwizardry\:lightning_disc.name=Magic +entity.ebwizardry\:thunderbolt.name=Magic +entity.ebwizardry\:decay.name=Magic +entity.ebwizardry\:ice_lance.name=Magic +entity.ebwizardry\:smoke_bomb.name=Magic +entity.ebwizardry\:ice_spike.name=Magic +entity.ebwizardry\:combustion_rune.name=Magic + +entity.ebwizardry\:black_hole.name=Black Hole +entity.ebwizardry\:shield.name=Shield +entity.ebwizardry\:meteor.name=Meteor +entity.ebwizardry\:blizzard.name=Blizzard +entity.ebwizardry\:bubble.name=Bubble +entity.ebwizardry\:tornado.name=Tornado +entity.ebwizardry\:lightning_hammer.name=Lightning Hammer +entity.ebwizardry\:arrow_rain.name=Arrow Rain +entity.ebwizardry\:healing_aura.name=Healing Aura +entity.ebwizardry\:forcefield.name=Forcefield +entity.ebwizardry\:ring_of_fire.name=Ring of Fire +entity.ebwizardry\:earthquake.name=Earthquake +entity.ebwizardry\:hailstorm.name=Hailstorm +entity.ebwizardry\:lightning_pulse.name=Lightning Pulse itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells +itemGroup.ebwizardrygear=Wizard Gear -advancement.wizardry:root=Wizardry -advancement.wizardry:root.desc=A wizard's journey to mastering the arcane -advancement.wizardry:crystal=A Curious Crystal... -advancement.wizardry:crystal.desc=Mine a magic crystal -advancement.wizardry:arcane_initiate=Arcane Initiate -advancement.wizardry:arcane_initiate.desc=Craft a magic wand with a gold nugget, a stick and a magic crystal -advancement.wizardry:apprentice=Wizard's Apprentice -advancement.wizardry:apprentice.desc=Use a tome of arcana to upgrade your wand -advancement.wizardry:master=Arcane Master -advancement.wizardry:master.desc=Obtain a master wand -advancement.wizardry:all_spells=Mage of All Trades -advancement.wizardry:all_spells.desc=Cast every single spell in the game -advancement.wizardry:wizard_trade=Magic Dealing -advancement.wizardry:wizard_trade.desc=Purchase an item from a wizard -advancement.wizardry:buy_master_spell=Knowledge is Power -advancement.wizardry:buy_master_spell.desc=Purchase a master spell from a wizard -advancement.wizardry:freeze_blaze=Not So Hot Now -advancement.wizardry:freeze_blaze.desc=Freeze a blaze solid -advancement.wizardry:charge_creeper=It's Gonna Blow -advancement.wizardry:charge_creeper.desc='Accidentally' charge a creeper -advancement.wizardry:frankenstein=Frankenstein -advancement.wizardry:frankenstein.desc=Turn a pig into a zombie pigman using the lightning bolt spell -advancement.wizardry:special_upgrade=Arcane Tinkering -advancement.wizardry:special_upgrade.desc=Apply a special upgrade to a wand -advancement.wizardry:craft_flask=It's Magic, Bottled! -advancement.wizardry:craft_flask.desc=Craft a mana flask -advancement.wizardry:elemental=Elemental -advancement.wizardry:elemental.desc=Obtain an elemental wand -advancement.wizardry:armour_set=Now You're a Proper Wizard -advancement.wizardry:armour_set.desc=Craft and equip a full set of wizard armor -advancement.wizardry:legendary=Legendary -advancement.wizardry:legendary.desc=Obtain a piece of legendary wizard armor -advancement.wizardry:self_destruct=That Backfired -advancement.wizardry:self_destruct.desc=Get killed by your own magic -advancement.wizardry:pig_tornado=Not Again... -advancement.wizardry:pig_tornado.desc=Ride a pig into a tornado -advancement.wizardry:jam_wizard=Jamming Session -advancement.wizardry:jam_wizard.desc=Use the arcane jammer spell on a wizard -advancement.wizardry:slime_skeleton=Sticky Situation -advancement.wizardry:slime_skeleton.desc=Engulf a skeleton in slime -advancement.wizardry:anger_wizard=You'll Regret That -advancement.wizardry:anger_wizard.desc=Make a wizard angry -advancement.wizardry:defeat_evil_wizard=Righteousness -advancement.wizardry:defeat_evil_wizard.desc=Defeat an evil wizard -advancement.wizardry:max_out_wand=Fully Equipped -advancement.wizardry:max_out_wand.desc=Apply the maximum number of upgrades to a master wand -advancement.wizardry:element_master=Element Mastery -advancement.wizardry:element_master.desc=Cast all the spells of any element -advancement.wizardry:identify_spell=Arcane Appraisal -advancement.wizardry:identify_spell.desc=Use a scroll of identification to identify a spell book or scroll +advancement.ebwizardry\:root=Wizardry +advancement.ebwizardry\:root.desc=A wizard's journey to mastering the arcane +advancement.ebwizardry\:crystal=A Curious Crystal... +advancement.ebwizardry\:crystal.desc=Mine a magic crystal +advancement.ebwizardry\:arcane_initiate=Arcane Initiate +advancement.ebwizardry\:arcane_initiate.desc=Craft a magic wand with a gold nugget, a stick and a magic crystal -tile.ebwizardry:transportation_stone.confirm=You will now be returned here upon casting %1$s -tile.ebwizardry:transportation_stone.invalid=You must make a circle with 8 stones of transportation first! +advancement.ebwizardry\:apprentice=Wizard's Apprentice +advancement.ebwizardry\:apprentice.desc=Use a tome of arcana to upgrade your wand +advancement.ebwizardry\:special_upgrade=Arcane Tinkering +advancement.ebwizardry\:special_upgrade.desc=Apply a special upgrade to a wand +advancement.ebwizardry\:advanced=Advancing Rapidly +advancement.ebwizardry\:advanced.desc=Upgrade your wand to advanced tier +advancement.ebwizardry\:master=Arcane Master +advancement.ebwizardry\:master.desc=Obtain a master wand +advancement.ebwizardry\:max_out_wand=Fully Equipped +advancement.ebwizardry\:max_out_wand.desc=Apply the maximum number of upgrades to a master wand +advancement.ebwizardry\:all_spells=Mage of All Trades +advancement.ebwizardry\:all_spells.desc=Cast every single spell in the game -container.ebwizardry:arcane_workbench=Arcane Workbench -container.ebwizardry:arcane_workbench.apply=Apply -container.ebwizardry:arcane_workbench.mana=Mana: -container.ebwizardry:arcane_workbench.upgrades=Applied Upgrades: +advancement.ebwizardry\:wizard_tower=Who Lives Here? +advancement.ebwizardry\:wizard_tower.desc=Visit a wizard's tower +advancement.ebwizardry\:wizard_trade=Magic Dealing +advancement.ebwizardry\:wizard_trade.desc=Purchase an item from a wizard +advancement.ebwizardry\:anger_wizard=You'll Regret That +advancement.ebwizardry\:anger_wizard.desc=Make a wizard angry +advancement.ebwizardry\:defeat_evil_wizard=Righteousness +advancement.ebwizardry\:defeat_evil_wizard.desc=Defeat an evil wizard +advancement.ebwizardry\:buy_master_spell=Knowledge is Power +advancement.ebwizardry\:buy_master_spell.desc=Purchase a master spell from a wizard -tier.basic=Novice +advancement.ebwizardry\:discover_spell=Trial And Error +advancement.ebwizardry\:discover_spell.desc=Identify an unknown spell by casting it and seeing what happens +advancement.ebwizardry\:identify_spell=Arcane Appraisal +advancement.ebwizardry\:identify_spell.desc=Find a more reliable way of identifying spells +advancement.ebwizardry\:spell_failure=That Backfired +advancement.ebwizardry\:spell_failure.desc=Find out the hard way how spells can go wrong +advancement.ebwizardry\:discover_master_spell=Dicing With Danger +advancement.ebwizardry\:discover_master_spell.desc=Successfully identify a master spell by trial and error + +advancement.ebwizardry\:visit_shrine=Enshrined +advancement.ebwizardry\:visit_shrine.desc=Enter a shrine and awaken its ancient magic +advancement.ebwizardry\:artefact=A Forgotten Relic +advancement.ebwizardry\:artefact.desc=Lift the protective enchantments from a shrine and claim the treasures within +advancement.ebwizardry\:all_artefacts=A Veritable Museum +advancement.ebwizardry\:all_artefacts.desc=Collect all of the rings, amulets and charms + +advancement.ebwizardry\:armour_set=Now You're a Proper Wizard +advancement.ebwizardry\:armour_set.desc=Craft and equip a full set of wizard armor +advancement.ebwizardry\:legendary=Legendary +advancement.ebwizardry\:legendary.desc=Obtain a piece of legendary wizard armor + +advancement.ebwizardry\:enchant_scroll=Scroll Up! +advancement.ebwizardry\:enchant_scroll.desc=Craft a blank scroll and enchant it with a spell at an arcane workbench + +handbook.toast.title=New Handbook Section Unlocked! + +container.ebwizardry\:arcane_workbench=Arcane Workbench +container.ebwizardry\:arcane_workbench.apply=Apply +container.ebwizardry\:arcane_workbench.mana=Mana\: +container.ebwizardry\:arcane_workbench.upgrades=Applied Upgrades\: + +tier.novice=Novice tier.apprentice=Apprentice tier.advanced=Advanced tier.master=Master -element.simple=None +element.magic=None element.fire=Fire element.ice=Ice element.lightning=Lightning @@ -286,7 +518,7 @@ element.earth=Earth element.sorcery=Sorcery element.healing=Healing -element.simple.wizard=Wizard +element.magic.wizard=Wizard element.fire.wizard=Pyromancer element.ice.wizard=Ice Mage element.lightning.wizard=Storm Mage @@ -299,323 +531,477 @@ spelltype.attack=Attack spelltype.defence=Defense spelltype.utility=Utility spelltype.minion=Minion +spelltype.buff=Buff +spelltype.construct=Construct +spelltype.projectile=Projectile +spelltype.alteration=Alteration spell.disabled=%1$s has been disabled in the config spell.resist=%1$s resisted %2$s spell.discover=Discovered the spell %1$s! -spell.ebwizardry:agility=Agility -spell.ebwizardry:arc=Arc -spell.ebwizardry:arcane_jammer=Arcane Jammer -spell.ebwizardry:arrow_rain=Arrow Rain -spell.ebwizardry:banish=Banish -spell.ebwizardry:black_hole=Black Hole -spell.ebwizardry:blink=Blink -spell.ebwizardry:blizzard=Blizzard -spell.ebwizardry:bubble=Bubble -spell.ebwizardry:chain_lightning=Chain Lightning -spell.ebwizardry:clairvoyance=Clairvoyance -spell.ebwizardry:cobwebs=Cobwebs -spell.ebwizardry:conjure_armour=Conjure Armor -spell.ebwizardry:conjure_bow=Conjure Bow -spell.ebwizardry:conjure_pickaxe=Conjure Pickaxe -spell.ebwizardry:conjure_sword=Conjure Sword -spell.ebwizardry:cure_effects=Cure Effects -spell.ebwizardry:curse_of_soulbinding=Curse of Soulbinding -spell.ebwizardry:darkness_orb=Darkness Orb -spell.ebwizardry:darkvision=Darkvision -spell.ebwizardry:dart=Dart -spell.ebwizardry:decay=Decay -spell.ebwizardry:decoy=Decoy -spell.ebwizardry:detonate=Detonate -spell.ebwizardry:diamondflesh=Diamondflesh -spell.ebwizardry:earthquake=Earthquake -spell.ebwizardry:entrapment=Entrapment -spell.ebwizardry:fireball=Fireball -spell.ebwizardry:firebolt=Firebolt -spell.ebwizardry:firebomb=Firebomb -spell.ebwizardry:fire_resistance=Fire Resistance -spell.ebwizardry:fire_sigil=Fire Sigil -spell.ebwizardry:fireskin=Fireskin -spell.ebwizardry:firestorm=Firestorm -spell.ebwizardry:flame_ray=Flame Ray -spell.ebwizardry:flaming_axe=Flaming Axe -spell.ebwizardry:flaming_weapon=Flaming Weapon -spell.ebwizardry:flight=Flight -spell.ebwizardry:font_of_mana=Font of Mana -spell.ebwizardry:font_of_vitality=Font of Vitality -spell.ebwizardry:force_arrow=Force Arrow -spell.ebwizardry:forcefield=Forcefield -spell.ebwizardry:force_orb=Force Orb -spell.ebwizardry:forests_curse=Forest's Curse -spell.ebwizardry:freeze=Freeze -spell.ebwizardry:freezing_weapon=Freezing Weapon -spell.ebwizardry:frost_axe=Frost Axe -spell.ebwizardry:frost_ray=Frost Ray -spell.ebwizardry:frost_sigil=Frost Sigil -spell.ebwizardry:glide=Glide -spell.ebwizardry:greater_fireball=Greater Fireball -spell.ebwizardry:greater_heal=Greater Heal -spell.ebwizardry:group_heal=Group Heal -spell.ebwizardry:growth_aura=Growth Aura -spell.ebwizardry:hailstorm=Hailstorm -spell.ebwizardry:heal=Heal -spell.ebwizardry:heal_ally=Heal Ally -spell.ebwizardry:healing_aura=Healing Aura -spell.ebwizardry:homing_spark=Homing Spark -spell.ebwizardry:ice_age=Ice Age -spell.ebwizardry:ice_charge=Ice Charge -spell.ebwizardry:ice_lance=Ice Lance -spell.ebwizardry:ice_shard=Ice Shard -spell.ebwizardry:ice_shroud=Ice Shroud -spell.ebwizardry:ice_spikes=Ice Spikes -spell.ebwizardry:ice_statue=Ice Statue -spell.ebwizardry:ignite=Ignite -spell.ebwizardry:imbue_weapon=Imbue Weapon -spell.ebwizardry:intimidate=Intimidate -spell.ebwizardry:invigorating_presence=Invigorating Presence -spell.ebwizardry:invisibility=Invisibility -spell.ebwizardry:invoke_weather=Invoke Weather -spell.ebwizardry:ironflesh=Ironflesh -spell.ebwizardry:leap=Leap -spell.ebwizardry:levitation=Levitation -spell.ebwizardry:life_drain=Life Drain -spell.ebwizardry:light=Light -spell.ebwizardry:lightning_arrow=Lightning Arrow -spell.ebwizardry:lightning_bolt=Lightning Bolt -spell.ebwizardry:lightning_disc=Lightning Disk -spell.ebwizardry:lightning_hammer=Lightning Hammer -spell.ebwizardry:lightning_pulse=Lightning Pulse -spell.ebwizardry:lightning_ray=Lightning Ray -spell.ebwizardry:lightning_sigil=Lightning Sigil -spell.ebwizardry:lightning_web=Lightning Web -spell.ebwizardry:magic_missile=Magic Missile -spell.ebwizardry:metamorphosis=Metamorphosis -spell.ebwizardry:meteor=Meteor -spell.ebwizardry:mind_control=Mind Control -spell.ebwizardry:mind_trick=Mind Trick -spell.ebwizardry:none=[Empty Slot] -spell.ebwizardry:oakflesh=Oakflesh -spell.ebwizardry:petrify=Petrify -spell.ebwizardry:phase_step=Phase Step -spell.ebwizardry:plague_of_darkness=Plague of Darkness -spell.ebwizardry:pocket_furnace=Pocket Furnace -spell.ebwizardry:pocket_workbench=Pocket Workbench -spell.ebwizardry:poison=Poison -spell.ebwizardry:poison_bomb=Poison Bomb -spell.ebwizardry:replenish_hunger=Replenish Hunger -spell.ebwizardry:ring_of_fire=Ring of Fire -spell.ebwizardry:shadow_ward=Shadow Ward -spell.ebwizardry:shield=Shield -spell.ebwizardry:shockwave=Shockwave -spell.ebwizardry:silverfish_swarm=Silverfish Swarm -spell.ebwizardry:sixth_sense=Sixth Sense -spell.ebwizardry:slime=Slime -spell.ebwizardry:smoke_bomb=Smoke Bomb -spell.ebwizardry:snare=Snare -spell.ebwizardry:snowball=Snowball -spell.ebwizardry:spark_bomb=Spark Bomb -spell.ebwizardry:spectral_pathway=Spectral Pathway -spell.ebwizardry:spider_swarm=Spider Swarm -spell.ebwizardry:static_aura=Static Aura -spell.ebwizardry:summon_blaze=Summon Blaze -spell.ebwizardry:summon_ice_giant=Summon Ice Giant -spell.ebwizardry:summon_ice_wraith=Summon Ice Wraith -spell.ebwizardry:summon_iron_golem=Summon Iron Golem -spell.ebwizardry:summon_lightning_wraith=Summon Lightning Wraith -spell.ebwizardry:summon_phoenix=Summon Phoenix -spell.ebwizardry:summon_shadow_wraith=Summon Shadow Wraith -spell.ebwizardry:summon_skeleton=Summon Skeleton -spell.ebwizardry:summon_skeleton_legion=Summon Skeleton Legion -spell.ebwizardry:summon_snow_golem=Summon Snow Golem -spell.ebwizardry:summon_spirit_horse=Summon Spirit Horse -spell.ebwizardry:summon_spirit_wolf=Summon Spirit Wolf -spell.ebwizardry:summon_storm_elemental=Summon Storm Elemental -spell.ebwizardry:summon_wither_skeleton=Summon Wither Skeleton -spell.ebwizardry:summon_zombie=Summon Zombie -spell.ebwizardry:telekinesis=Telekinesis -spell.ebwizardry:thunderbolt=Thunderbolt -spell.ebwizardry:thunderstorm=Thunderstorm -spell.ebwizardry:tornado=Tornado -spell.ebwizardry:transience=Transience -spell.ebwizardry:transportation=Transportation -spell.ebwizardry:vanishing_box=Vanishing Box -spell.ebwizardry:wall_of_frost=Wall of Frost -spell.ebwizardry:water_breathing=Water Breathing -spell.ebwizardry:whirlwind=Whirlwind -spell.ebwizardry:wither=Wither -spell.ebwizardry:wither_skull=Wither Skull +spell.ebwizardry\:agility=Agility +spell.ebwizardry\:arc=Arc +spell.ebwizardry\:arcane_jammer=Arcane Jammer +spell.ebwizardry\:arcane_lock=Arcane Lock +spell.ebwizardry\:arrow_rain=Arrow Rain +spell.ebwizardry\:banish=Banish +spell.ebwizardry\:black_hole=Black Hole +spell.ebwizardry\:blink=Blink +spell.ebwizardry\:blizzard=Blizzard +spell.ebwizardry\:bubble=Bubble +spell.ebwizardry\:chain_lightning=Chain Lightning +spell.ebwizardry\:charge=Charge +spell.ebwizardry\:clairvoyance=Clairvoyance +spell.ebwizardry\:cobwebs=Cobwebs +spell.ebwizardry\:combustion_rune=Combustion Rune +spell.ebwizardry\:conjure_armour=Conjure Armor +spell.ebwizardry\:conjure_block=Conjure Block +spell.ebwizardry\:conjure_bow=Conjure Bow +spell.ebwizardry\:conjure_pickaxe=Conjure Pickaxe +spell.ebwizardry\:conjure_sword=Conjure Sword +spell.ebwizardry\:containment=Containment +spell.ebwizardry\:cure_effects=Cure Effects +spell.ebwizardry\:curse_of_enfeeblement=Curse of Enfeeblement +spell.ebwizardry\:curse_of_soulbinding=Curse of Soulbinding +spell.ebwizardry\:curse_of_undeath=Curse of Undeath +spell.ebwizardry\:darkness_orb=Darkness Orb +spell.ebwizardry\:darkvision=Darkvision +spell.ebwizardry\:dart=Dart +spell.ebwizardry\:decay=Decay +spell.ebwizardry\:decoy=Decoy +spell.ebwizardry\:detonate=Detonate +spell.ebwizardry\:diamondflesh=Diamondflesh +spell.ebwizardry\:disintegration=Disintegration +spell.ebwizardry\:divination=Divination +spell.ebwizardry\:dragon_fireball=Dragon Fireball +spell.ebwizardry\:earthquake=Earthquake +spell.ebwizardry\:empowering_presence=Empowering Presence +spell.ebwizardry\:entrapment=Entrapment +spell.ebwizardry\:evade=Evade +spell.ebwizardry\:fireball=Fireball +spell.ebwizardry\:firebolt=Firebolt +spell.ebwizardry\:firebomb=Firebomb +spell.ebwizardry\:fire_resistance=Fire Resistance +spell.ebwizardry\:fire_sigil=Fire Sigil +spell.ebwizardry\:fireskin=Fireskin +spell.ebwizardry\:fire_breath=Fire Breath +spell.ebwizardry\:flame_ray=Flame Ray +spell.ebwizardry\:flaming_axe=Flaming Axe +spell.ebwizardry\:flaming_weapon=Flaming Weapon +spell.ebwizardry\:flight=Flight +spell.ebwizardry\:font_of_mana=Font of Mana +spell.ebwizardry\:font_of_vitality=Font of Vitality +spell.ebwizardry\:force_arrow=Force Arrow +spell.ebwizardry\:forcefield=Forcefield +spell.ebwizardry\:force_orb=Force Orb +spell.ebwizardry\:forests_curse=Forest's Curse +spell.ebwizardry\:forest_of_thorns=Forest of Thorns +spell.ebwizardry\:freeze=Freeze +spell.ebwizardry\:freezing_weapon=Freezing Weapon +spell.ebwizardry\:frost_axe=Frost Axe +spell.ebwizardry\:frost_ray=Frost Ray +spell.ebwizardry\:frost_sigil=Frost Sigil +spell.ebwizardry\:frost_step=Frost Step +spell.ebwizardry\:glide=Glide +spell.ebwizardry\:grapple=Grapple +spell.ebwizardry\:greater_fireball=Greater Fireball +spell.ebwizardry\:greater_heal=Greater Heal +spell.ebwizardry\:greater_telekinesis=Greater Telekinesis +spell.ebwizardry\:greater_ward=Greater Ward +spell.ebwizardry\:group_heal=Group Heal +spell.ebwizardry\:growth_aura=Growth Aura +spell.ebwizardry\:hailstorm=Hailstorm +spell.ebwizardry\:heal=Heal +spell.ebwizardry\:heal_ally=Heal Ally +spell.ebwizardry\:healing_aura=Healing Aura +spell.ebwizardry\:homing_spark=Homing Spark +spell.ebwizardry\:iceball=Iceball +spell.ebwizardry\:ice_age=Ice Age +spell.ebwizardry\:ice_charge=Ice Charge +spell.ebwizardry\:ice_lance=Ice Lance +spell.ebwizardry\:ice_shard=Ice Shard +spell.ebwizardry\:ice_shroud=Ice Shroud +spell.ebwizardry\:ice_spikes=Ice Spikes +spell.ebwizardry\:ice_statue=Ice Statue +spell.ebwizardry\:ignite=Ignite +spell.ebwizardry\:imbue_weapon=Imbue Weapon +spell.ebwizardry\:intimidate=Intimidate +spell.ebwizardry\:invigorating_presence=Invigorating Presence +spell.ebwizardry\:invisibility=Invisibility +spell.ebwizardry\:invoke_weather=Invoke Weather +spell.ebwizardry\:ironflesh=Ironflesh +spell.ebwizardry\:leap=Leap +spell.ebwizardry\:levitation=Levitation +spell.ebwizardry\:life_drain=Life Drain +spell.ebwizardry\:light=Light +spell.ebwizardry\:lightning_arrow=Lightning Arrow +spell.ebwizardry\:lightning_bolt=Lightning Bolt +spell.ebwizardry\:lightning_disc=Lightning Disk +spell.ebwizardry\:lightning_hammer=Lightning Hammer +spell.ebwizardry\:lightning_pulse=Lightning Pulse +spell.ebwizardry\:lightning_ray=Lightning Ray +spell.ebwizardry\:lightning_sigil=Lightning Sigil +spell.ebwizardry\:lightning_web=Lightning Web +spell.ebwizardry\:magic_missile=Magic Missile +spell.ebwizardry\:metamorphosis=Metamorphosis +spell.ebwizardry\:meteor=Meteor +spell.ebwizardry\:mind_control=Mind Control +spell.ebwizardry\:mind_trick=Mind Trick +spell.ebwizardry\:mine=Mine +spell.ebwizardry\:muffle=Muffle +spell.ebwizardry\:none=[Empty Slot] +spell.ebwizardry\:oakflesh=Oakflesh +spell.ebwizardry\:paralysis=Paralysis +spell.ebwizardry\:petrify=Petrify +spell.ebwizardry\:phase_step=Phase Step +spell.ebwizardry\:plague_of_darkness=Plague of Darkness +spell.ebwizardry\:pocket_furnace=Pocket Furnace +spell.ebwizardry\:pocket_workbench=Pocket Workbench +spell.ebwizardry\:poison=Poison +spell.ebwizardry\:poison_bomb=Poison Bomb +spell.ebwizardry\:possession=Possession +spell.ebwizardry\:ray_of_purification=Ray of Purification +spell.ebwizardry\:remove_curse=Remove Curse +spell.ebwizardry\:replenish_hunger=Replenish Hunger +spell.ebwizardry\:resurrection=Resurrection +spell.ebwizardry\:reversal=Reversal +spell.ebwizardry\:ring_of_fire=Ring of Fire +spell.ebwizardry\:satiety=Satiety +spell.ebwizardry\:shadow_ward=Shadow Ward +spell.ebwizardry\:shield=Shield +spell.ebwizardry\:shockwave=Shockwave +spell.ebwizardry\:shulker_bullet=Shulker Bullet +spell.ebwizardry\:silverfish_swarm=Silverfish Swarm +spell.ebwizardry\:sixth_sense=Sixth Sense +spell.ebwizardry\:slime=Slime +spell.ebwizardry\:slow_time=Slow Time +spell.ebwizardry\:smoke_bomb=Smoke Bomb +spell.ebwizardry\:snare=Snare +spell.ebwizardry\:snowball=Snowball +spell.ebwizardry\:spark_bomb=Spark Bomb +spell.ebwizardry\:spectral_pathway=Spectral Pathway +spell.ebwizardry\:speed_time=Speed Time +spell.ebwizardry\:spider_swarm=Spider Swarm +spell.ebwizardry\:static_aura=Static Aura +spell.ebwizardry\:summon_blaze=Summon Blaze +spell.ebwizardry\:summon_ice_giant=Summon Ice Giant +spell.ebwizardry\:summon_ice_wraith=Summon Ice Wraith +spell.ebwizardry\:summon_iron_golem=Summon Iron Golem +spell.ebwizardry\:summon_lightning_wraith=Summon Lightning Wraith +spell.ebwizardry\:summon_phoenix=Summon Phoenix +spell.ebwizardry\:summon_shadow_wraith=Summon Shadow Wraith +spell.ebwizardry\:summon_skeleton=Summon Skeleton +spell.ebwizardry\:summon_skeleton_legion=Summon Skeleton Legion +spell.ebwizardry\:summon_snow_golem=Summon Snow Golem +spell.ebwizardry\:summon_spirit_horse=Summon Spirit Horse +spell.ebwizardry\:summon_spirit_wolf=Summon Spirit Wolf +spell.ebwizardry\:summon_storm_elemental=Summon Storm Elemental +spell.ebwizardry\:summon_wither_skeleton=Summon Wither Skeleton +spell.ebwizardry\:summon_zombie=Summon Zombie +spell.ebwizardry\:telekinesis=Telekinesis +spell.ebwizardry\:thunderbolt=Thunderbolt +spell.ebwizardry\:thunderstorm=Thunderstorm +spell.ebwizardry\:tornado=Tornado +spell.ebwizardry\:transience=Transience +spell.ebwizardry\:transportation=Transportation +spell.ebwizardry\:vanishing_box=Vanishing Box +spell.ebwizardry\:vex_swarm=Vex Swarm +spell.ebwizardry\:wall_of_frost=Wall of Frost +spell.ebwizardry\:ward=Ward +spell.ebwizardry\:water_breathing=Water Breathing +spell.ebwizardry\:whirlwind=Whirlwind +spell.ebwizardry\:wither=Wither +spell.ebwizardry\:wither_skull=Wither Skull -spell.ebwizardry:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. -spell.ebwizardry:arc.desc=Fires a spark of lightning at the target. -spell.ebwizardry:arcane_jammer.desc=Prevents the target from using magic for 15 seconds. -spell.ebwizardry:arrow_rain.desc="Archers, fire!" -spell.ebwizardry:banish.desc=Teleports the target against its will to a random location within a certain range. -spell.ebwizardry:black_hole.desc=Tear reality asunder. -spell.ebwizardry:blink.desc=Teleports the caster over a short distance to where they are pointing. -spell.ebwizardry:blizzard.desc=Creates a zone of swirling icy wind which slows and continually damages anything trapped inside. The caster is immune to the damage but is still slowed. -spell.ebwizardry:bubble.desc=Fires a jet of bubbles which causes anything it hits to float upwards helplessly. The target will fall after a certain time or if it is damaged. -spell.ebwizardry:chain_lightning.desc=Fires a spark of lightning at the target, which then chains to additional targets up to twice. -spell.ebwizardry:clairvoyance.desc=Reveals the path to a remembered location. With this spell selected, sneak-right-click on a block to set the location. Cast this spell normally to reveal the path. The path will fade after 90 seconds. -spell.ebwizardry:cobwebs.desc=Creates cobwebs where you are pointing, which greatly hamper the movement of any creatures caught amongst them. The cobwebs will vanish after 20 seconds or if broken. -spell.ebwizardry:conjure_armour.desc=Creates spectral armor around the caster which offers protection equal to that of iron armor. The armor lasts for 60 seconds. The caster must have an empty armor slot. -spell.ebwizardry:conjure_bow.desc=Creates a spectral bow with unlimited arrows that lasts for 30 seconds. -spell.ebwizardry:conjure_pickaxe.desc=Creates a spectral pickaxe of equal strength to an iron pickaxe that lasts for 30 seconds. -spell.ebwizardry:conjure_sword.desc=Creates a spectral sword of equal strength to an iron sword that lasts for 30 seconds. -spell.ebwizardry:cure_effects.desc=Removes all potion effects currently affecting the caster, good or bad. -spell.ebwizardry:curse_of_soulbinding.desc=Causes the target's soul to be inextricably bound to that of the caster, meaning all damage dealt to the caster is also dealt to the victim. Lasts until either the victim or the caster dies. -spell.ebwizardry:darkness_orb.desc=Fires a slow moving bolt of dark energy in the direction you are pointing, which withers whatever it hits. -spell.ebwizardry:darkvision.desc=Grants the caster night vision for 45 seconds. -spell.ebwizardry:dart.desc=Fires a dart in the direction you are pointing which damages and weakens its target. -spell.ebwizardry:decay.desc=Creates a patch of decay on the ground which infects any creature that touches it, causing lingering damage over time and spreading more decay wherever it walks. -spell.ebwizardry:decoy.desc=Creates an illusory clone of the caster which tricks mobs into attacking it instead. The decoy will vanish after 30 seconds. -spell.ebwizardry:detonate.desc=Causes an explosion where you are pointing, damaging all nearby creatures - including the caster, if they are too close. -spell.ebwizardry:diamondflesh.desc="Your arrows are no match for me!" -spell.ebwizardry:earthquake.desc=A true master of earth magic can move mountains. -spell.ebwizardry:entrapment.desc=Traps the target in a sphere of darkness which pulls it helplessly upwards and continually damages it. -spell.ebwizardry:fireball.desc=Launches a fireball in the direction you are pointing. -spell.ebwizardry:firebolt.desc=Shoots a jet of fire a short distance in front of you. -spell.ebwizardry:firebomb.desc=Lanches a firebomb in the direction you are pointing which explodes on impact, setting targets on fire. -spell.ebwizardry:fire_resistance.desc=Grants the caster fire resistance for 30 seconds. -spell.ebwizardry:fire_sigil.desc=Places a magical fire trap on the ground which damages and sets on fire the creature that triggers it. -spell.ebwizardry:fireskin.desc=Cloaks the caster in flames for 30 seconds, causing anything that attacks them to catch fire. -spell.ebwizardry:firestorm.desc="I am the dragon." -spell.ebwizardry:flame_ray.desc=Creates a stream of flames in the direction you are pointing which sets on fire and continually damages targets. -spell.ebwizardry:flaming_axe.desc=Creates a flaming axe which sets enemies on fire when hit. Lasts for 30 seconds. -spell.ebwizardry:flaming_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of flame, causing it to set fire to its victims. The magic wears off after 45 seconds. -spell.ebwizardry:flight.desc=Soar like an eagle. -spell.ebwizardry:font_of_mana.desc="We were filled with an intense magical energy appearing to emanate from the centre of the..." - Extract from the journal of a forgotten mage; the rest of the page has been burnt away. -spell.ebwizardry:font_of_vitality.desc=It feels amazing. -spell.ebwizardry:force_arrow.desc=Shoots an arrow of force in the direction you are pointing. -spell.ebwizardry:forcefield.desc=Creates a forcefield around the caster which repels creatures and deflects projectiles. -spell.ebwizardry:force_orb.desc=Launches a sphere of force which damages and repels nearby creatures on impact. -spell.ebwizardry:forests_curse.desc="How dare you enter my forest!" -spell.ebwizardry:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. -spell.ebwizardry:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. -spell.ebwizardry:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. -spell.ebwizardry:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. -spell.ebwizardry:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. -spell.ebwizardry:glide.desc=Allows the caster to glide downwards while in the air and holding the use item button. -spell.ebwizardry:greater_fireball.desc=Launches a large fireball in the direction you are pointing which explodes on impact. -spell.ebwizardry:greater_heal.desc=Heals the caster by 4 hearts. -spell.ebwizardry:group_heal.desc=Heals the caster and all nearby allies and summoned creatures by 3 hearts. -spell.ebwizardry:growth_aura.desc=Grows all crops near the caster. Also grows tall grass and flowers on grass. -spell.ebwizardry:hailstorm.desc=It was during the great winter of the third age that the ice mages discovered their true power. -spell.ebwizardry:heal.desc=Heals the caster by 2 hearts. -spell.ebwizardry:heal_ally.desc=Heals the target by 2 and a half hearts. -spell.ebwizardry:healing_aura.desc=Creates a zone of healing energy which regenerates the health of any ally inside it. Any undead inside the healing aura will slowly take damage. -spell.ebwizardry:homing_spark.desc=Creates a floating spark which moves towards enemies. -spell.ebwizardry:ice_age.desc="You shall be frozen for an eternity!" -spell.ebwizardry:ice_charge.desc=Launches an ice charge which explodes on impact, freezing nearby creatures and releasing shards in all directions. -spell.ebwizardry:ice_lance.desc=Fires a great spear of ice in the direction you are pointing which overpenetrates targets, damaging and freezing them in the process. -spell.ebwizardry:ice_shard.desc=Fires a shard of ice in the direction you are pointing which damages and slows targets when hit. -spell.ebwizardry:ice_shroud.desc=Creates a shroud of cold around the caster for 30 seconds, causing anything that attacks them to be frozen. -spell.ebwizardry:ice_spikes.desc=Causes razor-sharp ice spikes to rise from the ground where you are pointing, skewering any creatures caught amongst them. -spell.ebwizardry:ice_statue.desc=Freezes the target solid for 20 seconds or until broken out. The target cannot move or do anything while frozen but is also impervious to all damage. -spell.ebwizardry:ignite.desc=Sets the target on fire for 10 seconds. Also works like a flint and steel. -spell.ebwizardry:imbue_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with magic, rendering it more effective. The magic wears off after 45 seconds. -spell.ebwizardry:intimidate.desc=Emits an intimidating growl which causes nearby creatures to run away in fear. Fear stricken creatures will recover after 30 seconds. -spell.ebwizardry:invigorating_presence.desc=Grants the caster and all nearby allies increased strength for 45 seconds. -spell.ebwizardry:invisibility.desc=Makes the caster invisible for 30 seconds. -spell.ebwizardry:invoke_weather.desc=Changes the weather in the world. -spell.ebwizardry:ironflesh.desc=Greatly improves the caster's damage resistance for 30 seconds. -spell.ebwizardry:leap.desc=Causes the caster to jump upwards several blocks and slightly forward. -spell.ebwizardry:levitation.desc=Raises the caster upwards while the use item button is held. Will also negate fall damage if used before hitting the ground. -spell.ebwizardry:life_drain.desc=Creates a stream of withering energy in the direction you are pointing which drains the life of the target and uses it to gradually regenerate your health. -spell.ebwizardry:light.desc=Creates a magical point of light which illuminates the surrounding area. Lasts for 30 seconds. -spell.ebwizardry:lightning_arrow.desc=Shoots an arrow of lightning in the direction you are pointing. -spell.ebwizardry:lightning_bolt.desc=Causes lightning to strike where you are pointing. -spell.ebwizardry:lightning_disc.desc=Sends a disk of lightning flying off in the direction you are pointing, which seeks targets. -spell.ebwizardry:lightning_hammer.desc="I smite you by the wrath of the heavens!" -spell.ebwizardry:lightning_pulse.desc=Charges the ground around the caster with lightning, damaging and repelling nearby creatures. -spell.ebwizardry:lightning_ray.desc=Creates a stream of lightning in the direction you are pointing which continually damages targets. -spell.ebwizardry:lightning_sigil.desc=Places a magical lightning trap on the ground which damages the creature that triggers it and chains lightning to other nearby creatures. -spell.ebwizardry:lightning_web.desc="Focus. Channel the storm in your mind through your wand and unleash its fury." -spell.ebwizardry:magic_missile.desc=Fires a bolt of magical energy in the direction you are pointing. -spell.ebwizardry:metamorphosis.desc=Changes the target into another form. Only works on some creatures. -spell.ebwizardry:meteor.desc=Some wizards just want to see the world burn... -spell.ebwizardry:mind_control.desc=Takes control of the target's mind for 30 seconds, causing it switch sides and fight for the caster instead. Will not work on creatures that are too strong-willed. -spell.ebwizardry:mind_trick.desc=Confuses and disorients the target for 15 seconds, rendering it unable to attack effectively. The effect will be dispelled if the target takes damage. -spell.ebwizardry:none.desc=To get a spell book with the /give command, use metadata: /give [player] ebwizardry:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). -spell.ebwizardry:oakflesh.desc=Improves the caster's damage resistance for 30 seconds. -spell.ebwizardry:petrify.desc=Turns the target to stone until broken out, with a chance for it to break out when it is dark. The target cannot move or do anything while petrified but is also impervious to all damage. -spell.ebwizardry:phase_step.desc=Teleports the caster through a 1 block thick wall in front of them. Range upgrades will increase the thickness you can teleport through. -spell.ebwizardry:plague_of_darkness.desc=The darkness will consume them all... -spell.ebwizardry:pocket_furnace.desc=Smelts up to 5 smeltable items in the caster's inventory. Items on the hotbar will be smelted first. -spell.ebwizardry:pocket_workbench.desc=Allows the caster to craft items as if they were at a crafting table. -spell.ebwizardry:poison.desc=Fires poison in the direction you are pointing. -spell.ebwizardry:poison_bomb.desc=Lanches a poison bomb in the direction you are pointing which explodes on impact, poisoning nearby creatures. -spell.ebwizardry:replenish_hunger.desc=Replenishes the caster's food level by 6 hunger points. -spell.ebwizardry:ring_of_fire.desc=Creates a ring of fire around the caster, damaging all nearby enemies and setting them on fire. -spell.ebwizardry:shadow_ward.desc=Creates a wall of darkness in front of the caster which causes half of all incoming damage to be inflicted upon the attacker instead. -spell.ebwizardry:shield.desc=Creates a protective barrier of force that blocks projectiles and magic. Also grants the caster a weak resistance effect. -spell.ebwizardry:shockwave.desc=Boom. -spell.ebwizardry:silverfish_swarm.desc="Ahhhh! They're MULTIPLYING!" -spell.ebwizardry:sixth_sense.desc=Allows the caster to sense the locations of nearby creatures, even through walls, for 20 seconds. -spell.ebwizardry:slime.desc=Engulfs the target in slime which slows and continually damages it. The slime bursts after 10 seconds. -spell.ebwizardry:smoke_bomb.desc=Launches a smoke bomb in the direction you are pointing which explodes on impact, releasing smoke and blinding nearby creatures for a short time. -spell.ebwizardry:snare.desc=Sets a trap on the ground which damages and briefly slows the creature that triggers it. -spell.ebwizardry:snowball.desc=Launches a snowball in the direction you are pointing. -spell.ebwizardry:spark_bomb.desc=Launches a shock charge in the direction you are pointing which releases sparks at nearby enemies on impact. -spell.ebwizardry:spectral_pathway.desc=Creates an indestructible magical bridge in front of you which extends for 15 blocks. The bridge vanishes after 60 seconds. -spell.ebwizardry:spider_swarm.desc=Summons a swarm of venomous spiders to fight for you. The spiders will disappear after 30 seconds or if they are killed. -spell.ebwizardry:static_aura.desc=Surrounds the caster with lightning for 30 seconds, firing a spark of lightning at anything that hits them. -spell.ebwizardry:summon_blaze.desc=Summons a blaze to fight for you. The blaze will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_ice_giant.desc="Smash them!" -spell.ebwizardry:summon_ice_wraith.desc=Summons an ice wraith to fight for you. The ice wraith will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_iron_golem.desc=Automatic automated autonomous automaton. -spell.ebwizardry:summon_lightning_wraith.desc=Summons a lightning wraith to fight for you. The lightning wraith will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_phoenix.desc=From the ashes... -spell.ebwizardry:summon_shadow_wraith.desc=Summons a shadow wraith to fight for you. -spell.ebwizardry:summon_skeleton.desc=Summons a skeleton to fight for you. The skeleton will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_skeleton_legion.desc="Rise, undead army!" -spell.ebwizardry:summon_snow_golem.desc=Creates a snow golem to fight for you. Lasts until the snow golem dies. -spell.ebwizardry:summon_spirit_horse.desc=Summons a spirit horse for you to ride. The spirit horse will vanish a short while after it is dismounted, or you can dismiss it by shift-right-clicking on it with any wand. -spell.ebwizardry:summon_spirit_wolf.desc=Summons a spirit wolf companion to fight for you. The spirit wolf will only disappear if it is killed, or you can dismiss it by shift-right-clicking on it with any wand. -spell.ebwizardry:summon_storm_elemental.desc="Storm Elemental: An ancient manifestation of the elements, it can hardly contain the raw power churning within it." - The Wizard's Guide to Arcane Beings, Volume I_i -spell.ebwizardry:summon_wither_skeleton.desc=Summons a wither skeleton to fight for you. The wither skeleton will disappear after 30 seconds or if it is killed. -spell.ebwizardry:summon_zombie.desc=Summons a zombie to fight for you. The zombie will disappear after 30 seconds or if it is killed. -spell.ebwizardry:telekinesis.desc=Moves an item or other small object towards you, or right-clicks the block you are looking at. Can also be used to disarm players. -spell.ebwizardry:thunderbolt.desc=Shoots a bolt of thunder which knocks back targets. -spell.ebwizardry:thunderstorm.desc="Mwahahahahahaha!" -spell.ebwizardry:tornado.desc=Unleashes a tornado in the direction you are pointing which hurls anything in its path skywards. -spell.ebwizardry:transience.desc=Makes the caster transient for 20 seconds. The caster is immune to all damage while transient but cannot break or place blocks or cause any damage. -spell.ebwizardry:transportation.desc=Transports the caster to their remembered stone circle. To use this spell, make a circle of stones of transportation, then right click it with a wand. -spell.ebwizardry:vanishing_box.desc=Grants the caster access to their ender chest storage. -spell.ebwizardry:wall_of_frost.desc=Winter at your fingertips. -spell.ebwizardry:water_breathing.desc=Allows the caster to breathe underwater for 60 seconds. -spell.ebwizardry:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. -spell.ebwizardry:wither.desc=Fires a ray of darkness which withers anything it touches. -spell.ebwizardry:wither_skull.desc=Launches a wither skull in the direction you are pointing. +spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. +spell.ebwizardry\:arc.desc=Fires a spark of lightning at the target. +spell.ebwizardry\:arcane_jammer.desc=Prevents the target from using magic for 15 seconds. +spell.ebwizardry\:arcane_lock.desc=Creates an impenetrable barrier of force around a container, protecting it from being opened or destroyed. The caster and their allies may still open it, however. +spell.ebwizardry\:arrow_rain.desc="Archers, fire!" +spell.ebwizardry\:banish.desc=Teleports the target against its will to a random locations within a certain range. +spell.ebwizardry\:black_hole.desc=Tear reality asunder. +spell.ebwizardry\:blink.desc=Teleports the caster over a short distance to where they are pointing. +spell.ebwizardry\:blizzard.desc=Creates a zone of swirling icy wind which slows and continually damages anything trapped inside. The caster is immune to the damage but is still slowed. +spell.ebwizardry\:bubble.desc=Fires a jet of bubbles which causes anything it hits to float upwards helplessly. The target will fall after a certain time or if it is damaged. +spell.ebwizardry\:chain_lightning.desc=Fires a spark of lightning at the target, which then chains to additional targets up to twice. +spell.ebwizardry\:charge.desc=Causes the caster to charge rapidly in the direction they are looking, damaging and knocking back anything in their path. +spell.ebwizardry\:clairvoyance.desc=Reveals the path to a remembered locations. With this spell selected, sneak-right-click on a block to set the locations. Cast this spell normally to reveal the path. The path will fade after 90 seconds. +spell.ebwizardry\:cobwebs.desc=Creates cobwebs where you are pointing, which greatly hamper the movement of any creatures caught amongst them. The cobwebs will vanish after 20 seconds or if broken. +spell.ebwizardry\:combustion_rune.desc=Places a magical landmine on the ground where you are pointing, which explodes when stepped upon. +spell.ebwizardry\:conjure_armour.desc=Creates spectral armor around the caster which offers protection equal to that of iron armor. The armor lasts for 60 seconds. The caster must have an empty armor slot. +spell.ebwizardry\:conjure_block.desc=Conjures a spectral block where you are pointing. The spectral block disappears after 45 seconds, or you can dispel it by casting this spell at it again. +spell.ebwizardry\:conjure_bow.desc=Creates a spectral bow with unlimited arrows that lasts for 30 seconds. +spell.ebwizardry\:conjure_pickaxe.desc=Creates a spectral pickaxe of equal strength to an iron pickaxe that lasts for 30 seconds. +spell.ebwizardry\:conjure_sword.desc=Creates a spectral sword of equal strength to an iron sword that lasts for 30 seconds. +spell.ebwizardry\:containment.desc=Contains the target to within a short distance of its current position for 20 seconds. +spell.ebwizardry\:cure_effects.desc=Removes all potion effects currently affecting the caster, good or bad. +spell.ebwizardry\:curse_of_enfeeblement.desc="He was suddenly weakened, as if the life had been wrenched from within him." - Testimony of the only person known to have encountered a member of the soulwalker cult and survived. +spell.ebwizardry\:curse_of_soulbinding.desc=Causes the target's soul to be inextricably bound to that of the caster, meaning all damage dealt to the caster is also dealt to the victim. Lasts until either the victim or the caster dies. +spell.ebwizardry\:curse_of_undeath.desc=Curses the target with undeath, causing it to burn in sunlight, like zombies and skeletons. Lasts until the victim dies. Has no effect on undead creatures. +spell.ebwizardry\:darkness_orb.desc=Fires a slow moving bolt of dark energy in the direction you are pointing, which withers whatever it hits. +spell.ebwizardry\:darkvision.desc=Grants the caster night vision for 45 seconds. +spell.ebwizardry\:dart.desc=Fires a dart in the direction you are pointing which damages and weakens its target. +spell.ebwizardry\:decay.desc=Creates a patch of decay on the ground which infects any creature that touches it, causing lingering damage over time and spreading more decay wherever it walks. +spell.ebwizardry\:decoy.desc=Creates an illusory clone of the caster which tricks mobs into attacking it instead. The decoy will vanish after 30 seconds. +spell.ebwizardry\:detonate.desc=Causes an explosion where you are pointing, damaging all nearby creatures - including the caster, if they are too close. +spell.ebwizardry\:diamondflesh.desc="Your arrows are no match for me!" +spell.ebwizardry\:disintegration.desc=Shoots a powerful bolt of flame a short distance in front of you which causes targets to explode into burning embers when killed. Creatures that step on the embers will be set on fire. +spell.ebwizardry\:divination.desc=Guides the caster to nearby ores and resources. Potency will increase the chance of finding more valuable ores. +spell.ebwizardry\:dragon_fireball.desc=Launches an enderdragon fireball in the direction you are pointing, which releases lingering poisonous clouds on impact. +spell.ebwizardry\:earthquake.desc=A true master of earth magic can move mountains. +spell.ebwizardry\:empowering_presence.desc=Grants the caster and nearby allies increased magic damage for 30 seconds. +spell.ebwizardry\:entrapment.desc=Traps the target in a sphere of darkness which pulls it helplessly upwards and continually damages it. +spell.ebwizardry\:evade.desc=Causes the caster to quickly jump sideways to dodge incoming attacks. +spell.ebwizardry\:fireball.desc=Launches a fireball in the direction you are pointing. +spell.ebwizardry\:firebolt.desc=Shoots a jet of fire a short distance in front of you. +spell.ebwizardry\:firebomb.desc=Lanches a firebomb in the direction you are pointing which explodes on impact, setting targets on fire. +spell.ebwizardry\:fire_resistance.desc=Grants the caster fire resistance for 30 seconds. +spell.ebwizardry\:fire_sigil.desc=Places a magical fire trap on the ground which damages and sets on fire the creature that triggers it. +spell.ebwizardry\:fireskin.desc=Cloaks the caster in flames for 30 seconds, causing anything that attacks them to catch fire. +spell.ebwizardry\:fire_breath.desc="I am the dragon." +spell.ebwizardry\:flame_ray.desc=Creates a stream of flames in the direction you are pointing which sets on fire and continually damages targets. +spell.ebwizardry\:flaming_axe.desc=Creates a flaming axe which sets enemies on fire when hit. Lasts for 30 seconds. +spell.ebwizardry\:flaming_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of flame, causing it to set fire to its victims. The magic wears off after 45 seconds. +spell.ebwizardry\:flight.desc=Soar like an eagle. +spell.ebwizardry\:font_of_mana.desc="We were filled with an intense magical energy appearing to emanate from the centre of the..." - Extract from the journal of a forgotten mage; the rest of the page has been burnt away. +spell.ebwizardry\:font_of_vitality.desc=It feels amazing. +spell.ebwizardry\:force_arrow.desc=Shoots an arrow of force in the direction you are pointing. +spell.ebwizardry\:forcefield.desc=Creates a forcefield around the caster which repels creatures and deflects projectiles. +spell.ebwizardry\:force_orb.desc=Launches a sphere of force which damages and repels nearby creatures on impact. +spell.ebwizardry\:forests_curse.desc="How dare you enter my forest!" +spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn the glade a wizard performs,\nMagical arts of an order untold,\nDeep within the forest of thorns. +spell.ebwizardry\:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. +spell.ebwizardry\:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. +spell.ebwizardry\:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. +spell.ebwizardry\:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. +spell.ebwizardry\:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. +spell.ebwizardry\:frost_step.desc=Allows the caster to freeze water as they walk for 30 seconds. +spell.ebwizardry\:grapple.desc=Shoots a magical vine which allows the caster to grapple towards blocks or reel in entities. Release the use item button to let go of the block or entity. +spell.ebwizardry\:glide.desc=Allows the caster to glide downwards while in the air and holding the use item button. +spell.ebwizardry\:greater_fireball.desc=Launches a large fireball in the direction you are pointing which explodes on impact. +spell.ebwizardry\:greater_heal.desc=Heals the caster by 4 hearts. +spell.ebwizardry\:greater_telekinesis.desc=Allows the caster to pick up a block or creature and hold it in the air for as long as the use item button is pressed. Sneaking whilst holding a block or creature will throw it a short distance. +spell.ebwizardry\:greater_ward.desc=Grants the caster a strong shielding effect that greatly reduces incoming magic damage for 30 seconds. +spell.ebwizardry\:group_heal.desc=Heals the caster and all nearby allies and summoned creatures by 3 hearts. +spell.ebwizardry\:growth_aura.desc=Grows all crops near the caster. Also grows tall grass and flowers on grass. +spell.ebwizardry\:hailstorm.desc=It was during the great winter of the third age that the ice mages discovered their true power. +spell.ebwizardry\:heal.desc=Heals the caster by 2 hearts. +spell.ebwizardry\:heal_ally.desc=Heals the target by 2 and a half hearts. +spell.ebwizardry\:healing_aura.desc=Creates a zone of healing energy which regenerates the health of any ally inside it. Any undead inside the healing aura will slowly take damage. +spell.ebwizardry\:homing_spark.desc=Creates a floating spark which moves towards enemies. +spell.ebwizardry\:iceball.desc=Launches an iceball in the direction you are pointing. +spell.ebwizardry\:ice_age.desc="You shall be frozen for an eternity!" +spell.ebwizardry\:ice_charge.desc=Launches an ice charge which explodes on impact, freezing nearby creatures and releasing shards in all directions. +spell.ebwizardry\:ice_lance.desc=Fires a great spear of ice in the direction you are pointing which overpenetrates targets, damaging and freezing them in the process. +spell.ebwizardry\:ice_shard.desc=Fires a shard of ice in the direction you are pointing which damages and slows targets when hit. +spell.ebwizardry\:ice_shroud.desc=Creates a shroud of cold around the caster for 30 seconds, causing anything that attacks them to be frozen. +spell.ebwizardry\:ice_spikes.desc=Causes razor-sharp ice spikes to rise from the ground where you are pointing, skewering any creatures caught amongst them. +spell.ebwizardry\:ice_statue.desc=Freezes the target solid for 20 seconds or until broken out. The target cannot move or do anything while frozen but is also impervious to all damage. +spell.ebwizardry\:ignite.desc=Sets the target on fire for 10 seconds. Also works like a flint and steel. +spell.ebwizardry\:imbue_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with magic, rendering it more effective. The magic wears off after 45 seconds. +spell.ebwizardry\:intimidate.desc=Emits an intimidating growl which causes nearby creatures to run away in fear. Fear stricken creatures will recover after 30 seconds. +spell.ebwizardry\:invigorating_presence.desc=Grants the caster and all nearby allies increased strength for 45 seconds. +spell.ebwizardry\:invisibility.desc=Makes the caster invisible for 30 seconds. +spell.ebwizardry\:invoke_weather.desc=Changes the weather in the world. +spell.ebwizardry\:ironflesh.desc=Greatly improves the caster's damage resistance for 30 seconds. +spell.ebwizardry\:leap.desc=Causes the caster to jump upwards several blocks and slightly forward. +spell.ebwizardry\:levitation.desc=Raises the caster upwards while the use item button is held. Will also negate fall damage if used before hitting the ground. +spell.ebwizardry\:life_drain.desc=Creates a stream of withering energy in the direction you are pointing which drains the life of the target and uses it to gradually regenerate your health. +spell.ebwizardry\:light.desc=Creates a magical point of light which illuminates the surrounding area. Lasts for 30 seconds. +spell.ebwizardry\:lightning_arrow.desc=Shoots an arrow of lightning in the direction you are pointing. +spell.ebwizardry\:lightning_bolt.desc=Causes lightning to strike where you are pointing. +spell.ebwizardry\:lightning_disc.desc=Sends a disk of lightning flying off in the direction you are pointing, which seeks targets. +spell.ebwizardry\:lightning_hammer.desc="I smite you by the wrath of the heavens!" +spell.ebwizardry\:lightning_pulse.desc=Charges the ground around the caster with lightning, damaging and repelling nearby creatures. +spell.ebwizardry\:lightning_ray.desc=Creates a stream of lightning in the direction you are pointing which continually damages targets. +spell.ebwizardry\:lightning_sigil.desc=Places a magical lightning trap on the ground which damages the creature that triggers it and chains lightning to other nearby creatures. +spell.ebwizardry\:lightning_web.desc="Focus. Channel the storm in your mind through your wand and unleash its fury." +spell.ebwizardry\:magic_missile.desc=Fires a bolt of magical energy in the direction you are pointing. +spell.ebwizardry\:metamorphosis.desc=Changes the target into another form. Only works on some creatures. +spell.ebwizardry\:meteor.desc=Some wizards just want to see the world burn... +spell.ebwizardry\:mind_control.desc=Takes control of the target's mind for 30 seconds, causing it switch sides and fight for the caster instead. Will not work on creatures that are too strong-willed. +spell.ebwizardry\:mind_trick.desc=Confuses and disorients the target for 15 seconds, rendering it unable to attack effectively. The effect will be dispelled if the target takes damage. +spell.ebwizardry\:mine.desc=Breaks the block the caster is looking at. Potency will allow harder blocks to be broken. +spell.ebwizardry\:muffle.desc=Silences any sounds made by the caster for 30 seconds. When muffled, mobs can only detect you when looking towards you. +spell.ebwizardry\:none.desc=To get a spell book with the /give command, use id\: /give [player] ebwizardry\:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). +spell.ebwizardry\:oakflesh.desc=Improves the caster's damage resistance for 30 seconds. +spell.ebwizardry\:paralysis.desc=Delivers a powerful lightning bolt that paralyses targets for 5 seconds. Paralysed creatures cannot move and will still take damage - but too much will snap the creature out of paralysis. +spell.ebwizardry\:petrify.desc=Turns the target to stone until broken out, with a chance for it to break out when it is dark. The target cannot move or do anything while petrified but is also impervious to all damage. +spell.ebwizardry\:phase_step.desc=Teleports the caster a short distance in front of them, including through walls. Range upgrades will increase the wall thickness you can teleport through. +spell.ebwizardry\:plague_of_darkness.desc=The darkness will consume them all... +spell.ebwizardry\:pocket_furnace.desc=Smelts up to 5 smeltable items in the caster's inventory. Items on the hotbar will be smelted first. +spell.ebwizardry\:pocket_workbench.desc=Allows the caster to craft items as if they were at a crafting table. +spell.ebwizardry\:poison.desc=Fires poison in the direction you are pointing. +spell.ebwizardry\:poison_bomb.desc=Lanches a poison bomb in the direction you are pointing which explodes on impact, poisoning nearby creatures. +spell.ebwizardry\:possession.desc="Become thy enemy." +spell.ebwizardry\:ray_of_purification.desc=Emits a ray of blinding light that damages and blinds its targets. Undead creatures take double damage and are set on fire. +spell.ebwizardry\:remove_curse.desc=Removes any curse currently affecting the caster. +spell.ebwizardry\:replenish_hunger.desc=Replenishes the caster's food level by 4 hunger points. +spell.ebwizardry\:resurrection.desc=With a master healer by your side, being dead is... optional. +spell.ebwizardry\:reversal.desc=Removes a random negative potion effect and inflicts it upon the target, which will suffer that effect for the remaining duration. Potency increases the number of effects that are reversed. +spell.ebwizardry\:ring_of_fire.desc=Creates a ring of fire around the caster, damaging all nearby enemies and setting them on fire. +spell.ebwizardry\:satiety.desc=Replenishes the caster's food level by 8 hunger points. +spell.ebwizardry\:shadow_ward.desc=Creates a wall of darkness in front of the caster which causes half of all incoming damage to be inflicted upon the attacker instead. +spell.ebwizardry\:shield.desc=Creates a protective barrier of force that blocks projectiles and magic. Also grants the caster a weak resistance effect. +spell.ebwizardry\:shockwave.desc=Boom. +spell.ebwizardry\:shulker_bullet.desc=Shoots a shulker bullet which seeks targets and causes them to levitate when hit. +spell.ebwizardry\:silverfish_swarm.desc="Ahhhh! They're MULTIPLYING!" +spell.ebwizardry\:sixth_sense.desc=Allows the caster to sense the locations of nearby creatures, even through walls, for 20 seconds. +spell.ebwizardry\:slime.desc=Engulfs the target in slime which slows and continually damages it. The slime bursts after 10 seconds. +spell.ebwizardry\:slow_time.desc=Chronomancy is the art of manipulating time itself to fit one's needs. It was widely believed to be lost in the past... until now. +spell.ebwizardry\:smoke_bomb.desc=Launches a smoke bomb in the direction you are pointing which explodes on impact, releasing smoke and blinding nearby creatures for a short time. +spell.ebwizardry\:snare.desc=Sets a trap on the ground which damages and briefly slows the creature that triggers it. +spell.ebwizardry\:snowball.desc=Launches a snowball in the direction you are pointing. +spell.ebwizardry\:spark_bomb.desc=Launches a shock charge in the direction you are pointing which releases sparks at nearby enemies on impact. +spell.ebwizardry\:spectral_pathway.desc=Creates an indestructible magical bridge in front of you which extends for 15 blocks. The bridge vanishes after 60 seconds. +spell.ebwizardry\:speed_time.desc=...day, night, dawn, dusk, sunrise and sunset\: thus is the passage of time, which traps us in an endless cycle of... +spell.ebwizardry\:spider_swarm.desc=Summons a swarm of venomous spiders to fight for you. The spiders will disappear after 30 seconds or if they are killed. +spell.ebwizardry\:static_aura.desc=Surrounds the caster with lightning for 30 seconds, firing a spark of lightning at anything that hits them. +spell.ebwizardry\:summon_blaze.desc=Summons a blaze to fight for you. The blaze will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_ice_giant.desc="Smash them!" +spell.ebwizardry\:summon_ice_wraith.desc=Summons an ice wraith to fight for you. The ice wraith will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_iron_golem.desc=Automatic automated autonomous automaton. +spell.ebwizardry\:summon_lightning_wraith.desc=Summons a lightning wraith to fight for you. The lightning wraith will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_phoenix.desc=From the ashes... +spell.ebwizardry\:summon_shadow_wraith.desc=Summons a shadow wraith to fight for you. +spell.ebwizardry\:summon_skeleton.desc=Summons a skeleton to fight for you. The skeleton will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_skeleton_legion.desc="Rise, undead army!" +spell.ebwizardry\:summon_snow_golem.desc=Creates a snow golem to fight for you. Lasts until the snow golem dies. +spell.ebwizardry\:summon_spirit_horse.desc=Summons a spirit horse for you to ride. The spirit horse will vanish a short while after it is dismounted, or you can dismiss it by shift-right-clicking on it with any wand. +spell.ebwizardry\:summon_spirit_wolf.desc=Summons a spirit wolf companion to fight for you. The spirit wolf will only disappear if it is killed, or you can dismiss it by shift-right-clicking on it with any wand. +spell.ebwizardry\:summon_storm_elemental.desc="Storm Elemental\: An ancient manifestation of the elements, it can hardly contain the raw power churning within it." - The Wizard's Guide to Arcane Beings, Volume II +spell.ebwizardry\:summon_wither_skeleton.desc=Summons a wither skeleton to fight for you. The wither skeleton will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:summon_zombie.desc=Summons a zombie to fight for you. The zombie will disappear after 30 seconds or if it is killed. +spell.ebwizardry\:telekinesis.desc=Moves an item or other small object towards you, or right-clicks the block you are looking at. Can also be used to disarm players. +spell.ebwizardry\:thunderbolt.desc=Shoots a bolt of thunder which knocks back targets. +spell.ebwizardry\:thunderstorm.desc="Mwahahahahahaha!" +spell.ebwizardry\:tornado.desc=Unleashes a tornado in the direction you are pointing which hurls anything in its path skywards. +spell.ebwizardry\:transience.desc=Makes the caster transient for 20 seconds. The caster is immune to all damage while transient but cannot break or place blocks or cause any damage. +spell.ebwizardry\:transportation.desc=Transports the caster to their remembered stone circle. To use this spell, make a circle of stones of transportation, then right click it with a wand. +spell.ebwizardry\:vanishing_box.desc=Grants the caster access to their ender chest storage. +spell.ebwizardry\:vex_swarm.desc=Summons a swarm of flying vexes to fight for you. The vexes will disappear after 30 seconds or if they are killed. +spell.ebwizardry\:wall_of_frost.desc=Winter at your fingertips. +spell.ebwizardry\:ward.desc=Grants the caster a shielding effect that reduces incoming magic damage for 30 seconds. +spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater for 60 seconds. +spell.ebwizardry\:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. +spell.ebwizardry\:wither.desc=Fires a ray of darkness which withers anything it touches. +spell.ebwizardry\:wither_skull.desc=Launches a wither skull in the direction you are pointing. -spell.ebwizardry:invoke_weather.sun=The rain begins to stop... -spell.ebwizardry:invoke_weather.rain=The heavens open... -spell.ebwizardry:transportation.missing=Your remembered stone circle is missing or obstructed... -spell.ebwizardry:transportation.undefined=You must remember the location of a stone circle first! -spell.ebwizardry:transportation.wrongdimension=Your remembered stone circle is in another dimension... -spell.ebwizardry:clairvoyance.searching=Searching... -spell.ebwizardry:clairvoyance.confirm=The path revealed upon casting %1$s will now lead back to this point -spell.ebwizardry:clairvoyance.outofrange=Your remembered location is too far away or inaccessible... -spell.ebwizardry:clairvoyance.undefined=You must remember a location first! -spell.ebwizardry:clairvoyance.wrongdimension=Your remembered location is in another dimension... +spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... +spell.ebwizardry\:invoke_weather.rain=The heavens open... +spell.ebwizardry\:transportation.missing=The stone circle is missing or obstructed... +spell.ebwizardry\:transportation.undefined=You must remember the location of a stone circle first! +spell.ebwizardry\:transportation.wrongdimension=No remembered stone circle in this dimension... +spell.ebwizardry\:clairvoyance.searching=Searching... +spell.ebwizardry\:clairvoyance.confirm=The path revealed upon casting %1$s will now lead back to this point +spell.ebwizardry\:clairvoyance.outofrange=Your remembered locations is too far away or inaccessible... +spell.ebwizardry\:clairvoyance.undefined=You must remember a locations first! +spell.ebwizardry\:clairvoyance.wrongdimension=Your remembered locations is in another dimension... +spell.ebwizardry\:possession.insufficienthealth=You don't have enough health to possess %1$s! +spell.ebwizardry\:possession.success=Press %s to stop possessing +spell.ebwizardry\:divination.nothing=Nothing happened. +spell.ebwizardry\:divination.weak=You can just feel your wand twitching %s. +spell.ebwizardry\:divination.moderate=You feel your wand being tugged %s. +spell.ebwizardry\:divination.strong=Your wand pulls %s strongly. +spell.ebwizardry\:divination.very_strong=Your wand lurches %s sharply. +spell.ebwizardry\:divination.down=downwards +spell.ebwizardry\:divination.up=upwards +spell.ebwizardry\:divination.front=forwards +spell.ebwizardry\:divination.back=backwards +spell.ebwizardry\:divination.left=to the left +spell.ebwizardry\:divination.right=to the right +spell.ebwizardry\:resurrection.resurrect_ally=%s was resurrected by %s +spell.ebwizardry\:resurrection.resurrect_self=%s came back to life +spell.ebwizardry\:resurrection.button_wait=Resurrect (%ss) +spell.ebwizardry\:resurrection.button_ready=Resurrect -potion.ebwizardry:frost=Frostbite -potion.ebwizardry:fireskin=Fireskin -potion.ebwizardry:ice_shroud=Ice Shroud -potion.ebwizardry:static_aura=Static Aura -potion.ebwizardry:transience=Transience -potion.ebwizardry:decay=Decay -potion.ebwizardry:sixth_sense=Sixth Sense -potion.ebwizardry:arcane_jammer=Arcane Jammer -potion.ebwizardry:mind_trick=Mind Trick -potion.ebwizardry:mind_control=Mind Control -potion.ebwizardry:font_of_mana=Font of Mana -potion.ebwizardry:fear=Fear +forfeit.ebwizardry\:do_nothing=Nothing happened. -enchantment.ebwizardry:magic_sword=Imbuement -enchantment.ebwizardry:magic_bow=Imbuement -enchantment.ebwizardry:flaming_weapon=Fire Imbuement -enchantment.ebwizardry:freezing_weapon=Frost Imbuement +forfeit.ebwizardry\:burn_self=The %s bursts into flames in your hand! +forfeit.ebwizardry\:fireball=A fireball materializes in front of you! +forfeit.ebwizardry\:firebomb=A firebomb appears right above you! +forfeit.ebwizardry\:explode=A sudden explosion knocks you to the ground! +forfeit.ebwizardry\:blazes=Suddenly, hostile blazes materialize around you! +forfeit.ebwizardry\:burn_surroundings=The area around you is set ablaze! +forfeit.ebwizardry\:meteors=Fiery armageddon rains from the sky! + +forfeit.ebwizardry\:freeze_self=You feel an icy chill rush through you! +forfeit.ebwizardry\:freeze_self_2=From the %s emanates a cold so intense that it roots you to the spot! +forfeit.ebwizardry\:ice_spikes=Ice spikes rise from the ground beneath your feet! +forfeit.ebwizardry\:blizzard=You are suddenly surrounded by a raging blizzard! +forfeit.ebwizardry\:ice_wraiths=Suddenly, hostile ice wraiths materialize around you! +forfeit.ebwizardry\:hailstorm=Razor-sharp ice starts raining down upon you! +forfeit.ebwizardry\:ice_giant=A hostile ice giant materializes in front of you! + +forfeit.ebwizardry\:thunder=The scroll emits a deafening noise, knocking you to the ground! +forfeit.ebwizardry\:storm=Looks like a storm is brewing! +forfeit.ebwizardry\:lightning_sigils=Watch your step! +forfeit.ebwizardry\:lightning=You are struck by lightning! +forfeit.ebwizardry\:paralyse_self=You are paralysed! +forfeit.ebwizardry\:lightning_wraiths=Suddenly, hostile lightning wraiths materialise around you! +forfeit.ebwizardry\:storm_elementals=Hostile storm elementals materialize on all sides! + +forfeit.ebwizardry\:nausea=...huh? What just happened? Where am I? +forfeit.ebwizardry\:zombie_horde=A horde of zombies rises from the ground around you! +forfeit.ebwizardry\:wither_self=Darkness withers your soul! +forfeit.ebwizardry\:cripple_self=The %s emits a deathly howl, and you are crippled to within an inch of your life! +forfeit.ebwizardry\:shadow_wraiths=Hostile shadow wraiths materialise on all sides! + +forfeit.ebwizardry\:snares=It's a trap! +forfeit.ebwizardry\:squid=Squid! +forfeit.ebwizardry\:uproot_plants=All nearby plants are suddenly uprooted! +forfeit.ebwizardry\:poison_self=You are poisoned! +forfeit.ebwizardry\:flood=Water materialises around you! +forfeit.ebwizardry\:bury_self=The ground collapses beneath you! + +forfeit.ebwizardry\:spill_inventory=Your items spill themselves everywhere! +forfeit.ebwizardry\:teleport_self=You are instantly teleported somewhere! +forfeit.ebwizardry\:levitate_self=You begin to float upwards helplessly! +forfeit.ebwizardry\:vex_horde=A horde of vexes materializes around you! +forfeit.ebwizardry\:black_hole=A swirling vortex appears in front of you! +forfeit.ebwizardry\:arrow_rain=A barrage of arrows starts raining down upon you! + +forfeit.ebwizardry\:damage_self=Ouch! +forfeit.ebwizardry\:spill_armour=All your armour falls off! +forfeit.ebwizardry\:hunger=You suddenly feel very hungry! +forfeit.ebwizardry\:blind_self=You are blinded! +forfeit.ebwizardry\:weaken_self=You are severely weakened! +forfeit.ebwizardry\:jam_self=Your magic is rendered useless! +forfeit.ebwizardry\:curse_self=You are cursed with undeath! + +potion.ebwizardry\:frost=Frostbite +potion.ebwizardry\:fireskin=Fireskin +potion.ebwizardry\:ice_shroud=Ice Shroud +potion.ebwizardry\:static_aura=Static Aura +potion.ebwizardry\:transience=Transience +potion.ebwizardry\:decay=Decay +potion.ebwizardry\:sixth_sense=Sixth Sense +potion.ebwizardry\:arcane_jammer=Arcane Jammer +potion.ebwizardry\:mind_trick=Mind Trick +potion.ebwizardry\:mind_control=Mind Control +potion.ebwizardry\:font_of_mana=Font of Mana +potion.ebwizardry\:fear=Fear +potion.ebwizardry\:curse_of_soulbinding=Curse of Soulbinding +potion.ebwizardry\:paralysis=Paralysis +potion.ebwizardry\:muffle=Muffle +potion.ebwizardry\:ward=Ward +potion.ebwizardry\:slow_time=Slow Time +potion.ebwizardry\:empowerment=Empowerment +potion.ebwizardry\:curse_of_enfeeblement=Curse of Enfeeblement +potion.ebwizardry\:curse_of_undeath=Curse of Undeath +potion.ebwizardry\:containment=Containment +potion.ebwizardry\:frost_step=Frost Step + +enchantment.ebwizardry\:magic_sword=Imbuement +enchantment.ebwizardry\:magic_bow=Imbuement +enchantment.ebwizardry\:flaming_weapon=Fire Imbuement +enchantment.ebwizardry\:freezing_weapon=Frost Imbuement +enchantment.ebwizardry\:shocking_weapon=Lightning Imbuement + +enchantment.ebwizardry\:magic_protection=Magic Protection +enchantment.ebwizardry\:frost_protection=Frost Protection +enchantment.ebwizardry\:shock_protection=Shock Protection key.categories.ebwizardry=Wizardry @@ -625,136 +1011,268 @@ key.ebwizardry.previous_spell=Previous Spell death.attack.wizardry_magic=%1$s was killed by %2$s using magic death.attack.indirect_wizardry_magic=%1$s was killed by %2$s using magic -commands.ebwizardry:cast.usage=/%1$s [player] [damage multiplier] [range multiplier] [duration multiplier] [blast multiplier] -commands.ebwizardry:cast.success=Successfully cast %1$s -commands.ebwizardry:cast.success_continuous=Successfully cast %1$s; repeat the command to stop -commands.ebwizardry:cast.success_remote=Successfully cast %1$s as %2$s -commands.ebwizardry:cast.success_remote_continuous=Successfully cast %1$s as %2$s; repeat the command to stop -commands.ebwizardry:cast.fail=Unable to cast %1$s -commands.ebwizardry:cast.not_found=There is no such spell with ID %1$s -commands.ebwizardry:cast.tag_error=Data tag parsing failed: %s +soundCategory.ebwizardry\:spells=Spells -commands.ebwizardry:ally.usage=/%1$s [player] -commands.ebwizardry:ally.addally=%1$s has been added to %2$s's list of allies -commands.ebwizardry:ally.removeally=%1$s has been removed from %2$s's list of allies -commands.ebwizardry:ally.self=Players cannot be an ally of themselves! -commands.ebwizardry:ally.permission=You do not have permission to change other players' allies +commands.ebwizardry\:cast.usage=/%1$s [player | x y z direction] [duration] [modifiers] +commands.ebwizardry\:cast.success=Successfully cast %s +commands.ebwizardry\:cast.success_continuous=Successfully cast %s for %s seconds +commands.ebwizardry\:cast.success_remote=Successfully cast %s as %s +commands.ebwizardry\:cast.success_remote_continuous=Successfully cast %s as %s for %s seconds +commands.ebwizardry\:cast.success_position=Successfully cast %s at %s, %s, %s +commands.ebwizardry\:cast.success_position_continuous=Successfully cast %s at %s, %s, %s for %s seconds +commands.ebwizardry\:cast.fail=Unable to cast %1$s +commands.ebwizardry\:cast.not_found=There is no such spell with ID %1$s +commands.ebwizardry\:cast.invalid_direction=Invalid direction: %1$s (valid directions are: up, down, north, south, east, west) +commands.ebwizardry\:cast.tag_error=Data tag parsing failed\: %s +commands.ebwizardry\:cast.origin_not_specified=You must specify a player or location to cast the spell from +commands.ebwizardry\:cast.duration_not_specified=You must specify a duration for this spell -commands.ebwizardry:allies.usage=/%1$s [player] -commands.ebwizardry:allies.list=Players allied to you: %1$s -commands.ebwizardry:allies.list_other=Players allied to %1$s: %2$s -commands.ebwizardry:allies.permission=You do not have permission to view other players' allies -commands.ebwizardry:allies.none=None +commands.ebwizardry\:ally.usage=/%1$s [player] +commands.ebwizardry\:ally.addally=%1$s has been added to %2$s's list of allies +commands.ebwizardry\:ally.removeally=%1$s has been removed from %2$s's list of allies +commands.ebwizardry\:ally.self=Players cannot be an ally of themselves! +commands.ebwizardry\:ally.permission=You do not have permission to change other players' allies -commands.ebwizardry:discoverspell.usage=/%1$s [player] -commands.ebwizardry:discoverspell.not_found=There is no such spell with ID %1$s -commands.ebwizardry:discoverspell.clear=Cleared all spell discovery data for %1$s -commands.ebwizardry:discoverspell.all=Added all spells to %1$s's spell discovery data -commands.ebwizardry:discoverspell.addspell=Added %1$s to %2$s's spell discovery data -commands.ebwizardry:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data +commands.ebwizardry\:allies.usage=/%1$s [player] +commands.ebwizardry\:allies.list=Players allied to you\: %1$s +commands.ebwizardry\:allies.list_other=Players allied to %1$s\: %2$s +commands.ebwizardry\:allies.permission=You do not have permission to view other players' allies +commands.ebwizardry\:allies.none=None + +commands.ebwizardry\:discoverspell.usage=/%1$s [player] +commands.ebwizardry\:discoverspell.not_found=There is no such spell with ID %1$s +commands.ebwizardry\:discoverspell.clear=Cleared all spell discovery data for %1$s +commands.ebwizardry\:discoverspell.all=Added all spells to %1$s's spell discovery data +commands.ebwizardry\:discoverspell.addspell=Added %1$s to %2$s's spell discovery data +commands.ebwizardry\:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data config.ebwizardry.title.general=Mod Options +config.ebwizardry.generic.true=Enabled +config.ebwizardry.generic.false=Disabled + +config.ebwizardry.category.spells=Spell Configuration +config.ebwizardry.category.spells.tooltip=Select which spells are enabled +config.ebwizardry.title.spells=Spell Configuration +config.ebwizardry.subtitle.spells=Disabling a spell globally here will override the finer controls in the spell JSON file. + config.ebwizardry.category.gameplay=Gameplay Settings config.ebwizardry.category.gameplay.tooltip=Configure wizardry's general gameplay config.ebwizardry.title.gameplay=Gameplay Settings config.ebwizardry.subtitle.gameplay=Global settings that affect game mechanics. +config.ebwizardry.discovery_mode=Discovery Mode +config.ebwizardry.discovery_mode.tooltip=For those who like a sense of mystery! When enabled, 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 disabled. +config.ebwizardry.legacy_wand_levelling=Legacy Wand Levelling +config.ebwizardry.legacy_wand_levelling.tooltip=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 silently when this is enabled, so if you go back to the new system you won't lose any progress. +config.ebwizardry.legacy_wand_levelling.true=Yes - I liked it how it was! +config.ebwizardry.legacy_wand_levelling.false=No - Level me up! +config.ebwizardry.friendly_fire=Friendly Fire +config.ebwizardry.friendly_fire.tooltip=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 makes them completely immune if disabled. +config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting +config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge-attack players or creatures that they would not otherwise be able to attack. +config.ebwizardry.minion_revenge_targeting.true=Yes - I'd never hurt them! +config.ebwizardry.minion_revenge_targeting.false=No - they must always obey me! +config.ebwizardry.players_move_each_other=Players Move Each Other +config.ebwizardry.players_move_each_other.tooltip=Whether to allow players to move other players around using magic. +config.ebwizardry.players_move_each_other.true=Yes - let the games begin! +config.ebwizardry.players_move_each_other.false=No - I won't be pushed around +config.ebwizardry.player_block_damage=Player Block Damage +config.ebwizardry.player_block_damage.tooltip=Whether spells cast by players can destroy blocks in the world. Disable this to prevent griefing. To prevent non-players from destroying blocks with magic, use the mobGriefing gamerule. +config.ebwizardry.player_block_damage.true=Yes - kaboom! +config.ebwizardry.player_block_damage.false=No - activate anti-grief (TM) +config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament +config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Disable to prevent stealing of items. +config.ebwizardry.telekinetic_disarmament.true=Yes - let people steal things +config.ebwizardry.telekinetic_disarmament.false=No - that's cheating! +config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks +config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. +config.ebwizardry.teleport_through_unbreakable_blocks.true=Yes - I wish to see the void! +config.ebwizardry.teleport_through_unbreakable_blocks.false=No - bedrock is impenetrable +config.ebwizardry.world_time_manipulation=World Time Manipulation +config.ebwizardry.world_time_manipulation.tooltip=Whether players are allowed to change the world time with the speed time spell. If disabled, the speed time spell will not change the world time but will still speed up nearby block, entity and tile entity ticks. +config.ebwizardry.world_time_manipulation.true=Yes - daytime, nighttime... +config.ebwizardry.world_time_manipulation.false=No - I need my sleep! +config.ebwizardry.replace_vanilla_fireballs=Replace Vanilla Fireballs +config.ebwizardry.replace_vanilla_fireballs.tooltip=Whether to replace Minecraft's own fireballs with wizardry fireballs. If this is disabled, only wizardry spells will use the custom fireballs. +config.ebwizardry.replace_vanilla_fireballs.true=Yes - give me FIRE! +config.ebwizardry.replace_vanilla_fireballs.false=No - blazes are mean enough +config.ebwizardry.replace_vanilla_fall_damage=Replace Vanilla Fall Damage +config.ebwizardry.replace_vanilla_fall_damage.tooltip=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 revert to a more simplistic method of resetting the player's fall damage in certain cases. +config.ebwizardry.replace_vanilla_fall_damage.true=Yes - it's parkour time +config.ebwizardry.replace_vanilla_fall_damage.false=No - break my legs normally +config.ebwizardry.creative_bypasses_arcane_lock=Bypass Arcane Lock +config.ebwizardry.creative_bypasses_arcane_lock.tooltip=Determines which players can bypass arcane locks. +config.ebwizardry.creative_bypasses_arcane_lock.true=Anyone in creative mode +config.ebwizardry.creative_bypasses_arcane_lock.false=Only ops in creative mode +config.ebwizardry.slow_time_affects_players=Slow Time Affects Players +config.ebwizardry.slow_time_affects_players.tooltip=Whether players are slowed when another nearby player uses the slow time spell. If this is disabled, mobs and projectiles will still be affected but players will move at normal speed. +config.ebwizardry.mob_loot_table_whitelist=Mob Loot Table Whitelist +config.ebwizardry.mob_loot_table_whitelist.tooltip=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. +config.ebwizardry.mob_loot_table_blacklist=Mob Loot Table Blacklist +config.ebwizardry.mob_loot_table_blacklist.tooltip=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. +config.ebwizardry.mob_spawn_dimensions=Mob Spawning Dimensions +config.ebwizardry.mob_spawn_dimensions.tooltip=List of ids of dimensions in which wizardry's hostile mobs can spawn. +config.ebwizardry.mob_spawn_biome_blacklist=Mob Spawning Biome Blacklist +config.ebwizardry.mob_spawn_biome_blaclist.tooltip=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). +config.ebwizardry.evil_wizard_spawn_rate=Evil Wizard Spawn Rate +config.ebwizardry.evil_wizard_spawn_rate.tooltip=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. +config.ebwizardry.ice_wraith_spawn_rate=Ice Wraith Spawn Rate +config.ebwizardry.ice_wraith_spawn_rate.tooltip=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. +config.ebwizardry.lightning_wraith_spawn_rate=Lightning Wraith Spawn Rate +config.ebwizardry.lightning_wraith_spawn_rate.tooltip=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. +config.ebwizardry.forfeit_chance=Forfeit Chance +config.ebwizardry.forfeit_chance.tooltip=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. +config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor +config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. +config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor +config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. +config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist +config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist +config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist +config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry\:wizard). +config.ebwizardry.pocket_furnace_item_blacklist=Pocket Furnace Item Blacklist +config.ebwizardry.pocket_furnace_item_blacklist.tooltip=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. ebwizardry\:crystal_ore). +config.ebwizardry.divination_ore_whitelist=Divination Ore Whitelist +config.ebwizardry.divination_ore_whitelist.tooltip=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. ebwizardry\:crystal_ore). +config.ebwizardry.sword_item_whitelist=Sword Item Whitelist +config.ebwizardry.sword_item_whitelist.tooltip=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). +config.ebwizardry.bow_item_whitelist=Bow Item Whitelist +config.ebwizardry.bow_item_whitelist.tooltip=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). +config.ebwizardry.currency_items=Currency Items +config.ebwizardry.currency_items.tooltip=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. + config.ebwizardry.category.worldgen=World Generation Settings config.ebwizardry.category.worldgen.tooltip=Configure wizardry's world generation features config.ebwizardry.title.worldgen=World Generation Settings config.ebwizardry.subtitle.worldgen=Settings that affect world generation. -config.ebwizardry.category.commands=Command Settings -config.ebwizardry.category.commands.tooltip=Configure wizardry's commands -config.ebwizardry.title.commands=Command Settings -config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. +config.ebwizardry.fast_worldgen=Structure Generation +config.ebwizardry.fast_worldgen.tooltip=Controls which algorithm wizardry uses for structure generation. Fancy worldgen prevents structures on cliffs, in caves and intersecting other structures, and cleans up floating trees after generating. Fast worldgen sacrifices these improvements, potentially resulting in faster world generation. 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. +config.ebwizardry.fast_worldgen.true=Fast +config.ebwizardry.fast_worldgen.false=Fancy +config.ebwizardry.tower_dimensions=Tower Dimensions +config.ebwizardry.tower_dimensions.tooltip=List of ids of dimensions in which wizard towers will generate. +config.ebwizardry.tower_rarity=Tower Rarity +config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. 1 in this many chunks will contain a wizard tower, meaning higher numbers are rarer. +config.ebwizardry.evil_wizard_chance=Evil Wizard Chance +config.ebwizardry.evil_wizard_chance.tooltip=The chance for wizard towers to generate with an evil wizard and chest inside, instead of a friendly wizard. +config.ebwizardry.tower_files=Tower Structure Files +config.ebwizardry.tower_files.tooltip=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. +config.ebwizardry.tower_with_chest_files=Tower With Chest Structure Files +config.ebwizardry.tower_with_chest_files.tooltip=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. +config.ebwizardry.obelisk_dimensions=Obelisk Dimensions +config.ebwizardry.obelisk_dimensions.tooltip=List of ids of dimensions in which obelisks will generate. +config.ebwizardry.obelisk_rarity=Obelisk Rarity +config.ebwizardry.obelisk_rarity.tooltip=Rarity of obelisks. 1 in this many chunks will contain an obelisk, meaning higher numbers are rarer. +config.ebwizardry.obelisk_files=Obelisk Structure Files +config.ebwizardry.obelisk_files.tooltip=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. +config.ebwizardry.shrine_dimensions=Shrine Dimensions +config.ebwizardry.shrine_dimensions.tooltip=List of ids of dimensions in which shrines will generate. +config.ebwizardry.shrine_rarity=Shrine Rarity +config.ebwizardry.shrine_rarity.tooltip=Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer. +config.ebwizardry.shrine_files=Shrine Structure Files +config.ebwizardry.shrine_files.tooltip=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. +config.ebwizardry.ore_dimensions=Ore Dimensions +config.ebwizardry.ore_dimensions.tooltip=List of ids of dimensions in which crystal ore will generate. Note that removing the overworld (id 0) from this list will make the mod VERY difficult to play! +config.ebwizardry.flower_dimensions=Flower Dimensions +config.ebwizardry.flower_dimensions.tooltip=List of ids of dimensions in which crystal flowers will generate. +config.ebwizardry.loot_injection_locations=Loot Injection Locations +config.ebwizardry.loot_injection_locations.tooltip=List of loot tables to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into. config.ebwizardry.category.client=Client Settings config.ebwizardry.category.client.tooltip=Configure wizardry's display and controls config.ebwizardry.title.client=Client Settings config.ebwizardry.subtitle.client=Client-side settings that only affect the local minecraft game. -config.ebwizardry.category.spells=Spell Configuration -config.ebwizardry.category.spells.tooltip=Select which spells are enabled -config.ebwizardry.title.spells=Spell Configuration -config.ebwizardry.subtitle.spells=Set a spell to false to disable it. +config.ebwizardry.shift_scrolling=Shift-scrolling +config.ebwizardry.shift_scrolling.tooltip=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. +config.ebwizardry.reverse_scroll_direction=Scroll Direction +config.ebwizardry.reverse_scroll_direction.tooltip=The scroll direction used to switch between spells on a wand while sneaking. +config.ebwizardry.reverse_scroll_direction.true=Reversed +config.ebwizardry.reverse_scroll_direction.false=Normal +config.ebwizardry.spell_hud_position=Spell HUD Position +config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. +config.ebwizardry.spell_hud_skin=Spell HUD Skin +config.ebwizardry.spell_hud_skin.tooltip=Change the look of the spell HUD... +config.ebwizardry.spell_hud_skin.preview=Preview +config.ebwizardry.handbook_progression=Handbook Progression +config.ebwizardry.handbook_progression.tooltip=When enabled, to help guide players through the mod, sections of The Wizard's Handbook are unlocked when a player gains the advancement that triggers them, and are hidden otherwise. When disabled, the entire handbook is readable regardless of advancement progress. The entire handbook is always readable in creative mode. +config.ebwizardry.handbook_progression.true=Yes - teach me, Sensei! +config.ebwizardry.handbook_progression.false=No - I know what I'm doing +config.ebwizardry.books_pause_game=Books Pause Game +config.ebwizardry.books_pause_game.tooltip=Whether opening any of wizardry's books pauses the game in singleplayer. Has no effect on servers or LAN worlds. +config.ebwizardry.books_pause_game.true=Yes - I don't want any distractions! +config.ebwizardry.books_pause_game.false=No - I read to pass the time +config.ebwizardry.summoned_creature_names=Summoned Creature Names +config.ebwizardry.summoned_creature_names.tooltip=Controls whether summoned creatures' names and owners are displayed above their heads. +config.ebwizardry.summoned_creature_names.true=Shown +config.ebwizardry.summoned_creature_names.false=Hidden +config.ebwizardry.use_shaders=Use Custom Shaders +config.ebwizardry.use_shaders.tooltip=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. +config.ebwizardry.use_shaders.true=Yes - gimme those sweet shaders! +config.ebwizardry.use_shaders.false=No - I'm running this on a potato + +config.ebwizardry.category.commands=Command Settings +config.ebwizardry.category.commands.tooltip=Configure wizardry's commands +config.ebwizardry.title.commands=Command Settings +config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. + +config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit +config.ebwizardry.cast_command_multiplier_limit.tooltip=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! +config.ebwizardry.cast_command_name=Cast Spell Command Name +config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. +config.ebwizardry.discoverspell_command_name=Discover Spell Command Name +config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. +config.ebwizardry.ally_command_name=Set Ally Command Name +config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. +config.ebwizardry.allies_command_name=View Allies Command Name +config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. config.ebwizardry.category.resistances=Resistance Configuration config.ebwizardry.category.resistances.tooltip=Configure which mobs are immune to different types of magic config.ebwizardry.title.resistances=Resistance Configuration config.ebwizardry.subtitle.resistances=Settings which allow entities to be made immune to certain types of magic. -config.ebwizardry.tower_rarity=Tower Rarity -config.ebwizardry.ore_dimensions=Ore Dimensions -config.ebwizardry.flower_dimensions=Flower Dimensions -config.ebwizardry.tower_dimensions=Tower Dimensions -config.ebwizardry.spell_book_drop_chance=Spell Book Drop Chance -config.ebwizardry.generate_loot=Generate Loot -config.ebwizardry.firebomb_is_craftable=Firebomb Is Craftable -config.ebwizardry.poison_bomb_is_craftable=Poison Bomb Is Craftable -config.ebwizardry.smoke_bomb_is_craftable=Smoke Bomb Is Craftable -config.ebwizardry.use_alternate_scroll_recipe=Use Alternate Scroll Recipe -config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks -config.ebwizardry.show_summoned_creature_names=Show Summoned Creature Names -config.ebwizardry.friendly_fire=Friendly Fire -config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament -config.ebwizardry.discovery_mode=Discovery Mode -config.ebwizardry.enable_shift_scrolling=Enable Shift-scrolling -config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting -config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor -config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor -config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit -config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist -config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist -config.ebwizardry.spell_hud_position=Spell HUD Position -config.ebwizardry.cast_command_name=Cast Spell Command Name -config.ebwizardry.discoverspell_command_name=Discover Spell Command Name -config.ebwizardry.ally_command_name=Set Ally Command Name -config.ebwizardry.allies_command_name=View Allies Command Name -config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist -config.ebwizardry.evil_wizard_dimensions=Evil Wizard Dimensions - config.ebwizardry.mobs_immune_to_fire=Mobs Immune To Fire +config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice +config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning +config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither +config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry\:wizard). config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison +config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry\:wizard). -config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely. -config.ebwizardry.ore_dimensions.tooltip=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! -config.ebwizardry.flower_dimensions.tooltip=List of dimension ids in which crystal flowers will generate. -config.ebwizardry.tower_dimensions.tooltip=List of dimension ids in which wizard towers will generate. -config.ebwizardry.spell_book_drop_chance.tooltip=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. -config.ebwizardry.generate_loot.tooltip=Whether to generate wizardry loot in dungeon chests. -config.ebwizardry.firebomb_is_craftable.tooltip=Whether firebombs can be crafted or not. -config.ebwizardry.poison_bomb_is_craftable.tooltip=Whether poison bombs can be crafted or not. -config.ebwizardry.smoke_bomb_is_craftable.tooltip=Whether smoke bombs can be crafted or not. -config.ebwizardry.use_alternate_scroll_recipe.tooltip=Whether to require a magic crystal in the shapeless crafting recipe for blank scrolls. Set to true if another mod adds a conflicting recipe. -config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. -config.ebwizardry.show_summoned_creature_names.tooltip=Whether to show summoned creatures' names and owners above their heads. -config.ebwizardry.friendly_fire.tooltip=Whether to allow players to damage their designated allies using magic. -config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items. -config.ebwizardry.discovery_mode.tooltip=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. -config.ebwizardry.enable_shift_scrolling.tooltip=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. -config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge attack their owner if their owner attacks them. -config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. -config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. -config.ebwizardry.cast_command_multiplier_limit.tooltip=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! -config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. -config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. -config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. -config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. -config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. -config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.evil_wizard_dimensions.tooltip=List of dimension ids in which evil wizards can spawn. +config.ebwizardry.category.compatibility=Mod Compatibility Settings +config.ebwizardry.category.compatibility.tooltip=Configure how wizardry interacts with other mods +config.ebwizardry.title.compatibility=Mod Compatibility Settings +config.ebwizardry.subtitle.compatibility=Settings that affect how wizardry interacts with other mods -config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry:wizard). +config.ebwizardry.damage_source_blacklist=Damage Source Blacklist +config.ebwizardry.damage_source_blacklist.tooltip=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. +config.ebwizardry.compatibility_warnings=Compatibility Warnings +config.ebwizardry.compatibility_warnings.tooltip=Whether to print compatibility warnings to the console. Set to false if excessive messages are being printed. +config.ebwizardry.baubles_integration=Baubles Integration +config.ebwizardry.baubles_integration.tooltip=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. +config.ebwizardry.jei_integration=JEI Integration +config.ebwizardry.jei_integration.tooltip=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. +config.ebwizardry.antique_atlas_integration=Antique Atlas Integration +config.ebwizardry.antique_atlas_integration.tooltip=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. +config.ebwizardry.auto_place_tower_markers=Auto-Place Tower Markers +config.ebwizardry.auto_place_tower_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of wizard towers. +config.ebwizardry.auto_place_obelisk_markers=Auto-Place Obelisk Markers +config.ebwizardry.auto_place_obelisk_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of obelisks. +config.ebwizardry.auto_place_shrine_markers=Auto-Place Shrine Markers +config.ebwizardry.auto_place_shrine_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of shrines. + +integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench + +integration.antiqueatlas.marker.ebwizardry\:wizard_tower=Wizard Tower +integration.antiqueatlas.marker.ebwizardry\:obelisk=Obelisk +integration.antiqueatlas.marker.ebwizardry\:shrine=Shrine wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/lang/es_es.lang b/src/main/resources/assets/ebwizardry/lang/es_es.lang index 3287ca13..ae625e31 100644 --- a/src/main/resources/assets/ebwizardry/lang/es_es.lang +++ b/src/main/resources/assets/ebwizardry/lang/es_es.lang @@ -29,13 +29,13 @@ item.ebwizardry:wand.mana=Mana: %1$s/%2$s item.ebwizardry:wand.addally=%1$s ha sido anadido a tu lista de aliados item.ebwizardry:wand.removeally=%1$s ha sido removido a tu lista de aliados -item.ebwizardry:basic_fire_wand.name=Varita de Ascuas -item.ebwizardry:basic_ice_wand.name=Varita de Escarcha -item.ebwizardry:basic_lightning_wand.name=Varita de Chispas -item.ebwizardry:basic_necromancy_wand.name=Varita de las Sombras -item.ebwizardry:basic_earth_wand.name=Varita del Bosque -item.ebwizardry:basic_sorcery_wand.name=Varita del Misterio -item.ebwizardry:basic_healing_wand.name=Varita de la Curacion +item.ebwizardry:novice_fire_wand.name=Varita de Ascuas +item.ebwizardry:novice_ice_wand.name=Varita de Escarcha +item.ebwizardry:novice_lightning_wand.name=Varita de Chispas +item.ebwizardry:novice_necromancy_wand.name=Varita de las Sombras +item.ebwizardry:novice_earth_wand.name=Varita del Bosque +item.ebwizardry:novice_sorcery_wand.name=Varita del Misterio +item.ebwizardry:novice_healing_wand.name=Varita de la Curacion item.ebwizardry:apprentice_fire_wand.name=Varita del Piromano Aprendiz item.ebwizardry:apprentice_ice_wand.name=Varita del Mago de Hielo Aprendiz @@ -212,56 +212,56 @@ entity.ebwizardry:lightning_pulse.name=Pulso de Relampago item_group.ebwizardry=Wizardry item_group.wizardryspells=Hechizos de Wizardry -advancement.wizardry:root=Wizardry -advancement.wizardry:root.desc=Avances -advancement.wizardry:crystal=Un Cristral Curioso... -advancement.wizardry:crystal.desc=Pica un Cristal Magico -advancement.wizardry:arcane_initiate=Inicio Arcano -advancement.wizardry:arcane_initiate.desc=Craftea una varita magica con una pepita de oro, un palo y un cristal magico -advancement.wizardry:apprentice=Aprendiz de Mago -advancement.wizardry:apprentice.desc=Usa un tomo de lo arcano para mejorar tu varita -advancement.wizardry:master=Maestro Arcano -advancement.wizardry:master.desc=Obten una varita de maestro -advancement.wizardry:all_spells=Mago de Todos los Oficios -advancement.wizardry:all_spells.desc=Lanza todos los hechizos disponibles -advancement.wizardry:wizard_trade=Comercio Magico -advancement.wizardry:wizard_trade.desc=Compra un objeto a un mago -advancement.wizardry:buy_master_spell=El Conocimiento es Poder -advancement.wizardry:buy_master_spell.desc=Compra un hechizo maestro a un mago -advancement.wizardry:freeze_blaze=Ya No Estas Tan Caliente -advancement.wizardry:freeze_blaze.desc=Congela a un blaze -advancement.wizardry:charge_creeper=Va a Explotar!! -advancement.wizardry:charge_creeper.desc='Accidentalmente' carga un creeper -advancement.wizardry:frankenstein=Frankenstein -advancement.wizardry:frankenstein.desc=Convierte a un cerdo en un hombrecerdo zombi usando el hechizo de rayo -advancement.wizardry:special_upgrade=Remiendo Arcano -advancement.wizardry:special_upgrade.desc=Aplica una mejora a una varita -advancement.wizardry:craft_flask=Es Magia, Embotellada! -advancement.wizardry:craft_flask.desc=Craftea un frasco de mana -advancement.wizardry:elemental=Elemental -advancement.wizardry:elemental.desc=Obten una varita elemental -advancement.wizardry:armour_set=Now Eres un Mago Apropiado -advancement.wizardry:armour_set.desc=Craftea y equipa el set completo de armadura de mago -advancement.wizardry:legendary=Legendario -advancement.wizardry:legendary.desc=Obten una pieza de armadura de mago legendaria -advancement.wizardry:self_destruct=Tiro por la culata -advancement.wizardry:self_destruct.desc=Muere por tu propia magia -advancement.wizardry:pig_tornado=No De Nuevo... -advancement.wizardry:pig_tornado.desc=Monta un cerdo hacia un tornado -advancement.wizardry:jam_wizard=Sesion de Interferencia -advancement.wizardry:jam_wizard.desc=Usa el hechizo arcano de interferencia en un mago -advancement.wizardry:slime_skeleton=Situacion Pegajosa -advancement.wizardry:slime_skeleton.desc=Sumerge un esqueleto en slime -advancement.wizardry:anger_wizard=Te Arrepentiras de Eso -advancement.wizardry:anger_wizard.desc=Haz que un mago se enoje -advancement.wizardry:defeat_evil_wizard=Justicia -advancement.wizardry:defeat_evil_wizard.desc=Derrota a un mago malvado -advancement.wizardry:max_out_wand=Equipada Al Maximo! -advancement.wizardry:max_out_wand.desc=Aplica el maximo numero de mejoras a una varita maestra -advancement.wizardry:element_master=Maestria Elemental -advancement.wizardry:element_master.desc=Lanza todos los hechizos de cualquier elemento -advancement.wizardry:identify_spell=Apreciacion Arcana -advancement.wizardry:identify_spell.desc=Usa un pergamino de la identificacion para identificar un libro de hechizos o pergamino +advancement.ebwizardry:root=Wizardry +advancement.ebwizardry:root.desc=Avances +advancement.ebwizardry:crystal=Un Cristral Curioso... +advancement.ebwizardry:crystal.desc=Pica un Cristal Magico +advancement.ebwizardry:arcane_initiate=Inicio Arcano +advancement.ebwizardry:arcane_initiate.desc=Craftea una varita magica con una pepita de oro, un palo y un cristal magico +advancement.ebwizardry:apprentice=Aprendiz de Mago +advancement.ebwizardry:apprentice.desc=Usa un tomo de lo arcano para mejorar tu varita +advancement.ebwizardry:master=Maestro Arcano +advancement.ebwizardry:master.desc=Obten una varita de maestro +advancement.ebwizardry:all_spells=Mago de Todos los Oficios +advancement.ebwizardry:all_spells.desc=Lanza todos los hechizos disponibles +advancement.ebwizardry:wizard_trade=Comercio Magico +advancement.ebwizardry:wizard_trade.desc=Compra un objeto a un mago +advancement.ebwizardry:buy_master_spell=El Conocimiento es Poder +advancement.ebwizardry:buy_master_spell.desc=Compra un hechizo maestro a un mago +advancement.ebwizardry:freeze_blaze=Ya No Estas Tan Caliente +advancement.ebwizardry:freeze_blaze.desc=Congela a un blaze +advancement.ebwizardry:charge_creeper=Va a Explotar!! +advancement.ebwizardry:charge_creeper.desc='Accidentalmente' carga un creeper +advancement.ebwizardry:frankenstein=Frankenstein +advancement.ebwizardry:frankenstein.desc=Convierte a un cerdo en un hombrecerdo zombi usando el hechizo de rayo +advancement.ebwizardry:special_upgrade=Remiendo Arcano +advancement.ebwizardry:special_upgrade.desc=Aplica una mejora a una varita +advancement.ebwizardry:craft_flask=Es Magia, Embotellada! +advancement.ebwizardry:craft_flask.desc=Craftea un frasco de mana +advancement.ebwizardry:elemental=Elemental +advancement.ebwizardry:elemental.desc=Obten una varita elemental +advancement.ebwizardry:armour_set=Now Eres un Mago Apropiado +advancement.ebwizardry:armour_set.desc=Craftea y equipa el set completo de armadura de mago +advancement.ebwizardry:legendary=Legendario +advancement.ebwizardry:legendary.desc=Obten una pieza de armadura de mago legendaria +advancement.ebwizardry:self_destruct=Tiro por la culata +advancement.ebwizardry:self_destruct.desc=Muere por tu propia magia +advancement.ebwizardry:pig_tornado=No De Nuevo... +advancement.ebwizardry:pig_tornado.desc=Monta un cerdo hacia un tornado +advancement.ebwizardry:jam_wizard=Sesion de Interferencia +advancement.ebwizardry:jam_wizard.desc=Usa el hechizo arcano de interferencia en un mago +advancement.ebwizardry:slime_skeleton=Situacion Pegajosa +advancement.ebwizardry:slime_skeleton.desc=Sumerge un esqueleto en slime +advancement.ebwizardry:anger_wizard=Te Arrepentiras de Eso +advancement.ebwizardry:anger_wizard.desc=Haz que un mago se enoje +advancement.ebwizardry:defeat_evil_wizard=Justicia +advancement.ebwizardry:defeat_evil_wizard.desc=Derrota a un mago malvado +advancement.ebwizardry:max_out_wand=Equipada Al Maximo! +advancement.ebwizardry:max_out_wand.desc=Aplica el maximo numero de mejoras a una varita maestra +advancement.ebwizardry:element_master=Maestria Elemental +advancement.ebwizardry:element_master.desc=Lanza todos los hechizos de cualquier elemento +advancement.ebwizardry:identify_spell=Apreciacion Arcana +advancement.ebwizardry:identify_spell.desc=Usa un pergamino de la identificacion para identificar un libro de hechizos o pergamino tile.ebwizardry:transportation_stone.confirm=A partir de ahora retornaras a este lugar cuando lances %1$s tile.ebwizardry:transportation_stone.invalid=Primero debes hacer un circulo con 8 piedras de transportacion! @@ -271,7 +271,7 @@ container.ebwizardry:arcane_workbench.apply=Aplicar container.ebwizardry:arcane_workbench.mana=Mana: container.ebwizardry:arcane_workbench.upgrades=Mejoras Aplicadas: -tier.basic=Novato +tier.novice=Novato tier.apprentice=Aprendiz tier.advanced=Avanzado tier.master=Maestro @@ -535,7 +535,7 @@ spell.ebwizardry:metamorphosis.desc=Cambia la forma del objetivo. Solo funciona spell.ebwizardry:meteor.desc=Algunos magos solo quieren ver el mundo arder... spell.ebwizardry:mind_control.desc=Toma control de la mente del objetivo por 30 segundos, provocando que cambie de bando y peleen a tu favor. No funciona con criaturas de voluntad muy fuerte. spell.ebwizardry:mind_trick.desc=Confunde y desorienta al objetivo por 15 segundos, volviendolo incapaz de atacar. El efecto se desactiva si el objetivo recibe dano. -spell.ebwizardry:none.desc=Para obtener un libro de hechizo con el comando /give, usa la metadata: /give [player] ebwizardry:spell_book 1 [spell id] (_si encontraste este libro en un cofre significa que otro mod esta alterando las cosas). +spell.ebwizardry:none.desc=Para obtener un libro de hechizo con el comando /give, usa la id: /give [player] ebwizardry:spell_book 1 [spell id] (_si encontraste este libro en un cofre significa que otro mod esta alterando las cosas). spell.ebwizardry:oakflesh.desc=Mejora la resistencia del amgo durante 30 segundos. spell.ebwizardry:petrify.desc=Convierte al objetivo en piedra hasta que se libere, con un chance de liberarse cuando se pone de noche. El objetivo no se puede mover ni atacar y no puede ser atacado. spell.ebwizardry:phase_step.desc=Transporta al mago a traves de una pared de 1 bloque de grosor. El rango mejora el grosor por el cual te puedes transportar. diff --git a/src/main/resources/assets/ebwizardry/lang/es_mx.lang b/src/main/resources/assets/ebwizardry/lang/es_mx.lang index 3287ca13..ae625e31 100644 --- a/src/main/resources/assets/ebwizardry/lang/es_mx.lang +++ b/src/main/resources/assets/ebwizardry/lang/es_mx.lang @@ -29,13 +29,13 @@ item.ebwizardry:wand.mana=Mana: %1$s/%2$s item.ebwizardry:wand.addally=%1$s ha sido anadido a tu lista de aliados item.ebwizardry:wand.removeally=%1$s ha sido removido a tu lista de aliados -item.ebwizardry:basic_fire_wand.name=Varita de Ascuas -item.ebwizardry:basic_ice_wand.name=Varita de Escarcha -item.ebwizardry:basic_lightning_wand.name=Varita de Chispas -item.ebwizardry:basic_necromancy_wand.name=Varita de las Sombras -item.ebwizardry:basic_earth_wand.name=Varita del Bosque -item.ebwizardry:basic_sorcery_wand.name=Varita del Misterio -item.ebwizardry:basic_healing_wand.name=Varita de la Curacion +item.ebwizardry:novice_fire_wand.name=Varita de Ascuas +item.ebwizardry:novice_ice_wand.name=Varita de Escarcha +item.ebwizardry:novice_lightning_wand.name=Varita de Chispas +item.ebwizardry:novice_necromancy_wand.name=Varita de las Sombras +item.ebwizardry:novice_earth_wand.name=Varita del Bosque +item.ebwizardry:novice_sorcery_wand.name=Varita del Misterio +item.ebwizardry:novice_healing_wand.name=Varita de la Curacion item.ebwizardry:apprentice_fire_wand.name=Varita del Piromano Aprendiz item.ebwizardry:apprentice_ice_wand.name=Varita del Mago de Hielo Aprendiz @@ -212,56 +212,56 @@ entity.ebwizardry:lightning_pulse.name=Pulso de Relampago item_group.ebwizardry=Wizardry item_group.wizardryspells=Hechizos de Wizardry -advancement.wizardry:root=Wizardry -advancement.wizardry:root.desc=Avances -advancement.wizardry:crystal=Un Cristral Curioso... -advancement.wizardry:crystal.desc=Pica un Cristal Magico -advancement.wizardry:arcane_initiate=Inicio Arcano -advancement.wizardry:arcane_initiate.desc=Craftea una varita magica con una pepita de oro, un palo y un cristal magico -advancement.wizardry:apprentice=Aprendiz de Mago -advancement.wizardry:apprentice.desc=Usa un tomo de lo arcano para mejorar tu varita -advancement.wizardry:master=Maestro Arcano -advancement.wizardry:master.desc=Obten una varita de maestro -advancement.wizardry:all_spells=Mago de Todos los Oficios -advancement.wizardry:all_spells.desc=Lanza todos los hechizos disponibles -advancement.wizardry:wizard_trade=Comercio Magico -advancement.wizardry:wizard_trade.desc=Compra un objeto a un mago -advancement.wizardry:buy_master_spell=El Conocimiento es Poder -advancement.wizardry:buy_master_spell.desc=Compra un hechizo maestro a un mago -advancement.wizardry:freeze_blaze=Ya No Estas Tan Caliente -advancement.wizardry:freeze_blaze.desc=Congela a un blaze -advancement.wizardry:charge_creeper=Va a Explotar!! -advancement.wizardry:charge_creeper.desc='Accidentalmente' carga un creeper -advancement.wizardry:frankenstein=Frankenstein -advancement.wizardry:frankenstein.desc=Convierte a un cerdo en un hombrecerdo zombi usando el hechizo de rayo -advancement.wizardry:special_upgrade=Remiendo Arcano -advancement.wizardry:special_upgrade.desc=Aplica una mejora a una varita -advancement.wizardry:craft_flask=Es Magia, Embotellada! -advancement.wizardry:craft_flask.desc=Craftea un frasco de mana -advancement.wizardry:elemental=Elemental -advancement.wizardry:elemental.desc=Obten una varita elemental -advancement.wizardry:armour_set=Now Eres un Mago Apropiado -advancement.wizardry:armour_set.desc=Craftea y equipa el set completo de armadura de mago -advancement.wizardry:legendary=Legendario -advancement.wizardry:legendary.desc=Obten una pieza de armadura de mago legendaria -advancement.wizardry:self_destruct=Tiro por la culata -advancement.wizardry:self_destruct.desc=Muere por tu propia magia -advancement.wizardry:pig_tornado=No De Nuevo... -advancement.wizardry:pig_tornado.desc=Monta un cerdo hacia un tornado -advancement.wizardry:jam_wizard=Sesion de Interferencia -advancement.wizardry:jam_wizard.desc=Usa el hechizo arcano de interferencia en un mago -advancement.wizardry:slime_skeleton=Situacion Pegajosa -advancement.wizardry:slime_skeleton.desc=Sumerge un esqueleto en slime -advancement.wizardry:anger_wizard=Te Arrepentiras de Eso -advancement.wizardry:anger_wizard.desc=Haz que un mago se enoje -advancement.wizardry:defeat_evil_wizard=Justicia -advancement.wizardry:defeat_evil_wizard.desc=Derrota a un mago malvado -advancement.wizardry:max_out_wand=Equipada Al Maximo! -advancement.wizardry:max_out_wand.desc=Aplica el maximo numero de mejoras a una varita maestra -advancement.wizardry:element_master=Maestria Elemental -advancement.wizardry:element_master.desc=Lanza todos los hechizos de cualquier elemento -advancement.wizardry:identify_spell=Apreciacion Arcana -advancement.wizardry:identify_spell.desc=Usa un pergamino de la identificacion para identificar un libro de hechizos o pergamino +advancement.ebwizardry:root=Wizardry +advancement.ebwizardry:root.desc=Avances +advancement.ebwizardry:crystal=Un Cristral Curioso... +advancement.ebwizardry:crystal.desc=Pica un Cristal Magico +advancement.ebwizardry:arcane_initiate=Inicio Arcano +advancement.ebwizardry:arcane_initiate.desc=Craftea una varita magica con una pepita de oro, un palo y un cristal magico +advancement.ebwizardry:apprentice=Aprendiz de Mago +advancement.ebwizardry:apprentice.desc=Usa un tomo de lo arcano para mejorar tu varita +advancement.ebwizardry:master=Maestro Arcano +advancement.ebwizardry:master.desc=Obten una varita de maestro +advancement.ebwizardry:all_spells=Mago de Todos los Oficios +advancement.ebwizardry:all_spells.desc=Lanza todos los hechizos disponibles +advancement.ebwizardry:wizard_trade=Comercio Magico +advancement.ebwizardry:wizard_trade.desc=Compra un objeto a un mago +advancement.ebwizardry:buy_master_spell=El Conocimiento es Poder +advancement.ebwizardry:buy_master_spell.desc=Compra un hechizo maestro a un mago +advancement.ebwizardry:freeze_blaze=Ya No Estas Tan Caliente +advancement.ebwizardry:freeze_blaze.desc=Congela a un blaze +advancement.ebwizardry:charge_creeper=Va a Explotar!! +advancement.ebwizardry:charge_creeper.desc='Accidentalmente' carga un creeper +advancement.ebwizardry:frankenstein=Frankenstein +advancement.ebwizardry:frankenstein.desc=Convierte a un cerdo en un hombrecerdo zombi usando el hechizo de rayo +advancement.ebwizardry:special_upgrade=Remiendo Arcano +advancement.ebwizardry:special_upgrade.desc=Aplica una mejora a una varita +advancement.ebwizardry:craft_flask=Es Magia, Embotellada! +advancement.ebwizardry:craft_flask.desc=Craftea un frasco de mana +advancement.ebwizardry:elemental=Elemental +advancement.ebwizardry:elemental.desc=Obten una varita elemental +advancement.ebwizardry:armour_set=Now Eres un Mago Apropiado +advancement.ebwizardry:armour_set.desc=Craftea y equipa el set completo de armadura de mago +advancement.ebwizardry:legendary=Legendario +advancement.ebwizardry:legendary.desc=Obten una pieza de armadura de mago legendaria +advancement.ebwizardry:self_destruct=Tiro por la culata +advancement.ebwizardry:self_destruct.desc=Muere por tu propia magia +advancement.ebwizardry:pig_tornado=No De Nuevo... +advancement.ebwizardry:pig_tornado.desc=Monta un cerdo hacia un tornado +advancement.ebwizardry:jam_wizard=Sesion de Interferencia +advancement.ebwizardry:jam_wizard.desc=Usa el hechizo arcano de interferencia en un mago +advancement.ebwizardry:slime_skeleton=Situacion Pegajosa +advancement.ebwizardry:slime_skeleton.desc=Sumerge un esqueleto en slime +advancement.ebwizardry:anger_wizard=Te Arrepentiras de Eso +advancement.ebwizardry:anger_wizard.desc=Haz que un mago se enoje +advancement.ebwizardry:defeat_evil_wizard=Justicia +advancement.ebwizardry:defeat_evil_wizard.desc=Derrota a un mago malvado +advancement.ebwizardry:max_out_wand=Equipada Al Maximo! +advancement.ebwizardry:max_out_wand.desc=Aplica el maximo numero de mejoras a una varita maestra +advancement.ebwizardry:element_master=Maestria Elemental +advancement.ebwizardry:element_master.desc=Lanza todos los hechizos de cualquier elemento +advancement.ebwizardry:identify_spell=Apreciacion Arcana +advancement.ebwizardry:identify_spell.desc=Usa un pergamino de la identificacion para identificar un libro de hechizos o pergamino tile.ebwizardry:transportation_stone.confirm=A partir de ahora retornaras a este lugar cuando lances %1$s tile.ebwizardry:transportation_stone.invalid=Primero debes hacer un circulo con 8 piedras de transportacion! @@ -271,7 +271,7 @@ container.ebwizardry:arcane_workbench.apply=Aplicar container.ebwizardry:arcane_workbench.mana=Mana: container.ebwizardry:arcane_workbench.upgrades=Mejoras Aplicadas: -tier.basic=Novato +tier.novice=Novato tier.apprentice=Aprendiz tier.advanced=Avanzado tier.master=Maestro @@ -535,7 +535,7 @@ spell.ebwizardry:metamorphosis.desc=Cambia la forma del objetivo. Solo funciona spell.ebwizardry:meteor.desc=Algunos magos solo quieren ver el mundo arder... spell.ebwizardry:mind_control.desc=Toma control de la mente del objetivo por 30 segundos, provocando que cambie de bando y peleen a tu favor. No funciona con criaturas de voluntad muy fuerte. spell.ebwizardry:mind_trick.desc=Confunde y desorienta al objetivo por 15 segundos, volviendolo incapaz de atacar. El efecto se desactiva si el objetivo recibe dano. -spell.ebwizardry:none.desc=Para obtener un libro de hechizo con el comando /give, usa la metadata: /give [player] ebwizardry:spell_book 1 [spell id] (_si encontraste este libro en un cofre significa que otro mod esta alterando las cosas). +spell.ebwizardry:none.desc=Para obtener un libro de hechizo con el comando /give, usa la id: /give [player] ebwizardry:spell_book 1 [spell id] (_si encontraste este libro en un cofre significa que otro mod esta alterando las cosas). spell.ebwizardry:oakflesh.desc=Mejora la resistencia del amgo durante 30 segundos. spell.ebwizardry:petrify.desc=Convierte al objetivo en piedra hasta que se libere, con un chance de liberarse cuando se pone de noche. El objetivo no se puede mover ni atacar y no puede ser atacado. spell.ebwizardry:phase_step.desc=Transporta al mago a traves de una pared de 1 bloque de grosor. El rango mejora el grosor por el cual te puedes transportar. diff --git a/src/main/resources/assets/ebwizardry/lang/fr_fr.lang b/src/main/resources/assets/ebwizardry/lang/fr_fr.lang new file mode 100644 index 00000000..b51fa23d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/lang/fr_fr.lang @@ -0,0 +1,760 @@ +tile.ebwizardry:arcane_workbench.name=Table des arcanes +tile.ebwizardry:crystal_ore.name=Minerais de cristal +tile.ebwizardry:petrified_stone.name=Roche pétrifiée +tile.ebwizardry:ice_statue.name=Statue de glace +tile.ebwizardry:crystal_flower.name=Fleur de cristal +tile.ebwizardry:snare.name=Ronces +tile.ebwizardry:transportation_stone.name=Pierre de Transportation +tile.ebwizardry:spectral_block.name=Bloc spectrale +tile.ebwizardry:crystal_block.name=Bloc de cristal + +item.ebwizardry:magic_crystal.name=Cristal magique +item.ebwizardry:magic_wand.name=Baguette magique +item.ebwizardry:apprentice_wand.name=Baguette d'apprenti +item.ebwizardry:advanced_wand.name=Baguette d'expert +item.ebwizardry:master_wand.name=Baguette de maître +item.ebwizardry:spell_book.name=Livre de sort + +item.ebwizardry:arcane_tome.name=Tome des arcanes +item.ebwizardry:arcane_tome.desc1=Améliore n'importe quelle +item.ebwizardry:arcane_tome.desc2=baguette du tier %1$s au tier %1$s + +item.ebwizardry:wizard_handbook.name=Le manuel du sorcier +item.ebwizardry:wizard_handbook.desc=par %1$s + +item.ebwizardry:wand.buff=+%1$s de puissance de %2$s +item.ebwizardry:wand.spell=Sort actuel: %1$s +item.ebwizardry:wand.mana=Mana: %1$s/%2$s + +item.ebwizardry:wand.addally=%1$s a été ajouté a vote liste d'alliés +item.ebwizardry:wand.removeally=%1$s a été retiré de votre liste d'alliés + +item.ebwizardry:basic_fire_wand.name=Baguette de braise +item.ebwizardry:basic_ice_wand.name=Baguette de gel +item.ebwizardry:basic_lightning_wand.name=Baguette d'étincelle +item.ebwizardry:basic_necromancy_wand.name=Baguette d'ombre +item.ebwizardry:basic_earth_wand.name=Bagette de bourgeon +item.ebwizardry:basic_sorcery_wand.name=Baguette de mystère +item.ebwizardry:basic_healing_wand.name=Baguette de soin + +item.ebwizardry:apprentice_fire_wand.name=Baguette d'apprenti pyromancien +item.ebwizardry:apprentice_ice_wand.name=Baguette d'apprenti cryomancien +item.ebwizardry:apprentice_lightning_wand.name=Baguette d'apprenti electromancien +item.ebwizardry:apprentice_necromancy_wand.name=Baguette d'apprenti necromancien +item.ebwizardry:apprentice_earth_wand.name=Baguette d'apprenti Geomancien +item.ebwizardry:apprentice_sorcery_wand.name=Baguette d'apprenti sorcier +item.ebwizardry:apprentice_healing_wand.name=Baguette d'apprenti soigneur + +item.ebwizardry:advanced_fire_wand.name=Baguette de brasier +item.ebwizardry:advanced_ice_wand.name=Baguette de blizzard +item.ebwizardry:advanced_lightning_wand.name=Baguette de foudre +item.ebwizardry:advanced_necromancy_wand.name=Baguette de necromancie +item.ebwizardry:advanced_earth_wand.name=Baguette de forêt +item.ebwizardry:advanced_sorcery_wand.name=Baguette de sorcier +item.ebwizardry:advanced_healing_wand.name=Baguette de soigneur + +item.ebwizardry:master_fire_wand.name=Baguette de maître pyromancien +item.ebwizardry:master_ice_wand.name=Baguette de maître cryomancien +item.ebwizardry:master_lightning_wand.name=Baguette de maître electromancien +item.ebwizardry:master_necromancy_wand.name=Baguette de maître necromancien +item.ebwizardry:master_earth_wand.name=Baguette de maître Geomancien +item.ebwizardry:master_sorcery_wand.name=Baguette de maître sorcier +item.ebwizardry:master_healing_wand.name=Baguette de maître soigneur + +item.ebwizardry:spectral_sword.name=Epée spectrale +item.ebwizardry:spectral_pickaxe.name=Pioche spectrale +item.ebwizardry:spectral_bow.name=Arc spectral + +item.ebwizardry:mana_flask.name=Fiole de mana +item.ebwizardry:storage_upgrade.name=Amélioration de baguette de capacité de mana +item.ebwizardry:siphon_upgrade.name=Amélioration de baguette de siphon de mana +item.ebwizardry:condenser_upgrade.name=Amélioration de puissance de baguette +item.ebwizardry:range_upgrade.name=Amélioration de portée de baguette +item.ebwizardry:duration_upgrade.name=Amélioration de durée de sort +item.ebwizardry:cooldown_upgrade.name=Amélioration de temps de rechargement de baguette +item.ebwizardry:blast_upgrade.name=Amélioration d'explosion de sort +item.ebwizardry:attunement_upgrade.name=Amélioration d'harmonisation de baguette + +item.ebwizardry:flaming_axe.name=Hache enflammée +item.ebwizardry:frost_axe.name=Hache gelée + +item.ebwizardry:firebomb.name=Bombe de feu +item.ebwizardry:poison_bomb.name=Bombe de poison +item.ebwizardry:smoke_bomb.name=Bombe de fumée + +item.ebwizardry:blank_scroll.name=Parchemin vierge +item.ebwizardry:scroll.name=Parchemin de %1$s +item.ebwizardry:scroll.undiscovered.name=Parchemin de "%1$s" +item.ebwizardry:identification_scroll.name=Parchemin d'identification +item.ebwizardry:identification_scroll.desc1=%1$sIdentifie un livre ou un +item.ebwizardry:identification_scroll.desc2=%1$sparchemin de sort inconnu +item.ebwizardry:identification_scroll.nothing_to_identify=Il n'y a rien à identifier! + +item.ebwizardry:armour_upgrade.name=Sceau arcanique de protection +item.ebwizardry:armour_upgrade.desc1=%1$sAméliore n'importe quelle +item.ebwizardry:armour_upgrade.desc2=%1$srobe au tier %2$slegendaire + +item.ebwizardry:magic_silk.name=Soie magique + +item.ebwizardry:wizard_armour.legendary=Legendaire +item.ebwizardry:wizard_armour.buff=-%1$s en coût de %2$s +item.ebwizardry:wizard_armour.mana=Mana: %1$s/%2$s + +item.ebwizardry:wizard_hat.name=Chapeau de sorcier +item.ebwizardry:wizard_robe.name=Robe de sorcier +item.ebwizardry:wizard_leggings.name=Pantalon de sorcier +item.ebwizardry:wizard_boots.name=Chaussures de sorcier + +item.ebwizardry:wizard_hat_fire.name=Chapeau de pyromancien +item.ebwizardry:wizard_robe_fire.name=Robe de pyromancien +item.ebwizardry:wizard_leggings_fire.name=Pantalon de pyromancien +item.ebwizardry:wizard_boots_fire.name=Bottes de pyromancien + +item.ebwizardry:wizard_hat_ice.name=Chapeau de cryomancien +item.ebwizardry:wizard_robe_ice.name=Robe de cryomancien +item.ebwizardry:wizard_leggings_ice.name=Pentalon de cryomancien +item.ebwizardry:wizard_boots_ice.name=Bottes de cryomancien + +item.ebwizardry:wizard_hat_lightning.name=Chapeau d'electomancien +item.ebwizardry:wizard_robe_lightning.name=Robe d'electromancien +item.ebwizardry:wizard_leggings_lightning.name=Pentalon d'electromancien +item.ebwizardry:wizard_boots_lightning.name=Bottes d'electromancien + +item.ebwizardry:wizard_hat_necromancy.name=Chapeau de necromancie +item.ebwizardry:wizard_robe_necromancy.name=Robe de necromancien +item.ebwizardry:wizard_leggings_necromancy.name=Pentalon de necromacien +item.ebwizardry:wizard_boots_necromancy.name=Bottes de necromancie + +item.ebwizardry:wizard_hat_earth.name=Chapeau de Geomancien +item.ebwizardry:wizard_robe_earth.name=Robe de Geomancien +item.ebwizardry:wizard_leggings_earth.name=Pentalon de Geomancien +item.ebwizardry:wizard_boots_earth.name=Bottes de Geomancien + +item.ebwizardry:wizard_hat_sorcery.name=Chapeau de sorcier +item.ebwizardry:wizard_robe_sorcery.name=Robe de sorcier +item.ebwizardry:wizard_leggings_sorcery.name=Pentalon de sorcier +item.ebwizardry:wizard_boots_sorcery.name=Bottes de sorcier + +item.ebwizardry:wizard_hat_healing.name=Chapeau de soigneur +item.ebwizardry:wizard_robe_healing.name=Robe de soigneur +item.ebwizardry:wizard_leggings_healing.name=Pentalon de soigneur +item.ebwizardry:wizard_boots_healing.name=Bottes de soigneur + +item.ebwizardry:spawn_wizard.name=Oeuf de sorcier +item.ebwizardry:spawn_evil_wizard.name=Oeuf de sorcier maléfique + +item.ebwizardry:spectral_helmet.name=Casque spectral +item.ebwizardry:spectral_chestplate.name=Plastron spectral +item.ebwizardry:spectral_leggings.name=Jambières spectrales +item.ebwizardry:spectral_boots.name=Bottes spectrales + +entity.ebwizardry:summonedcreature.nameplate=%1$s's %2$s +entity.ebwizardry:summonedcreature.nameplate_fallback=Someone's %1$s + +entity.ebwizardry:zombie_minion.name=Zombie +entity.ebwizardry:skeleton_minion.name=Squelette +entity.ebwizardry:spider_minion.name=Araignée +entity.ebwizardry:blaze_minion.name=Blaze +entity.ebwizardry:wither_skeleton_minion.name=Wither squelette +entity.ebwizardry:ice_wraith.name=Spectre de glace +entity.ebwizardry:lightning_wraith.name=Spectre de foudre +entity.ebwizardry:shadow_wraith.name=Spectre d'ombre +entity.ebwizardry:spirit_wolf.name=Esprit de loup +entity.ebwizardry:spirit_horse.name=Esprit de cheval +entity.ebwizardry:ice_giant.name=Geant de glace +entity.ebwizardry:phoenix.name=Phoenix +entity.ebwizardry:wizard.name=Sorcier +entity.ebwizardry:magic_slime.name=Slime magique +entity.ebwizardry:silverfish_minion.name=Poisson d'argent +entity.ebwizardry:storm_elemental.name=Tempête elementaire +entity.ebwizardry:evil_wizard.name=Sorcier +entity.ebwizardry:decoy.name=Leurre + +entity.ebwizardry:magic_missile.name=Magique +entity.ebwizardry:arc.name=Magique +entity.ebwizardry:spark_bomb.name=Magique +entity.ebwizardry:ice_shard.name=Magique +entity.ebwizardry:firebomb.name=Magique +entity.ebwizardry:poison_bomb.name=Magique +entity.ebwizardry:force_orb.name=Magique +entity.ebwizardry:spark.name=Magique +entity.ebwizardry:darkness_orb.name=Magique +entity.ebwizardry:fire_sigil.name=Magique +entity.ebwizardry:frost_sigil.name=Magique +entity.ebwizardry:lightning_sigil.name=Magique +entity.ebwizardry:lightning_arrow.name=Magique +entity.ebwizardry:firebolt.name=Magique +entity.ebwizardry:ice_charge.name=Magique +entity.ebwizardry:force_arrow.name=Magique +entity.ebwizardry:dart.name=Magique +entity.ebwizardry:lightning_disc.name=Magique +entity.ebwizardry:thunderbolt.name=Magique +entity.ebwizardry:decay.name=Magique +entity.ebwizardry:ice_lance.name=Magique +entity.ebwizardry:smoke_bomb.name=Magique +entity.ebwizardry:ice_spike.name=Magique + +entity.ebwizardry:black_hole.name=Trou noir +entity.ebwizardry:shield.name=Bouclier +entity.ebwizardry:meteor.name=Meteor +entity.ebwizardry:blizzard.name=Blizzard +entity.ebwizardry:bubble.name=Bulle +entity.ebwizardry:tornado.name=Tornade +entity.ebwizardry:lightning_hammer.name=Marteau de foudre +entity.ebwizardry:arrow_rain.name=Pluie de flèches +entity.ebwizardry:healing_aura.name=Aura de soin +entity.ebwizardry:forcefield.name=Champ de force +entity.ebwizardry:ring_of_fire.name=Anneau de feu +entity.ebwizardry:earthquake.name=Seisme +entity.ebwizardry:falling_grass.name=Chute d'herbe +entity.ebwizardry:hailstorm.name=Grêle +entity.ebwizardry:lightning_pulse.name=Pulsation electrique + +itemGroup.ebwizardry=Wizardry +itemGroup.ebwizardryspells=Spells + +advancement.ebwizardry:root=Sorcier +advancement.ebwizardry:root.desc=Le voyage d'un sorcier pour maîtriser les arcanes +advancement.ebwizardry:crystal=Un mystérieux cristal... +advancement.ebwizardry:crystal.desc=Miner un cristal magique +advancement.ebwizardry:arcane_initiate=Initiation arcanique +advancement.ebwizardry:arcane_initiate.desc=Fabriquer une baguette magique avec une pépite d'or, un bâton et un cristal magique +advancement.ebwizardry:apprentice=Apprenti Magicien +advancement.ebwizardry:apprentice.desc=Utilisez un tome d'arcane pour améliorer votre baguette +advancement.ebwizardry:master=Maître des Arcanes +advancement.ebwizardry:master.desc=Obtenir une baguette de maître +advancement.ebwizardry:all_spells=Mage de tous les métiers +advancement.ebwizardry:all_spells.desc=Lancer chaque sort du jeu +advancement.ebwizardry:wizard_trade=Transaction magique +advancement.ebwizardry:wizard_trade.desc=Acheter un objet auprès d'un sorcier +advancement.ebwizardry:buy_master_spell=Le savoir c'est le pouvoir +advancement.ebwizardry:buy_master_spell.desc=Acheter un sort de maître auprès d'un sorcier +advancement.ebwizardry:freeze_blaze=Petite brise... +advancement.ebwizardry:freeze_blaze.desc=Geler un blaze +advancement.ebwizardry:charge_creeper=Ça va peter! +advancement.ebwizardry:charge_creeper.desc=Charger accidentellement un creeper +advancement.ebwizardry:frankenstein=Frankenstein +advancement.ebwizardry:frankenstein.desc=Transformer un cochon en zombie cochon avec un éclair +advancement.ebwizardry:special_upgrade=Bricolage arcanique +advancement.ebwizardry:special_upgrade.desc=Appliquer une amélioration à une baguette +advancement.ebwizardry:craft_flask=De la magie en bouteille! +advancement.ebwizardry:craft_flask.desc=Fabriquer une fiole de mana +advancement.ebwizardry:elemental=Elementaire +advancement.ebwizardry:elemental.desc=Obtenir une baguette elementaire +advancement.ebwizardry:armour_set=Tu est un sorcier, Harry +advancement.ebwizardry:armour_set.desc=Fabriquer et equiper un set complet de sorcier +advancement.ebwizardry:legendary=Legendaire +advancement.ebwizardry:legendary.desc=Obtenir une pièce d'armure légendaire de sorcier +advancement.ebwizardry:self_destruct=Retour de flammes +advancement.ebwizardry:self_destruct.desc=Mourrir de sa propre magie +advancement.ebwizardry:pig_tornado=Pas encore... +advancement.ebwizardry:pig_tornado.desc=Monter un cochon dans une tornade +advancement.ebwizardry:jam_wizard=Perturbation arcanique +advancement.ebwizardry:jam_wizard.desc=Utiliser le sort de perturbation sur un sorcier +advancement.ebwizardry:slime_skeleton=Situation collante +advancement.ebwizardry:slime_skeleton.desc=Engluer un squelette dans un slime +advancement.ebwizardry:anger_wizard=Tu va le regreter... +advancement.ebwizardry:anger_wizard.desc=Enerver un sorcier +advancement.ebwizardry:defeat_evil_wizard=Mage vertueux +advancement.ebwizardry:defeat_evil_wizard.desc=Tuer un sorcier maléfique +advancement.ebwizardry:max_out_wand=Equipé de pied en cap +advancement.ebwizardry:max_out_wand.desc=Appliquer le maximum d'amélioration à une baguette de maître +advancement.ebwizardry:element_master=Maître des éléments +advancement.ebwizardry:element_master.desc=Lancer tout les sorts d'un élément +advancement.ebwizardry:identify_spell=Examin arcanique +advancement.ebwizardry:identify_spell.desc=Utiliser un parchemin pour identifier un livre ou un autre parchemin + +tile.ebwizardry:transportation_stone.confirm=Vous pouvez désormais revenir ici plus tard en lançant le sort %1$s +tile.ebwizardry:transportation_stone.invalid=Vous devez dabord faire un carré avec 8 pierres de transportation et vous y lier avec une baguette! + +container.ebwizardry:arcane_workbench=Table des arcanes +container.ebwizardry:arcane_workbench.apply=Appliquer +container.ebwizardry:arcane_workbench.mana=Mana: +container.ebwizardry:arcane_workbench.upgrades=Amélioration appliquée: + +tier.basic=Novice +tier.apprentice=Apprenti +tier.advanced=Expert +tier.master=Maître + +element.simple=Aucun +element.fire=Feu +element.ice=Glace +element.lightning=Foudre +element.necromancy=Necromancie +element.earth=Terra +element.sorcery=Sorcellerie +element.healing=Soin + +element.simple.wizard=Sorcier +element.fire.wizard=Pyromancien +element.ice.wizard=Cryomancien +element.lightning.wizard=Electromancien +element.necromancy.wizard=Necromancien +element.earth.wizard=Geomancien +element.sorcery.wizard=Sorcier +element.healing.wizard=Soigneur + +spelltype.attack=Attaque +spelltype.defence=Defense +spelltype.utility=Utilité +spelltype.minion=Sbire + +spell.disabled=%1$s a été désactivé dans la config! +spell.resist=%1$s a résisté à %2$s +spell.discover=Le sort %1$s a été découvert! + +spell.ebwizardry:agility=Agilité +spell.ebwizardry:arc=Arc +spell.ebwizardry:arcane_jammer=Perturbation arcanique +spell.ebwizardry:arrow_rain=Pluie de flèche +spell.ebwizardry:banish=Banissement +spell.ebwizardry:black_hole=Trou noir +spell.ebwizardry:blink=Teleportation +spell.ebwizardry:blizzard=Blizzard +spell.ebwizardry:bubble=Bulle +spell.ebwizardry:chain_lightning=Chaîne d'éclair +spell.ebwizardry:clairvoyance=Clairvoyance +spell.ebwizardry:cobwebs=Toile +spell.ebwizardry:conjure_armour=Conjuration d'armure +spell.ebwizardry:conjure_bow=Conjuration d'arc +spell.ebwizardry:conjure_pickaxe=Conjuration de pioche +spell.ebwizardry:conjure_sword=Conjuration d'épée +spell.ebwizardry:cure_effects=Curation +spell.ebwizardry:curse_of_soulbinding=Malédiction de liaison d'âme +spell.ebwizardry:darkness_orb=Orbe de ténèbre +spell.ebwizardry:darkvision=Nyctalopie +spell.ebwizardry:dart=Dard +spell.ebwizardry:decay=Decomposition +spell.ebwizardry:decoy=Leurre +spell.ebwizardry:detonate=Detonation +spell.ebwizardry:diamondflesh=Peau de diamant +spell.ebwizardry:earthquake=Seisme +spell.ebwizardry:entrapment=Piège de ténèbre +spell.ebwizardry:fireball=Boule de feu +spell.ebwizardry:firebolt=Eclair de feu +spell.ebwizardry:firebomb=Bombe de feu +spell.ebwizardry:fire_resistance=Resistance aux flammes +spell.ebwizardry:fire_sigil=Rune de feu +spell.ebwizardry:fireskin=Aura de feu +spell.ebwizardry:firestorm=Tempête de feu +spell.ebwizardry:flame_ray=Trait de feu +spell.ebwizardry:flaming_axe=Hache enflammé +spell.ebwizardry:flaming_weapon=Enflamment d'arme +spell.ebwizardry:flight=Vol +spell.ebwizardry:font_of_mana=Fontaine de mana +spell.ebwizardry:font_of_vitality=Fontaine de vitalité +spell.ebwizardry:force_arrow=Flèche de force +spell.ebwizardry:forcefield=Champ de force +spell.ebwizardry:force_orb=Orbe de force +spell.ebwizardry:forests_curse=Malédiction de la forêt +spell.ebwizardry:freeze=Gel +spell.ebwizardry:freezing_weapon=Gel d'arme +spell.ebwizardry:frost_axe=Hache de glace +spell.ebwizardry:frost_ray=Trait de glace +spell.ebwizardry:frost_sigil=Rune de glace +spell.ebwizardry:glide=Vol plané +spell.ebwizardry:greater_fireball=Grosse boule de feu +spell.ebwizardry:greater_heal=Soin magistral +spell.ebwizardry:group_heal=Soin de groupe +spell.ebwizardry:growth_aura=Aura de croissance +spell.ebwizardry:hailstorm=Grêle +spell.ebwizardry:heal=Soin +spell.ebwizardry:heal_ally=Touché soignant +spell.ebwizardry:healing_aura=Aura de soin +spell.ebwizardry:homing_spark=Etincelle auto-guidé +spell.ebwizardry:ice_age=Âge de glace +spell.ebwizardry:ice_charge=Charge de glace +spell.ebwizardry:ice_lance=Lance de glace +spell.ebwizardry:ice_shard=Eclat de glace +spell.ebwizardry:ice_shroud=Aura de glace +spell.ebwizardry:ice_spikes=Pic de glace +spell.ebwizardry:ice_statue=Statue de glace +spell.ebwizardry:ignite=Enflammer +spell.ebwizardry:imbue_weapon=imprégnation d'arme +spell.ebwizardry:intimidate=Intimidation +spell.ebwizardry:invigorating_presence=Présance tonifiante +spell.ebwizardry:invisibility=Invisibilité +spell.ebwizardry:invoke_weather=Tempête +spell.ebwizardry:ironflesh=Peau de fer +spell.ebwizardry:leap=Saut +spell.ebwizardry:levitation=Levitation +spell.ebwizardry:life_drain=Drain de vie +spell.ebwizardry:light=Lumière +spell.ebwizardry:lightning_arrow=Flèche de foudre +spell.ebwizardry:lightning_bolt=Eclair +spell.ebwizardry:lightning_disc=Disque de foudre +spell.ebwizardry:lightning_hammer=Marteau de foudre +spell.ebwizardry:lightning_pulse=Pulsation electrique +spell.ebwizardry:lightning_ray=Trait de foudre +spell.ebwizardry:lightning_sigil=Rune de foudre +spell.ebwizardry:lightning_web=Eclair en chaîne +spell.ebwizardry:magic_missile=Missile magique +spell.ebwizardry:metamorphosis=Metamorphose +spell.ebwizardry:meteor=Meteor +spell.ebwizardry:mind_control=Hypnose +spell.ebwizardry:mind_trick=Confusion +spell.ebwizardry:none=[Emplacement vide] +spell.ebwizardry:oakflesh=Peau de chêne +spell.ebwizardry:petrify=Petrification +spell.ebwizardry:phase_step=Passe muraille +spell.ebwizardry:plague_of_darkness=Peste ténébreuse +spell.ebwizardry:pocket_furnace=Cuisson +spell.ebwizardry:pocket_workbench=Conjuration d'etabli +spell.ebwizardry:poison=Poison +spell.ebwizardry:poison_bomb=Bombe de poison +spell.ebwizardry:replenish_hunger=Restoration +spell.ebwizardry:ring_of_fire=Anneau de feu +spell.ebwizardry:shadow_ward=Miroir d'ombre +spell.ebwizardry:shield=Barrière +spell.ebwizardry:shockwave=Onde de choc +spell.ebwizardry:silverfish_swarm=Essaim de poisson d'argent +spell.ebwizardry:sixth_sense=Sixième sense +spell.ebwizardry:slime=Slime +spell.ebwizardry:smoke_bomb=Bombe de fumée +spell.ebwizardry:snare=Ronce +spell.ebwizardry:snowball=Boule de neige +spell.ebwizardry:spark_bomb=Bombe electrique +spell.ebwizardry:spectral_pathway=Chemin spectral +spell.ebwizardry:spider_swarm=Essaim d'araignées +spell.ebwizardry:static_aura=Aura electrique +spell.ebwizardry:summon_blaze=Invocation de blaize +spell.ebwizardry:summon_ice_giant=Invocation de geant de glace +spell.ebwizardry:summon_ice_wraith=Invocation de spectre de glace +spell.ebwizardry:summon_iron_golem=Invocation de golem de fer +spell.ebwizardry:summon_lightning_wraith=Invocation de spectre de foudre +spell.ebwizardry:summon_phoenix=Invocation de Phoenix +spell.ebwizardry:summon_shadow_wraith=Invocation de spectre d'ombre +spell.ebwizardry:summon_skeleton=Invocation de squelette +spell.ebwizardry:summon_skeleton_legion=Invocation de legion de squelette +spell.ebwizardry:summon_snow_golem=Invocation de golem de neige +spell.ebwizardry:summon_spirit_horse=Invocation de cheval spectral +spell.ebwizardry:summon_spirit_wolf=Invocation de chien spectral +spell.ebwizardry:summon_storm_elemental=Invocation d'élémentaire de foudre +spell.ebwizardry:summon_wither_skeleton=Invocation de squelette wither +spell.ebwizardry:summon_zombie=Invocation de zombie +spell.ebwizardry:telekinesis=Telekinesie +spell.ebwizardry:thunderbolt=Coup de tonnerre +spell.ebwizardry:thunderstorm=Orage +spell.ebwizardry:tornado=Tornade +spell.ebwizardry:transience=Forme éthérée +spell.ebwizardry:transportation=Transportation +spell.ebwizardry:vanishing_box=Conjuration de coffre de l'ender +spell.ebwizardry:wall_of_frost=Mur de glace +spell.ebwizardry:water_breathing=Respiration aquatique +spell.ebwizardry:whirlwind=Bourrasque +spell.ebwizardry:wither=Wither +spell.ebwizardry:wither_skull=Crâne de wither + +spell.ebwizardry:agility.desc=Augmente la vitesse de déplacement et la hauteur de saut du lanceur pendant 30 secondes. +spell.ebwizardry:arc.desc=Tire une étincelle de foudre sur la cible. +spell.ebwizardry:arcane_jammer.desc=Empêche la cible d'utiliser la magie pendant 15 secondes. +spell.ebwizardry:arrow_rain.desc="Archers, tirez!" +spell.ebwizardry:banish.desc=Téléporte la cible contre sa volonté vers un emplacement aléatoire dans une certaine distance. +spell.ebwizardry:black_hole.desc=Déchirez la réalité. +spell.ebwizardry:blink.desc=Téléporte le lanceur sur une courte distance à l'endroit où il se vise. +spell.ebwizardry:blizzard.desc=Crée une zone de vent glacé tourbillonnant qui ralentit et endommage continuellement tout ce qui est piégé à l'intérieur. Le lanceur est immunisé contre les dégâts, mais il est quand même ralenti. +spell.ebwizardry:bubble.desc=Lance un jet de bulles qui capture tout ce qu’il frappe dans une bulle qui flottera vers le haut. La cible tombera après un certain temps ou si la bulle est endommagée. +spell.ebwizardry:chain_lightning.desc=Lance une étincelle de foudre sur la cible, qui frappe ensuite deux cibles supplémentaires. +spell.ebwizardry:clairvoyance.desc=Révèle le chemin vers un lieu mémorisé. Avec ce sort sélectionné, sneak-cliquez sur un bloc pour définir l'emplacement. Jetez ce sort normalement pour révéler le chemin. Le chemin disparaîtra au bout de 90 secondes. +spell.ebwizardry:cobwebs.desc=Crée des toiles d'araignées à l'endroit que vous visez, ce qui gêne grandement le mouvement des créatures prises dedans. Les toiles d'araignées disparaîtront après 20 secondes ou si elles sont cassées. +spell.ebwizardry:conjure_armour.desc=Crée une armure spectrale autour du lanceur qui offre une protection égale à celle d’une armure de fer. L'armure dure 60 secondes. Le lanceur doit avoir un emplacement d'armure vide. +spell.ebwizardry:conjure_bow.desc=Crée un arc spectral avec des flèches illimitées qui dure 30 secondes. +spell.ebwizardry:conjure_pickaxe.desc=Crée une pioche spectrale de force égale à une pioche de fer qui dure 30 secondes. +spell.ebwizardry:conjure_sword.desc=Crée une épée spectrale de force égale à une épée de fer qui dure 30 secondes. +spell.ebwizardry:cure_effects.desc=Supprime tous les effets de potion affectant actuellement le lanceur, bons ou mauvais. +spell.ebwizardry:curse_of_soulbinding.desc=Fait en sorte que l'âme de la cible soit inextricablement liée à celle du lanceur, ce qui signifie que tous les dommages qui lui sont infligés sont également infligés à la victime. Dure jusqu'à ce que la victime ou le lanceur meurt. +spell.ebwizardry:darkness_orb.desc=Tire un orbe d'énergie sombre se déplaçant lentement dans la direction que vous visez, causant l'effet wither à ce qu'il frappera. +spell.ebwizardry:darkvision.desc=Donne la vision nocturne au lanceur pendant 45 secondes. +spell.ebwizardry:dart.desc=Tire une flèche dans la direction que vous visez qui endommage et affaiblit sa cible. +spell.ebwizardry:decay.desc=Crée une flaque de décomposition sur le sol qui infecte toute créature qui la touche, causant des dégâts persistants au fil du temps et générant d'avantage de décomposition partout où elle se promène. +spell.ebwizardry:decoy.desc=Crée un clone illusoire du lanceur qui incite les monstres à l'attaquer à la place. Le leurre disparaîtra au bout de 30 secondes. +spell.ebwizardry:detonate.desc=Provoque une explosion à l'endroit où vous vous visez, causant des dommages à toutes les créatures proches, y compris le lanceur, si il est trop proche. +spell.ebwizardry:diamondflesh.desc="Vos flèches ne sont pas de taille contre moi!" +spell.ebwizardry:earthquake.desc=Un vrai maître de la magie de la terre peut déplacer des montagnes. +spell.ebwizardry:entrapment.desc=Enferme la cible dans une sphère de ténèbres qui la tire de façon impuissante vers le haut et l’endommage continuellement. +spell.ebwizardry:fireball.desc=Lance une boule de feu dans la direction que vous pointez. +spell.ebwizardry:firebolt.desc=Lance un jet de feu à une courte distance de vous. +spell.ebwizardry:firebomb.desc=Déclenche une bombe incendiaire dans la direction que vous pointez qui explose sous l’impact, ce qui met le feu à vos cibles. +spell.ebwizardry:fire_resistance.desc=Confère au lanceur une résistance au feu pendant 30 secondes. +spell.ebwizardry:fire_sigil.desc=Place un piège magique sur le sol qui endommage et met le feu à la créature qui le déclenche. +spell.ebwizardry:fireskin.desc=Le lanceur s'enveloppe de feu pendant 30 secondes, ce qui fait s'enflammer tout ce qui l'attaque. +spell.ebwizardry:firestorm.desc="Je suis le Dragon" +spell.ebwizardry:flame_ray.desc=Crée un souffle de flammes dans la direction que vous pointez qui enflamme et endommage continuellement les cibles. +spell.ebwizardry:flaming_axe.desc=Crée une hache enflammée qui met le feu à l'ennemi lorsqu'il est touché. Dure 30 secondes. +spell.ebwizardry:flaming_weapon.desc=La première arme de la barre de raccourci du lanceur est temporairement imprégnée du pouvoir de la flamme, ce qui incendie ses victimes. L'effet disparaît après 45 secondes. +spell.ebwizardry:flight.desc=Volez comme un aigle. +spell.ebwizardry:font_of_mana.desc="Nous étions remplis d'une intense énergie magique semblant émaner du centre de la ..." - Extrait du journal d'un mage oublié; le reste de la page a été brûlé. +spell.ebwizardry:font_of_vitality.desc=C'est incroyable. +spell.ebwizardry:force_arrow.desc=Tire une flèche de force dans la direction que vous pointez. +spell.ebwizardry:forcefield.desc=Crée un champ de force autour du lanceur qui repousse les créatures et dévie les projectiles. +spell.ebwizardry:force_orb.desc=Lance une sphère de force qui endommage et repousse les créatures proches lors de l'impact. +spell.ebwizardry:forests_curse.desc="Comment osez-vous entrer dans ma forêt!" +spell.ebwizardry:freeze.desc=Gèle la cible pendant 10 secondes. Peut également geler l'eau et créer de la neige sur le sol. +spell.ebwizardry:freezing_weapon.desc=La première arme située sur la barre de raccourci du lanceur est temporairement imprégnée de gel, ce qui gèle ses victimes. L'effet disparaît après 45 secondes. +spell.ebwizardry:frost_axe.desc=Crée une hache gelée qui gèle les ennemis quand ils sont touchés. Dure 30 secondes. +spell.ebwizardry:frost_ray.desc=Crée un jet de givre dans la direction que vous visez, ce qui ralentit et endommage continuellement les cibles. +spell.ebwizardry:frost_sigil.desc=Place un piège de glace magique sur le sol qui endommage et gèle la créature qui le déclenche. +spell.ebwizardry:glide.desc=Permet au lanceur de planer en l'air tout en maintenant enfoncé le bouton d'utilisation. +spell.ebwizardry:greater_fireball.desc=Lance une grosse boule de feu dans la direction que vous pointez qui explose à l’impact. +spell.ebwizardry:greater_heal.desc=Soigne le lanceur de 4 coeurs. +spell.ebwizardry:group_heal.desc=Soigne le lanceur et tous les alliés proches et les créatures invoquées de 3 coeurs. +spell.ebwizardry:growth_aura.desc=Fais pousser toutes les cultures près du lanceur. Fais pousser également des herbes hautes et des fleurs sur l’herbe. +spell.ebwizardry:hailstorm.desc=C'est au cours du grand hiver du troisième âge que les mages de glace ont découvert leur véritable pouvoir. +spell.ebwizardry:heal.desc=Soigne le lanceur de sorts de 2 coeurs. +spell.ebwizardry:heal_ally.desc=Soigne la cible de 2 cœurs et demi. +spell.ebwizardry:healing_aura.desc=Crée une zone d'énergie de guérison qui régénère la santé de tous les alliés qui s'y trouvent. Tout mort-vivant à l'intérieur de l'aura de guérison subira lentement des dégâts. +spell.ebwizardry:homing_spark.desc=Crée une étincelle flottante qui se déplace vers les ennemis. +spell.ebwizardry:ice_age.desc="Vous serez gelés pour une éternité!" +spell.ebwizardry:ice_charge.desc=Lance une charge de glace qui explose à l’impact, gèle les créatures proches et libère des éclats dans toutes les directions. +spell.ebwizardry:ice_lance.desc=Tire une grande lance de glace dans la direction que vous pointez, qui transperce les cibles, les endommageant et les gelant. +spell.ebwizardry:ice_shard.desc=Tire un éclat de glace dans la direction que vous pointez qui endommage et ralentit les cibles. +spell.ebwizardry:ice_shroud.desc=Crée un voile de froid autour du lanceur pendant 30 secondes, ce qui bloque tout les attaques. +spell.ebwizardry:ice_spikes.desc=Invoque des pointes de glace acérées comme des lames de rasoir qui s'élèvent du sol à l'endroit où vous visez, embrochant ainsi toutes les créatures dans la zone. +spell.ebwizardry:ice_statue.desc=Congèle entierement la cible pendant 20 secondes ou jusqu'à ce qu'elle soit cassée. La cible ne peut pas bouger ni faire quoi que ce soit tant qu'elle est gelée, mais est également insensible à tous les dégâts. +spell.ebwizardry:ignite.desc=Met le feu à la cible pendant 10 secondes. Fonctionne également comme un briquet. +spell.ebwizardry:imbue_weapon.desc=Imprègne temporairement la première arme de la barre de raccourci du lanceur pour la rendre plus efficace. L'effet disparaît après 45 secondes. +spell.ebwizardry:intimidate.desc=Émet un grondement intimidant qui provoque la peur des créatures proches. Les créatures frappées par la peur vont récupérer leur état normal après 30 secondes. +spell.ebwizardry:invigorating_presence.desc=Confère au lanceur et à tous les alliés à proximité une force accrue pendant 45 secondes. +spell.ebwizardry:invisibility.desc=Rend le lanceur invisible pendant 30 secondes. +spell.ebwizardry:invoke_weather.desc=Change la météo dans le monde. +spell.ebwizardry:ironflesh.desc=Améliore considérablement la résistance aux dégâts du lanceur pendant 30 secondes. +spell.ebwizardry:leap.desc=Le lanceur effectue un super saut. +spell.ebwizardry:levitation.desc=Fait léviter le lanceur vers le haut pendant que le bouton d'utilisation est maintenu enfoncé. Annulera également les dégâts de chute si utilisé avant de heurter le sol. +spell.ebwizardry:life_drain.desc=Crée un flux d’énergie de wither dans la direction que vous visez qui draine la vie de la cible et l’utilise pour régénérer progressivement votre santé. +spell.ebwizardry:light.desc=Crée un point de lumière magique qui illumine les environs. Dure 30 secondes. +spell.ebwizardry:lightning_arrow.desc=Tire une flèche de foudre dans la direction que vous visez. +spell.ebwizardry:lightning_bolt.desc=Fait frapper la foudre là où vous visez. +spell.ebwizardry:lightning_disc.desc=Envoie un disque de foudre dans la direction que vous pointez, qui cherche des cibles. +spell.ebwizardry:lightning_hammer.desc="Je te frappe par la colère des cieux!" +spell.ebwizardry:lightning_pulse.desc=Charge le sol autour du lanceur avec la foudre, endommageant et repoussant les créatures proches. +spell.ebwizardry:lightning_ray.desc=Crée un flot de foudre dans la direction que vous pointez qui endommage continuellement les cibles. +spell.ebwizardry:lightning_sigil.desc=Place un piège de foudre magique sur le sol qui endommage la créature qui le déclenche et diffuse la foudre en chaine à d'autres créatures proches. +spell.ebwizardry:lightning_web.desc="Concentre-toi. Canalise la tempête dans ton esprit à travers ta baguette et déchaîne sa fureur." +spell.ebwizardry:magic_missile.desc=Tire un éclair d'énergie magique dans la direction que vous pointez. +spell.ebwizardry:metamorphosis.desc=Change la cible en une autre variante. Ne fonctionne que sur certaines créatures. +spell.ebwizardry:meteor.desc=Certains sorciers veulent juste voir le monde brûler ... +spell.ebwizardry:mind_control.desc=Prend le contrôle de l'esprit de la cible pendant 30 secondes, la faisant changer de côté et se battre pour le lanceur à sa place. Ne fonctionnera pas sur des créatures à la volonté trop forte. +spell.ebwizardry:mind_trick.desc=Confond et désoriente la cible pendant 15 secondes, la rendant incapable d’attaquer efficacement. L'effet sera dissipé si la cible subit des dégâts. +spell.ebwizardry:none.desc=Pour obtenir un livre de sorts avec la commande / give, utilisez les métadonnées: / give [joueur] ebwizardry: spell_book 1 [nom de sort] (si vous avez trouvé ce livre dans un coffre, un autre mod a tout gâché). +spell.ebwizardry:oakflesh.desc=Améliore la résistance aux dégâts du lanceur pendant 30 secondes. +spell.ebwizardry:petrify.desc=Transforme la cible en pierre jusqu'à ce qu'elle soit éclatée, avec une chance pour qu'elle éclate quand il fait noir. La cible ne peut ni bouger ni faire quoi que ce soit tant qu'elle est pétrifiée, mais est également insensible à tous les dégâts. +spell.ebwizardry:phase_step.desc=Téléporte le lanceur à travers un mur épais d'un bloc devant eux. Les améliorations de portée augmenteront l'épaisseur à travers laquelle vous pourrez vous téléporter. +spell.ebwizardry:plague_of_darkness.desc=Les ténèbres les dévoreront tous ... +spell.ebwizardry:pocket_furnace.desc=Fait cuire jusqu'à 5 objets dans l'inventaire du lanceur. Les objets sur la barre de raccourci seront cuit en premier. +spell.ebwizardry:pocket_workbench.desc=Permet au lanceur de fabriquer des objets comme s'il était à une table d'artisanat. +spell.ebwizardry:poison.desc=Tire du poison dans la direction que vous pointez. +spell.ebwizardry:poison_bomb.desc=Lance une bombe empoisonnée dans la direction que vous visez, qui explose à l’impact et empoisonne les créatures proches. +spell.ebwizardry:replenish_hunger.desc=Remonte le niveau de nourriture du lanceur de 6 points de stamina. +spell.ebwizardry:ring_of_fire.desc=Crée un anneau de feu autour du lanceur, infligeant des dégâts à tous les ennemis proches et les incendiant. +spell.ebwizardry:shadow_ward.desc=Crée un mur de ténèbres devant le lanceur de sorts, qui inflige la moitié des dégâts à l'attaquant. +spell.ebwizardry:shield.desc=Crée une barrière protectrice de force qui bloque les projectiles et la magie. Confère également au lanceur un faible effet de résistance. +spell.ebwizardry:shockwave.desc=Boom. +spell.ebwizardry:silverfish_swarm.desc="Ahhhh! Ils se MULTIPLIENT!" +spell.ebwizardry:sixth_sense.desc=Permet au lanceur de détecter les emplacements des créatures proches, même à travers les murs, pendant 20 secondes. +spell.ebwizardry:slime.desc=Embourbe la cible dans la boue qui le ralentit et l’endommage continuellement. La boue éclate après 10 secondes. +spell.ebwizardry:smoke_bomb.desc=Lance une bombe de fumée dans la direction que vous pointez qui explose sous l’impact, libérant de la fumée et aveuglant les créatures à proximité pendant un court instant. +spell.ebwizardry:snare.desc=Met au sol un piège qui endommage et ralentit brièvement la créature qui le déclenche. +spell.ebwizardry:snowball.desc=Lance une boule de neige dans la direction que vous visez. +spell.ebwizardry:spark_bomb.desc=Lance une charge de choc dans la direction que vous pointez, ce qui libère des étincelles chez les ennemis proches lors de l’impact. +spell.ebwizardry:spectral_pathway.desc=Crée devant vous un pont magique indestructible qui s'étend sur 15 blocs. Le pont disparaît après 60 secondes. +spell.ebwizardry:spider_swarm.desc=Invoque un essaim d'araignées venimeuses qui se battent pour vous. Les araignées disparaîtront au bout de 30 secondes ou si elles sont tuées. +spell.ebwizardry:static_aura.desc=Entoure le lanceur avec un éclair pendant 30 secondes, projetant une étincelle sur tout ce qui le frappe. +spell.ebwizardry:summon_blaze.desc=Invoque un blaze pour se battre à vos côtés. Le blaze disparaîtra après 30 secondes ou s'il est tué. +spell.ebwizardry:summon_ice_giant.desc="Ecrasez les !" +spell.ebwizardry:summon_ice_wraith.desc=Invoque un spectre de glace qui se bat pour vous. Le spectre de glace disparaîtra après 30 secondes ou s'il est tué. +spell.ebwizardry:summon_iron_golem.desc=Automate autonome automatiquement automatisé. +spell.ebwizardry:summon_lightning_wraith.desc=Invoque un spectre de foudre pour qu'il se batte pour vous. Le spectre de foudre disparaîtra au bout de 30 secondes ou s'il est tué. +spell.ebwizardry:summon_phoenix.desc=De la cendre ... +spell.ebwizardry:summon_shadow_wraith.desc=Invoque un spectre de l'ombre qui se bat pour vous. +spell.ebwizardry:summon_skeleton.desc=Invoque un squelette pour qu'il se batte pour vous. Le squelette disparaîtra après 30 secondes ou s'il est tué. +spell.ebwizardry:summon_skeleton_legion.desc="Lèvez-vous, armée de morts-vivants!" +spell.ebwizardry:summon_snow_golem.desc=Crée un golem de neige qui se bat pour vous. Dure jusqu'à la mort du golem des neiges. +spell.ebwizardry:summon_spirit_horse.desc=Invoque un cheval spirituel à monter. Le cheval spirituel disparaîtra peu de temps après en être descendu, ou vous pouvez le renvoyer en faisant un shift-clic droit dessus avec n'importe quelle baguette. +spell.ebwizardry:summon_spirit_wolf.desc=Invoque un loup spirituel qui se bat pour vous. L'esprit loup ne disparaîtra que s'il est tué, ou vous pouvez le renvoyer en faisant un shift-clic droit dessus avec n'importe quelle baguette. +spell.ebwizardry:summon_storm_elemental.desc="Elementaire de foudre: Une ancienne manifestation des éléments, il peut difficilement contenir la puissance brute qu'il contient." - Le guide du sorcier sur les êtres arcaniques, volume I_i +spell.ebwizardry:summon_wither_skeleton.desc=Invoque un squelette wither qui se bat pour vous. Le squelette flétri disparaîtra après 30 secondes ou s'il est tué. +spell.ebwizardry:summon_zombie.desc=Invoque un zombie pour qu'il se batte pour vous. Le zombie disparaîtra au bout de 30 secondes ou s'il est tué. +spell.ebwizardry:telekinesis.desc=Déplace un objet ou un autre petit objet vers vous, ou cliquez avec le bouton droit sur le bloc que vous regardez. Peut également être utilisé pour désarmer les joueurs. +spell.ebwizardry:thunderbolt.desc=Tire un coup de tonnerre qui repousse les cibles. +spell.ebwizardry:thunderstorm.desc="Mwahahahahahaha!" +spell.ebwizardry:tornado.desc=Libère une tornade dans la direction que vous pointez qui projette tout ce qui se trouve sur son chemin vers le ciel. +spell.ebwizardry:transience.desc=Rend la lanceur éthéré pendant 20 secondes. Le lanceur est à l'abri de tous les dégâts en forme éthérée mais ne peut pas casser, placer de blocs ou causer des dégâts. +spell.ebwizardry:transportation.desc=Transporte le lanceur vers son cercle de pierres mémorisé. Pour utiliser ce sort, faites un cercle de pierres de transport, puis faites un clic droit dessus avec une baguette. +spell.ebwizardry:vanishing_box.desc=Donne au lanceur accès à son coffre de l'end. +spell.ebwizardry:wall_of_frost.desc=L'hiver au bout des doigts. +spell.ebwizardry:water_breathing.desc=Permet au lanceur de respirer sous l'eau pendant 60 secondes. +spell.ebwizardry:whirlwind.desc=Propulse la cible. +spell.ebwizardry:wither.desc=Tire un rayon d'obscurité qui inflige l'effet wither à tout ce qu'il touche. +spell.ebwizardry:wither_skull.desc=Lance un crâne de wither dans la direction que vous pointez. + +spell.ebwizardry:invoke_weather.sun=La pluie a cessé... +spell.ebwizardry:invoke_weather.rain=Le ciel se couvre... +spell.ebwizardry:transportation.missing=Vos pierres de téléportation sont obstruées ou inaccessible +spell.ebwizardry:transportation.undefined=Vous devez vous lier à un cercle de pierre de téléportation avant! +spell.ebwizardry:transportation.wrongdimension=Votre cercle de téléportation est dans une autre dimension... +spell.ebwizardry:clairvoyance.searching=Recherche... +spell.ebwizardry:clairvoyance.confirm=Une marque a été laissé. Utilisez le sort %1$s pour vous montrer le chemin pour y revenir plus tard. +spell.ebwizardry:clairvoyance.outofrange=La marque est trop loin ou est inaccessible... +spell.ebwizardry:clairvoyance.undefined=Vous devez dabord deposer une marque avec sneak-clic droit! +spell.ebwizardry:clairvoyance.wrongdimension=Votre marque est dans une dimension... + +potion.ebwizardry:frost=Morsure du froid +potion.ebwizardry:fireskin=Aura de feu +potion.ebwizardry:ice_shroud=Aura de glace +potion.ebwizardry:static_aura=Aura electrique +potion.ebwizardry:transience=Forme etheree +potion.ebwizardry:decay=Decomposition +potion.ebwizardry:sixth_sense=Sixième sense +potion.ebwizardry:arcane_jammer=Pertubation arcanique +potion.ebwizardry:mind_trick=Confusion +potion.ebwizardry:mind_control=Hypnose +potion.ebwizardry:font_of_mana=Fontaine de mana +potion.ebwizardry:fear=Peur + +enchantment.ebwizardry:magic_sword=Imprégné +enchantment.ebwizardry:magic_bow=Imprégné +enchantment.ebwizardry:flaming_weapon=Imprégné de feu +enchantment.ebwizardry:freezing_weapon=Imprégné de gel + +key.categories.ebwizardry=Wizardry + +key.ebwizardry.next_spell=Prochain sort +key.ebwizardry.previous_spell=Sort précédent + +death.attack.wizardry_magic=%1$s a été tué par %2$s avec de la magie +death.attack.indirect_wizardry_magic=%1$s a été tué par %2$s avec de la magie + +commands.ebwizardry:cast.usage=/%1$s [joueur] [multiplicateur dommage] [multiplicateur distance] [multiplicateur durée] [multiplicateur explosion] +commands.ebwizardry:cast.success=Sort %1$s lancé avec succès +commands.ebwizardry:cast.success_continuous=Sort %1$s lancé; recommencer la commande pour arreter +commands.ebwizardry:cast.success_remote=Sort %1$s lancé en tant que %2$s +commands.ebwizardry:cast.success_remote_continuous=Sort %1$s lancé en tant que %2$s; recommencer la commande pour arreter +commands.ebwizardry:cast.fail=Impossible de lancer %1$s +commands.ebwizardry:cast.not_found=Il n'y a pas de sort avec le nom %1$s +commands.ebwizardry:cast.tag_error=L'analyse des balises de données a échoué: %s + +commands.ebwizardry:ally.usage=/%1$s [joueur] +commands.ebwizardry:ally.addally=%1$s a été ajouté à la liste d'alliés de %2$s +commands.ebwizardry:ally.removeally=%1$s a été retiré de la liste d'alliés de %2$s +commands.ebwizardry:ally.self=Les joueurs ne peuvent pas être allié à eux même! +commands.ebwizardry:ally.permission=Vous n'avez pas la permission de changer les alliés des autres joueurs + +commands.ebwizardry:allies.usage=/%1$s [joueur] +commands.ebwizardry:allies.list=Vos alliés %1$s +commands.ebwizardry:allies.list_other=Joueurs alliés de %1$s: %2$s +commands.ebwizardry:allies.permission=Vous n'avez pas la permission de voir les alliés des autres +commands.ebwizardry:allies.none=Aucun + +commands.ebwizardry:discoverspell.usage=/%1$s [joueur] +commands.ebwizardry:discoverspell.not_found=Il n'y a pas de sort avec le nom: %1$s +commands.ebwizardry:discoverspell.clear=%1$s a oublié tout ses sortilèges +commands.ebwizardry:discoverspell.all=%1$s a appris tout les sortilèges +commands.ebwizardry:discoverspell.addspell=Ajout de %1$s à la mémoire de %2$s +commands.ebwizardry:discoverspell.removespell=Retrait de %1$s à la mémoire %2$s + +config.ebwizardry.title.general=Mod Options + +config.ebwizardry.category.gameplay=Paramètre de gameplay +config.ebwizardry.category.gameplay.tooltip=Configuration général de wizardry +config.ebwizardry.title.gameplay=Paramètre de gameplay +config.ebwizardry.subtitle.gameplay=Paramètres globaux qui affecte les mécaniques de jeu + +config.ebwizardry.category.worldgen=Paramètre de génération de monde +config.ebwizardry.category.worldgen.tooltip=Configuration des paramètre de génération de wizardry +config.ebwizardry.title.worldgen=Paramètre de génération de monde +config.ebwizardry.subtitle.worldgen=Paramètres qui affecte la génération du monde + +config.ebwizardry.category.commands=Paramètre de commande +config.ebwizardry.category.commands.tooltip=Configuration des commandes de wizardry +config.ebwizardry.title.commands=Paramètre de commande +config.ebwizardry.subtitle.commands=Paramètres pour les commandes ajoutés par wizardry + +config.ebwizardry.category.client=Paramètre personnel +config.ebwizardry.category.client.tooltip=Configuration coté client +config.ebwizardry.title.client=Paramètre personnel +config.ebwizardry.subtitle.client=Paramètres personnel qui n'affecte que votre jeu et non le serveur + +config.ebwizardry.category.spells=Configuration des sortilèges +config.ebwizardry.category.spells.tooltip=Selection du sort activé +config.ebwizardry.title.spells=Configuration des sortilèges +config.ebwizardry.subtitle.spells=Mettez un sort sur 'false' pour le desactiver + +config.ebwizardry.category.resistances=Configuration de la resistance +config.ebwizardry.category.resistances.tooltip=Configuration de quel mob est resistant a quel type de magie +config.ebwizardry.title.resistances=Configuration de la resistance +config.ebwizardry.subtitle.resistances=Paramètres permettant à certain monstres d'être résistant à tel sort + +config.ebwizardry.tower_rarity=Rareté des tours de sorcier +config.ebwizardry.ore_dimensions=Rareté des minerais +config.ebwizardry.flower_dimensions=Rareté des fleurs de cristal +config.ebwizardry.tower_dimensions=Rareté des tours +config.ebwizardry.spell_book_drop_chance=Chance de loot des livres de sorts +config.ebwizardry.generate_loot=Generation de loot +config.ebwizardry.firebomb_is_craftable=Bombe de feu fabricable +config.ebwizardry.poison_bomb_is_craftable=Bombe de poison fabricable +config.ebwizardry.smoke_bomb_is_craftable=Bombe de fumée fabricable +config.ebwizardry.use_alternate_scroll_recipe=Utilisation alternative de la recette du parchemin +config.ebwizardry.teleport_through_unbreakable_blocks=Teleportation au travers des blocs indestructible +config.ebwizardry.show_summoned_creature_names=Montrer les noms des invocations +config.ebwizardry.friendly_fire=Friendly Fire +config.ebwizardry.telekinetic_disarmament=Desarmement avec le sort de telekinesie +config.ebwizardry.discovery_mode=Mode decouverte +config.ebwizardry.enable_shift_scrolling=Permettre le Shift-scrolling +config.ebwizardry.minion_revenge_targeting=Les sbires peuvent se venger +config.ebwizardry.player_damage_scaling=Degats en fonction du joueur +config.ebwizardry.npc_damage_scaling=Degats en fonction du npc +config.ebwizardry.cast_command_multiplier_limit=Limite de multiplieur de la commande de cast +config.ebwizardry.summoned_creature_targets_whitelist=Whitelist des creatures ciblé par les invocations +config.ebwizardry.summoned_creature_targets_blacklist=Blacklist des creatures ciblé par les invocations +config.ebwizardry.spell_hud_position=Sortilège HUD Position +config.ebwizardry.cast_command_name=Nom de la commande de cast +config.ebwizardry.discoverspell_command_name=Nom de la commande de decouverte de sort +config.ebwizardry.ally_command_name=Nom de la commande pour s'ajouter des alliés +config.ebwizardry.allies_command_name=Nom de la commande pour voir les alliés +config.ebwizardry.mind_control_targets_blacklist=Blacklist des entités pouvant être controlé +config.ebwizardry.evil_wizard_dimensions=Dimension des sorcier maléfique + +config.ebwizardry.mobs_immune_to_fire=Monstre immunisé au feu +config.ebwizardry.mobs_immune_to_ice=Monstre immunisé à la glace +config.ebwizardry.mobs_immune_to_lightning=Monstre immunisé à la foudre +config.ebwizardry.mobs_immune_to_wither=Monstre immunisé au wither +config.ebwizardry.mobs_immune_to_poison=Monstre immunisé au poison + +config.ebwizardry.tower_rarity.tooltip=Rareté des tour de sorcier. Plus le chiffre est élevé, plus les tours sont rare. mettez la valeur sur 0 pour desactiver les tours completement. +config.ebwizardry.ore_dimensions.tooltip=La liste des dimensions où les minerais de cristaux peuvent apparaitre. Note: Enlever l'overworld (id:0) rendra le mod tres difficile! +config.ebwizardry.flower_dimensions.tooltip=La liste des dimensions où les fleurs de cristal peuvent apparaitre. +config.ebwizardry.tower_dimensions.tooltip=La liste des dimensions où les tours de sorcier peuvent apparaitre. +config.ebwizardry.spell_book_drop_chance.tooltip=La chance que les monstres drop un livre de sort. Plus le chiffre est élevé, plus vous avez de chance d'en loot. Valeur sur 0 = Aucun loot, valeur sur 200 = loot assuré +config.ebwizardry.generate_loot.tooltip=Generation des loot de wizardry dans les coffre de donjon ou non +config.ebwizardry.firebomb_is_craftable.tooltip=Frabrication des bombes de feu ou non +config.ebwizardry.poison_bomb_is_craftable.tooltip=Frabrication des bombes de poison ou non +config.ebwizardry.smoke_bomb_is_craftable.tooltip=Frabrication des bombes de fumée ou non +config.ebwizardry.use_alternate_scroll_recipe.tooltip=Ajouter ou non un cristal magic a la recette des parchemins. Mettez la valeur sur true si il y a un conflit. +config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Teleportation possible ou non au traver des blocs indestructible(exemple bedrock) en utilisant le sort de passe muraille. (phase) +config.ebwizardry.show_summoned_creature_names.tooltip=Montrer ou non le nom des créatures invoqué ainsi que ceux de leur invocateur au dessus de celle-ci +config.ebwizardry.friendly_fire.tooltip=Autorise ou non les joueurs à blesser leurs alliés avec de la magie. +config.ebwizardry.telekinetic_disarmament.tooltip=Autorise ou non un joueur à desarmer un autre joueur avec le sort de telekinesie (false pour empecher les vols) +config.ebwizardry.discovery_mode.tooltip=Pour ceux qui aime un peu de mystères! Mettez la valeur sur true pour cacher le nom des sorts jusqu'a leur utilisation. Si la valeur est sur false, les parchemins d'identification ne pourrons plus être obtenable en survie. +config.ebwizardry.enable_shift_scrolling.tooltip=Configuration client, n'affecte pas les autres joueurs. Permet de switcher facilement entre les sorts en maintenant sneak et en scrollant la roulette de la souris. +config.ebwizardry.minion_revenge_targeting.tooltip=Vengeance ou non des invocations sur les invocateur si ceux ci les ont attaqué +config.ebwizardry.player_damage_scaling.tooltip=Facteur d’échelle de dommage global pour les dégâts infligés par les joueurs qui lancent des sorts, par rapport à 1. +config.ebwizardry.npc_damage_scaling.tooltip=Facteur d'échelle de dégâts global pour les dégâts infligés par les PNJ qui lancent des sorts, par rapport à 1. +config.ebwizardry.cast_command_multiplier_limit.tooltip=Limite supérieure pour les multiplicateurs passés dans la commande / cast. Ceci existe pour empêcher les joueurs de casser accidentellement un monde / serveur. Les multiplicateurs de grosses explosions peuvent causer un lag extrême - vous avez été prévenu! +config.ebwizardry.summoned_creature_targets_whitelist.tooltip=Liste des noms des entités que les créatures invoquées et les sorciers sont autorisés à attaquer, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez que les créatures invoquées les attaquent et qu'elles ne le font pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.summoned_creature_targets_blacklist.tooltip=Liste des noms des entités que les créatures invoquées et les sorciers ont l'interdiction d'attaquer, outrepassant ainsi les valeurs par défaut et la liste blanche. Ajouter des créatures à cette liste si les laisser se faire attaquer provoque des problèmes ou est trop destructif (le retrait des creeper de cette liste se fait à vos risques et périls!). Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.spell_hud_position.tooltip=La position du HUD des sorts. +config.ebwizardry.cast_command_name.tooltip=Le nom de la commande /cast. C'est ce que vous tapez directement après le /; Par exemple, si cela était réglé sur 'magic', au lieu de taper /cast, vous taperiez plutôt /magic. +config.ebwizardry.discoverspell_command_name.tooltip=Le nom de la commande /discoverspell. C'est ce que vous tapez directement après le /; Par exemple, si cela était réglé sur 'magic', au lieu de taper /discoverspell, vous taperiez plutôt /magic. +config.ebwizardry.ally_command_name.tooltip=Le nom de la commande /ally. C'est ce que vous tapez directement après le /; Par exemple, si cette option est définie sur 'magic', au lieu de taper /allié, vous devez taper /magic à la place. +config.ebwizardry.allies_command_name.tooltip=Le nom de la commande /allies. C'est ce que vous tapez directement après le /; Par exemple, si cela était réglé sur 'magic', au lieu de taper /allies, vous taperiez plutôt /magic. +config.ebwizardry.mind_control_targets_blacklist.tooltip=Liste des noms d'entités qui ne peuvent pas être contrôlées par l'esprit, en plus des valeurs par défaut. Ajouter des créatures à cette liste si leur permettre d'être contrôlées par l'esprit crée des problèmes ou peut être exploité. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.evil_wizard_dimensions.tooltip=Liste des dimensions où les sorciers maléfiques peuvent apparaitre + +config.ebwizardry.mobs_immune_to_fire.tooltip=Liste des noms des entités immunisées au feu, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez qu'elles soient immunisées à la magie de feu et qu'elles ne le sont pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_ice.tooltip=Liste des noms des entités immunisées contre la glace, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez qu'elles soient immunisées contre la magie de glace et qu'elles ne le sont pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_lightning.tooltip=Liste des noms des entités immunisées contre la foudre, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez qu'elles soient immunisées contre la magie de foudre et qu'elles ne le sont pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard).. +config.ebwizardry.mobs_immune_to_wither.tooltip=Liste des noms des entités immunisées contre l'effet wither, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez qu'elles soient immunisées contre la magie de wither et qu'elles ne le sont pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_poison.tooltip=Liste des noms des entités immunisées contre le poison, en plus des valeurs par défaut. Ajoutez des créatures de mod à cette liste si vous voulez qu'elles soient immunisées contre la magie de poison et qu'elles ne le sont pas déjà. Les noms d'entité ne sont pas sensibles à la case. Pour les entités de mod, préfixez l’ID du mod (par exemple, ebwizardry:wizard). + +wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/lang/ko_kr.lang b/src/main/resources/assets/ebwizardry/lang/ko_kr.lang new file mode 100644 index 00000000..468dd01f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/lang/ko_kr.lang @@ -0,0 +1,760 @@ +tile.ebwizardry:arcane_workbench.name=아케인 작업대 +tile.ebwizardry:crystal_ore.name=수정 광석 +tile.ebwizardry:petrified_stone.name=석화된 돌 +tile.ebwizardry:ice_statue.name=얼음 기둥 +tile.ebwizardry:crystal_flower.name=수정 꽃 +tile.ebwizardry:snare.name=덫 +tile.ebwizardry:transportation_stone.name=전이의 돌 +tile.ebwizardry:spectral_block.name=허상 블록 +tile.ebwizardry:crystal_block.name=수정 블록 + +item.ebwizardry:magic_crystal.name=마법 수정 +item.ebwizardry:magic_wand.name=시작의 지팡이 +item.ebwizardry:apprentice_wand.name=마법 지팡이 +item.ebwizardry:advanced_wand.name=고급 지팡이 +item.ebwizardry:master_wand.name=대가의 지팡이 +item.ebwizardry:spell_book.name=주문서 + +item.ebwizardry:arcane_tome.name=마도서 +item.ebwizardry:arcane_tome.desc1=아무 %1$s 지팡이를 +item.ebwizardry:arcane_tome.desc2=%1$s급으로 강화합니다 + +item.ebwizardry:wizard_handbook.name=마법사의 안내서 +item.ebwizardry:wizard_handbook.desc=by %1$s + +item.ebwizardry:wand.buff=+%1$s %2$s 강화 +item.ebwizardry:wand.spell=대기중인 주문: %1$s +item.ebwizardry:wand.mana=마나: %1$s/%2$s + +item.ebwizardry:wand.addally=%1$s has been added to your list of allies +item.ebwizardry:wand.removeally=%1$s has been removed from your list of allies + +item.ebwizardry:basic_fire_wand.name=따뜻한 지팡이 +item.ebwizardry:basic_ice_wand.name=차가운 지팡이 +item.ebwizardry:basic_lightning_wand.name=먹구름 지팡이 +item.ebwizardry:basic_necromancy_wand.name=음산한 지팡이 +item.ebwizardry:basic_earth_wand.name=단단한 지팡이 +item.ebwizardry:basic_sorcery_wand.name=신기한 지팡이 +item.ebwizardry:basic_healing_wand.name=온화한 지팡이 + +item.ebwizardry:apprentice_fire_wand.name=불꽃 지팡이 +item.ebwizardry:apprentice_ice_wand.name=서리 지팡이 +item.ebwizardry:apprentice_lightning_wand.name=번개 지팡이 +item.ebwizardry:apprentice_necromancy_wand.name=그림자 지팡이 +item.ebwizardry:apprentice_earth_wand.name=숲의 지팡이 +item.ebwizardry:apprentice_sorcery_wand.name=힘의 지팡이 +item.ebwizardry:apprentice_healing_wand.name=기력의 지팡이 + +item.ebwizardry:advanced_fire_wand.name=화염 지팡이 +item.ebwizardry:advanced_ice_wand.name=얼음 지팡이 +item.ebwizardry:advanced_lightning_wand.name=벼락 지팡이 +item.ebwizardry:advanced_necromancy_wand.name=암흑 지팡이 +item.ebwizardry:advanced_earth_wand.name=대지 지팡이 +item.ebwizardry:advanced_sorcery_wand.name=환영 지팡이 +item.ebwizardry:advanced_healing_wand.name=치유 지팡이 + +item.ebwizardry:master_fire_wand.name=겁화의 지팡이 +item.ebwizardry:master_ice_wand.name=빙결의 지팡이 +item.ebwizardry:master_lightning_wand.name=폭풍의 지팡이 +item.ebwizardry:master_necromancy_wand.name=심연의 지팡이 +item.ebwizardry:master_earth_wand.name=자연의 지팡이 +item.ebwizardry:master_sorcery_wand.name=미지의 지팡이 +item.ebwizardry:master_healing_wand.name=성자의 지팡이 + +item.ebwizardry:spectral_sword.name=허상의 검 +item.ebwizardry:spectral_pickaxe.name=허상의 곡괭이 +item.ebwizardry:spectral_bow.name=허상의 활 + +item.ebwizardry:mana_flask.name=마나 플라스크 +item.ebwizardry:storage_upgrade.name=마나 수용량 증가 +item.ebwizardry:siphon_upgrade.name=Wand Siphon Upgrade +item.ebwizardry:condenser_upgrade.name=마력 수복 +item.ebwizardry:range_upgrade.name=주문 사거리 증가 +item.ebwizardry:duration_upgrade.name=지속시간 증가 +item.ebwizardry:cooldown_upgrade.name=Wand Cooldown Upgrade +item.ebwizardry:blast_upgrade.name=Wand Blast Upgrade +item.ebwizardry:attunement_upgrade.name=주문 허용량 증가 + +item.ebwizardry:flaming_axe.name=불타오르는 도끼 +item.ebwizardry:frost_axe.name=얼어붙은 도끼 + +item.ebwizardry:firebomb.name=화염탄 +item.ebwizardry:poison_bomb.name=독 폭탄 +item.ebwizardry:smoke_bomb.name=연막탄 + +item.ebwizardry:blank_scroll.name=빈 두루마리 +item.ebwizardry:scroll.name=%1$s의 두루마리 +item.ebwizardry:scroll.undiscovered.name=Scroll "%1$s" +item.ebwizardry:identification_scroll.name=지식의 두루마리 +item.ebwizardry:identification_scroll.desc1=%1$s주문서나 두루마리를 +item.ebwizardry:identification_scroll.desc2=%1$s확인할 수 있습니다 +item.ebwizardry:identification_scroll.nothing_to_identify=알아볼 주문이 없습니다! + +item.ebwizardry:armour_upgrade.name=보호의 문양 +item.ebwizardry:armour_upgrade.desc1=%1$s아무 마법사의 보호구를 +item.ebwizardry:armour_upgrade.desc2=%1$s전설급으로 강화합니다 + +item.ebwizardry:magic_silk.name=마법 천 + +item.ebwizardry:wizard_armour.legendary=전설급 +item.ebwizardry:wizard_armour.buff=-%1$s %2$s 비용 +item.ebwizardry:wizard_armour.mana=마나: %1$s/%2$s + +item.ebwizardry:wizard_hat.name=마법사 모자 +item.ebwizardry:wizard_robe.name=마법사 로브 +item.ebwizardry:wizard_leggings.name=마법사 바지 +item.ebwizardry:wizard_boots.name=마법사 부츠 + +item.ebwizardry:wizard_hat_fire.name=화염술사의 모자 +item.ebwizardry:wizard_robe_fire.name=화염술사의 로브 +item.ebwizardry:wizard_leggings_fire.name=화염술사의 바지 +item.ebwizardry:wizard_boots_fire.name=화염술사의 부츠 + +item.ebwizardry:wizard_hat_ice.name=빙결술사의 모자 +item.ebwizardry:wizard_robe_ice.name=빙결술사의 로브 +item.ebwizardry:wizard_leggings_ice.name=빙결술사의 바지 +item.ebwizardry:wizard_boots_ice.name=빙결술사의 부츠 + +item.ebwizardry:wizard_hat_lightning.name=전격술사의 모자 +item.ebwizardry:wizard_robe_lightning.name=전격술사의 로브 +item.ebwizardry:wizard_leggings_lightning.name=전격술사의 바지 +item.ebwizardry:wizard_boots_lightning.name=전격술사의 부츠 + +item.ebwizardry:wizard_hat_necromancy.name=사령술사의 모자 +item.ebwizardry:wizard_robe_necromancy.name=사령술사의 로브 +item.ebwizardry:wizard_leggings_necromancy.name=사령술사의 바지 +item.ebwizardry:wizard_boots_necromancy.name=사령술사의 부츠 + +item.ebwizardry:wizard_hat_earth.name=자연술사의 모자 +item.ebwizardry:wizard_robe_earth.name=자연술사의 로브 +item.ebwizardry:wizard_leggings_earth.name=자연술사의 바지 +item.ebwizardry:wizard_boots_earth.name=자연술사의 부츠 + +item.ebwizardry:wizard_hat_sorcery.name=신비술사의 모자 +item.ebwizardry:wizard_robe_sorcery.name=신비술사의 로브 +item.ebwizardry:wizard_leggings_sorcery.name=신비술사의 바지 +item.ebwizardry:wizard_boots_sorcery.name=신비술사의 부츠 + +item.ebwizardry:wizard_hat_healing.name=치유사의 모자 +item.ebwizardry:wizard_robe_healing.name=치유사의 로브 +item.ebwizardry:wizard_leggings_healing.name=치유사의 바지 +item.ebwizardry:wizard_boots_healing.name=치유사의 부츠 + +item.ebwizardry:spawn_wizard.name=마법사 소환 +item.ebwizardry:spawn_evil_wizard.name=사악한 마법사 소환 + +item.ebwizardry:spectral_helmet.name=허상의 투구 +item.ebwizardry:spectral_chestplate.name=허상의 흉갑 +item.ebwizardry:spectral_leggings.name=허상의 각반 +item.ebwizardry:spectral_boots.name=허상의 부츠 + +entity.ebwizardry:summonedcreature.nameplate=%1$s의 %2$s +entity.ebwizardry:summonedcreature.nameplate_fallback=누군가의 %1$s + +entity.ebwizardry:zombie_minion.name=좀비 +entity.ebwizardry:skeleton_minion.name=스켈레톤 +entity.ebwizardry:spider_minion.name=독 거미 +entity.ebwizardry:blaze_minion.name=블레이즈 +entity.ebwizardry:wither_skeleton_minion.name=위더 스켈레톤 +entity.ebwizardry:ice_wraith.name=빙령 +entity.ebwizardry:lightning_wraith.name=뇌령 +entity.ebwizardry:shadow_wraith.name=암령 +entity.ebwizardry:spirit_wolf.name=늑대 혼령 +entity.ebwizardry:spirit_horse.name=말 혼령 +entity.ebwizardry:ice_giant.name=얼음 거인 +entity.ebwizardry:phoenix.name=피닉스 +entity.ebwizardry:wizard.name=마법사 +entity.ebwizardry:magic_slime.name=마법 슬라임 +entity.ebwizardry:silverfish_minion.name=좀벌레 +entity.ebwizardry:storm_elemental.name=폭풍 정령 +entity.ebwizardry:evil_wizard.name=사악한 마법사 +entity.ebwizardry:decoy.name=분신 + +entity.ebwizardry:magic_missile.name=마법 +entity.ebwizardry:arc.name=마법 +entity.ebwizardry:spark_bomb.name=마법 +entity.ebwizardry:ice_shard.name=마법 +entity.ebwizardry:firebomb.name=마법 +entity.ebwizardry:poison_bomb.name=마법 +entity.ebwizardry:force_orb.name=마법 +entity.ebwizardry:spark.name=마법 +entity.ebwizardry:darkness_orb.name=마법 +entity.ebwizardry:fire_sigil.name=마법 +entity.ebwizardry:frost_sigil.name=마법 +entity.ebwizardry:lightning_sigil.name=마법 +entity.ebwizardry:lightning_arrow.name=마법 +entity.ebwizardry:firebolt.name=마법 +entity.ebwizardry:ice_charge.name=마법 +entity.ebwizardry:force_arrow.name=마법 +entity.ebwizardry:dart.name=마법 +entity.ebwizardry:lightning_disc.name=마법 +entity.ebwizardry:thunderbolt.name=마법 +entity.ebwizardry:decay.name=마법 +entity.ebwizardry:ice_lance.name=마법 +entity.ebwizardry:smoke_bomb.name=마법 +entity.ebwizardry:ice_spike.name=마법 + +entity.ebwizardry:black_hole.name=블랙홀 +entity.ebwizardry:shield.name=방어막 +entity.ebwizardry:meteor.name=메테오 +entity.ebwizardry:blizzard.name=눈보라 +entity.ebwizardry:bubble.name=물방울 +entity.ebwizardry:tornado.name=회오리바람 +entity.ebwizardry:lightning_hammer.name=벼락 망치 +entity.ebwizardry:arrow_rain.name=화살 세례 +entity.ebwizardry:healing_aura.name=치유 공간 +entity.ebwizardry:forcefield.name=역장 +entity.ebwizardry:ring_of_fire.name=화염의 고리 +entity.ebwizardry:earthquake.name=지진 +entity.ebwizardry:falling_grass.name=떨어지는 풀 +entity.ebwizardry:hailstorm.name=얼음 폭풍 +entity.ebwizardry:lightning_pulse.name=번개 파동 + +itemGroup.ebwizardry=마법의 길 +itemGroup.ebwizardryspells=주문 + +advancement.ebwizardry:root=마법의 길 +advancement.ebwizardry:root.desc=마도를 걷는 여정길 +advancement.ebwizardry:crystal=신기한 수정이군... +advancement.ebwizardry:crystal.desc=마법 수정을 캐세요 +advancement.ebwizardry:arcane_initiate=마법 시작! +advancement.ebwizardry:arcane_initiate.desc=황금 조각, 막대기, 마법 수정 한개로 마법 지팡이를 만드세요 +advancement.ebwizardry:apprentice=마법사의 제자 +advancement.ebwizardry:apprentice.desc=마도서를 사용해 지팡이를 강화하세요 +advancement.ebwizardry:master=마법의 대가 +advancement.ebwizardry:master.desc=대가의 지팡이를 얻으세요 +advancement.ebwizardry:all_spells=현자 +advancement.ebwizardry:all_spells.desc=모든 주문을 시전하세요 +advancement.ebwizardry:wizard_trade=마법 거래 +advancement.ebwizardry:wizard_trade.desc=마법사에게서 아이템 한개를 구입하세요 +advancement.ebwizardry:buy_master_spell=지식은 힘이다 +advancement.ebwizardry:buy_master_spell.desc=마법사에게서 대가의 주문을 구입하세요 +advancement.ebwizardry:freeze_blaze=이젠 안 뜨거워 +advancement.ebwizardry:freeze_blaze.desc=블레이즈를 얼리세요 +advancement.ebwizardry:charge_creeper=바람이 부나? +advancement.ebwizardry:charge_creeper.desc='사고로' 충전된 크리퍼를 만드세요 +advancement.ebwizardry:frankenstein=프랑켄슈타인 +advancement.ebwizardry:frankenstein.desc='벼락' 주문으로 돼지를 좀비 피그맨으로 만드세요 +advancement.ebwizardry:special_upgrade=마개조 +advancement.ebwizardry:special_upgrade.desc=지팡이에 특별한 개조를 하세요 +advancement.ebwizardry:craft_flask=플라스크 속의 마법 +advancement.ebwizardry:craft_flask.desc=마나 플라스크를 만드세요 +advancement.ebwizardry:elemental=속성? +advancement.ebwizardry:elemental.desc=속성 지팡이 한개를 얻으세요 +advancement.ebwizardry:armour_set=이제야 제대로 된 마법사 같네 +advancement.ebwizardry:armour_set.desc=마법사 방어구 세트를 입으세요 +advancement.ebwizardry:legendary=전설! +advancement.ebwizardry:legendary.desc=전설급 마법사 방어구 한개를 얻으세요 +advancement.ebwizardry:self_destruct=마법은 돌아오는 거야 +advancement.ebwizardry:self_destruct.desc=자신의 마법에 죽으세요 +advancement.ebwizardry:pig_tornado=다시는... +advancement.ebwizardry:pig_tornado.desc=돼지를 타고 토네이도로 뛰어드세요 +advancement.ebwizardry:jam_wizard=방해 시간 +advancement.ebwizardry:jam_wizard.desc='마법 방해' 주문을 마법사에게 사용하세요 +advancement.ebwizardry:slime_skeleton=끈적한 상황 +advancement.ebwizardry:slime_skeleton.desc=스켈레톤에게 슬라임을 씌우세요 +advancement.ebwizardry:anger_wizard=후회할걸 +advancement.ebwizardry:anger_wizard.desc=마법사를 화나게 하세요 +advancement.ebwizardry:defeat_evil_wizard=당연한 일이야 +advancement.ebwizardry:defeat_evil_wizard.desc=사악한 마법사를 물리치세요 +advancement.ebwizardry:max_out_wand=강력하군! +advancement.ebwizardry:max_out_wand.desc=대가의 지팡이를 한계까지 개조하세요 +advancement.ebwizardry:element_master=Element Mastery +advancement.ebwizardry:element_master.desc=Cast all the spells of any element +advancement.ebwizardry:identify_spell=마법 감정 +advancement.ebwizardry:identify_spell.desc=지식의 두루마리로 미확인 주문을 확인하세요 + +tile.ebwizardry:transportation_stone.confirm=이제 %1$s을 사용하면 이곳으로 귀환합니다. +tile.ebwizardry:transportation_stone.invalid=먼저 전이의 돌 8개로 귀환진을 만들어야 합니다. + +container.ebwizardry:arcane_workbench=아케인 작업대 +container.ebwizardry:arcane_workbench.apply=완료 +container.ebwizardry:arcane_workbench.mana=마나: +container.ebwizardry:arcane_workbench.upgrades=개조 내역: + +tier.basic=초보자 +tier.apprentice=견습생 +tier.advanced=숙련자 +tier.master=대가 + +element.simple=무 +element.fire=화염 +element.ice=빙결 +element.lightning=전격 +element.necromancy=사령 +element.earth=자연 +element.sorcery=신비 +element.healing=치유 + +element.simple.wizard=마법사 +element.fire.wizard=화염술사 +element.ice.wizard=빙결술사 +element.lightning.wizard=전격술사 +element.necromancy.wizard=사령술사 +element.earth.wizard=자연술사 +element.sorcery.wizard=신비술사 +element.healing.wizard=치유사 + +spelltype.attack=공격 +spelltype.defence=수비 +spelltype.utility=보조 +spelltype.minion=소환 + +spell.disabled=%1$s은 config에 의해 비활성화 되었습니다. +spell.resist=%1$s(이)가 %2$s에 저항했습니다 +spell.discover=%1$s 주문을 알아냈습니다! + +spell.ebwizardry:agility=민첩 강화 +spell.ebwizardry:arc=전격파 +spell.ebwizardry:arcane_jammer=마법 방해 +spell.ebwizardry:arrow_rain=화살 세례 +spell.ebwizardry:banish=강제 전이 +spell.ebwizardry:black_hole=블랙홀 +spell.ebwizardry:blink=점멸 +spell.ebwizardry:blizzard=눈보라 +spell.ebwizardry:bubble=물방울 +spell.ebwizardry:chain_lightning=연쇄 번개 +spell.ebwizardry:clairvoyance=자연의 인도 +spell.ebwizardry:cobwebs=옭아매는 거미줄 +spell.ebwizardry:conjure_armour=허상의 갑옷 +spell.ebwizardry:conjure_bow=허상의 활 +spell.ebwizardry:conjure_pickaxe=허상의 곡괭이 +spell.ebwizardry:conjure_sword=허상의 검 +spell.ebwizardry:cure_effects=회복의 바람 +spell.ebwizardry:curse_of_soulbinding=묶여있는 영혼 +spell.ebwizardry:darkness_orb=암흑의 보주 +spell.ebwizardry:darkvision=암흑 시야 +spell.ebwizardry:dart=다트 +spell.ebwizardry:decay=부패하는 역병 +spell.ebwizardry:decoy=분산 +spell.ebwizardry:detonate=폭렬 +spell.ebwizardry:diamondflesh=불멸의 신체 +spell.ebwizardry:earthquake=지진 +spell.ebwizardry:entrapment=공포의 손아귀 +spell.ebwizardry:fireball=화염구 +spell.ebwizardry:firebolt=화염 탄환 +spell.ebwizardry:firebomb=화염탄 +spell.ebwizardry:fire_resistance=화염 저항 +spell.ebwizardry:fire_sigil=불의 표식 +spell.ebwizardry:fireskin=불타는 피부 +spell.ebwizardry:firestorm=불의 세례 +spell.ebwizardry:flame_ray=불꽃 광선 +spell.ebwizardry:flaming_axe=타오르는도끼 +spell.ebwizardry:flaming_weapon=타오르는무기 +spell.ebwizardry:flight=창공의 가호 +spell.ebwizardry:font_of_mana=마나의 축복 +spell.ebwizardry:font_of_vitality=생명의 축복 +spell.ebwizardry:force_arrow=인력의 화살 +spell.ebwizardry:forcefield=역장 +spell.ebwizardry:force_orb=반발의 보주 +spell.ebwizardry:forests_curse=자연의 저주 +spell.ebwizardry:freeze=서리바람 +spell.ebwizardry:freezing_weapon=얼어붙은 무기 +spell.ebwizardry:frost_axe=얼어붙은 도끼 +spell.ebwizardry:frost_ray=서리 광선 +spell.ebwizardry:frost_sigil=냉기의 표식 +spell.ebwizardry:glide=바람의 호의 +spell.ebwizardry:greater_fireball=불덩이 작렬 +spell.ebwizardry:greater_heal=대 치유 +spell.ebwizardry:group_heal=광역 치유 +spell.ebwizardry:growth_aura=수확의 축복 +spell.ebwizardry:hailstorm=얼음 폭풍 +spell.ebwizardry:heal=치유 +spell.ebwizardry:heal_ally=치유의 손길 +spell.ebwizardry:healing_aura=차오르는 체력 +spell.ebwizardry:homing_spark=전기 추적자 +spell.ebwizardry:ice_age=빙하기 +spell.ebwizardry:ice_charge=얼음 폭탄 +spell.ebwizardry:ice_lance=얼음창 +spell.ebwizardry:ice_shard=얼음 화살 +spell.ebwizardry:ice_shroud=서리 가호 +spell.ebwizardry:ice_spikes=차가운 가시밭 +spell.ebwizardry:ice_statue=결빙 +spell.ebwizardry:ignite=모닥불 +spell.ebwizardry:imbue_weapon=마력 부여 +spell.ebwizardry:intimidate=공포 유발 +spell.ebwizardry:invigorating_presence=강력한 손아귀 +spell.ebwizardry:invisibility=투명화 +spell.ebwizardry:invoke_weather=뇌우 +spell.ebwizardry:ironflesh=강철같은 신체 +spell.ebwizardry:leap=도약 +spell.ebwizardry:levitation=중력 반발 +spell.ebwizardry:life_drain=생명력 흡수 +spell.ebwizardry:light=빛 +spell.ebwizardry:lightning_arrow=번개 화살 +spell.ebwizardry:lightning_bolt=벼락 +spell.ebwizardry:lightning_disc=번개의 고리 +spell.ebwizardry:lightning_hammer=벼락 망치 +spell.ebwizardry:lightning_pulse=번개 파동 +spell.ebwizardry:lightning_ray=번개 광선 +spell.ebwizardry:lightning_sigil=번개의 표식 +spell.ebwizardry:lightning_web=이어지는 번개 +spell.ebwizardry:magic_missile=마법 화살 +spell.ebwizardry:metamorphosis=형태의 변이 +spell.ebwizardry:meteor=메테오 +spell.ebwizardry:mind_control=정신지배 +spell.ebwizardry:mind_trick=정신착란 +spell.ebwizardry:none=[빈 주문] +spell.ebwizardry:oakflesh=나무의 단단함 +spell.ebwizardry:petrify=석화 +spell.ebwizardry:phase_step=벽 안의 길 +spell.ebwizardry:plague_of_darkness=잠식하는 어둠 +spell.ebwizardry:pocket_furnace=마법 화로 +spell.ebwizardry:pocket_workbench=허상 작업대 +spell.ebwizardry:poison=독 +spell.ebwizardry:poison_bomb=독 폭탄 +spell.ebwizardry:replenish_hunger=풍족한 식사 +spell.ebwizardry:ring_of_fire=화염의 고리 +spell.ebwizardry:shadow_ward=마주하는 고통 +spell.ebwizardry:shield=방어막 +spell.ebwizardry:shockwave=충격 파동 +spell.ebwizardry:silverfish_swarm=소환 : 좀벌레 무리 +spell.ebwizardry:sixth_sense=제육감 +spell.ebwizardry:slime=바람의 거부 +spell.ebwizardry:smoke_bomb=연막탄 +spell.ebwizardry:snare=구속의 올가미 +spell.ebwizardry:snowball=눈덩이 +spell.ebwizardry:spark_bomb=전기 폭탄 +spell.ebwizardry:spectral_pathway=허상의 길 +spell.ebwizardry:spider_swarm=늪의 분노 +spell.ebwizardry:static_aura=벼락의 기운 +spell.ebwizardry:summon_blaze=소환 : 블레이즈 +spell.ebwizardry:summon_ice_giant=소환 : 얼음 거인 +spell.ebwizardry:summon_ice_wraith=소환 : 빙령 +spell.ebwizardry:summon_iron_golem=소환 : 철 골렘 +spell.ebwizardry:summon_lightning_wraith=소환 : 뇌령 +spell.ebwizardry:summon_phoenix=소환 : 피닉스 +spell.ebwizardry:summon_shadow_wraith=소환 : 암령 +spell.ebwizardry:summon_skeleton=소환 : 스켈레톤 +spell.ebwizardry:summon_skeleton_legion=소환 : 스켈레톤 군단 +spell.ebwizardry:summon_snow_golem=소환 : 눈 골렘 +spell.ebwizardry:summon_spirit_horse=소환 : 말 혼령 +spell.ebwizardry:summon_spirit_wolf=소환 : 늑대 혼령 +spell.ebwizardry:summon_storm_elemental=소환 : 폭풍 정령 +spell.ebwizardry:summon_wither_skeleton=소환 : 위더 스켈레톤 +spell.ebwizardry:summon_zombie=소환 : 좀비 +spell.ebwizardry:telekinesis=염동력 +spell.ebwizardry:thunderbolt=번개 충격 +spell.ebwizardry:thunderstorm=번개폭풍 +spell.ebwizardry:tornado=회오리바람 +spell.ebwizardry:transience=공허의 보호 +spell.ebwizardry:transportation=귀환 +spell.ebwizardry:vanishing_box=아공간 +spell.ebwizardry:wall_of_frost=서리 장벽 +spell.ebwizardry:water_breathing=수중 호흡 +spell.ebwizardry:whirlwind=땅의 추방 +spell.ebwizardry:wither=쇠약의 물결 +spell.ebwizardry:wither_skull=사악한 폭격 + +spell.ebwizardry:agility.desc=시전자에게 신속과 점프 강화를 30초 부여합니다. +spell.ebwizardry:arc.desc=대상에게 전기충격을 가합니다. +spell.ebwizardry:arcane_jammer.desc=대상의 마법 사용을 15초 막습니다. +spell.ebwizardry:arrow_rain.desc="궁수! 발사!" +spell.ebwizardry:banish.desc=대상을 일정 범위 내에 무작위로 전이시킵니다. +spell.ebwizardry:black_hole.desc="도망쳐 보시지..." +spell.ebwizardry:blink.desc=시전자를 약간 앞으로 전이시킵니다 +spell.ebwizardry:blizzard.desc=휘몰아치는 눈보라를 만들어 범위 내의 대상을 느리게 하고 지속적인 피해를 줍니다. 시전자는 피해는 입지 않지만 느려집니다. +spell.ebwizardry:bubble.desc=닿은 대상을 하늘로 떠올리는 물거품을 발사합니다. 지속시간이 다 되거나 피해를 입으면 사라집니다. +spell.ebwizardry:chain_lightning.desc=발사된 번개가 대상과 근처 적에게 피해를 입힙니다. +spell.ebwizardry:clairvoyance.desc=지정된 위치를 안내하는 빛무리를 보여줍니다. 이 주문을 든 상태에서 지정할 위치의 블록을 쉬프트-우클릭 하세요. 빛무리는 90동안 유지됩니다. +spell.ebwizardry:cobwebs.desc=사용한 위치에 20초간 유지되는 거미줄을 만듭니다. +spell.ebwizardry:conjure_armour.desc=철 갑옷과 같은 방어도를 갖는 허상의 갑옷을 착용합니다. 갑옷은 1분동안 유지되며 빈 장비칸이 필요합니다. +spell.ebwizardry:conjure_bow.desc=30초 동안 유지되는 무한한 허상의 활을 만듭니다. +spell.ebwizardry:conjure_pickaxe.desc=30초 동안 유지되는 철 곡괭이와 동급의 허상의 곡괭이를 만듭니다. +spell.ebwizardry:conjure_sword.desc=30초 동안 유지되는 철 검과 동급의 허상의 검을 만듭니다. +spell.ebwizardry:cure_effects.desc=시전자에게 적용된 모든 상태이상을 제거합니다. +spell.ebwizardry:curse_of_soulbinding.desc=대상과 자신의 영혼을 묶습니다. 모든 피해를 공유하며 누구 하나가 죽을 때까지 지속됩니다. +spell.ebwizardry:darkness_orb.desc=느리게 움직이는 암흑 에너지로 된 구체를 날립니다. 맞은 대상은 시듦 상태에 빠집니다. +spell.ebwizardry:darkvision.desc=야간 투시 45초를 얻습니다. +spell.ebwizardry:dart.desc=피해와 나약함을 입히는 다트를 발사합니다. +spell.ebwizardry:decay.desc=부패된 땅을 만들고 그 위를 지나는 모든 생물체에게 '부패'를 입힙니다. 부패된 생물체가 움직이면 그곳에도 '부패'가 퍼집니다. +spell.ebwizardry:decoy.desc=30초 동안 유지되는 시전자와 똑같이 생긴 분신을 만들어 적을 혼란시킵니다. +spell.ebwizardry:detonate.desc=시전한 지점에 폭발을 일으켜 주변의 모든 크리쳐를 공격합니다. +spell.ebwizardry:diamondflesh.desc="네 공격은 통하지 않는다!" +spell.ebwizardry:earthquake.desc="진정한 자연의 마법사는 산을 옮길수도 있지." +spell.ebwizardry:entrapment.desc=대상을 어둠에 구체에 가둬 지속 피해를 주며 하늘로 띄웁니다. +spell.ebwizardry:fireball.desc=시전한 방향으로 화염구를 발사합니다. +spell.ebwizardry:firebolt.desc=전방으로 짧은 불꽃 탄환을 발사합니다. +spell.ebwizardry:firebomb.desc=시전한 방향으로 화염탄을 던져서 주변 적을 불태웁니다. +spell.ebwizardry:fire_resistance.desc=화염 저항 30초를 얻습니다. +spell.ebwizardry:fire_sigil.desc=약간의 피해와 화상을 입히는 마법 함정을 설치합니다. +spell.ebwizardry:fireskin.desc=30초 동안 시전자를 화염으로 감싸, 공격하는 적에게 화상을 입힙니다. +spell.ebwizardry:firestorm.desc="내가 바로 화염룡이다!" +spell.ebwizardry:flame_ray.desc=불타는 광선을 내뿜어 땅과 적을 불태웁니다. +spell.ebwizardry:flaming_axe.desc=30초 동안 유지되는 적을 불태우는 도끼를 만듭니다. +spell.ebwizardry:flaming_weapon.desc=시전자의 핫바에 존재하는 무기 하나에 45초 동안 지속되는 '불꽃의 마력' 인챈트를 부여합니다. +spell.ebwizardry:flight.desc="날기를 매와 같이." +spell.ebwizardry:font_of_mana.desc="우리를 가득 채운 강력한 에너지는 중앙..." - 잊혀진 마법사의 일지에서 발췌, 나머지 페이지는 불타 사라졌다. +spell.ebwizardry:font_of_vitality.desc="끝내주는 기분이군." +spell.ebwizardry:force_arrow.desc=시전한 방향으로 나아가는 힘의 화살을 발사합니다. +spell.ebwizardry:forcefield.desc=시전자를 중심으로 허가받지 못한 모든 것을 거부하는 공간을 만듭니다. +spell.ebwizardry:force_orb.desc=주변을 밀쳐내며 피해를 주는 힘의 구체를 던집니다. +spell.ebwizardry:forests_curse.desc="이곳이 어디라고 발을 디디느냐!" +spell.ebwizardry:freeze.desc=대상을 10초 동안 얼립니다. 또는 물을 얼리거나 눈을 내릴 수 있습니다. +spell.ebwizardry:freezing_weapon.desc=시전자의 핫바에 존재하는 무기 하나에 45초 동안 지속되는 '서리의 마력' 인챈트를 부여합니다. +spell.ebwizardry:frost_axe.desc=30초 동안 유지되는 적을 얼리는 도끼를 만듭니다. +spell.ebwizardry:frost_ray.desc=대상에게 피해와 동상을 주는 서리 광선을 발사합니다. +spell.ebwizardry:frost_sigil.desc=약간의 피해와 동상을 입히는 마법 함정을 설치합니다. +spell.ebwizardry:glide.desc=낙하중 사용하는 동안 부드럽게 활강합니다. +spell.ebwizardry:greater_fireball.desc=주변을 파괴하고 충격을 주는 거대한 화염구를 발사합니다. +spell.ebwizardry:greater_heal.desc=시전자의 하트 4칸을 회복합니다. +spell.ebwizardry:group_heal.desc=시전자와 주변 생명의 하트 3칸을 회복합니다. +spell.ebwizardry:growth_aura.desc=시전자 주변의 모든 식물이 자라납니다. +spell.ebwizardry:hailstorm.desc="빙결술사들이 진정한 힘을 깨닳은건 3번째 겨울이었습니다." +spell.ebwizardry:heal.desc=시전자의 하트 2칸을 회복합니다. +spell.ebwizardry:heal_ally.desc=대상의 하트 2개 반을 회복시킵니다. +spell.ebwizardry:healing_aura.desc=체력을 서서히 회복하는 신성한 공간을 만듭니다. 범위 내의 언데드는 지속 피해를 입습니다. +spell.ebwizardry:homing_spark.desc=주변 적으로 움직이는 전기 구슬을 발사합니다. +spell.ebwizardry:ice_age.desc="내가, 겨울이다." +spell.ebwizardry:ice_charge.desc=산산조각나 주변에 한기와 얼음 파편을 뿌리는 얼음 폭탄을 던집니다. +spell.ebwizardry:ice_lance.desc=맞은 대상에게 피해와 감속을 입히는 관통하는 얼음의 창을 발사합니다. +spell.ebwizardry:ice_shard.desc=피해와 감속을 입히는 얼음 파편을 날립니다. +spell.ebwizardry:ice_shroud.desc=30초 동안 시전자를 서리로 감싸 공격하는 적을 얼어붙게 합니다. +spell.ebwizardry:ice_spikes.desc=날카로운 얼음 가시들을 땅에서 솟게합니다. +spell.ebwizardry:ice_statue.desc=대상을 20초 동안이나 얼음이 깨질때 까지 얼음 기둥에 가둡니다. 갖힌 대상은 피해를 받지 않습니다. +spell.ebwizardry:ignite.desc=사용 지점에 불을 붙입니다. 라이터와 같습니다. +spell.ebwizardry:imbue_weapon.desc=시전자의 핫바에 존재하는 무기 하나에 45초 동안 마력을 부여합니다. +spell.ebwizardry:intimidate.desc=주변 크리쳐들을 공포를 주어 도망가게 합니다. 대상은 30초 뒤에 회복됩니다. +spell.ebwizardry:invigorating_presence.desc=시전자와 주변 동맹에 힘2를 부여합니다. +spell.ebwizardry:invisibility.desc=투명화 30초를 얻습니다. +spell.ebwizardry:invoke_weather.desc=날씨를 바꿉니다. +spell.ebwizardry:ironflesh.desc=저항3 30초를 얻습니다. +spell.ebwizardry:leap.desc=약간 앞으로 나아가며 높이 도약합니다. +spell.ebwizardry:levitation.desc=주문을 유지하는 동안 시전자를 하늘로 떠오르게 합니다. +spell.ebwizardry:life_drain.desc=쇠약의 기운을 발사해 대상의 체력을 뺏어와 시전자의 체력을 보충합니다. +spell.ebwizardry:light.desc=30초 동안 유지되는 마법적인 조명을 생성합니다. +spell.ebwizardry:lightning_arrow.desc=전방으로 번개의 화살을 발사합니다. +spell.ebwizardry:lightning_bolt.desc=사용 지점에 벼락을 내리꽂습니다. +spell.ebwizardry:lightning_disc.desc=고리모양의 번개를 빠른 속도로 날립니다. +spell.ebwizardry:lightning_hammer.desc="신의 망치를 받아라!" +spell.ebwizardry:lightning_pulse.desc=시전자를 중심으로 번개를 방출하여 주변 적에게 피해를 주고 밀쳐냅니다. +spell.ebwizardry:lightning_ray.desc=적을 감전시켜 피해를 입히는 번개를 지속적으로 방출합니다. +spell.ebwizardry:lightning_sigil.desc=연쇄 번개를 내뿜는 마법 함정을 설치합니다. +spell.ebwizardry:lightning_web.desc="집중해. 네 안의 폭풍을 지팡이 밖으로 내뿜는거야." +spell.ebwizardry:magic_missile.desc=약간의 피해를 입히는 기본적인 마법 화살을 발사합니다. +spell.ebwizardry:metamorphosis.desc=대상을 같은 종류의 다른 형태로 바꿉니다. +spell.ebwizardry:meteor.desc="가끔은 이 세상이 불타는걸 보고싶기도 하지..." +spell.ebwizardry:mind_control.desc=의지가 약한 대상의 정신을 사로잡아 30초 동안 시전자를 위해 싸우게 합니다. +spell.ebwizardry:mind_trick.desc=대상의 정신에 충격을 가해 15초가 지나거나 공격을 받을때 까지 아무것도 하지 못하게 합니다. +spell.ebwizardry:none.desc=주문서를 얻으세요. /give command, use id: /give [player] ebwizardry:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). +spell.ebwizardry:oakflesh.desc=저항2 30초를 얻습니다. +spell.ebwizardry:petrify.desc=대상을 돌에 가둬 아무것도 할수 없지만 피해 또한 받지 않게 합니다. 캐거나 어두운 곳에 있을때 까지 지속됩니다. +spell.ebwizardry:phase_step.desc=전방의 1칸 두깨의 벽을 통과합니다. 사거리 추가 개조로 강화할 수 있습니다. +spell.ebwizardry:plague_of_darkness.desc="어둠이 모든것을 먹어 치우리라..." +spell.ebwizardry:pocket_furnace.desc=인벤토리의 제련할 수 있는 아이템을 5개씩 제련합니다. 핫바를 우선 합니다. +spell.ebwizardry:pocket_workbench.desc=어디서든 작업대를 열 수 있습니다. +spell.ebwizardry:poison.desc=전방으로 독을 발사합니다. +spell.ebwizardry:poison_bomb.desc=약간의 피해와 주변에 독을 뿌리는 폭탄을 던집니다. +spell.ebwizardry:replenish_hunger.desc=시전자의 배고픔을 6점(3칸) 회복합니다. +spell.ebwizardry:ring_of_fire.desc=시전자 주위로 화염의 고리를 만들어서 들어오는 적을 불태웁니다. +spell.ebwizardry:shadow_ward.desc=시전자 전방에 어둠의 방패를 만들어 적이 가하는 공격의 절반을 돌려줍니다. +spell.ebwizardry:shield.desc=발사체와 마법을 막는 방패를 소환합니다. 유지되는 동안 저항을 얻습니다. +spell.ebwizardry:shockwave.desc="펑." +spell.ebwizardry:silverfish_swarm.desc="으아아! 벌레다!!!" +spell.ebwizardry:sixth_sense.desc=20초 동안 주변의 모든 크리쳐를 어디에 있던 볼 수 있습니다. +spell.ebwizardry:slime.desc=대상에게 10초 동안 슬라임을 씌워 느리게 하고 피해를 입힙니다. +spell.ebwizardry:smoke_bomb.desc=연막을 터트려 짧은 시간 대상을 실명 시킵니다. +spell.ebwizardry:snare.desc=피해와 구속을 거는 덫을 설치합니다. +spell.ebwizardry:snowball.desc=눈덩이를 던집니다. +spell.ebwizardry:spark_bomb.desc=주변 적에게 전기 충격을 가하는 전기 덩어리를 던집니다. +spell.ebwizardry:spectral_pathway.desc=전방에 60초 동안 유지되는 파괴할 수 없는 마법의 다리를 만듭니다. +spell.ebwizardry:spider_swarm.desc=30초 동안 시전자를 위해 싸우는 독 거미 무리를 소환합니다. +spell.ebwizardry:static_aura.desc=시전자를 전기로 감싸 공격하는 적들을 지집니다. +spell.ebwizardry:summon_blaze.desc=30초 동안 시전자를 위해 싸우는 블레이즈를 소환합니다. +spell.ebwizardry:summon_ice_giant.desc="날려버려!" +spell.ebwizardry:summon_ice_wraith.desc=30초 동안 시전자를 위해 싸우는 얼어붙은 귀신을 소환합니다. +spell.ebwizardry:summon_iron_golem.desc="시스템, 정상 가동 중." +spell.ebwizardry:summon_lightning_wraith.desc=30초 동안 시전자를 위해 싸우는 번개의 귀신을 소환합니다. +spell.ebwizardry:summon_phoenix.desc="잿더미에서 다시 타오르리라." +spell.ebwizardry:summon_shadow_wraith.desc="어둠이, 함께한다..." +spell.ebwizardry:summon_skeleton.desc=30초 동안 시전자를 위해 싸우는 스켈레톤을 소환합니다. +spell.ebwizardry:summon_skeleton_legion.desc="어둠의 병사들이여! 명을 받들라!" +spell.ebwizardry:summon_snow_golem.desc=죽을때 까지 남아있는 눈 골렘을 소환합니다. +spell.ebwizardry:summon_spirit_horse.desc=조련된 유령마를 소환합니다. 유령마는 내린 후 잠시 뒤 사라지거나 아무 지팡이로 쉬프트-우클릭을 통해 송환할 수 있습니다. +spell.ebwizardry:summon_spirit_wolf.desc=죽을때 까지 시전자를 위해 싸우는 유령 늑대를 소환합니다. 아무 지팡이로 쉬프트-우클릭을 통해 송환할 수 있습니다. +spell.ebwizardry:summon_storm_elemental.desc="폭풍의 정령 : 원소의 순수한 형태. 그 힘을 억제할 수 없다." - 마법의 길 : 원소의 서 발췌 +spell.ebwizardry:summon_wither_skeleton.desc=30초 동안 시전자를 위해 싸우는 위더 스켈레톤을 소환합니다. +spell.ebwizardry:summon_zombie.desc=30초 동안 시전자를 위해 싸우는 좀비를 소환합니다. +spell.ebwizardry:telekinesis.desc=멀리 있는 아이템을 가져옵니다. +spell.ebwizardry:thunderbolt.desc=적을 밀치는 빠른 전기 구슬을 발사합니다. +spell.ebwizardry:thunderstorm.desc="으하하하하하하하!" +spell.ebwizardry:tornado.desc=앞으로 나아가는 회오리를 내보내, 적을 날려버립니다. +spell.ebwizardry:transience.desc=20초 동안 공허에 숨어 모든 피해를 무시합니다. +spell.ebwizardry:transportation.desc=전이의 진을 만들고 전이의 돌을 우클릭해 위치를 기록합니다. 다시 우클릭을 하면 귀환진으로 귀환합니다. +spell.ebwizardry:vanishing_box.desc=어디서든 엔더 상자를 열 수 있습니다. +spell.ebwizardry:wall_of_frost.desc="겨울이 온다..." +spell.ebwizardry:water_breathing.desc=수중 호흡 60초를 얻습니다. +spell.ebwizardry:whirlwind.desc=대상을 빠르게 당신에게서 밀쳐냅니다. +spell.ebwizardry:wither.desc=암흑의 광선을 쏘아 닿는 크리쳐에게 위더를 부여합니다. +spell.ebwizardry:wither_skull.desc=위더 폭탄을 발사합니다. + +spell.ebwizardry:invoke_weather.sun=뇌우가 물러갑니다. +spell.ebwizardry:invoke_weather.rain=뇌우가 몰려듭니다. +spell.ebwizardry:transportation.missing=귀환진이 사라졌거나 파괴되었습니다. +spell.ebwizardry:transportation.undefined=먼저 귀환진을 만드세요! +spell.ebwizardry:transportation.wrongdimension=귀환진이 다른 세계에 있습니다. +spell.ebwizardry:clairvoyance.searching=탐색중... +spell.ebwizardry:clairvoyance.confirm=이제 %1$s를 사용하면 이곳을 알려줍니다. +spell.ebwizardry:clairvoyance.outofrange=장소가 너무 멀거나 연결된 길이 없습니다. +spell.ebwizardry:clairvoyance.undefined=먼저 위치를 지정하세요! +spell.ebwizardry:clairvoyance.wrongdimension=장소가 다른 세계에 있습니다... + +potion.ebwizardry:frost=동상 +potion.ebwizardry:fireskin=불타는 피부 +potion.ebwizardry:ice_shroud=서리 가호 +potion.ebwizardry:static_aura=벼락의 기운 +potion.ebwizardry:transience=공허의 보호 +potion.ebwizardry:decay=부패하는 역병 +potion.ebwizardry:sixth_sense=제육감 +potion.ebwizardry:arcane_jammer=마법 방해 +potion.ebwizardry:mind_trick=정신착란 +potion.ebwizardry:mind_control=정신지배 +potion.ebwizardry:font_of_mana=마나의 축복 +potion.ebwizardry:fear=공포 + +enchantment.ebwizardry:magic_sword=마력 부여 +enchantment.ebwizardry:magic_bow=마력 부여 +enchantment.ebwizardry:flaming_weapon=불꽃의 마력 +enchantment.ebwizardry:freezing_weapon=서리의 마력 + +key.categories.ebwizardry=마법의 길 + +key.ebwizardry.next_spell=다음 주문 +key.ebwizardry.previous_spell=이전 주문 + +death.attack.wizardry_magic=%1$s(은)는 %2$s(이)가 사용한 마법에 살해당했습니다. +death.attack.indirect_wizardry_magic=%1$s(은)는 %2$s(이)가 사용한 마법에 살해당했습니다. + +commands.ebwizardry:cast.usage=/%1$s [player] [damage multiplier] [range multiplier] [duration multiplier] [blast multiplier] +commands.ebwizardry:cast.success=Successfully cast %1$s +commands.ebwizardry:cast.success_continuous=Successfully cast %1$s; repeat the command to stop +commands.ebwizardry:cast.success_remote=Successfully cast %1$s as %2$s +commands.ebwizardry:cast.success_remote_continuous=Successfully cast %1$s as %2$s; repeat the command to stop +commands.ebwizardry:cast.fail=Unable to cast %1$s +commands.ebwizardry:cast.not_found=There is no such spell with ID %1$s +commands.ebwizardry:cast.tag_error=Data tag parsing failed: %s + +commands.ebwizardry:ally.usage=/%1$s [player] +commands.ebwizardry:ally.addally=%1$s has been added to %2$s's list of allies +commands.ebwizardry:ally.removeally=%1$s has been removed from %2$s's list of allies +commands.ebwizardry:ally.self=Players cannot be an ally of themselves! +commands.ebwizardry:ally.permission=You do not have permission to change other players' allies + +commands.ebwizardry:allies.usage=/%1$s [player] +commands.ebwizardry:allies.list=Players allied to you: %1$s +commands.ebwizardry:allies.list_other=Players allied to %1$s: %2$s +commands.ebwizardry:allies.permission=You do not have permission to view other players' allies +commands.ebwizardry:allies.none=None + +commands.ebwizardry:discoverspell.usage=/%1$s [player] +commands.ebwizardry:discoverspell.not_found=There is no such spell with ID %1$s +commands.ebwizardry:discoverspell.clear=Cleared all spell discovery data for %1$s +commands.ebwizardry:discoverspell.all=Added all spells to %1$s's spell discovery data +commands.ebwizardry:discoverspell.addspell=Added %1$s to %2$s's spell discovery data +commands.ebwizardry:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data + +config.ebwizardry.title.general=Mod Options + +config.ebwizardry.category.gameplay=Gameplay Settings +config.ebwizardry.category.gameplay.tooltip=Configure wizardry's general gameplay +config.ebwizardry.title.gameplay=Gameplay Settings +config.ebwizardry.subtitle.gameplay=Global settings that affect game mechanics. + +config.ebwizardry.category.worldgen=World Generation Settings +config.ebwizardry.category.worldgen.tooltip=Configure wizardry's world generation features +config.ebwizardry.title.worldgen=World Generation Settings +config.ebwizardry.subtitle.worldgen=Settings that affect world generation. + +config.ebwizardry.category.commands=Command Settings +config.ebwizardry.category.commands.tooltip=Configure wizardry's commands +config.ebwizardry.title.commands=Command Settings +config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. + +config.ebwizardry.category.client=Client Settings +config.ebwizardry.category.client.tooltip=Configure wizardry's display and controls +config.ebwizardry.title.client=Client Settings +config.ebwizardry.subtitle.client=Client-side settings that only affect the local minecraft game. + +config.ebwizardry.category.spells=Spell Configuration +config.ebwizardry.category.spells.tooltip=Select which spells are enabled +config.ebwizardry.title.spells=Spell Configuration +config.ebwizardry.subtitle.spells=Set a spell to false to disable it. + +config.ebwizardry.category.resistances=Resistance Configuration +config.ebwizardry.category.resistances.tooltip=Configure which mobs are immune to different types of magic +config.ebwizardry.title.resistances=Resistance Configuration +config.ebwizardry.subtitle.resistances=Settings which allow entities to be made immune to certain types of magic. + +config.ebwizardry.tower_rarity=Tower Rarity +config.ebwizardry.ore_dimensions=Ore Dimensions +config.ebwizardry.flower_dimensions=Flower Dimensions +config.ebwizardry.tower_dimensions=Tower Dimensions +config.ebwizardry.spell_book_drop_chance=Spell Book Drop Chance +config.ebwizardry.generate_loot=Generate Loot +config.ebwizardry.firebomb_is_craftable=Firebomb Is Craftable +config.ebwizardry.poison_bomb_is_craftable=Poison Bomb Is Craftable +config.ebwizardry.smoke_bomb_is_craftable=Smoke Bomb Is Craftable +config.ebwizardry.use_alternate_scroll_recipe=Use Alternate Scroll Recipe +config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks +config.ebwizardry.show_summoned_creature_names=Show Summoned Creature Names +config.ebwizardry.friendly_fire=Friendly Fire +config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament +config.ebwizardry.discovery_mode=Discovery Mode +config.ebwizardry.enable_shift_scrolling=Enable Shift-scrolling +config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting +config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor +config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor +config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit +config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist +config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist +config.ebwizardry.spell_hud_position=Spell HUD Position +config.ebwizardry.cast_command_name=Cast Spell Command Name +config.ebwizardry.discoverspell_command_name=Discover Spell Command Name +config.ebwizardry.ally_command_name=Set Ally Command Name +config.ebwizardry.allies_command_name=View Allies Command Name +config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist +config.ebwizardry.evil_wizard_dimensions=Evil Wizard Dimensions + +config.ebwizardry.mobs_immune_to_fire=Mobs Immune To Fire +config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice +config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning +config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither +config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison + +config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely. +config.ebwizardry.ore_dimensions.tooltip=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! +config.ebwizardry.flower_dimensions.tooltip=List of dimension ids in which crystal flowers will generate. +config.ebwizardry.tower_dimensions.tooltip=List of dimension ids in which wizard towers will generate. +config.ebwizardry.spell_book_drop_chance.tooltip=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. +config.ebwizardry.generate_loot.tooltip=Whether to generate wizardry loot in dungeon chests. +config.ebwizardry.firebomb_is_craftable.tooltip=Whether firebombs can be crafted or not. +config.ebwizardry.poison_bomb_is_craftable.tooltip=Whether poison bombs can be crafted or not. +config.ebwizardry.smoke_bomb_is_craftable.tooltip=Whether smoke bombs can be crafted or not. +config.ebwizardry.use_alternate_scroll_recipe.tooltip=Whether to require a magic crystal in the shapeless crafting recipe for blank scrolls. Set to true if another mod adds a conflicting recipe. +config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. +config.ebwizardry.show_summoned_creature_names.tooltip=Whether to show summoned creatures' names and owners above their heads. +config.ebwizardry.friendly_fire.tooltip=Whether to allow players to damage their designated allies using magic. +config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items. +config.ebwizardry.discovery_mode.tooltip=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. +config.ebwizardry.enable_shift_scrolling.tooltip=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. +config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge attack their owner if their owner attacks them. +config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. +config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. +config.ebwizardry.cast_command_multiplier_limit.tooltip=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! +config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry:wizard). +config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry:wizard). +config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. +config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. +config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. +config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. +config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. +config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry:wizard). +config.ebwizardry.evil_wizard_dimensions.tooltip=List of dimension ids in which evil wizards can spawn. + +config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry:wizard). +config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry:wizard). + +wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/lang/ru_ru.lang b/src/main/resources/assets/ebwizardry/lang/ru_ru.lang index beef503c..0a5fdac1 100644 --- a/src/main/resources/assets/ebwizardry/lang/ru_ru.lang +++ b/src/main/resources/assets/ebwizardry/lang/ru_ru.lang @@ -1,174 +1,157 @@ -tile.ebwizardry:arcane_workbench.name=Чародейский стол -tile.ebwizardry:crystal_ore.name=Кристальная руда -tile.ebwizardry:petrified_stone.name=Окаменелый камень -tile.ebwizardry:ice_statue.name=Ледяная статуя -tile.ebwizardry:crystal_flower.name=Кристальный цветок +tile.ebwizardry:arcane_workbench.name=Чародейский Стол +tile.ebwizardry:crystal_ore.name=Кристальная Руда +tile.ebwizardry:petrified_stone.name=Окаменелый Камень +tile.ebwizardry:ice_statue.name=Ледяная Статуя +tile.ebwizardry:crystal_flower.name=Кристальный Цветок tile.ebwizardry:snare.name=Ловушка -tile.ebwizardry:transportation_stone.name=Камень транспортировки -tile.ebwizardry:spectral_block.name=Спектральный блок -tile.ebwizardry:crystal_block.name=Кристальный блок +tile.ebwizardry:transportation_stone.name=Камень Транспортировки +tile.ebwizardry:spectral_block.name=Спектральный Блок +tile.ebwizardry:crystal_block.name=Кристальный Блок -item.ebwizardry:magic_crystal.name=Магический кристалл -item.ebwizardry:magic_wand.name=Волшебный жезл -item.ebwizardry:apprentice_wand.name=Жезл ученика -item.ebwizardry:advanced_wand.name=Продвинутый жезл -item.ebwizardry:master_wand.name=Жезл мастера -item.ebwizardry:spell_book.name=Книга заклинаний +item.ebwizardry:magic_crystal.name=Магический Кристалл +item.ebwizardry:magic_wand.name=Волшебный Жезл +item.ebwizardry:apprentice_wand.name=Жезл Ученика +item.ebwizardry:advanced_wand.name=Продвинутый Жезл +item.ebwizardry:master_wand.name=Жезл Мастера +item.ebwizardry:spell_book.name=Книга Заклинаний item.ebwizardry:arcane_tome.name=Фолиант Арканы item.ebwizardry:arcane_tome.desc1=Улучшает любые %1$s item.ebwizardry:arcane_tome.desc2=жезл %1$s уровень -item.ebwizardry:wizard_handbook.name=Книга волшебника +item.ebwizardry:wizard_handbook.name=Книга Волшебника item.ebwizardry:wizard_handbook.desc=от %1$s item.ebwizardry:wand.buff=+%1$s %2$s эффективность -item.ebwizardry:wand.spell=Текущее заклинание: %1$s +item.ebwizardry:wand.spell=Текущее Заклинание: %1$s item.ebwizardry:wand.mana=Мана: %1$s/%2$s -item.ebwizardry:wand.addally=%1$s был добавлен в Ваш список союзников -item.ebwizardry:wand.removeally=%1$s был удален из Вашего списка союзников +item.ebwizardry:novice_fire_wand.name=Жезл Огня +item.ebwizardry:novice_ice_wand.name=Жезл Мороза +item.ebwizardry:novice_lightning_wand.name=Жезл Искр +item.ebwizardry:novice_necromancy_wand.name=Жезл Теней +item.ebwizardry:novice_earth_wand.name=Жезл Леса +item.ebwizardry:novice_sorcery_wand.name=Жезл Тайн +item.ebwizardry:novice_healing_wand.name=Жезл Лечения -item.ebwizardry:basic_fire_wand.name=Жезл огня -item.ebwizardry:basic_ice_wand.name=Жезл мороза -item.ebwizardry:basic_lightning_wand.name=Жезл искр -item.ebwizardry:basic_necromancy_wand.name=Жезл теней -item.ebwizardry:basic_earth_wand.name=Жезл леса -item.ebwizardry:basic_sorcery_wand.name=Жезл тайн -item.ebwizardry:basic_healing_wand.name=Жезл лечения +item.ebwizardry:apprentice_fire_wand.name=Жезл Ученика Пироманта +item.ebwizardry:apprentice_ice_wand.name=Жезл Ученика Ледяного Мага +item.ebwizardry:apprentice_lightning_wand.name=Жезл Ученика Штормового Мага +item.ebwizardry:apprentice_necromancy_wand.name=Жезл Ученика Некроманта +item.ebwizardry:apprentice_earth_wand.name=Жезл Ученика Земляного Мага +item.ebwizardry:apprentice_sorcery_wand.name=Жезл Ученика Колдуна +item.ebwizardry:apprentice_healing_wand.name=Жезл Ученика Целителя -item.ebwizardry:apprentice_fire_wand.name=Жезл ученика пироманта -item.ebwizardry:apprentice_ice_wand.name=Жезл ученика ледяного мага -item.ebwizardry:apprentice_lightning_wand.name=Жезл ученика штормового мага -item.ebwizardry:apprentice_necromancy_wand.name=Жезл ученика некроманта -item.ebwizardry:apprentice_earth_wand.name=Жезл ученика земляного мага -item.ebwizardry:apprentice_sorcery_wand.name=Жезл ученика колдуна -item.ebwizardry:apprentice_healing_wand.name=Жезл ученика целителя +item.ebwizardry:advanced_fire_wand.name=Жезл Пироманта +item.ebwizardry:advanced_ice_wand.name=Жезл Ледяного Мага +item.ebwizardry:advanced_lightning_wand.name=Жезл Штормового Мага +item.ebwizardry:advanced_necromancy_wand.name=Жезл Некроманта +item.ebwizardry:advanced_earth_wand.name=Жезл Земляного Мага +item.ebwizardry:advanced_sorcery_wand.name=Жезл Колдуна +item.ebwizardry:advanced_healing_wand.name=Жезл Целителя -item.ebwizardry:advanced_fire_wand.name=Жезл пироманта -item.ebwizardry:advanced_ice_wand.name=Жезл ледяного мага -item.ebwizardry:advanced_lightning_wand.name=Жезл штормового мага -item.ebwizardry:advanced_necromancy_wand.name=Жезл некроманта -item.ebwizardry:advanced_earth_wand.name=Жезл земляного мага -item.ebwizardry:advanced_sorcery_wand.name=Жезл колдуна -item.ebwizardry:advanced_healing_wand.name=Жезл целителя +item.ebwizardry:master_fire_wand.name=Жезл Мастера Пироманта +item.ebwizardry:master_ice_wand.name=Жезл Мастера Ледяного Мага +item.ebwizardry:master_lightning_wand.name=Жезл Мастера Штормового Мага +item.ebwizardry:master_necromancy_wand.name=Жезл Мастера Некроманта +item.ebwizardry:master_earth_wand.name=Жезл Мастера Земляного Мага +item.ebwizardry:master_sorcery_wand.name=Жезл Мастера Колдуна +item.ebwizardry:master_healing_wand.name=Жезл Мастера Целителя -item.ebwizardry:master_fire_wand.name=Жезл мастера пироманта -item.ebwizardry:master_ice_wand.name=Жезл мастера ледяного мага -item.ebwizardry:master_lightning_wand.name=Жезл мастера штормового мага -item.ebwizardry:master_necromancy_wand.name=Жезл мастера некроманта -item.ebwizardry:master_earth_wand.name=Жезл мастера земляного мага -item.ebwizardry:master_sorcery_wand.name=Жезл мастера колдуна -item.ebwizardry:master_healing_wand.name=Жезл мастера целителя +item.ebwizardry:spectral_sword.name=Спектральный Меч +item.ebwizardry:spectral_pickaxe.name=Спектральная Кирка +item.ebwizardry:spectral_bow.name=Спектральный Лук -item.ebwizardry:spectral_sword.name=Спектральный меч -item.ebwizardry:spectral_pickaxe.name=Спектральная кирка -item.ebwizardry:spectral_bow.name=Спектральный лук +item.ebwizardry:mana_flask.name=Бутылка Маны +item.ebwizardry:storage_upgrade.name=Улучшение Хранилища Маны Жезла +item.ebwizardry:siphon_upgrade.name=Улучшение Сифона Жезла +item.ebwizardry:condenser_upgrade.name=Улучшение Конденсатора Жезла +item.ebwizardry:range_upgrade.name=Улучшение Радиуса Жезла +item.ebwizardry:duration_upgrade.name=Улучшение Длительности Жезла +item.ebwizardry:cooldown_upgrade.name=Улучшение Кулдауна Жезла +item.ebwizardry:blast_upgrade.name=Улучшение Обновления Жезла +item.ebwizardry:attunement_upgrade.name=Улучшение Хранилища Заклинаний Жезла -item.ebwizardry:mana_flask.name=Бутылка маны -item.ebwizardry:storage_upgrade.name=Улучшение хранилища маны жезла -item.ebwizardry:siphon_upgrade.name=Улучшение сифона жезла -item.ebwizardry:condenser_upgrade.name=Улучшение конденсатора жезла -item.ebwizardry:range_upgrade.name=Улучшение радиуса жезла -item.ebwizardry:duration_upgrade.name=Улучшение длительности жезла -item.ebwizardry:cooldown_upgrade.name=Улучшение кулдауна жезла -item.ebwizardry:blast_upgrade.name=Улучшение обновления жезла -item.ebwizardry:attunement_upgrade.name=Улучшение хранилища заклинаний жезла +item.ebwizardry:flaming_axe.name=Огненный Топор +item.ebwizardry:frost_axe.name=Ледяной Топор -item.ebwizardry:flaming_axe.name=Огненный топор -item.ebwizardry:frost_axe.name=Ледяной топор +item.ebwizardry:firebomb.name=Огненная Бомба +item.ebwizardry:poison_bomb.name=Ядовитая Бомба +item.ebwizardry:smoke_bomb.name=Дымовая Бомба -item.ebwizardry:firebomb.name=Огненная бомба -item.ebwizardry:poison_bomb.name=Ядовитая бомба -item.ebwizardry:smoke_bomb.name=Дымовая бомба - -item.ebwizardry:blank_scroll.name=Пустой свиток +item.ebwizardry:blank_scroll.name=Пустой Свиток item.ebwizardry:scroll.name=Свиток %1$s -item.ebwizardry:scroll.undiscovered.name=Свиток "%1$s" -item.ebwizardry:identification_scroll.name=Свиток идентификации -item.ebwizardry:identification_scroll.desc1=%1$sИдентифицирует неизвестную -item.ebwizardry:identification_scroll.desc2=%1$sкнигу, заклинание или свиток -item.ebwizardry:identification_scroll.nothing_to_identify=Нечего идентифицировать +item.ebwizardry:identification_scroll.name=Свиток Идентификации +item.ebwizardry:identification_scroll.desc1=%1$sИдентифицирует неизвестное +item.ebwizardry:identification_scroll.desc2=%1$sкнигу заклинания или свиток +item.ebwizardry:identification_scroll.nothing_to_identify=Нечего Идентифицировать -item.ebwizardry:armour_upgrade.name=Печать магической защиты -item.ebwizardry:armour_upgrade.desc1=%1$sУлучшает любые одеяния мага +item.ebwizardry:armour_upgrade.name=Печать Магической Защиты +item.ebwizardry:armour_upgrade.desc1=%1$sУлучшает Любые Одеяния Мага item.ebwizardry:armour_upgrade.desc2=%1$sсделать это %2$sлегендарным -item.ebwizardry:magic_silk.name=Магический шёлк +item.ebwizardry:magic_silk.name=Магический Шёлк item.ebwizardry:wizard_armour.legendary=Легендарный item.ebwizardry:wizard_armour.buff=-%1$s %2$s стоимость item.ebwizardry:wizard_armour.mana=Мана: %1$s/%2$s -item.ebwizardry:wizard_hat.name=Шляпа волшебника -item.ebwizardry:wizard_robe.name=Роба волшебника -item.ebwizardry:wizard_leggings.name=Поножи волшебника -item.ebwizardry:wizard_boots.name=Ботинки волшебника +item.ebwizardry:wizard_hat.name=Шляпа Волшебника +item.ebwizardry:wizard_robe.name=Роба Волшебника +item.ebwizardry:wizard_leggings.name=Поножи Волшебника +item.ebwizardry:wizard_boots.name=Ботинки Волшебника -item.ebwizardry:wizard_hat_fire.name=Шляпа пироманта -item.ebwizardry:wizard_robe_fire.name=Роба пироманта -item.ebwizardry:wizard_leggings_fire.name=Поножи пироманта -item.ebwizardry:wizard_boots_fire.name=Ботинки пироманта +item.ebwizardry:wizard_hat_fire.name=Шляпа Пироманта +item.ebwizardry:wizard_robe_fire.name=Роба Пироманта +item.ebwizardry:wizard_leggings_fire.name=Поножи Пироманта +item.ebwizardry:wizard_boots_fire.name=Ботинки Пироманта -item.ebwizardry:wizard_hat_ice.name=Шляпа ледяного мага -item.ebwizardry:wizard_robe_ice.name=Роба ледяного мага -item.ebwizardry:wizard_leggings_ice.name=Поножи ледяного мага -item.ebwizardry:wizard_boots_ice.name=Ботинки ледяного мага +item.ebwizardry:wizard_hat_ice.name=Шляпа Ледяного Мага +item.ebwizardry:wizard_robe_ice.name=Роба Ледяного Мага +item.ebwizardry:wizard_leggings_ice.name=Поножи Ледяного Мага +item.ebwizardry:wizard_boots_ice.name=Ботинки Ледяного Мага -item.ebwizardry:wizard_hat_lightning.name=Шляпа штормового мага -item.ebwizardry:wizard_robe_lightning.name=Роба штормового мага -item.ebwizardry:wizard_leggings_lightning.name=Поножи штормового мага -item.ebwizardry:wizard_boots_lightning.name=Ботинки штормового мага +item.ebwizardry:wizard_hat_lightning.name=Шляпа Штормового Мага +item.ebwizardry:wizard_robe_lightning.name=Роба Штормового Мага +item.ebwizardry:wizard_leggings_lightning.name=Поножи Штормового Мага +item.ebwizardry:wizard_boots_lightning.name=Ботинки Штормового Мага -item.ebwizardry:wizard_hat_necromancy.name=Шляпа некроманта -item.ebwizardry:wizard_robe_necromancy.name=Роба некроманта -item.ebwizardry:wizard_leggings_necromancy.name=Поножи некроманта -item.ebwizardry:wizard_boots_necromancy.name=Ботинки некроманта +item.ebwizardry:wizard_hat_necromancy.name=Шляпа Некроманта +item.ebwizardry:wizard_robe_necromancy.name=Роба Некроманта +item.ebwizardry:wizard_leggings_necromancy.name=Поножи Некроманта +item.ebwizardry:wizard_boots_necromancy.name=Ботинки Некроманта -item.ebwizardry:wizard_hat_earth.name=Шляпа земляного мага -item.ebwizardry:wizard_robe_earth.name=Роба земляного мага -item.ebwizardry:wizard_leggings_earth.name=Поножи земляного мага -item.ebwizardry:wizard_boots_earth.name=Ботинки земляного мага +item.ebwizardry:wizard_hat_earth.name=Шляпа Земляного Мага +item.ebwizardry:wizard_robe_earth.name=Роба Земляного Мага +item.ebwizardry:wizard_leggings_earth.name=Поножи Земляного Мага +item.ebwizardry:wizard_boots_earth.name=Ботинки Земляного Мага -item.ebwizardry:wizard_hat_sorcery.name=Шляпа колдуна -item.ebwizardry:wizard_robe_sorcery.name=Роба колдуна -item.ebwizardry:wizard_leggings_sorcery.name=Поножи колдуна -item.ebwizardry:wizard_boots_sorcery.name=Ботинки колдуна +item.ebwizardry:wizard_hat_sorcery.name=Шляпа Колдуна +item.ebwizardry:wizard_robe_sorcery.name=Роба Колдуна +item.ebwizardry:wizard_leggings_sorcery.name=Поножи Колдуна +item.ebwizardry:wizard_boots_sorcery.name=Ботинки Колдуна -item.ebwizardry:wizard_hat_healing.name=Шляпа целителя -item.ebwizardry:wizard_robe_healing.name=Роба целителя -item.ebwizardry:wizard_leggings_healing.name=Поножи целителя -item.ebwizardry:wizard_boots_healing.name=Ботинки целителя +item.ebwizardry:wizard_hat_healing.name=Шляпа Целителя +item.ebwizardry:wizard_robe_healing.name=Роба Целителя +item.ebwizardry:wizard_leggings_healing.name=Поножи Целителя +item.ebwizardry:wizard_boots_healing.name=Ботинки Целителя -item.ebwizardry:spawn_wizard.name=Призвать существо - Волшебник -item.ebwizardry:spawn_evil_wizard.name=Призвать существо - Злой волшебник - -item.ebwizardry:spectral_helmet.name=Спектральный шлем -item.ebwizardry:spectral_chestplate.name=Спектральный нагрудник -item.ebwizardry:spectral_leggings.name=Спектральные поножи -item.ebwizardry:spectral_boots.name=Спектральные ботинки - -entity.ebwizardry:summonedcreature.nameplate=%1$s %2$s -entity.ebwizardry:summonedcreature.nameplate_fallback=Чей-то %1$s +item.ebwizardry:spawn_wizard.name=Спаун Волшебника entity.ebwizardry:zombie_minion.name=Зомби entity.ebwizardry:skeleton_minion.name=Скелет entity.ebwizardry:spider_minion.name=Паук -entity.ebwizardry:blaze_minion.name=Ифрит -entity.ebwizardry:wither_skeleton_minion.name=Скелет-иссушитель -entity.ebwizardry:ice_wraith.name=Ледяной призрак -entity.ebwizardry:lightning_wraith.name=Штормовой призрак -entity.ebwizardry:shadow_wraith.name=Теневой призрак -entity.ebwizardry:spirit_wolf.name=Призрачный волк -entity.ebwizardry:spirit_horse.name=Призрачная лошадь -entity.ebwizardry:ice_giant.name=Ледяной гигант +entity.ebwizardry:blaze_minion.name=Блейз +entity.ebwizardry:ice_wraith.name=Ледяной Призрак +entity.ebwizardry:lightning_wraith.name=Штормовой Призрак +entity.ebwizardry:shadow_wraith.name=Теневой Призрак +entity.ebwizardry:spirit_wolf.name=Призрачный Волк +entity.ebwizardry:spirit_horse.name=Призрачная Лошадь +entity.ebwizardry:ice_giant.name=Ледяной Гигант entity.ebwizardry:phoenix.name=Феникс entity.ebwizardry:wizard.name=Волшебник -entity.ebwizardry:magic_slime.name=Магический слизень +entity.ebwizardry:magic_slime.name=Магический Слизень entity.ebwizardry:silverfish_minion.name=Чешуйница -entity.ebwizardry:storm_elemental.name=Грозовой элементаль -entity.ebwizardry:evil_wizard.name=Волшебник -entity.ebwizardry:decoy.name=Приманка entity.ebwizardry:magic_missile.name=Магия entity.ebwizardry:arc.name=Магия @@ -190,89 +173,84 @@ entity.ebwizardry:dart.name=Магия entity.ebwizardry:lightning_disc.name=Магия entity.ebwizardry:thunderbolt.name=Магия entity.ebwizardry:decay.name=Магия -entity.ebwizardry:ice_lance.name=Магия -entity.ebwizardry:smoke_bomb.name=Магия -entity.ebwizardry:ice_spike.name=Магия -entity.ebwizardry:black_hole.name=Чёрная дыра +entity.ebwizardry:black_hole.name=Чёрная Дыра entity.ebwizardry:shield.name=Щит entity.ebwizardry:meteor.name=Метеор entity.ebwizardry:blizzard.name=Метель entity.ebwizardry:bubble.name=Пузыри entity.ebwizardry:tornado.name=Торнадо -entity.ebwizardry:lightning_hammer.name=Молот шторма -entity.ebwizardry:arrow_rain.name=Дождь стрел -entity.ebwizardry:healing_aura.name=Аура лечения -entity.ebwizardry:forcefield.name=Силовое поле -entity.ebwizardry:ring_of_fire.name=Кольцо огня -entity.ebwizardry:earthquake.name=Землетрясение -entity.ebwizardry:falling_grass.name=Падающая трава -entity.ebwizardry:hailstorm.name=Град -entity.ebwizardry:lightning_pulse.name=Импульс молнии +entity.ebwizardry:lightning_hammer.name=Молот Шторма +entity.ebwizardry:arrow_rain.name=Дождь Стрел +entity.ebwizardry:healing_aura.name=Аура Лечения +entity.ebwizardry:forcefield.name=Силовое Поле +entity.ebwizardry:ring_of_fire.name=Кольцо Огня + +item_group.ebwizardry=Волшебство +item_group.wizardryspells=Волшебные Заклинания + +advancement.ebwizardry:root=Wizardry +advancement.ebwizardry:root.desc=достижения +advancement.ebwizardry:crystal=Любопытный Кристалл... +advancement.ebwizardry:crystal.desc=Добудьте Магический Кристалл +advancement.ebwizardry:arcane_initiate=Посвящение В Магию +advancement.ebwizardry:arcane_initiate.desc=Создайте Волшебную Палочку Из Золотого Самородка И Магического Кристалла +advancement.ebwizardry:apprentice=Ученик Волшебника +advancement.ebwizardry:apprentice.desc=Используйте Фолиант Арканы Для Улучшения Жезла +advancement.ebwizardry:master=Мастер Магии +advancement.ebwizardry:master.desc=Получите Жезл Мастера +advancement.ebwizardry:all_spells=Маг Всех Профессий +advancement.ebwizardry:all_spells.desc=Используйте Каждое Заклинание В Игре +advancement.ebwizardry:wizard_trade=Магическая Сделка +advancement.ebwizardry:wizard_trade.desc=Купите Предмет У Волшебника +advancement.ebwizardry:buy_master_spell=Знание-сила +advancement.ebwizardry:buy_master_spell.desc=Купите заклинание мастера у волшебника +advancement.ebwizardry:freeze_blaze=Не Так Жарко +advancement.ebwizardry:freeze_blaze.desc=Заморозьте Блейза +advancement.ebwizardry:charge_creeper=Это Сейчас Взорвётся +advancement.ebwizardry:charge_creeper.desc="Случайно" Зарядите Крипира +advancement.ebwizardry:frankenstein=Франкенштейн +advancement.ebwizardry:frankenstein.desc=Привратите Свинью В Свинозомби С Помощью Молнии +advancement.ebwizardry:special_upgrade=Чародейское Ремесло +advancement.ebwizardry:special_upgrade.desc=Применить Специальное Улучшение Для Жезла +advancement.ebwizardry:craft_flask=Это Магия В Бутылке! +advancement.ebwizardry:craft_flask.desc=Создайте Ману В Бутылке +advancement.ebwizardry:elemental=Стихии +advancement.ebwizardry:elemental.desc=Получите Стихийный Жезл +advancement.ebwizardry:armour_set=Теперь Ты Настоящий Волшебник +advancement.ebwizardry:armour_set.desc=Создайте И Экипируйте Полный Набор Одеяния Волшебника +advancement.ebwizardry:legendary=Легендарный +advancement.ebwizardry:legendary.desc=Получите Часть Легендарного Одеяния Волшебника +advancement.ebwizardry:self_destruct=Обратно +advancement.ebwizardry:self_destruct.desc=Убейся Собственной Магией +advancement.ebwizardry:pig_tornado=Не Сейчас... +advancement.ebwizardry:pig_tornado.desc=Заедьте На Свинье В Торнадо +advancement.ebwizardry:max_out_wand=Полное Оснощение +advancement.ebwizardry:max_out_wand.desc=Примените максимальное количество улучшений к жезлу мастера +advancement.ebwizardry:slime_skeleton=Липкая Ситуация +advancement.ebwizardry:slime_skeleton.desc=Захватите скелета слизьнем +advancement.ebwizardry:jam_wizard=Помехи +advancement.ebwizardry:jam_wizard.desc=Используйте заклинание магическая глушилка на волшебнике +advancement.ebwizardry:identify_spell=Тайная Экспертиза +advancement.ebwizardry:identify_spell.desc=Используйте свиток идентификации для неизвестной книги заклинания или свитка +advancement.ebwizardry:element_master=Мастер Элементов +advancement.ebwizardry:element_master.desc=Используйте все заклинания любого элемента +advancement.ebwizardry:anger_wizard=Ты пожалеешь об этом +advancement.ebwizardry:anger_wizard.desc=Разозлите волшебника +advancement.ebwizardry:defeat_evil_wizard=Праведность +advancement.ebwizardry:defeat_evil_wizard.desc=Победите злого волшебника -itemGroup.ebwizardry=Wizardry -itemGroup.ebwizardryspells=Spells -advancement.wizardry:root=Колдовство -advancement.wizardry:root.desc=Путь волшебника к овладению тайной -advancement.wizardry:crystal=Любопытный кристалл... -advancement.wizardry:crystal.desc=Добудьте магический кристалл -advancement.wizardry:arcane_initiate=Посвящение в магию -advancement.wizardry:arcane_initiate.desc=Создайте волшебную палочку из золотого самородка и магического кристалла -advancement.wizardry:apprentice=Ученик Волшебника -advancement.wizardry:apprentice.desc=Используйте Фолиант Арканы для улучшения жезла -advancement.wizardry:master=Тайный мастер -advancement.wizardry:master.desc=Получите жезл мастера -advancement.wizardry:all_spells=Маг всех профессий -advancement.wizardry:all_spells.desc=Используйте каждое заклинание в игре -advancement.wizardry:wizard_trade=Магическая сделка -advancement.wizardry:wizard_trade.desc=Купите предмет у волшебника -advancement.wizardry:buy_master_spell=Знание-сила -advancement.wizardry:buy_master_spell.desc=Купите заклинание мастера у волшебника -advancement.wizardry:freeze_blaze=Не так жарко -advancement.wizardry:freeze_blaze.desc=Заморозьте ифрита -advancement.wizardry:charge_creeper=Это сейчас взорвётся -advancement.wizardry:charge_creeper.desc="Случайно" зарядите крипера -advancement.wizardry:frankenstein=Франкенштейн -advancement.wizardry:frankenstein.desc=Привратите свинью в свинозомби с помощью молнии -advancement.wizardry:special_upgrade=Чародейское ремесло -advancement.wizardry:special_upgrade.desc=Применить специальное улучшение для жезла -advancement.wizardry:craft_flask=Это магия в бутылке! -advancement.wizardry:craft_flask.desc=Создайте ману в бутылке -advancement.wizardry:elemental=Стихии -advancement.wizardry:elemental.desc=Получите стихийный жезл -advancement.wizardry:armour_set=Теперь Ты настоящий волшебник -advancement.wizardry:armour_set.desc=Создайте и экипируйте полный набор одеяния волшебника -advancement.wizardry:legendary=Легендарный -advancement.wizardry:legendary.desc=Получите часть легендарного одеяния волшебника -advancement.wizardry:self_destruct=Обратно -advancement.wizardry:self_destruct.desc=Убейся собственной магией -advancement.wizardry:pig_tornado=Не сейчас... -advancement.wizardry:pig_tornado.desc=Заедьте на свинье в торнадо -advancement.wizardry:jam_wizard=Помехи -advancement.wizardry:jam_wizard.desc=Используйте заклинание магическая глушилка на волшебнике -advancement.wizardry:slime_skeleton=Липкая ситуация -advancement.wizardry:slime_skeleton.desc=Захватите скелета слизьнем -advancement.wizardry:anger_wizard=Ты пожалеешь об этом -advancement.wizardry:anger_wizard.desc=Разозлите волшебника -advancement.wizardry:defeat_evil_wizard=Праведность -advancement.wizardry:defeat_evil_wizard.desc=Победите злого волшебника -advancement.wizardry:max_out_wand=Полностью оснащенный -advancement.wizardry:max_out_wand.desc=Примените максимальное количество улучшений к жезлу мастера -advancement.wizardry:element_master=Элемент мастерства -advancement.wizardry:element_master.desc=Используйте все заклинания любого элемента -advancement.wizardry:identify_spell=Тайная Экспертиза -advancement.wizardry:identify_spell.desc=Используйте свиток идентификации для неизвестной книги заклинания или свитка tile.ebwizardry:transportation_stone.confirm=Теперь вы будете возвращены сюда при использовании заклинания %1$s tile.ebwizardry:transportation_stone.invalid=Сначала вы должны сделать круг из 8 камней транспортировки! -container.ebwizardry:arcane_workbench=Чародейский стол -container.ebwizardry:arcane_workbench.apply=Принять +container.ebwizardry:arcane_workbench=Чародейский Стол +container.ebwizardry:arcane_workbench.apply=Применить container.ebwizardry:arcane_workbench.mana=Мана: -container.ebwizardry:arcane_workbench.upgrades=Применённые улучшения: +container.ebwizardry:arcane_workbench.upgrades=Применённые Улучшения: -tier.basic=Начинающий +tier.novice=Начинающий tier.apprentice=Ученик tier.advanced=Продвинутый tier.master=Мастер @@ -288,10 +266,10 @@ element.healing=Исцеление element.simple.wizard=Волшебник element.fire.wizard=Пиромант -element.ice.wizard=Ледяной маг -element.lightning.wizard=Штормовой маг +element.ice.wizard=Ледяной Мага +element.lightning.wizard=Штормовой Маг element.necromancy.wizard=Некромант -element.earth.wizard=Земляной маг +element.earth.wizard=Земляной Маг element.sorcery.wizard=Колдун element.healing.wizard=Целитель @@ -300,299 +278,298 @@ spelltype.defence=Защита spelltype.utility=Утилита spelltype.minion=Миньон -spell.disabled=%1$s был отключен в конфиге -spell.resist=%1$s сопротивлялся %2$s -spell.discover=Обнаружил заклинание %1$s! +spell.disabled=%1$s было отключенно в конфиге spell.ebwizardry:agility=Ловкость spell.ebwizardry:arc=Дуга -spell.ebwizardry:arcane_jammer=Магическая глушилка -spell.ebwizardry:arrow_rain=Дождь стрел -spell.ebwizardry:banish=Изгнание -spell.ebwizardry:black_hole=Чёрная дыра +spell.ebwizardry:arrow_rain=Дождь Стрел +spell.ebwizardry:black_hole=Чёрная Дыра spell.ebwizardry:blink=Блинк spell.ebwizardry:blizzard=Метель spell.ebwizardry:bubble=Пузыри -spell.ebwizardry:chain_lightning=Цепная молния -spell.ebwizardry:clairvoyance=Ясновидение -spell.ebwizardry:cobwebs=Паутины -spell.ebwizardry:conjure_armour=Призвать броню -spell.ebwizardry:conjure_bow=Призвать лук -spell.ebwizardry:conjure_pickaxe=Призвать кирку -spell.ebwizardry:conjure_sword=Призвать меч -spell.ebwizardry:cure_effects=Эффект лечения -spell.ebwizardry:curse_of_soulbinding=Проклятие связанной души -spell.ebwizardry:darkness_orb=Тёмная сфера -spell.ebwizardry:darkvision=Тёмное зрение +spell.ebwizardry:chain_lightning=Цепная Молния +spell.ebwizardry:conjure_bow=Призвать Лук +spell.ebwizardry:conjure_pickaxe=Призвать Кирку +spell.ebwizardry:conjure_sword=Призвать Меч +spell.ebwizardry:cure_effects=Эффект Лечения +spell.ebwizardry:darkness_orb=Тёмная Сфера spell.ebwizardry:dart=Дротик spell.ebwizardry:decay=Гниение -spell.ebwizardry:decoy=Приманка spell.ebwizardry:detonate=Взрыв -spell.ebwizardry:diamondflesh=Алмазная плоть -spell.ebwizardry:earthquake=Землетрясение +spell.ebwizardry:diamondflesh=Алмазная Кожа spell.ebwizardry:entrapment=Захват -spell.ebwizardry:fireball=Огненный шар -spell.ebwizardry:firebolt=Огненный болт -spell.ebwizardry:firebomb=Огенная бомба -spell.ebwizardry:fire_resistance=Огнестойкость -spell.ebwizardry:fire_sigil=Огненная печать -spell.ebwizardry:fireskin=Огненная кожа -spell.ebwizardry:firestorm=Огненный шторм -spell.ebwizardry:flame_ray=Дождь огня -spell.ebwizardry:flaming_axe=Огненный топор -spell.ebwizardry:flaming_weapon=Огненное оружие +spell.ebwizardry:fireball=Огненный Шар +spell.ebwizardry:firebolt=Огненный Болт +spell.ebwizardry:firebomb=Огенная Бомба +spell.ebwizardry:fire_resistance=Огестойкость +spell.ebwizardry:fire_sigil=Огненная Печать +spell.ebwizardry:fireskin=Огненная Кожа +spell.ebwizardry:firestorm=Огненный Шторм +spell.ebwizardry:flame_ray=Дождь Огня +spell.ebwizardry:flaming_axe=Огненный Топор spell.ebwizardry:flight=Полёт -spell.ebwizardry:font_of_mana=Купель маны -spell.ebwizardry:font_of_vitality=Источник жизненной силы -spell.ebwizardry:force_arrow=Силовая стрела -spell.ebwizardry:forcefield=Силовое поле -spell.ebwizardry:force_orb=Силовая сфера -spell.ebwizardry:forests_curse=Проклятие леса +spell.ebwizardry:font_of_vitality=Источник Жизненной Силы +spell.ebwizardry:force_arrow=Силовая Стрела +spell.ebwizardry:forcefield=Силовое Поле +spell.ebwizardry:force_orb=Силовая Сфера +spell.ebwizardry:forests_curse=Проклятие Леса spell.ebwizardry:freeze=Замораживание -spell.ebwizardry:freezing_weapon=Ледяное оружие -spell.ebwizardry:frost_axe=Ледяной топор -spell.ebwizardry:frost_ray=Ледяной луч -spell.ebwizardry:frost_sigil=Ледяная печать +spell.ebwizardry:mind_trick=Обман Разума +spell.ebwizardry:frost_axe=Ледяной Топор +spell.ebwizardry:frost_ray=Ледяной Луч +spell.ebwizardry:frost_sigil=Ледяная Печать spell.ebwizardry:glide=Скольжение -spell.ebwizardry:greater_fireball=Большой огненный шар -spell.ebwizardry:greater_heal=Великое исцеление -spell.ebwizardry:group_heal=Массовое исцеление -spell.ebwizardry:growth_aura=Аура роста -spell.ebwizardry:hailstorm=Град +spell.ebwizardry:greater_heal=Великое Исцеление +spell.ebwizardry:growth_aura=Аура Роста spell.ebwizardry:heal=Исцеление -spell.ebwizardry:heal_ally=Исцеление союзника -spell.ebwizardry:healing_aura=Аура исцеления -spell.ebwizardry:homing_spark=Самонаводящаяся искра -spell.ebwizardry:ice_age=Ледниковый период -spell.ebwizardry:ice_charge=Ледяной заряд -spell.ebwizardry:ice_lance=Ледяное копье -spell.ebwizardry:ice_shard=Ледяной осколок -spell.ebwizardry:ice_shroud=Ледяной покров -spell.ebwizardry:ice_spikes=Ледяные шипы -spell.ebwizardry:ice_statue=Ледяная статуя +spell.ebwizardry:heal_ally=Исцеление Союзника +spell.ebwizardry:healing_aura=Аура Исцеления +spell.ebwizardry:homing_spark=Самонаводящаяся Искра +spell.ebwizardry:ice_age=Ледниковый Период +spell.ebwizardry:ice_charge=Ледяной Заряд +spell.ebwizardry:ice_shard=Ледяной Осколок +spell.ebwizardry:ice_shroud=Ледяная Кожа +spell.ebwizardry:ice_statue=Ледяная Статуя spell.ebwizardry:ignite=Воспламенение -spell.ebwizardry:imbue_weapon=Зачарованное оружие -spell.ebwizardry:intimidate=Устрашение -spell.ebwizardry:invigorating_presence=Воодушевление spell.ebwizardry:invisibility=Невидимость -spell.ebwizardry:invoke_weather=Вызов погоды -spell.ebwizardry:ironflesh=Железная плоть -spell.ebwizardry:leap=Прыжок +spell.ebwizardry:invoke_weather=Вызов Погоды +spell.ebwizardry:ironflesh=Железная Кожа spell.ebwizardry:levitation=Левитация -spell.ebwizardry:life_drain=Похищение жизни +spell.ebwizardry:life_drain=Похищение Жизни spell.ebwizardry:light=Свет -spell.ebwizardry:lightning_arrow=Стрела молнии -spell.ebwizardry:lightning_bolt=Удар молнии -spell.ebwizardry:lightning_disc=Диск молнии -spell.ebwizardry:lightning_hammer=Молот молнии -spell.ebwizardry:lightning_pulse=Импульс молнии -spell.ebwizardry:lightning_ray=Луч молнии -spell.ebwizardry:lightning_sigil=Печать молнии -spell.ebwizardry:lightning_web=Поток молнии -spell.ebwizardry:magic_missile=Магическая ракета +spell.ebwizardry:lightning_arrow=Стрела Молнии +spell.ebwizardry:lightning_bolt=Удар Молнии +spell.ebwizardry:lightning_disc=Диск Молнии +spell.ebwizardry:lightning_hammer=Молот Молнии +spell.ebwizardry:lightning_ray=Луч Молнии +spell.ebwizardry:lightning_sigil=Печать Молнии +spell.ebwizardry:magic_missile=Магическая Ракета spell.ebwizardry:metamorphosis=Превращение spell.ebwizardry:meteor=Метеор -spell.ebwizardry:mind_control=Контроль разума -spell.ebwizardry:mind_trick=Ментальный фокус -spell.ebwizardry:none=[Пустой слот] -spell.ebwizardry:oakflesh=Дубовая плоть +spell.ebwizardry:mind_control=Контроль Разума +spell.ebwizardry:none=[Пустой Слот] spell.ebwizardry:petrify=Окаменение -spell.ebwizardry:phase_step=Фазовый шаг -spell.ebwizardry:plague_of_darkness=Чума темноты -spell.ebwizardry:pocket_furnace=Карманная печь -spell.ebwizardry:pocket_workbench=Карманный верстак +spell.ebwizardry:phase_step=Фазовый Шаг +spell.ebwizardry:plague_of_darkness=Чума Темноты spell.ebwizardry:poison=Отравление -spell.ebwizardry:poison_bomb=Ядовитая бомба +spell.ebwizardry:poison_bomb=Ядовитая Бомба spell.ebwizardry:replenish_hunger=Насыщение -spell.ebwizardry:ring_of_fire=Кольцо огня -spell.ebwizardry:shadow_ward=Теневая защита +spell.ebwizardry:ring_of_fire=Кольцо Огня +spell.ebwizardry:shadow_ward=Теневая Защита spell.ebwizardry:shield=Щит -spell.ebwizardry:shockwave=Ударная волна -spell.ebwizardry:silverfish_swarm=Рой чешуйниц -spell.ebwizardry:sixth_sense=Шестое чувство +spell.ebwizardry:shockwave=Ударная Волна +spell.ebwizardry:silverfish_swarm=Рой Чешуйниц spell.ebwizardry:slime=Слизь -spell.ebwizardry:smoke_bomb=Дымовая бомба spell.ebwizardry:snare=Ловушка spell.ebwizardry:snowball=Снежок -spell.ebwizardry:spark_bomb=Бомба искр -spell.ebwizardry:spectral_pathway=Спектральный путь -spell.ebwizardry:spider_swarm=Рой пауков -spell.ebwizardry:static_aura=Статическая аура -spell.ebwizardry:summon_blaze=Призыв ифрита -spell.ebwizardry:summon_ice_giant=Призыв ледяного гиганта -spell.ebwizardry:summon_ice_wraith=Призыв ледяного призрака -spell.ebwizardry:summon_iron_golem=Призыв железного голема -spell.ebwizardry:summon_lightning_wraith=Призыв штормового призрака -spell.ebwizardry:summon_phoenix=Призыв феникса -spell.ebwizardry:summon_shadow_wraith=Призыв тёмного призрака -spell.ebwizardry:summon_skeleton=Призыв скелета -spell.ebwizardry:summon_skeleton_legion=Призыв легиона скелета -spell.ebwizardry:summon_snow_golem=Призыв снеговика -spell.ebwizardry:summon_spirit_horse=Призыв призрачной оошади -spell.ebwizardry:summon_spirit_wolf=Призыв призрачного волка -spell.ebwizardry:summon_storm_elemental=Призыв грозового элементаля -spell.ebwizardry:summon_wither_skeleton=Призыв скелета-иссушителя -spell.ebwizardry:summon_zombie=Призыв зомби +spell.ebwizardry:smoke_bomb=Дымовая Бомба +spell.ebwizardry:spark_bomb=Бомба Искр +spell.ebwizardry:spectral_pathway=Спектральный Путь +spell.ebwizardry:spider_swarm=Рой Пауков +spell.ebwizardry:static_aura=Статическая Аура +spell.ebwizardry:summon_blaze=Призыв Блейза +spell.ebwizardry:summon_ice_giant=Призыв Ледяного Гиганта +spell.ebwizardry:summon_ice_wraith=Призыв Ледяного Призрака +spell.ebwizardry:summon_iron_golem=Призыв Железного Голема +spell.ebwizardry:summon_lightning_wraith=Призыв Штормового Призрака +spell.ebwizardry:summon_phoenix=Призыв Феникса +spell.ebwizardry:summon_shadow_wraith=Призыв Тёмного Призрака +spell.ebwizardry:summon_skeleton=Призыв Скелета +spell.ebwizardry:summon_skeleton_legion=Призыв Легиона Скелета +spell.ebwizardry:summon_snow_golem=Призыв Снеговика +spell.ebwizardry:summon_spirit_horse=Призыв Призрачной Лошади +spell.ebwizardry:summon_spirit_wolf=Призыв Призрачного Волка +spell.ebwizardry:summon_wither_skeleton=Призыв Скелета-Иссушителя +spell.ebwizardry:summon_zombie=Призыв Зомби spell.ebwizardry:telekinesis=Телекинез -spell.ebwizardry:thunderbolt=Штормовой удар +spell.ebwizardry:thunderbolt=Штормовой Удар spell.ebwizardry:thunderstorm=Гроза spell.ebwizardry:tornado=Торнадо spell.ebwizardry:transience=Быстрота spell.ebwizardry:transportation=Транспортировка -spell.ebwizardry:vanishing_box=Исчезающая коробка -spell.ebwizardry:wall_of_frost=Стена мороза -spell.ebwizardry:water_breathing=Дыхание под водой +spell.ebwizardry:vanishing_box=Исчезающая Коробка +spell.ebwizardry:wall_of_frost=Стена Мороза +spell.ebwizardry:water_breathing=Дыхание Под Водой spell.ebwizardry:whirlwind=Вихрь spell.ebwizardry:wither=Иссушение -spell.ebwizardry:wither_skull=Череп иссушителя +spell.ebwizardry:wither_skull=Череп Иссушителя +spell.ebwizardry:leap=Прыжок +spell.ebwizardry:pocket_furnace=Карманная Печь +spell.ebwizardry:intimidate=Устрашение +spell.ebwizardry:banish=Изгнание +spell.ebwizardry:sixth_sense=Шестое Чувство +spell.ebwizardry:darkvision=Тёмное Зрение +spell.ebwizardry:imbue_weapon=Зачарованное Оружие +spell.ebwizardry:pocket_workbench=Карманный Верстак +spell.ebwizardry:clairvoyance=Ясновидение +spell.ebwizardry:invigorating_presence=Воодушевление +spell.ebwizardry:oakflesh=Дубовая Плоть +spell.ebwizardry:flaming_weapon=Пылающее Оружие +spell.ebwizardry:greater_fireball=Большой Огненный Шар +spell.ebwizardry:ice_lance=Ледяное Копье +spell.ebwizardry:freezing_weapon=Ледяное Оружие +spell.ebwizardry:ice_spikes=Ледяные Шипы +spell.ebwizardry:lightning_pulse=Импульс Молнии +spell.ebwizardry:curse_of_soulbinding=Проклятие Свзязанной Души +spell.ebwizardry:decoy=Приманка +spell.ebwizardry:arcane_jammer=Магическая Глушилка +spell.ebwizardry:conjure_armour=Призывная Броня +spell.ebwizardry:group_heal=Массовое Исцеление +spell.ebwizardry:summon_storm_elemental=Призыв Грозового Элементаля +spell.ebwizardry:lightning_web=Поток Молнии +spell.ebwizardry:font_of_mana=Купель Маны +spell.ebwizardry:earthquake=Землетрясение +spell.ebwizardry:hailstorm=Град -spell.ebwizardry:agility.desc=Дает заклинателю более высокую скорость передвижения и большой прыжок на 30 секунд. -spell.ebwizardry:arc.desc=Выстреливает искрой молнии в цель -spell.ebwizardry:arcane_jammer.desc=Запрещает использовать магию цели в течение 15 секунд. -spell.ebwizardry:arrow_rain.desc="Лучники, огонь!" -spell.ebwizardry:banish.desc=Телепортирует цель против своей воли в случайное место в пределах определенного диапазона. -spell.ebwizardry:black_hole.desc=Разрывай реальность на части -spell.ebwizardry:blink.desc=Телепортирует заклинателя в любой блок, на который он указывает, в определенном радиусе. -spell.ebwizardry:blizzard.desc=Создает зону закрученного ледяного ветра, которая замедляет и постоянно повреждает все, что попало внутрь. Заклинатель невосприимчив к урону, но все еще замедляется. -spell.ebwizardry:bubble.desc=Стреляет потоком пузырьков, который помещает существо в большой пузырь, при этом поднимает его. Цель будет падать или просто получит урон. -spell.ebwizardry:chain_lightning.desc=Выстреливает искру молнии у цели, которая затем цепляется к дополнительным целям до двух раз. -spell.ebwizardry:clairvoyance.desc=Показывает путь к запоминающемуся местоположению. Когда заклинание выбрано, присесть-правая кнопка мыши на блок, чтобы установить местоположение. Используйте заклинание, чтобы показать путь. Путь исчезнет через 90 секунд. -spell.ebwizardry:cobwebs.desc=Создает паутину, на которую вы указываете, что значительно затрудняет движение любых существ, пойманных среди них. Паутина исчезнет через 20 секунд или если сломается. -spell.ebwizardry:conjure_armour.desc=Создаёт спектральную броню вокруг заклинателя равную по защите железной. Заклинание длится 60 секунд. Заклинатель должен иметь пустой слот для брони. -spell.ebwizardry:conjure_bow.desc=Создает спектральный лук с неограниченными стрелами, который длится 30 секунд. -spell.ebwizardry:conjure_pickaxe.desc=Создает спектральную кирку равной силы железной кирке, которая длится 30 секунд. -spell.ebwizardry:conjure_sword.desc=Создает спектральный меч одинаковой силы с железным мечом, который длится 30 секунд. -spell.ebwizardry:cure_effects.desc=Удаляет все эффекты зелий, которые в настоящее время воздействуют на заклинателя, хорошие или плохие. -spell.ebwizardry:curse_of_soulbinding.desc=Связывает души заклинателя и существа в следствии чего существо получает тот же урон, что и заклинатель. Длится до тех пор, пока существо или заклинатель не умрёт. -spell.ebwizardry:darkness_orb.desc=Выстреливает медленно перемещающимся болтом тёмной энергии в направлении, на которое вы указываете, которое вызывает гниение всего, что попадает. -spell.ebwizardry:darkvision.desc=Дает заклинателю ночное зрение в течение 45 секунд. -spell.ebwizardry:dart.desc=Выстреливает дротик в том направлении, в котором вы указываете, которое наносит урон и ослабляет его цель. -spell.ebwizardry:decay.desc=Создает пятно гнили на земле, которое заражает любое существо, которое его касается, вызывая длительный урон со временем и распространяя больше гнили, где бы он ни ходил. -spell.ebwizardry:decoy.desc=Создает иллюзорный клон заклинателя, который заставляет мобов атаковать его. Приманка исчезнет через 30 секунд. -spell.ebwizardry:detonate.desc=Вызывает взрыв, в указанном месте, нанося урон всем находящимся поблизости существам, включая заклинателя, если они слишком близко. -spell.ebwizardry:diamondflesh.desc="Твои стрелы мне не подходят!" + + + +spell.ebwizardry:agility.desc=Дает Заклинателю Более Высокую Скорость Передвижения И Большой Прыжок на 30 Секунд. +spell.ebwizardry:hailstorm.desc=Именно во время Великой зимы третьего века маги льда открыли свою истинную силу. spell.ebwizardry:earthquake.desc=Настоящий мастер магии земли может двигать горы. -spell.ebwizardry:entrapment.desc=Заманивает цель в сферу темноты, которая поднимает её вверх и постоянно её повреждает. -spell.ebwizardry:fireball.desc=Запускает огненный шар в том направлении, в которое вы указали. -spell.ebwizardry:firebolt.desc=Стреляет на небольшом расстоянии от вас. -spell.ebwizardry:firebomb.desc=Бросает зажигательную бомбу в направлении, которое вы указываете, которая взрывается при ударе, поджигая цели. -spell.ebwizardry:fire_resistance.desc=Даёт сопротивление огню на 30% -spell.ebwizardry:fire_sigil.desc=Помещает магическую ловушку огня на землю, которая наносит урон и поджигает существо, которое наступило на ловушку -spell.ebwizardry:fireskin.desc=Покрывает заклинателя огнём на 30 секунд. Если заклинатель будет атакован, то враг загорится. -spell.ebwizardry:firestorm.desc="Я - дракон." -spell.ebwizardry:flame_ray.desc=Создает поток пламени в направлении, на которое вы указали, который поджигает и постоянно повреждает цели. -spell.ebwizardry:flaming_axe.desc=Создает огненный топор, который поджигает врагов при попадании. Длится 30 секунд. -spell.ebwizardry:flaming_weapon.desc=Временно наполняет первое оружие панели заклинателя силой пламени, заставляя его поджигать своих жертв. Магия исчезает через 45 секунд. -spell.ebwizardry:flight.desc=Парящий, как орёл. spell.ebwizardry:font_of_mana.desc="Мы были наполнены интенсивной магической энергией, появляющейся из центра...- Выписка из дневника забытого мага; остальная часть страницы сгорела. -spell.ebwizardry:font_of_vitality.desc=Это потрясающе. -spell.ebwizardry:force_arrow.desc=Стреляет стрелой силы в направлении, на которое вы указываете. -spell.ebwizardry:forcefield.desc=Создает силовое поле вокруг заклинателя, который отталкивает существ и отклоняет снаряды. -spell.ebwizardry:force_orb.desc=Запускает сферу силы, которая наносит урон и отбрасывает близких существ при ударе. -spell.ebwizardry:forests_curse.desc="Как ты посмел войти в мой лес?!" -spell.ebwizardry:freeze.desc=Замораживает цель на 10 секунд. Также заморозит воду и создаст снег на земле. -spell.ebwizardry:freezing_weapon.desc=Временно наполняет первое оружие на панели заклинателя силой льда, заставляя его замораживать своих жертв. Магия исчезает через 45 секунд. -spell.ebwizardry:frost_axe.desc=Создает ледяной топор, который замораживает врагов при попадании. Длится 30 секунд. -spell.ebwizardry:frost_ray.desc=Создает поток мороза в направлении, на которое вы указываете, который замедляет и постоянно повреждает цели. -spell.ebwizardry:frost_sigil.desc=Помещает магическую ледяную ловушку на землю, которая наносит урон и замораживает существо, которое наступило на неё. -spell.ebwizardry:glide.desc=Позволяет заклинателю скользить вниз, находясь в воздухе, удерживая кнопку использования предмета. -spell.ebwizardry:greater_fireball.desc=Запускает большой огненный шар в направлении вашего курсора, который взрывается при ударе. -spell.ebwizardry:greater_heal.desc=Восстанавливает заклинателю 4 сердца. -spell.ebwizardry:group_heal.desc=Восстанавливает 3 сердца заклинателю и всем близлежащим союзникам и призванным существам. -spell.ebwizardry:growth_aura.desc=Выращивает все зерновые культуры рядом с заклинателем. -spell.ebwizardry:hailstorm.desc=Именно во время великой зимы третьего века маги льда открыли свою истинную силу. -spell.ebwizardry:heal.desc=Исцеляет заклинателя на 2 сердца. -spell.ebwizardry:heal_ally.desc=Восстанавливает цели 2,5 сердца. -spell.ebwizardry:healing_aura.desc=Создает зону исцеляющей энергии, которая восстанавливает здоровье кого-либо внутри него, кроме нежити, которая медленно получает урон. -spell.ebwizardry:homing_spark.desc=Создает плавающую искру, которая движется к врагам. -spell.ebwizardry:ice_age.desc="Вы замерзнете навсегда!" -spell.ebwizardry:ice_charge.desc=Запускает заряд льда, который взрывается при ударе, замораживая близлежащих существ и выпуская осколки во всех направлениях. -spell.ebwizardry:ice_lance.desc=Стреляет большим копьём льда в направлении вашего курсора,которе замедляет врага и наносит урон. -spell.ebwizardry:ice_shard.desc=Выстреливает ледяной осколок в том направлении, в котором вы указали, который наносит урон и замедляет цель при попадании. -spell.ebwizardry:ice_shroud.desc=Окутывает заклинателя во льду на 30 секунд, замораживая все, что попадает или попадает. -spell.ebwizardry:ice_spikes.desc=Призывает бритвенно-острые ледяные шипы из-под земли в точке вашего курсора, которые впиваются в любых существ и сдерживают их. -spell.ebwizardry:ice_statue.desc=Замораживает цель на 20 секунд или до тех пор, пока она не вырвется наружу. Цель не может двигаться или делать что-либо в замороженном состоянии, но также невосприимчива к любому урону. -spell.ebwizardry:ignite.desc=Устанавливает огонь в течение 10 секунд. Также работает как кремень и сталь. -spell.ebwizardry:imbue_weapon.desc=Временно наполняет магией первое оружие на панели заклинателя, делая его более эффективным. Магия исчезает через 45 секунд. -spell.ebwizardry:intimidate.desc=Испускает устрашающее рычание, которое заставляет близлежащих существ убегать в страхе. Страх пораженных существ восстановится через 30 секунд. -spell.ebwizardry:invigorating_presence.desc=Дарует заклинателю и всем ближайшим союзникам повышенную силу в течение 45 секунд. -spell.ebwizardry:invisibility.desc=Делает заклинателя невидимым в течении 30 секунд. -spell.ebwizardry:invoke_weather.desc=Изменяет погоду в мире. -spell.ebwizardry:ironflesh.desc=Увеличивает сопротивление к оглушению заклинателя на 30 секунд. -spell.ebwizardry:leap.desc=Заставляет заклинателя прыгать вверх на несколько блоков и немного вперед. -spell.ebwizardry:levitation.desc=Поднимает заклинателя вверх при нажатой кнопке использования предмета. Также будет нейтрализован урон от падения, если он используется до удара об землю. -spell.ebwizardry:life_drain.desc=Создает поток иссушающей энергии в направлении, на которое вы указываете, который истощает жизнь цели и использует её для постепенного восстановления здоровья. -spell.ebwizardry:light.desc=Создает магическую точку света, которая освещает окружающую область. Длится 30 секунд. -spell.ebwizardry:lightning_arrow.desc=Стреляет стрелой молнии в направлении, на которое вы указываете. -spell.ebwizardry:lightning_bolt.desc=Заставляет молнию ударить туда, куда вы указываете. -spell.ebwizardry:lightning_disc.desc=Посылает диск молнии, летящий в направлении, на которое вы указали и ищет цели. -spell.ebwizardry:lightning_hammer.desc="Я поражу тебя гневом небес!" -spell.ebwizardry:lightning_pulse.desc=Заряжает землю вокруг заклинателя молнией, повреждая и отталкивая близлежащих существ. -spell.ebwizardry:lightning_ray.desc=Создает поток молнии в направлении, на которое вы указываете, который постоянно повреждает цели. -spell.ebwizardry:lightning_sigil.desc=Помещает магическую молниеносную ловушку на землю, которая наносит урон существу, которое наступило на ловушку, и приковывает молнии к другим близлежащим существам. spell.ebwizardry:lightning_web.desc="Сфокусируйтесь. Направьте шторм в вашем разуме через вашу палочку и дайте волю своей ярости." -spell.ebwizardry:magic_missile.desc=Выстреливает магический заряд в том направлении, в котором вы указали. -spell.ebwizardry:metamorphosis.desc=Изменяет цель в другую форму. работает только на некоторых существах. -spell.ebwizardry:meteor.desc=Некоторые волшебники просто хотят, чтобы мир горел... -spell.ebwizardry:mind_control.desc=Контролирует ум цели, заставляя её атаковать ближайшую живую цель, отличную от заклинателя. Когда это существо будет убито, цель вернется к нормальной жизни. -spell.ebwizardry:mind_trick.desc=Путает и дезориентирует цель в течение 15 секунд, что делает его не в состоянии атаковать. Эффект будет рассеян, если цель получит урон. -spell.ebwizardry:none.desc=Чтобы получить книгу заклинаний с командой /give, используйте метаданные: /give [player] ebwizardry:spell_book 1 [spell id] (если вы нашли эту книгу в сундуке, какой-то другой мод всё испортил). +spell.ebwizardry:summon_storm_elemental.desc="Грозовой Элементаль: древнее проявление элементов, он вряд ли может содержать грубую силу, сбивающуюся внутри него."- Руководство мастера по тайным существам, том I_i +spell.ebwizardry:group_heal.desc=Восстанавливает 3 сердца заклинателю и всем близлежащим союзникам и призванным существам. +spell.ebwizardry:conjure_armour.desc=Создаёт спектральную броню вокруг заклинателя равную по защите железной. Заклинание длится 60 секунд. Заклинатель должен иметь пустой слот для брони. +spell.ebwizardry:arcane_jammer.desc=Запрещает использовать магию цели в течение 15 секунд. +spell.ebwizardry:decoy.desc=Создает иллюзорный клон заклинателя, который заставляет мобов атаковать его. Приманка исчезнет через 30 секунд. +spell.ebwizardry:curse_of_soulbinding.desc=Связывает души заклинателя и существа в следствии чего существо получает тот же урон, что и заклинатель. Длится до тех пор, пока существо или заклинатель не умрёт. +spell.ebwizardry:lightning_pulse.desc=Заряжает землю вокруг заклинателя молнией, повреждая и отталкивая близлежащих существ. +spell.ebwizardry:ice_spikes.desc=Призывает бритвенно-острые ледяные шипы из-под земли в точке вашего курсора, которые впиваются в любых существ и сдерживают их. +spell.ebwizardry:freezing_weapon.desc=Временно наполняет первое оружие на панели заклинателя силой льда, заставляя его замораживать своих жертв. Магия исчезает через 45 секунд. +spell.ebwizardry:ice_lance.desc=Стреляет большим копьём льда в направлении вашего курсора,которе замедляет врага и наносит урон. +spell.ebwizardry:greater_fireball.desc=Запускает большой огненный шар в направлении вашего курсора, который взрывается при ударе. +spell.ebwizardry:flaming_weapon.desc=Временно наполняет первое оружие панели заклинателя силой пламени, заставляя его поджигать своих жертв. Магия исчезает через 45 секунд. spell.ebwizardry:oakflesh.desc=Улучшает сопротивление заклинателя в течение 30 секунд. -spell.ebwizardry:petrify.desc=Повергает цель в камень до следующего заката или пока не будет разбит. Цель не может двигаться или делать что-либо, пока она окаменела, но также невосприимчива к любому урону. -spell.ebwizardry:phase_step.desc=Телепортирует заклинателя через стену толщиной 1 блок перед ним. Улучшение дальности увеличит толщину, через которую вы сможете телепортироваться. -spell.ebwizardry:plague_of_darkness.desc=Тьма поглотит их всех... -spell.ebwizardry:pocket_furnace.desc=Переплавляет до 5 предметов в инвентаре заклинателя. Предметы на панели переплавляются первые. +spell.ebwizardry:invigorating_presence.desc=Дарует заклинателю и всем ближайшим союзникам повышенную силу в течение 45 секунд. +spell.ebwizardry:clairvoyance.desc=Показывает путь к запоминающемуся местоположению. Когда заклинание выбрано, присесть-правая кнопка мыши на блок, чтобы установить местоположение. Используйте заклинание, чтобы показать путь. Путь исчезнет через 90 секунд. spell.ebwizardry:pocket_workbench.desc=Позволяет заклинателю создавать предметы, так же как и в верстаке. -spell.ebwizardry:poison.desc=Выстреливает яд в направлении, на которое вы указываете. -spell.ebwizardry:poison_bomb.desc=Бросает ядовитую бомбу в направлении, которое вы указываете, которая взрывается при ударе, отравляя близких существ. -spell.ebwizardry:replenish_hunger.desc=Пополняет запас пищи заклинателя на 6 пунктов голода. -spell.ebwizardry:ring_of_fire.desc=Создает огненное кольцо вокруг заклинателя, нанося урон всем ближайшим противникам и поджигая их. -spell.ebwizardry:shadow_ward.desc=Создает стену тьмы перед заклинателем, которая наносит нападающему половину всего входящего урона. -spell.ebwizardry:shield.desc=Создает защитный барьер силы, который блокирует снаряды и магию. Также дает заклинателю слабый эффект сопротивления. +spell.ebwizardry:imbue_weapon.desc=Временно наполняет магией первое оружие на панели заклинателя, делая его более эффективным. Магия исчезает через 45 секунд. +spell.ebwizardry:darkvision.desc=Дает заклинателю ночное зрение в течение 45 секунд. +spell.ebwizardry:sixth_sense.desc=Позволяет заклинателю ощущать расположение близлежащих существ, даже через стены, в течение 20 секунд. +spell.ebwizardry:banish.desc=Телепортирует цель против своей воли в случайное место в пределах определенного диапазона. +spell.ebwizardry:arc.desc=Выстреливает Искрой Молнии В Цель +spell.ebwizardry:intimidate.desc=Испускает устрашающее рычание, которое заставляет близлежащих существ убегать в страхе. Страх пораженных существ восстановится через 30 секунд. +spell.ebwizardry:arrow_rain.desc="Лучники, огонь!" +spell.ebwizardry:leap.desc=Заставляет заклинателя прыгать вверх на несколько блоков и немного вперед. +spell.ebwizardry:black_hole.desc=Разрывай Реальность На Части +spell.ebwizardry:blink.desc=Телепортирует Заклинателя В Любой Блок, На Который Он Указывает, В Определенном Радиусе. +spell.ebwizardry:blizzard.desc=Создает Зону Закрученного Ледяного Ветра, Которая Замедляет И Постоянно Повреждает Все, Что Попало Внутрь. Заклинатель Невосприимчив К Урону, Но Все Еще Замедляется. +spell.ebwizardry:bubble.desc=Стреляет Потоком Пузырьков, Который Помещает Существо В Большой Пузырь, При Это Поднимает Его. Цель Будет Падать Или Просто Получит Урон. +spell.ebwizardry:chain_lightning.desc=Выстреливает Искру Молнии У Цели, Которая Затем Цепляется К Дополнительным Целям До Двух Раз. +spell.ebwizardry:conjure_bow.desc=Создает Спектральный Лук С Неограниченными Стрелами, Который Длится 30 Секунд. +spell.ebwizardry:conjure_pickaxe.desc=Создает Спектральную Кирку Равной Силы Железной Кирке, Которая Длится 30 Ссекунд. +spell.ebwizardry:conjure_sword.desc=Создает Спектральный Меч Одинаковой Силы С Железным Мечом, Который Длится 30 Секунд. +spell.ebwizardry:cure_effects.desc=Удаляет Все Эффекты Зелий, Которые В Настоящее Время Воздействуют На Заклинателя, Хорошие Или Плохие. +spell.ebwizardry:darkness_orb.desc=Выстреливает Медленно Перемещающимся Болтом Тёмной Энергии В Направлении, На Которое Вы Указываете, Которое Вызывает Гниение Всего, Что Попадает. +spell.ebwizardry:dart.desc=Выстреливает Дротик В Том Направлении, В Котором Вы Указываете, Которое Наносит Урон И Ослабляет Его Цель. +spell.ebwizardry:decay.desc=Создает Пятно Гнили На Земле, Которое Заражает Любое Существо, Которое Его Касается, Вызывая Длительный Урон Со Временем И Распространяя Больше Гнили, Где Бы Он Ни Ходил. +spell.ebwizardry:detonate.desc=Вызывает Взрыв, В Указанном Месте, Нанося Урон Всем Находящимся Поблизости Существам, Включая Заклинателя, Если Они Слишком Близко. +spell.ebwizardry:diamondflesh.desc="Твои Стрелы Мне Не Подходят!" +spell.ebwizardry:smoke_bomb.desc=Запускает дымовую бомбу в направлении вашего курсора, которая взрывается выпукая дым и ослепляя близлежащих существ на короткое время. +spell.ebwizardry:entrapment.desc=Заманивает Цель В Сферу Темноты, Которая Поднимает Её Вверх И Постоянно Её Повреждает. +spell.ebwizardry:fireball.desc=Запускает Огненный Шар В Том Направлении, В Которое Вы Указали. +spell.ebwizardry:firebolt.desc=Стреляет На Небольшом Расстоянии От Вас. +spell.ebwizardry:firebomb.desc=Брасает Зажигательную Бомбу В Направлении, Которое Вы Указываете, Которая Взрывается При Ударе, Поджигая Цели. +spell.ebwizardry:fire_resistance.desc=Даёт Сопротивление Огню На 30% +spell.ebwizardry:fire_sigil.desc=Помещает Магическую Ловушку Огня На Землю, Которая Наносит Урон И Поджигает Существо, Которое Наступило На Ловушку +spell.ebwizardry:fireskin.desc=Покрывает Заклинателя Огнём На 30 Секунд. Если Заклинатель Будет Атакован, То Враг Загорится. +spell.ebwizardry:firestorm.desc="Я Дракон." +spell.ebwizardry:flame_ray.desc=Создает Поток Пламени В Направлении, На Которое Вы Указали, Который Поджигает И Постоянно Повреждает Цели. +spell.ebwizardry:flaming_axe.desc=Создает Огненный Топор, Который Поджигает Врагов При Попадании. Длится 30 Секунд. +spell.ebwizardry:flight.desc=Парящий, Как Орёл. +spell.ebwizardry:font_of_vitality.desc=Это Потрясающе. +spell.ebwizardry:force_arrow.desc=Стреляет Стрелой Силы В Направлении, На Которое Вы Указываете. +spell.ebwizardry:forcefield.desc=Создает Силовое Поле Вокруг Заклинателя, Который Отталкивает Существ И Отклоняет Снаряды. +spell.ebwizardry:force_orb.desc=Запускает Сферу Силы, Которая Наносит Урон И Отбрасывает Близких Существ При Ударе. +spell.ebwizardry:forests_curse.desc="Как Ты Посмел Войти В Мой Лес?!" +spell.ebwizardry:freeze.desc=Замораживает Цель На 10 Секунд. Также Заморозит Воду И Создаст Снег На Земле. +spell.ebwizardry:frost_axe.desc=Создает Ледяной Топор, Который Замораживает Врагов При Попадании. Длится 30 Секунд. +spell.ebwizardry:frost_ray.desc=Создает Поток Мороза В Направлении, На Которое Вы Указываете, Который Замедляет И Постоянно Повреждает Цели. +spell.ebwizardry:frost_sigil.desc=Помещает Магическую Ледяную Ловушку На Землю, Которая Наносит Урон И Замораживает Существо, Которое Наступило На Неё. +spell.ebwizardry:glide.desc=Позволяет Заклинателю Скользить Вниз, Находясь В Воздухе, Удерживая Кнопку Использования Предмета. +spell.ebwizardry:greater_heal.desc=Восстанавливает Заклинателю 4 Сердца. +spell.ebwizardry:growth_aura.desc=Выращивает Все Зерновые Культуры Рядом С Заклинателем. +spell.ebwizardry:heal.desc=Исцеляет Заклинателя На 2 Сердца. +spell.ebwizardry:heal_ally.desc=Восстанавливает Цели 2 С Половиной Сердца. +spell.ebwizardry:healing_aura.desc=Создает Зону Исцеляющей Энергии, Которая Восстанавливает Здоровье Кого-либо Внутри Него, Кроме Нежити, Которая Медленно Получает Урон. +spell.ebwizardry:homing_spark.desc=Создает Плавающую Искру, Которая Движется К Врагам. +spell.ebwizardry:ice_age.desc="Вы Замерзнете Навсегда!" +spell.ebwizardry:ice_charge.desc=Запускает Заряд Льда, Который Взрывается При Ударе, Замораживая Близлежащих Существ И Выпуская Осколки Во Всех Направлениях. +spell.ebwizardry:ice_shard.desc=Выстреливает Ледяной Осколок В Том Направлении, В Котором Вы Указали, Который Наносит Урон И Замедляет Цель При Попадании. +spell.ebwizardry:ice_shroud.desc=Окутывает Заклинателя Во Льду На 30 Секунд, Замораживая Все, Что Попадает Или Попадает. +spell.ebwizardry:ice_statue.desc=Замораживает Цель На 20 Секунд Или До Тех Пор, Пока Она Не Вырвется Наружу. Цель Не Может Двигаться Или Делать Что-либо В Замороженном Состоянии, Но Также Невосприимчива К Любому Урону. +spell.ebwizardry:ignite.desc=Устанавливает Огонь В Течение 10 Секунд. Также Работает Как Кремень И Сталь. +spell.ebwizardry:invisibility.desc=Делает Заклинателя Невидимым В Течение 30 Секунд. +spell.ebwizardry:invoke_weather.desc=Изменяет Погоду В Мире. +spell.ebwizardry:ironflesh.desc=Увеличивает Сопротивление К Оглушению Заклинателя На 30 Секунд. +spell.ebwizardry:levitation.desc=Поднимает Заклинателя Вверх При Нажатой Кнопке Использования Предмета. Также Будет Нейтрализован Урон От Падения, Если Он Используется До Удара Об Землю. +spell.ebwizardry:life_drain.desc=Создает Поток Иссушающей Энергии В Направлении, На Которое Вы Указываете, Который Истощает Жизнь Цели И Использует Её Для Постепенного Восстановления Здоровья. +spell.ebwizardry:light.desc=Создает Магическую Точку Света, Которая Освещает Окружающую Область. Длится 30 Секунд. +spell.ebwizardry:lightning_arrow.desc=Стреляет Стрелой Молнии В Направлении, На Которое Вы Указываете. +spell.ebwizardry:lightning_bolt.desc=Заставляет Молнию Ударить Туда, Куда Вы Указываете. +spell.ebwizardry:lightning_disc.desc=Посылает Диск Молнии, Летящий В Направлении, На Которое Вы Указали И Ищет Цели. +spell.ebwizardry:lightning_hammer.desc="Я Поражу Тебя Гневом Небес!" +spell.ebwizardry:lightning_ray.desc=Создает Поток Молнии В Направлении, На Которое Вы Указываете, Который Постоянно Повреждает Цели. +spell.ebwizardry:lightning_sigil.desc=Помещает Магическую Молниеносную Ловушку На Землю, Которая Наносит Урон Существу, Которое Наступило На Ловушку, И Приковывает Молнии К Другим Близлежащим Существам. +spell.ebwizardry:magic_missile.desc=Выстреливает Магический Заряд В Том Направлении, В Котором Вы Указали. +spell.ebwizardry:metamorphosis.desc=Изменяет Цель В Другую Форму. Работает Только На Некоторых Существах. +spell.ebwizardry:meteor.desc=Некоторые Волшебники Просто Хотят, Чтобы Мир Горел ... +spell.ebwizardry:mind_control.desc=Контролирует Ум Цели, Заставляя Её Атаковать Ближайшую Живую Цель, Отличную От Заклинателя. Когда Это Существо Будет Убито, Цель Вернется К Нормальной Жизни. +spell.ebwizardry:none.desc=Чтобы Получить Книгу Заклинания /give command, использовать метаданные: /give [player] ebwizardry:spell_book 1 [spell id] (if you found this book in a chest, some other mod has messed things up). +spell.ebwizardry:petrify.desc=Повергает Цель В Камень До Следующего Заката Или Пока Не Будет Разбит. Цель Не Может Двигаться Или Делать Что-либо, Пока Она Окаменела, Но Также Невосприимчива К Любому Урону. +spell.ebwizardry:phase_step.desc=Телепортирует Заклинателя Через Стену Толщиной 1 Блок Перед Ним. Улучшение Дальности Увеличит Толщину, Через Которую Вы Сможете Телепортироваться. +spell.ebwizardry:plague_of_darkness.desc=Тьма Поглотит Их Всех... +spell.ebwizardry:pocket_furnace.desc=Переплавляет до 5 предметов в инвентаре заклинателя. Предметы на панели переплавляются первые. +spell.ebwizardry:poison.desc=Выстреливает Яд В Направлении, На Которое Вы Указываете. +spell.ebwizardry:poison_bomb.desc=Бросает Ядовитую Бомбу В Направлении, Которое Вы Указываете, Которая Взрывается При Ударе, Отравляя Близких Существ. +spell.ebwizardry:replenish_hunger.desc=Пополняет Запас Пищи Заклинателя На 6 Пунктов Голода. +spell.ebwizardry:ring_of_fire.desc=Создает Огненное Кольцо Вокруг Заклинателя, Нанося Урон Всем Ближайшим Противникам И Поджигая Их. +spell.ebwizardry:shadow_ward.desc=Создает Стену Тьмы Перед Заклинателем, Которая Наносит Нападающему Половину Всего Входящего Урона. +spell.ebwizardry:shield.desc=Создает Защитный Барьер Силы, Который Блокирует Снаряды И Магию. Также Дает Заклинателю Слабый Эффект Сопротивления. spell.ebwizardry:shockwave.desc=Бум. spell.ebwizardry:silverfish_swarm.desc="ААААА! ИХ ОЧЕНЬ МНОГО!" -spell.ebwizardry:sixth_sense.desc=Позволяет заклинателю ощущать расположение близлежащих существ, даже через стены, в течение 20 секунд. -spell.ebwizardry:slime.desc=Охватывает цель слизью, которая замедляет и постоянно её повреждает. Слизь разрывается через 10 секунд. -spell.ebwizardry:smoke_bomb.desc=Запускает дымовую бомбу в направлении вашего курсора, которая взрывается выпукая дым и ослепляя близлежащих существ на короткое время. -spell.ebwizardry:snare.desc=Устанавливает ловушку на земле, которая наносит урон и кратко замедляет существо, которое наступило на ловушку. -spell.ebwizardry:snowball.desc=Запускает снежок в направлении, которое вы указываете. -spell.ebwizardry:spark_bomb.desc=Запускает бомбу искр в том направлении, в котором вы указываете. Искры появляются у ближайших противников при ударе. -spell.ebwizardry:spectral_pathway.desc=Создает перед вами неразрушимый магический мост, который простирается на 15 блоков. Через 60 секунд мост исчезает. -spell.ebwizardry:spider_swarm.desc=Призывает рой ядовитых пауков, чтобы сражаться за вас. Пауки исчезнут через 30 секунд или если они будут убиты. -spell.ebwizardry:static_aura.desc=Окружает заклинателя молнией в течение 30 секунд, стреляет искрами молнии во все, что ударит вас. -spell.ebwizardry:summon_blaze.desc=Призывает ифрита, чтобы сражаться за вас. Ифрит исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:summon_ice_giant.desc="Разбей их!" -spell.ebwizardry:summon_ice_wraith.desc=Призывает ледяного призрака, который сражается за вас. Ледяной призрак исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:summon_iron_golem.desc=Автоматический автономный автомат. -spell.ebwizardry:summon_lightning_wraith.desc=Призывает молниеносного призрака сражаться за вас. Молниеносный призрак исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:summon_phoenix.desc=Из пепла... -spell.ebwizardry:summon_shadow_wraith.desc=Призывает тёмного призрака, который будет сражаться за вас. -spell.ebwizardry:summon_skeleton.desc=Призывает скелета, который сражается за вас. Скелет исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:summon_skeleton_legion.desc="Восстань, армия нежети!" -spell.ebwizardry:summon_snow_golem.desc=Создает снежного голема, который сражается за вас. Длится до тех пор, пока не умрет снежный голем. -spell.ebwizardry:summon_spirit_horse.desc=Призывает для вас верховую лошадь. Призрачная лошадь исчезнет через некоторое время после того, как вы с неё слезите, или вы можете убрать её с помощью жезла - щелкните ПКМ на ней. -spell.ebwizardry:summon_spirit_wolf.desc=Призывает спутника духа волка, который сражается за вас. Призрачный волк исчезнет, только если его убьют, или вы можете его убрать, щелкнув ПКМ на нём с помощью жезла. -spell.ebwizardry:summon_storm_elemental.desc="Грозовой элементаль: древнее проявление элементов, он вряд ли может содержать грубую силу, сбивающуюся внутри него."- Руководство мастера по тайным существам, том I_i -spell.ebwizardry:summon_wither_skeleton.desc=Призывает скелета-иссушителя, который сражается за вас. Скелет-иссушитель исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:summon_zombie.desc=Призывает зомби, который сражается за вас. Зомби исчезнет через 30 секунд или если он будет убит. -spell.ebwizardry:telekinesis.desc=Перемещает объект или другой маленький объект к себе или щелкает правой кнопкой мыши на блоке, на который вы смотрите. -spell.ebwizardry:thunderbolt.desc=Стреляет громом, который отбрасывает цели. +spell.ebwizardry:slime.desc=Охватывает Цель Слизью, Которая Замедляет И Постоянно Её Повреждает. Слизь Разрывается Через 10 Секунд. +spell.ebwizardry:snare.desc=Устанавливает Ловушку На Земле, Которая Наносит Урон И Кратко Замедляет Существо, Которое Наступило На Ловушку. +spell.ebwizardry:snowball.desc=Launches a snowball in the direction you are pointing. +spell.ebwizardry:spark_bomb.desc=Запускает Бомбу Искр В Том Направлении, В Котором Вы Указываете. Искры Появляются У Ближайших Противников При Ударе. +spell.ebwizardry:spectral_pathway.desc=Создает Перед Вами Неразрушимый Магический Мост, Который Простирается На 15 Блоков. Через 60 Секунд Мост Исчезает. +spell.ebwizardry:spider_swarm.desc=Призывает Рой Ядовитых Пауков, Чтобы Сражаться За Вас. Пауки Исчезнут Через 30 Секунд Или Если Они Будут Убиты. +spell.ebwizardry:static_aura.desc=Окружает Заклинателя Молнией В Течение 30 Секунд, Стреляет Искрами Молнии Во Все, Что Ударит Вас. +spell.ebwizardry:summon_blaze.desc=Призывает Блейза, Чтобы Сражаться За Вас. Блейз Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:summon_ice_giant.desc="Разбей Их!" +spell.ebwizardry:summon_ice_wraith.desc=Призывает Ледяного Призрака, Который Сражается За Вас. Ледяной Призрак Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:summon_iron_golem.desc=Автоматический Автономный Автомат. +spell.ebwizardry:summon_lightning_wraith.desc=Призывает Молниеносного Призрака Сражаться За Вас. Молниеносный Призрак Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:summon_phoenix.desc=Из Пепла... +spell.ebwizardry:summon_shadow_wraith.desc=Призывает Тёмного Призрака, Который Будет Сражаться За Вас. +spell.ebwizardry:summon_skeleton.desc=Призывает Скелета, Который Сражается За вас. Скелет Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:summon_skeleton_legion.desc="Восстань, Армия Нежети!" +spell.ebwizardry:summon_snow_golem.desc=Создает Снежного Голема, Который Сражается За Вас. Длится До Тех Пор, Пока Не Умрет Снежный Голем. +spell.ebwizardry:summon_spirit_horse.desc=Призывает Для Вас Верховую Лошадь. Призрачная Лошадь Исчезнет Через Некоторое Время После Того, Как Вы С Неё Слезите, Или Вы Можете Убрать Её С Помощью Жезла - Щелкните Правой Кнопкой Мыши На Ней. +spell.ebwizardry:summon_spirit_wolf.desc=Призывает Спутника Духа Волка, Который Сражается За Вас. Призрачный Волк Исчезнет, Только Если Его Убьют, Или Вы Можете Его Убрать, Щелкнув Правой Кнопкой Мыши На Нём С Помощью Жезла. +spell.ebwizardry:summon_wither_skeleton.desc=Призывает Скелета-Иссушителя, Который Сражается За Вас. Скелет-Иссушитель Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:summon_zombie.desc=Призывает Зомби, Который Сражается За Вас. Зомби Исчезнет Через 30 Секунд Или Если Он Будет Убит. +spell.ebwizardry:telekinesis.desc=Перемещает Объект Или Другой Маленький Объект К Себе Или Щелкает Правой Кнопкой Мыши На Блоке,На Который Вы Смотрите. +spell.ebwizardry:thunderbolt.desc=Стреляет Громом, Который Отбрасывает Цели. spell.ebwizardry:thunderstorm.desc="МУХАХАХАХАХ!" -spell.ebwizardry:tornado.desc=Развязывает торнадо в направлении, на которое вы указываете, который бросает что-либо на своем пути в небо. -spell.ebwizardry:transience.desc=Делает переход заклинателя на 20 секунд. Заклинатель невосприимчив ко всему урону в переходный период, но не может сломать или поместить блоки или нанести какой-либо урон. -spell.ebwizardry:transportation.desc=Транспортирует заклинателя в запомненный каменный круг. Чтобы использовать это заклинание, сделайте круг камней транспортировки, затем щелкните ПКМ с помощью жезла. -spell.ebwizardry:vanishing_box.desc=Предоставляет заклинателю доступ к сундуку Края. -spell.ebwizardry:wall_of_frost.desc=Зима у вас под рукой. -spell.ebwizardry:water_breathing.desc=Позволяет заклинателю дышать под водой в течение 60 секунд. -spell.ebwizardry:whirlwind.desc=Заставляет цель взлететь вверх далеко от вас на скорости. -spell.ebwizardry:wither.desc=Стреляет лучом тьмы, который увядает всё, к чему он прикасается. -spell.ebwizardry:wither_skull.desc=Запускает череп иссушителя в направлении, на которое вы указываете. +spell.ebwizardry:tornado.desc=Развязывает Торнадо В Направлении, На Которое Вы Указываете, Который Бросает Что-либо На Своем Пути В Небо. +spell.ebwizardry:transience.desc=Делает Переход Заклинателя На 20 Секунд. Заклинатель Невосприимчив Ко Всему Урону В Переходный Период, Но Не Может Сломать Или Поместить Блоки Или Нанести Какой-либо Урон. +spell.ebwizardry:transportation.desc=Транспортирует Заклинателя В Запомненный Каменный Круг. Чтобы Использовать Это Заклинание, Сделайте Круг Камней Транспортировки, Затем Щелкните Правой Кнопкой Мыши С Помощью Жезла. +spell.ebwizardry:vanishing_box.desc=Grants the caster access to their ender chest storage. +spell.ebwizardry:wall_of_frost.desc=Зима У Вас Под Рукой. +spell.ebwizardry:water_breathing.desc=Позволяет Заклинателю Дышать Под Водой В Течение 60 Секунд. +spell.ebwizardry:whirlwind.desc=Заставляет Цель Взлететь Вверх Далеко От Вас На Скорости. +spell.ebwizardry:wither.desc=Стреляет Лучом Тьмы, Который Увядает Всё, К Чему Он Прикасается. +spell.ebwizardry:wither_skull.desc=Запускает Череп Иссушителя В Направлении, На Которое Вы Указываете. +spell.ebwizardry:mind_trick.desc=Путает и дезориентирует цель в течение 15 секунд, что делает его не в состоянии атаковать. Эффект будет рассеян, если цель получит урон. -spell.ebwizardry:invoke_weather.sun=Дождь начинает останавливаться... -spell.ebwizardry:invoke_weather.rain=Небеса открываются... -spell.ebwizardry:transportation.missing=Ваш запомненный каменный круг отсутствует или затруднен ... -spell.ebwizardry:transportation.undefined=Вы должны сначала запомнить местоположение каменного круга! -spell.ebwizardry:transportation.wrongdimension=Ваш запомненный каменный круг находится в другом измерении ... +spell.ebwizardry:invoke_weather.sun=Дождь Начинает Останавливаться ... +spell.ebwizardry:invoke_weather.rain=Небеса Открываются ... +spell.ebwizardry:transportation.missing=Ваш Запомненный Каменный Круг Отсутствует Или Затруднен ... +spell.ebwizardry:transportation.undefined=Вы Должны Сначала Запомнить Местоположение Каменного Круга! +spell.ebwizardry:transportation.wrongdimension=Ваш Запомненный Каменный Круг Находится В Другом Измерении ... spell.ebwizardry:clairvoyance.searching=Поиск... spell.ebwizardry:clairvoyance.confirm=Путь, показанный при использовании заклинания %1$s теперь приведёт к этой точке spell.ebwizardry:clairvoyance.outofrange=Ваше запоминающееся местоположение слишком далеко или недоступно... @@ -600,91 +577,24 @@ spell.ebwizardry:clairvoyance.undefined=Вы должны запомнить п spell.ebwizardry:clairvoyance.wrongdimension=Ваше запоминающееся местоположение находится в другом измерении... potion.ebwizardry:frost=Отморожение -potion.ebwizardry:fireskin=Огненная кожа -potion.ebwizardry:ice_shroud=Ледяная кожа -potion.ebwizardry:static_aura=Статическая аура +potion.ebwizardry:fireskin=Огненная Кожа +potion.ebwizardry:ice_shroud=Ледяная Кожа +potion.ebwizardry:static_aura=Статическая Аура potion.ebwizardry:transience=Быстрота potion.ebwizardry:decay=Гниение -potion.ebwizardry:sixth_sense=Шестое чувство -potion.ebwizardry:arcane_jammer=Магическая глушилка -potion.ebwizardry:mind_trick=Обман разума -potion.ebwizardry:mind_control=Контроль разума -potion.ebwizardry:font_of_mana=Купель маны -potion.ebwizardry:fear=Страх - -enchantment.ebwizardry:magic_sword=Насыщение -enchantment.ebwizardry:magic_bow=Насыщение -enchantment.ebwizardry:flaming_weapon=Огненное насыщение -enchantment.ebwizardry:freezing_weapon=Морозное насыщение +potion.ebwizardry:mind_trick=Обман Разума +potion.ebwizardry:sixth_sense=Шестое Чувство +potion.ebwizardry:font_of_mana=Купель Маны key.categories.ebwizardry=Wizardry -key.ebwizardry.next_spell=Следующее заклинание -key.ebwizardry.previous_spell=Предыдущее заклинание - -death.attack.wizardry_magic=%1$s был убит %2$s, используя магию -death.attack.indirect_wizardry_magic=%1$s был убит %2$s, используя магию - -commands.ebwizardry:cast.usage=/%1$s [player] [damage multiplier] [range multiplier] [duration multiplier] [blast multiplier] -commands.ebwizardry:cast.success=Successfully cast %1$s -commands.ebwizardry:cast.success_continuous=Successfully cast %1$s; repeat the command to stop -commands.ebwizardry:cast.success_remote=Successfully cast %1$s as %2$s -commands.ebwizardry:cast.success_remote_continuous=Successfully cast %1$s as %2$s; repeat the command to stop -commands.ebwizardry:cast.fail=Unable to cast %1$s -commands.ebwizardry:cast.not_found=There is no such spell with ID %1$s -commands.ebwizardry:cast.tag_error=Data tag parsing failed: %s - -commands.ebwizardry:ally.usage=/%1$s [player] -commands.ebwizardry:ally.addally=%1$s has been added to %2$s's list of allies -commands.ebwizardry:ally.removeally=%1$s has been removed from %2$s's list of allies -commands.ebwizardry:ally.self=Players cannot be an ally of themselves! -commands.ebwizardry:ally.permission=You do not have permission to change other players' allies - -commands.ebwizardry:allies.usage=/%1$s [player] -commands.ebwizardry:allies.list=Players allied to you: %1$s -commands.ebwizardry:allies.list_other=Players allied to %1$s: %2$s -commands.ebwizardry:allies.permission=You do not have permission to view other players' allies -commands.ebwizardry:allies.none=None - -commands.ebwizardry:discoverspell.usage=/%1$s [player] -commands.ebwizardry:discoverspell.not_found=There is no such spell with ID %1$s -commands.ebwizardry:discoverspell.clear=Cleared all spell discovery data for %1$s -commands.ebwizardry:discoverspell.all=Added all spells to %1$s's spell discovery data -commands.ebwizardry:discoverspell.addspell=Added %1$s to %2$s's spell discovery data -commands.ebwizardry:discoverspell.removespell=Removed %1$s from %2$s's spell discovery data +death.attack.wizardry_magic=%1$s был убит %2$s используя магию +death.attack.indirect_wizardry_magic=%1$s был убит %2$s используя магию config.ebwizardry.title.general=Mod Options - -config.ebwizardry.category.gameplay=Gameplay Settings -config.ebwizardry.category.gameplay.tooltip=Configure wizardry's general gameplay -config.ebwizardry.title.gameplay=Gameplay Settings -config.ebwizardry.subtitle.gameplay=Global settings that affect game mechanics. - -config.ebwizardry.category.worldgen=World Generation Settings -config.ebwizardry.category.worldgen.tooltip=Configure wizardry's world generation features -config.ebwizardry.title.worldgen=World Generation Settings -config.ebwizardry.subtitle.worldgen=Settings that affect world generation. - -config.ebwizardry.category.commands=Command Settings -config.ebwizardry.category.commands.tooltip=Configure wizardry's commands -config.ebwizardry.title.commands=Command Settings -config.ebwizardry.subtitle.commands=Settings for the commands added by Wizardry. - -config.ebwizardry.category.client=Client Settings -config.ebwizardry.category.client.tooltip=Configure wizardry's display and controls -config.ebwizardry.title.client=Client Settings -config.ebwizardry.subtitle.client=Client-side settings that only affect the local minecraft game. - -config.ebwizardry.category.spells=Spell Configuration -config.ebwizardry.category.spells.tooltip=Select which spells are enabled config.ebwizardry.title.spells=Spell Configuration config.ebwizardry.subtitle.spells=Set a spell to false to disable it. -config.ebwizardry.category.resistances=Resistance Configuration -config.ebwizardry.category.resistances.tooltip=Configure which mobs are immune to different types of magic -config.ebwizardry.title.resistances=Resistance Configuration -config.ebwizardry.subtitle.resistances=Settings which allow entities to be made immune to certain types of magic. - config.ebwizardry.tower_rarity=Tower Rarity config.ebwizardry.ore_dimensions=Ore Dimensions config.ebwizardry.flower_dimensions=Flower Dimensions @@ -693,68 +603,10 @@ config.ebwizardry.spell_book_drop_chance=Spell Book Drop Chance config.ebwizardry.generate_loot=Generate Loot config.ebwizardry.firebomb_is_craftable=Firebomb Is Craftable config.ebwizardry.poison_bomb_is_craftable=Poison Bomb Is Craftable -config.ebwizardry.smoke_bomb_is_craftable=Smoke Bomb Is Craftable config.ebwizardry.use_alternate_scroll_recipe=Use Alternate Scroll Recipe config.ebwizardry.teleport_through_unbreakable_blocks=Teleport Through Unbreakable Blocks config.ebwizardry.show_summoned_creature_names=Show Summoned Creature Names -config.ebwizardry.friendly_fire=Friendly Fire -config.ebwizardry.telekinetic_disarmament=Telekinetic Disarmament -config.ebwizardry.discovery_mode=Discovery Mode -config.ebwizardry.enable_shift_scrolling=Enable Shift-scrolling -config.ebwizardry.minion_revenge_targeting=Minion Revenge Targeting -config.ebwizardry.player_damage_scaling=Player Damage Scaling Factor -config.ebwizardry.npc_damage_scaling=NPC Damage Scaling Factor -config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit -config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist -config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist config.ebwizardry.spell_hud_position=Spell HUD Position -config.ebwizardry.cast_command_name=Cast Spell Command Name -config.ebwizardry.discoverspell_command_name=Discover Spell Command Name -config.ebwizardry.ally_command_name=Set Ally Command Name -config.ebwizardry.allies_command_name=View Allies Command Name -config.ebwizardry.mind_control_targets_blacklist=Mind Control Targets Blacklist -config.ebwizardry.evil_wizard_dimensions=Evil Wizard Dimensions -config.ebwizardry.mobs_immune_to_fire=Mobs Immune To Fire -config.ebwizardry.mobs_immune_to_ice=Mobs Immune To Ice -config.ebwizardry.mobs_immune_to_lightning=Mobs Immune To Lightning -config.ebwizardry.mobs_immune_to_wither=Mobs Immune To Wither -config.ebwizardry.mobs_immune_to_poison=Mobs Immune To Poison - -config.ebwizardry.tower_rarity.tooltip=Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely. -config.ebwizardry.ore_dimensions.tooltip=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! -config.ebwizardry.flower_dimensions.tooltip=List of dimension ids in which crystal flowers will generate. -config.ebwizardry.tower_dimensions.tooltip=List of dimension ids in which wizard towers will generate. -config.ebwizardry.spell_book_drop_chance.tooltip=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. -config.ebwizardry.generate_loot.tooltip=Whether to generate wizardry loot in dungeon chests. -config.ebwizardry.firebomb_is_craftable.tooltip=Whether firebombs can be crafted or not. -config.ebwizardry.poison_bomb_is_craftable.tooltip=Whether poison bombs can be crafted or not. -config.ebwizardry.smoke_bomb_is_craftable.tooltip=Whether smoke bombs can be crafted or not. -config.ebwizardry.use_alternate_scroll_recipe.tooltip=Whether to require a magic crystal in the shapeless crafting recipe for blank scrolls. Set to true if another mod adds a conflicting recipe. -config.ebwizardry.teleport_through_unbreakable_blocks.tooltip=Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell. -config.ebwizardry.show_summoned_creature_names.tooltip=Whether to show summoned creatures' names and owners above their heads. -config.ebwizardry.friendly_fire.tooltip=Whether to allow players to damage their designated allies using magic. -config.ebwizardry.telekinetic_disarmament.tooltip=Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items. -config.ebwizardry.discovery_mode.tooltip=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. -config.ebwizardry.enable_shift_scrolling.tooltip=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. -config.ebwizardry.minion_revenge_targeting.tooltip=Whether summoned creatures can revenge attack their owner if their owner attacks them. -config.ebwizardry.player_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by players casting spells, relative to 1. -config.ebwizardry.npc_damage_scaling.tooltip=Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1. -config.ebwizardry.cast_command_multiplier_limit.tooltip=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! -config.ebwizardry.summoned_creature_targets_whitelist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.summoned_creature_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD. -config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead. -config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead. -config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead. -config.ebwizardry.allies_command_name.tooltip=The name of the /allies command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /allies you would type /magic instead. -config.ebwizardry.mind_control_targets_blacklist.tooltip=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. ebwizardry:wizard). -config.ebwizardry.evil_wizard_dimensions.tooltip=List of dimension ids in which evil wizards can spawn. - -config.ebwizardry.mobs_immune_to_fire.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_ice.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_lightning.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_wither.tooltip=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. ebwizardry:wizard). -config.ebwizardry.mobs_immune_to_poison.tooltip=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. ebwizardry:wizard). - -wizard.debug=%1$s, %2$s, %3$s +config.ebwizardry.category.spells=Configure Spells +config.ebwizardry.category.spells.tooltip=Select which spells are enabled. \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/lang/zh_cn.lang b/src/main/resources/assets/ebwizardry/lang/zh_cn.lang index 5ad2ffae..353baa89 100644 --- a/src/main/resources/assets/ebwizardry/lang/zh_cn.lang +++ b/src/main/resources/assets/ebwizardry/lang/zh_cn.lang @@ -29,13 +29,13 @@ item.ebwizardry:wand.mana=魔力: %1$s/%2$s item.ebwizardry:wand.addally=%1$s 已添加到你的盟友名单 item.ebwizardry:wand.removeally=%1$s 已移出你的盟友名单 -item.ebwizardry:basic_fire_wand.name=余火法杖 -item.ebwizardry:basic_ice_wand.name=寒霜法杖 -item.ebwizardry:basic_lightning_wand.name=星火法杖 -item.ebwizardry:basic_necromancy_wand.name=暗影法杖 -item.ebwizardry:basic_earth_wand.name=森林法杖 -item.ebwizardry:basic_sorcery_wand.name=神秘法杖 -item.ebwizardry:basic_healing_wand.name=治愈法杖 +item.ebwizardry:novice_fire_wand.name=余火法杖 +item.ebwizardry:novice_ice_wand.name=寒霜法杖 +item.ebwizardry:novice_lightning_wand.name=星火法杖 +item.ebwizardry:novice_necromancy_wand.name=暗影法杖 +item.ebwizardry:novice_earth_wand.name=森林法杖 +item.ebwizardry:novice_sorcery_wand.name=神秘法杖 +item.ebwizardry:novice_healing_wand.name=治愈法杖 item.ebwizardry:apprentice_fire_wand.name=学徒烈焰法杖 item.ebwizardry:apprentice_ice_wand.name=学徒冰霜法杖 @@ -213,56 +213,56 @@ entity.ebwizardry:lightning_pulse.name=闪电脉冲 itemGroup.ebwizardry=巫术学 itemGroup.ebwizardryspells=卷轴 -advancement.wizardry:root=新兴巫术 -advancement.wizardry:root.desc=一个巫师掌握奥术的旅程 -advancement.wizardry:crystal=一个奇异的水晶... -advancement.wizardry:crystal.desc=挖掘一个魔法水晶 -advancement.wizardry:arcane_initiate=奥术启程 -advancement.wizardry:arcane_initiate.desc=利用金锭,木棍和魔法水晶制造一根法杖 -advancement.wizardry:apprentice=见习巫师 -advancement.wizardry:apprentice.desc=使用奥法宝典来升级你的法杖 -advancement.wizardry:master=大师级巫师 -advancement.wizardry:master.desc=获得大师级法杖 -advancement.wizardry:all_spells=巫师的自我修养 -advancement.wizardry:all_spells.desc=在游戏中施放所有法术 -advancement.wizardry:wizard_trade=魔法交易 -advancement.wizardry:wizard_trade.desc=向巫师购买物品 -advancement.wizardry:buy_master_spell=知识就是力量 -advancement.wizardry:buy_master_spell.desc=向巫师购买一个大师级卷轴 -advancement.wizardry:freeze_blaze=现在不太热了 -advancement.wizardry:freeze_blaze.desc=把烈焰人冻成傻子 -advancement.wizardry:charge_creep=它要炸了 -advancement.wizardry:charge_creeper.desc='意外' 捕获爬行者 -advancement.wizardry:frankenstein=弗兰肯斯坦 -advancement.wizardry:frankenstein.desc=用闪电法术将猪变成僵尸猪人 -advancement.wizardry:special_upgrade=奥术工匠 -advancement.wizardry:special_upgrade.desc=对法杖进行特殊升级 -advancement.wizardry:craft_flask=这是魔力,装瓶! -advancement.wizardry:craft_flask.desc=制作一个魔力瓶 -advancement.wizardry:elemental=元素 -advancement.wizardry:elemental.desc=获得元素法杖 -advancement.wizardry:armour_set=现在你是一个正确的巫师 -advancement.wizardry:armour_set.desc=制作并装备全套巫师护具 -advancement.wizardry:legendary=传奇! -advancement.wizardry:legendary.desc=获得一件传奇级装备 -advancement.wizardry:self_destruct=真是倒霉 -advancement.wizardry:self_destruct.desc=被自己的魔法杀死 -advancement.wizardry:pig_tornado=不会再次... -advancement.wizardry:pig_tornado.desc=骑着猪卷进飓风 -advancement.wizardry:jam_wizard=扰乱会议 -advancement.wizardry:jam_wizard.desc=对一个巫师使用奥术干扰 -advancement.wizardry:slime_skeleton=棘手局面 -advancement.wizardry:slime_skeleton.desc=利用史莱姆吞噬骷髅 -advancement.wizardry:anger_wizard=你会后悔的 -advancement.wizardry:anger_wizard.desc=使一个巫师发怒 -advancement.wizardry:defeat_evil_wizard=正义降临 -advancement.wizardry:defeat_evil_wizard.desc=击败邪恶巫师 -advancement.wizardry:max_out_wand=整装上阵 -advancement.wizardry:max_out_wand.desc=将大师级法杖升级到最强 -advancement.wizardry:element_master=元素掌控 -advancement.wizardry:element_master.desc=施放任何元素的法术 -advancement.wizardry:identify_spell=奥术鉴定 -advancement.wizardry:identify_spell.desc=使用鉴定卷轴来鉴定法术书或卷轴 +advancement.ebwizardry:root=新兴巫术 +advancement.ebwizardry:root.desc=一个巫师掌握奥术的旅程 +advancement.ebwizardry:crystal=一个奇异的水晶... +advancement.ebwizardry:crystal.desc=挖掘一个魔法水晶 +advancement.ebwizardry:arcane_initiate=奥术启程 +advancement.ebwizardry:arcane_initiate.desc=利用金锭,木棍和魔法水晶制造一根法杖 +advancement.ebwizardry:apprentice=见习巫师 +advancement.ebwizardry:apprentice.desc=使用奥法宝典来升级你的法杖 +advancement.ebwizardry:master=大师级巫师 +advancement.ebwizardry:master.desc=获得大师级法杖 +advancement.ebwizardry:all_spells=巫师的自我修养 +advancement.ebwizardry:all_spells.desc=在游戏中施放所有法术 +advancement.ebwizardry:wizard_trade=魔法交易 +advancement.ebwizardry:wizard_trade.desc=向巫师购买物品 +advancement.ebwizardry:buy_master_spell=知识就是力量 +advancement.ebwizardry:buy_master_spell.desc=向巫师购买一个大师级卷轴 +advancement.ebwizardry:freeze_blaze=现在不太热了 +advancement.ebwizardry:freeze_blaze.desc=把烈焰人冻成傻子 +advancement.ebwizardry:charge_creep=它要炸了 +advancement.ebwizardry:charge_creeper.desc='意外' 捕获爬行者 +advancement.ebwizardry:frankenstein=弗兰肯斯坦 +advancement.ebwizardry:frankenstein.desc=用闪电法术将猪变成僵尸猪人 +advancement.ebwizardry:special_upgrade=奥术工匠 +advancement.ebwizardry:special_upgrade.desc=对法杖进行特殊升级 +advancement.ebwizardry:craft_flask=这是魔力,装瓶! +advancement.ebwizardry:craft_flask.desc=制作一个魔力瓶 +advancement.ebwizardry:elemental=元素 +advancement.ebwizardry:elemental.desc=获得元素法杖 +advancement.ebwizardry:armour_set=现在你是一个正确的巫师 +advancement.ebwizardry:armour_set.desc=制作并装备全套巫师护具 +advancement.ebwizardry:legendary=传奇! +advancement.ebwizardry:legendary.desc=获得一件传奇级装备 +advancement.ebwizardry:self_destruct=真是倒霉 +advancement.ebwizardry:self_destruct.desc=被自己的魔法杀死 +advancement.ebwizardry:pig_tornado=不会再次... +advancement.ebwizardry:pig_tornado.desc=骑着猪卷进飓风 +advancement.ebwizardry:jam_wizard=扰乱会议 +advancement.ebwizardry:jam_wizard.desc=对一个巫师使用奥术干扰 +advancement.ebwizardry:slime_skeleton=棘手局面 +advancement.ebwizardry:slime_skeleton.desc=利用史莱姆吞噬骷髅 +advancement.ebwizardry:anger_wizard=你会后悔的 +advancement.ebwizardry:anger_wizard.desc=使一个巫师发怒 +advancement.ebwizardry:defeat_evil_wizard=正义降临 +advancement.ebwizardry:defeat_evil_wizard.desc=击败邪恶巫师 +advancement.ebwizardry:max_out_wand=整装上阵 +advancement.ebwizardry:max_out_wand.desc=将大师级法杖升级到最强 +advancement.ebwizardry:element_master=元素掌控 +advancement.ebwizardry:element_master.desc=施放任何元素的法术 +advancement.ebwizardry:identify_spell=奥术鉴定 +advancement.ebwizardry:identify_spell.desc=使用鉴定卷轴来鉴定法术书或卷轴 tile.ebwizardry:transportation_stone.confirm=施展法术时你将会回到这里 %1$s tile.ebwizardry:transportation_stone.invalid=你必须先将八个传送石围成圆圈! @@ -272,7 +272,7 @@ container.ebwizardry:arcane_workbench.apply=应用 container.ebwizardry:arcane_workbench.mana=魔力: container.ebwizardry:arcane_workbench.upgrades=应用升级: -tier.basic=新手 +tier.novice=新手 tier.apprentice=学徒 tier.advanced=进阶 tier.master=大师 diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/dungeon_additions.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/dungeon_additions.json index 354086ff..db383025 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/chests/dungeon_additions.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/dungeon_additions.json @@ -3,34 +3,34 @@ { "name": "wizardry", "rolls": { - "min": 3, - "max": 7 + "min": 1, + "max": 3 }, "entries": [ { "type": "loot_table", - "name": "ebwizardry:subsets/novice_wands", - "weight": 3 + "name": "ebwizardry:subsets/elemental_crystals", + "weight": 6 }, { "type": "loot_table", "name": "ebwizardry:subsets/wizard_armour", - "weight": 6 + "weight": 10 }, { "type": "loot_table", "name": "ebwizardry:subsets/arcane_tomes", - "weight": 6 + "weight": 8 }, { "type": "loot_table", "name": "ebwizardry:subsets/wand_upgrades", - "weight": 2 + "weight": 4 }, { "type": "item", "name": "ebwizardry:magic_crystal", - "weight": 5, + "weight": 10, "functions": [ { "function": "set_count", @@ -44,23 +44,57 @@ { "type": "item", "name": "ebwizardry:spell_book", - "weight": 20, + "weight": 40, "functions": [ { - "function": "ebwizardry:random_spell" + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 } ] }, { "type": "item", "name": "ebwizardry:scroll", - "weight": 10, + "weight": 14, "functions": [ { - "function": "ebwizardry:random_spell" + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } } ] }, + { + "type": "item", + "name": "ebwizardry:identification_scroll", + "weight": 6 + }, + { + "type": "item", + "name": "ebwizardry:firebomb", + "weight": 8 + }, + { + "type": "item", + "name": "ebwizardry:poison_bomb", + "weight": 8 + }, + { + "type": "item", + "name": "ebwizardry:smoke_bomb", + "weight": 8 + }, + { + "type": "item", + "name": "ebwizardry:spark_bomb", + "weight": 8 + }, { "type": "item", "name": "ebwizardry:armour_upgrade", @@ -68,23 +102,18 @@ }, { "type": "item", - "name": "ebwizardry:identification_scroll", - "weight": 3 + "name": "ebwizardry:astral_diamond", + "weight": 1 }, { "type": "item", - "name": "ebwizardry:firebomb", - "weight": 4 + "name": "ebwizardry:purifying_elixir", + "weight": 1 }, { "type": "item", - "name": "ebwizardry:poison_bomb", - "weight": 4 - }, - { - "type": "item", - "name": "ebwizardry:smoke_bomb", - "weight": 4 + "name": "ebwizardry:grand_crystal", + "weight": 1 } ] } diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/jungle_dispenser_additions.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/jungle_dispenser_additions.json new file mode 100644 index 00000000..c517f0fb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/jungle_dispenser_additions.json @@ -0,0 +1,65 @@ +{ + "pools": [ + { + "name": "wizardry_dispenser", + "rolls": 1, + "conditions": [ + { + "condition": "random_chance", + "chance": 0.4 + } + ], + "entries": [ + { + "type": "item", + "name": "ebwizardry:scroll", + "weight": 1, + "functions": [ + { + "function": "ebwizardry:random_spell", + "spells": [ + "magic_missile", + "arc", + "thunderbolt", + "summon_zombie", + "dart", + "ice_shard", + "firebolt", + "poison", + "flame_ray", + "frost_ray" + ] + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:firebomb", + "weight": 3 + }, + { + "type": "item", + "name": "ebwizardry:poison_bomb", + "weight": 3 + }, + { + "type": "item", + "name": "ebwizardry:smoke_bomb", + "weight": 3 + }, + { + "type": "item", + "name": "ebwizardry:spark_bomb", + "weight": 3 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/obelisk.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/obelisk.json new file mode 100644 index 00000000..7a0a73a4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/obelisk.json @@ -0,0 +1,195 @@ +{ + "pools": [ + { + "name": "high_value", + "rolls": { + "min": 1, + "max": 1 + }, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/arcane_tomes", + "weight": 3 + }, + { + "type": "loot_table", + "name": "ebwizardry:subsets/wand_upgrades", + "weight": 2 + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "weight": 20, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:scroll", + "weight": 7, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:identification_scroll", + "weight": 3 + }, + { + "type": "item", + "name": "minecraft:gold_nugget", + "weight": 5, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 4 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:emerald", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:grand_crystal", + "weight": 1 + } + ] + }, + { + "name": "low_value", + "rolls": { + "min": 3, + "max": 5 + }, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/elemental_crystals", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:magic_crystal", + "weight": 5, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 4 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:firebomb", + "weight": 4 + }, + { + "type": "item", + "name": "ebwizardry:poison_bomb", + "weight": 4 + }, + { + "type": "item", + "name": "ebwizardry:smoke_bomb", + "weight": 4 + }, + { + "type": "item", + "name": "ebwizardry:spark_bomb", + "weight": 4 + }, + { + "type": "item", + "name": "minecraft:book", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 2 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:string", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:paper", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:crystal_shard", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/shrine.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/shrine.json new file mode 100644 index 00000000..76af240f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/shrine.json @@ -0,0 +1,210 @@ +{ + "pools": [ + { + "name": "artefact", + "rolls": 1, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/uncommon_artefacts", + "weight": 5 + }, + { + "type": "loot_table", + "name": "ebwizardry:subsets/rare_artefacts", + "weight": 3 + }, + { + "type": "loot_table", + "name": "ebwizardry:subsets/epic_artefacts", + "weight": 1 + } + ] + }, + { + "name": "high_value", + "rolls": { + "min": 1, + "max": 2 + }, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/arcane_tomes", + "weight": 6 + }, + { + "type": "loot_table", + "name": "ebwizardry:subsets/wand_upgrades", + "weight": 4 + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "weight": 40, + "functions": [ + { + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:scroll", + "weight": 14, + "functions": [ + { + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:identification_scroll", + "weight": 6 + }, + { + "type": "item", + "name": "minecraft:gold_ingot", + "weight": 6, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 6 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:emerald", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 3 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:armour_upgrade", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:astral_diamond", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:purifying_elixir", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:grand_crystal", + "weight": 1 + } + ] + }, + { + "name": "low_value", + "rolls": { + "min": 2, + "max": 4 + }, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/elemental_crystals", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:magic_crystal", + "weight": 5, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 12 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:book", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 2 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:string", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "minecraft:paper", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:crystal_shard", + "weight": 2, + "functions": [ + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/wizard_tower.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/wizard_tower.json index 6e6dc805..4bb9c235 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/chests/wizard_tower.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/wizard_tower.json @@ -9,7 +9,7 @@ "entries": [ { "type": "loot_table", - "name": "ebwizardry:subsets/novice_wands", + "name": "ebwizardry:subsets/elemental_crystals", "weight": 3 }, { @@ -20,7 +20,7 @@ { "type": "loot_table", "name": "ebwizardry:subsets/arcane_tomes", - "weight": 6 + "weight": 4 }, { "type": "loot_table", @@ -47,17 +47,26 @@ "weight": 20, "functions": [ { - "function": "ebwizardry:random_spell" + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 } ] }, { "type": "item", "name": "ebwizardry:scroll", - "weight": 10, + "weight": 7, "functions": [ { - "function": "ebwizardry:random_spell" + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } } ] }, diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/mob_additions.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/mob_additions.json index bf5c34d1..fa25f7e6 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/entities/mob_additions.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/mob_additions.json @@ -1,6 +1,7 @@ { "pools": [ { + "name": "wizardry", "conditions": [ { "condition": "killed_by_player" @@ -19,7 +20,12 @@ "weight": 1, "functions": [ { - "function": "ebwizardry:random_spell" + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ] } ] } diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/elemental_crystals.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/elemental_crystals.json new file mode 100644 index 00000000..56631c6d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/elemental_crystals.json @@ -0,0 +1,24 @@ +{ + "pools": [ + { + "name": "crystals", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:magic_crystal", + "weight": 1, + "functions": [ + { + "function": "set_data", + "data": { + "min": 1, + "max": 7 + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/epic_artefacts.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/epic_artefacts.json new file mode 100644 index 00000000..dd8cd8cb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/epic_artefacts.json @@ -0,0 +1,75 @@ +{ + "pools": [ + { + "name": "epic_artefacts", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:ring_combustion", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_arcane_frost", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_seeking", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_hammer", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_mana_return", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_interdiction", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_ice_immunity", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_wither_immunity", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_glide", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_resurrection", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_experience_tome", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_silk_touch", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_stop_time", + "weight": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/novice_wands.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/novice_wands.json deleted file mode 100644 index 8ed04e9b..00000000 --- a/src/main/resources/assets/ebwizardry/loot_tables/subsets/novice_wands.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "pools": [ - { - "name": "wands", - "rolls": 1, - "entries": [ - { - "type": "item", - "name": "ebwizardry:magic_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_fire_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_ice_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_lightning_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_necromancy_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_earth_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_sorcery_wand", - "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:basic_healing_wand", - "weight": 1 - } - ] - } - ] -} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/rare_artefacts.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/rare_artefacts.json new file mode 100644 index 00000000..24a74e24 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/rare_artefacts.json @@ -0,0 +1,145 @@ +{ + "pools": [ + { + "name": "rare_artefacts", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:ring_condensing", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_battlemage", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_disintegration", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_shattering", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_storm", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_soulbinding", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_leeching", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_mind_control", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_poison", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_full_moon", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_blockwrangler", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_conjurer", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_defender", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_arcane_defence", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_wisdom", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_fire_cloaking", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_potential", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_anchoring", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_transience", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_auto_shield", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_haggler", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_auto_smelt", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_storm", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_minion_variants", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_flight", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_abseiling", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_light", + "weight": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/uncommon_artefacts.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/uncommon_artefacts.json new file mode 100644 index 00000000..c52abb3d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/uncommon_artefacts.json @@ -0,0 +1,115 @@ +{ + "pools": [ + { + "name": "uncommon_artefacts", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:ring_siphoning", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_fire_melee", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_fire_biome", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_ice_melee", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_ice_biome", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_lightning_melee", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_necromancy_melee", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_earth_melee", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_earth_biome", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_extraction", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:ring_paladin", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_warding", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_fire_protection", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_ice_protection", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_channeling", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_lich", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_banishing", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:amulet_recovery", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_minion_health", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_growth", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_feeding", + "weight": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/subsets/wand_upgrades.json b/src/main/resources/assets/ebwizardry/loot_tables/subsets/wand_upgrades.json index 3409fd48..327c379b 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/subsets/wand_upgrades.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/wand_upgrades.json @@ -43,7 +43,12 @@ "type": "item", "name": "ebwizardry:attunement_upgrade", "weight": 1 - } + }, + { + "type": "item", + "name": "ebwizardry:melee_upgrade", + "weight": 1 + } ] } ] diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/earth_crystal_block.json new file mode 100644 index 00000000..c0baec0f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_earth" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_1.json new file mode 100644 index 00000000..105a76c9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_earth_0", + "rune": "ebwizardry:blocks/runestone_earth_1", + "overlay": "ebwizardry:blocks/runestone_earth_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_2.json new file mode 100644 index 00000000..5cba9240 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_earth_0", + "rune": "ebwizardry:blocks/runestone_earth_2", + "overlay": "ebwizardry:blocks/runestone_earth_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_3.json new file mode 100644 index 00000000..72f72fd0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_earth_0", + "rune": "ebwizardry:blocks/runestone_earth_3", + "overlay": "ebwizardry:blocks/runestone_earth_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_4.json new file mode 100644 index 00000000..e712f365 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_earth_0", + "rune": "ebwizardry:blocks/runestone_earth_4", + "overlay": "ebwizardry:blocks/runestone_earth_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/earth_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_pedestal.json new file mode 100644 index 00000000..c0c68819 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/earth_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_earth", + "top": "ebwizardry:blocks/runestone_earth_0", + "bottom": "ebwizardry:blocks/runestone_earth_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_earth_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/fire_crystal_block.json new file mode 100644 index 00000000..fc76eed1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_fire" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_1.json new file mode 100644 index 00000000..91f6f742 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_fire_0", + "rune": "ebwizardry:blocks/runestone_fire_1", + "overlay": "ebwizardry:blocks/runestone_fire_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_2.json new file mode 100644 index 00000000..4b09137d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_fire_0", + "rune": "ebwizardry:blocks/runestone_fire_2", + "overlay": "ebwizardry:blocks/runestone_fire_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_3.json new file mode 100644 index 00000000..8a29a569 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_fire_0", + "rune": "ebwizardry:blocks/runestone_fire_3", + "overlay": "ebwizardry:blocks/runestone_fire_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_4.json new file mode 100644 index 00000000..b947eff0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_fire_0", + "rune": "ebwizardry:blocks/runestone_fire_4", + "overlay": "ebwizardry:blocks/runestone_fire_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/fire_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_pedestal.json new file mode 100644 index 00000000..217052eb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/fire_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_fire", + "top": "ebwizardry:blocks/runestone_fire_0", + "bottom": "ebwizardry:blocks/runestone_fire_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_fire_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/healing_crystal_block.json new file mode 100644 index 00000000..8b3b9b59 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_healing" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_1.json new file mode 100644 index 00000000..0d22b1c2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_healing_0", + "rune": "ebwizardry:blocks/runestone_healing_1", + "overlay": "ebwizardry:blocks/runestone_healing_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_2.json new file mode 100644 index 00000000..d53d274d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_healing_0", + "rune": "ebwizardry:blocks/runestone_healing_2", + "overlay": "ebwizardry:blocks/runestone_healing_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_3.json new file mode 100644 index 00000000..072acd5d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_healing_0", + "rune": "ebwizardry:blocks/runestone_healing_3", + "overlay": "ebwizardry:blocks/runestone_healing_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_4.json new file mode 100644 index 00000000..c4c5c93e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_healing_0", + "rune": "ebwizardry:blocks/runestone_healing_4", + "overlay": "ebwizardry:blocks/runestone_healing_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/healing_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_pedestal.json new file mode 100644 index 00000000..ae6c6622 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/healing_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_healing", + "top": "ebwizardry:blocks/runestone_healing_0", + "bottom": "ebwizardry:blocks/runestone_healing_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_healing_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/ice_crystal_block.json new file mode 100644 index 00000000..d9a986c8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_ice" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_1.json new file mode 100644 index 00000000..29ad56da --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_ice_0", + "rune": "ebwizardry:blocks/runestone_ice_1", + "overlay": "ebwizardry:blocks/runestone_ice_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_2.json new file mode 100644 index 00000000..238ba14b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_ice_0", + "rune": "ebwizardry:blocks/runestone_ice_2", + "overlay": "ebwizardry:blocks/runestone_ice_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_3.json new file mode 100644 index 00000000..08b19d15 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_ice_0", + "rune": "ebwizardry:blocks/runestone_ice_3", + "overlay": "ebwizardry:blocks/runestone_ice_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_4.json new file mode 100644 index 00000000..657dbaa1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_ice_0", + "rune": "ebwizardry:blocks/runestone_ice_4", + "overlay": "ebwizardry:blocks/runestone_ice_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/ice_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_pedestal.json new file mode 100644 index 00000000..b627123b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/ice_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_ice", + "top": "ebwizardry:blocks/runestone_ice_0", + "bottom": "ebwizardry:blocks/runestone_ice_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_ice_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/lightning_crystal_block.json new file mode 100644 index 00000000..b9dd389e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_lightning" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_1.json new file mode 100644 index 00000000..3773208b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_lightning_0", + "rune": "ebwizardry:blocks/runestone_lightning_1", + "overlay": "ebwizardry:blocks/runestone_lightning_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_2.json new file mode 100644 index 00000000..2e461a41 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_lightning_0", + "rune": "ebwizardry:blocks/runestone_lightning_2", + "overlay": "ebwizardry:blocks/runestone_lightning_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_3.json new file mode 100644 index 00000000..1db68f01 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_lightning_0", + "rune": "ebwizardry:blocks/runestone_lightning_3", + "overlay": "ebwizardry:blocks/runestone_lightning_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_4.json new file mode 100644 index 00000000..e4cca3cc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_lightning_0", + "rune": "ebwizardry:blocks/runestone_lightning_4", + "overlay": "ebwizardry:blocks/runestone_lightning_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_pedestal.json new file mode 100644 index 00000000..212353ee --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lightning_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_lightning", + "top": "ebwizardry:blocks/runestone_lightning_0", + "bottom": "ebwizardry:blocks/runestone_lightning_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_lightning_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/magic_crystal_block.json similarity index 100% rename from src/main/resources/assets/ebwizardry/models/block/crystal_block.json rename to src/main/resources/assets/ebwizardry/models/block/magic_crystal_block.json diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_crystal_block.json new file mode 100644 index 00000000..52268c00 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_necromancy" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_1.json new file mode 100644 index 00000000..9d9bf318 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_necromancy_0", + "rune": "ebwizardry:blocks/runestone_necromancy_1", + "overlay": "ebwizardry:blocks/runestone_necromancy_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_2.json new file mode 100644 index 00000000..8004eac5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_necromancy_0", + "rune": "ebwizardry:blocks/runestone_necromancy_2", + "overlay": "ebwizardry:blocks/runestone_necromancy_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_3.json new file mode 100644 index 00000000..0eea58e9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_necromancy_0", + "rune": "ebwizardry:blocks/runestone_necromancy_3", + "overlay": "ebwizardry:blocks/runestone_necromancy_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_4.json new file mode 100644 index 00000000..4a83a330 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_necromancy_0", + "rune": "ebwizardry:blocks/runestone_necromancy_4", + "overlay": "ebwizardry:blocks/runestone_necromancy_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_pedestal.json new file mode 100644 index 00000000..a787cd59 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/necromancy_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_necromancy", + "top": "ebwizardry:blocks/runestone_necromancy_0", + "bottom": "ebwizardry:blocks/runestone_necromancy_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_necromancy_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_0.json b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_0.json new file mode 100644 index 00000000..d353e00c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_0.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/obsidian_crust_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_1.json b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_1.json new file mode 100644 index 00000000..a18b3ea2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_1.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/obsidian_crust_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_2.json b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_2.json new file mode 100644 index 00000000..b29fb73b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_2.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/obsidian_crust_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_3.json b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_3.json new file mode 100644 index 00000000..a25d73bd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/obsidian_crust_3.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/obsidian_crust_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/runestone.json b/src/main/resources/assets/ebwizardry/models/block/runestone.json new file mode 100644 index 00000000..ed3e9fa6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/runestone.json @@ -0,0 +1,27 @@ +{ + "parent": "block/block", + "textures": { + "particle": "#rune" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "up" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#rune", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "north" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/block/runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/runestone_pedestal.json new file mode 100644 index 00000000..63677186 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/runestone_pedestal.json @@ -0,0 +1,30 @@ +{ + "parent": "block/block", + "textures": { + "particle": "#side" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top", "cullface": "up" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "east" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_crystal_block.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_crystal_block.json new file mode 100644 index 00000000..2c775847 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_crystal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/crystal_block_sorcery" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_1.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_1.json new file mode 100644 index 00000000..75c4f206 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_1.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_sorcery_0", + "rune": "ebwizardry:blocks/runestone_sorcery_1", + "overlay": "ebwizardry:blocks/runestone_sorcery_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_2.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_2.json new file mode 100644 index 00000000..a8d1eb5a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_2.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_sorcery_0", + "rune": "ebwizardry:blocks/runestone_sorcery_2", + "overlay": "ebwizardry:blocks/runestone_sorcery_2_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_3.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_3.json new file mode 100644 index 00000000..f280d0e4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_3.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_sorcery_0", + "rune": "ebwizardry:blocks/runestone_sorcery_3", + "overlay": "ebwizardry:blocks/runestone_sorcery_3_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_4.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_4.json new file mode 100644 index 00000000..37cc5471 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_4.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:block/runestone", + "textures": { + "side": "ebwizardry:blocks/runestone_sorcery_0", + "rune": "ebwizardry:blocks/runestone_sorcery_4", + "overlay": "ebwizardry:blocks/runestone_sorcery_4_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_pedestal.json new file mode 100644 index 00000000..05fa63dc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/sorcery_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/runestone_pedestal", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_sorcery", + "top": "ebwizardry:blocks/runestone_sorcery_0", + "bottom": "ebwizardry:blocks/runestone_sorcery_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_sorcery_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_0.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_0.json new file mode 100644 index 00000000..78a4386f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_0.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_1.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_1.json new file mode 100644 index 00000000..70929b50 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_1.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_2.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_2.json new file mode 100644 index 00000000..09671006 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_2.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_3.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_3.json new file mode 100644 index 00000000..9ad8e92d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_3.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_4.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_4.json new file mode 100644 index 00000000..00234c0d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_4.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_5.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_5.json new file mode 100644 index 00000000..3697dbd0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_5.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_6.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_6.json new file mode 100644 index 00000000..8bf7cd08 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_6.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_lower_7.json b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_7.json new file mode 100644 index 00000000..558dfa08 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_lower_7.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_lower_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_0.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_0.json new file mode 100644 index 00000000..8a652816 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_0.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_1.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_1.json new file mode 100644 index 00000000..b7d8b60f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_1.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_2.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_2.json new file mode 100644 index 00000000..aef33fc7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_2.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_3.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_3.json new file mode 100644 index 00000000..795e030d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_3.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_4.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_4.json new file mode 100644 index 00000000..47360602 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_4.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_5.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_5.json new file mode 100644 index 00000000..78b61bd2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_5.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_6.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_6.json new file mode 100644 index 00000000..ffc50f97 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_6.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/thorns_upper_7.json b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_7.json new file mode 100644 index 00000000..c1cf103f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/thorns_upper_7.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cross", + "textures": { + "cross": "ebwizardry:blocks/thorns_upper_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_anchoring.json b/src/main/resources/assets/ebwizardry/models/item/amulet_anchoring.json new file mode 100644 index 00000000..9414c2cb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_anchoring.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_anchoring" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_arcane_defence.json b/src/main/resources/assets/ebwizardry/models/item/amulet_arcane_defence.json new file mode 100644 index 00000000..fd13bda3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_arcane_defence.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_arcane_defence" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_auto_shield.json b/src/main/resources/assets/ebwizardry/models/item/amulet_auto_shield.json new file mode 100644 index 00000000..e587e072 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_auto_shield.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_auto_shield" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_banishing.json b/src/main/resources/assets/ebwizardry/models/item/amulet_banishing.json new file mode 100644 index 00000000..d79c9040 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_banishing.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_banishing" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_channeling.json b/src/main/resources/assets/ebwizardry/models/item/amulet_channeling.json new file mode 100644 index 00000000..a1e52aa6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_channeling.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_channeling" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_fire_cloaking.json b/src/main/resources/assets/ebwizardry/models/item/amulet_fire_cloaking.json new file mode 100644 index 00000000..a08ebc3a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_fire_cloaking.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_fire_cloaking" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_fire_protection.json b/src/main/resources/assets/ebwizardry/models/item/amulet_fire_protection.json new file mode 100644 index 00000000..cea2dd3c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_fire_protection.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_fire_protection" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_glide.json b/src/main/resources/assets/ebwizardry/models/item/amulet_glide.json new file mode 100644 index 00000000..7ffa1018 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_glide.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_glide" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_ice_immunity.json b/src/main/resources/assets/ebwizardry/models/item/amulet_ice_immunity.json new file mode 100644 index 00000000..e0794036 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_ice_immunity.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_ice_immunity" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_ice_protection.json b/src/main/resources/assets/ebwizardry/models/item/amulet_ice_protection.json new file mode 100644 index 00000000..d18a09cd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_ice_protection.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_ice_protection" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_lich.json b/src/main/resources/assets/ebwizardry/models/item/amulet_lich.json new file mode 100644 index 00000000..79c28033 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_lich.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_lich" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_potential.json b/src/main/resources/assets/ebwizardry/models/item/amulet_potential.json new file mode 100644 index 00000000..1d0e2976 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_potential.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_potential" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_recovery.json b/src/main/resources/assets/ebwizardry/models/item/amulet_recovery.json new file mode 100644 index 00000000..dd9d0739 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_recovery.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_recovery" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_resurrection.json b/src/main/resources/assets/ebwizardry/models/item/amulet_resurrection.json new file mode 100644 index 00000000..4191270c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_resurrection.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_resurrection" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_transience.json b/src/main/resources/assets/ebwizardry/models/item/amulet_transience.json new file mode 100644 index 00000000..116b66ff --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_transience.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_transience" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_warding.json b/src/main/resources/assets/ebwizardry/models/item/amulet_warding.json new file mode 100644 index 00000000..88587ffd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_warding.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_warding" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_wisdom.json b/src/main/resources/assets/ebwizardry/models/item/amulet_wisdom.json new file mode 100644 index 00000000..290e0231 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_wisdom.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_wisdom" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_wither_immunity.json b/src/main/resources/assets/ebwizardry/models/item/amulet_wither_immunity.json new file mode 100644 index 00000000..71c142fa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_wither_immunity.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_wither_immunity" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/astral_diamond.json b/src/main/resources/assets/ebwizardry/models/item/astral_diamond.json new file mode 100644 index 00000000..c5f31ce7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/astral_diamond.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/astral_diamond" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_lightning_wand.json b/src/main/resources/assets/ebwizardry/models/item/basic_lightning_wand.json deleted file mode 100644 index b65b8970..00000000 --- a/src/main/resources/assets/ebwizardry/models/item/basic_lightning_wand.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "ebwizardry:items/wand_basic_lightning" - } -} diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_necromancy_wand.json b/src/main/resources/assets/ebwizardry/models/item/basic_necromancy_wand.json deleted file mode 100644 index 6af0d640..00000000 --- a/src/main/resources/assets/ebwizardry/models/item/basic_necromancy_wand.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "ebwizardry:items/wand_basic_necromancy" - } -} diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_sorcery_wand.json b/src/main/resources/assets/ebwizardry/models/item/basic_sorcery_wand.json deleted file mode 100644 index c46f8182..00000000 --- a/src/main/resources/assets/ebwizardry/models/item/basic_sorcery_wand.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "item/handheld", - "textures": { - "layer0": "ebwizardry:items/wand_basic_sorcery" - } -} diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_abseiling.json b/src/main/resources/assets/ebwizardry/models/item/charm_abseiling.json new file mode 100644 index 00000000..d8981e3b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_abseiling.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_abseiling" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_auto_smelt.json b/src/main/resources/assets/ebwizardry/models/item/charm_auto_smelt.json new file mode 100644 index 00000000..e1463ad7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_auto_smelt.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_auto_smelt" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_experience_tome.json b/src/main/resources/assets/ebwizardry/models/item/charm_experience_tome.json new file mode 100644 index 00000000..c4a13245 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_experience_tome.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_experience_tome" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_feeding.json b/src/main/resources/assets/ebwizardry/models/item/charm_feeding.json new file mode 100644 index 00000000..a44e41bb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_feeding.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_feeding" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_flight.json b/src/main/resources/assets/ebwizardry/models/item/charm_flight.json new file mode 100644 index 00000000..3afe790e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_flight.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_flight" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_growth.json b/src/main/resources/assets/ebwizardry/models/item/charm_growth.json new file mode 100644 index 00000000..8538a441 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_growth.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_growth" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_haggler.json b/src/main/resources/assets/ebwizardry/models/item/charm_haggler.json new file mode 100644 index 00000000..351127fa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_haggler.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_haggler" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_lava_walking.json b/src/main/resources/assets/ebwizardry/models/item/charm_lava_walking.json new file mode 100644 index 00000000..380c928b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_lava_walking.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_lava_walking" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_light.json b/src/main/resources/assets/ebwizardry/models/item/charm_light.json new file mode 100644 index 00000000..a26d6b90 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_light.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_light" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_minion_health.json b/src/main/resources/assets/ebwizardry/models/item/charm_minion_health.json new file mode 100644 index 00000000..ae89ce50 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_minion_health.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_minion_health" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_minion_variants.json b/src/main/resources/assets/ebwizardry/models/item/charm_minion_variants.json new file mode 100644 index 00000000..0ad912bd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_minion_variants.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_minion_variants" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_silk_touch.json b/src/main/resources/assets/ebwizardry/models/item/charm_silk_touch.json new file mode 100644 index 00000000..0461fcb6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_silk_touch.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_silk_touch" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_stop_time.json b/src/main/resources/assets/ebwizardry/models/item/charm_stop_time.json new file mode 100644 index 00000000..5f518c1d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_stop_time.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_stop_time" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_storm.json b/src/main/resources/assets/ebwizardry/models/item/charm_storm.json new file mode 100644 index 00000000..3132cab8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_storm.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_storm" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_transportation.json b/src/main/resources/assets/ebwizardry/models/item/charm_transportation.json new file mode 100644 index 00000000..71c0e708 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_transportation.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_transportation" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/crystal_block.json deleted file mode 100644 index 0070e57f..00000000 --- a/src/main/resources/assets/ebwizardry/models/item/crystal_block.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "parent": "ebwizardry:block/crystal_block" -} diff --git a/src/main/resources/assets/ebwizardry/models/item/magic_crystal.json b/src/main/resources/assets/ebwizardry/models/item/crystal_earth.json similarity index 53% rename from src/main/resources/assets/ebwizardry/models/item/magic_crystal.json rename to src/main/resources/assets/ebwizardry/models/item/crystal_earth.json index 9e2e4903..723d42e0 100644 --- a/src/main/resources/assets/ebwizardry/models/item/magic_crystal.json +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_earth.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "ebwizardry:items/magic_crystal" + "layer0": "ebwizardry:items/crystal_earth" } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_fire.json b/src/main/resources/assets/ebwizardry/models/item/crystal_fire.json new file mode 100644 index 00000000..573dfe03 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_fire.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_fire" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_healing.json b/src/main/resources/assets/ebwizardry/models/item/crystal_healing.json new file mode 100644 index 00000000..d91d3939 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_healing.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_healing" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_ice.json b/src/main/resources/assets/ebwizardry/models/item/crystal_ice.json new file mode 100644 index 00000000..c879ac46 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_ice.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_ice" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_lightning.json b/src/main/resources/assets/ebwizardry/models/item/crystal_lightning.json new file mode 100644 index 00000000..1c09de74 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_lightning.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_lightning" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_magic.json b/src/main/resources/assets/ebwizardry/models/item/crystal_magic.json new file mode 100644 index 00000000..23f6c4c2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_magic.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_magic" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_necromancy.json b/src/main/resources/assets/ebwizardry/models/item/crystal_necromancy.json new file mode 100644 index 00000000..bd3310a9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_necromancy.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_necromancy" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_shard.json b/src/main/resources/assets/ebwizardry/models/item/crystal_shard.json new file mode 100644 index 00000000..b0cd0f4d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_shard.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_shard" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/crystal_sorcery.json b/src/main/resources/assets/ebwizardry/models/item/crystal_sorcery.json new file mode 100644 index 00000000..0291367b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/crystal_sorcery.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_sorcery" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/earth_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/earth_crystal_block.json new file mode 100644 index 00000000..dcdb4abd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/earth_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/earth_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/earth_runestone.json b/src/main/resources/assets/ebwizardry/models/item/earth_runestone.json new file mode 100644 index 00000000..cd9da38a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/earth_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_earth_0", + "rune": "ebwizardry:blocks/runestone_earth_1", + "overlay": "ebwizardry:blocks/runestone_earth_1_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/earth_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/earth_runestone_pedestal.json new file mode 100644 index 00000000..a63834d2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/earth_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_earth", + "top": "ebwizardry:blocks/runestone_earth_0", + "bottom": "ebwizardry:blocks/runestone_earth_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_earth_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/fire_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/fire_crystal_block.json new file mode 100644 index 00000000..d3b21460 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/fire_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/fire_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/fire_runestone.json b/src/main/resources/assets/ebwizardry/models/item/fire_runestone.json new file mode 100644 index 00000000..9d526484 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/fire_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_fire_0", + "rune": "ebwizardry:blocks/runestone_fire_1", + "overlay": "ebwizardry:blocks/runestone_fire_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/fire_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/fire_runestone_pedestal.json new file mode 100644 index 00000000..e82c1691 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/fire_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_fire", + "top": "ebwizardry:blocks/runestone_fire_0", + "bottom": "ebwizardry:blocks/runestone_fire_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_fire_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe.json index d2893024..8275b14c 100644 --- a/src/main/resources/assets/ebwizardry/models/item/flaming_axe.json +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe.json @@ -2,5 +2,62 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/flaming_axe" - } + }, + "overrides": [ + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/flaming_axe_conjuring_7" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_0.json new file mode 100644 index 00000000..6465ed5b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_1.json new file mode 100644 index 00000000..9b42df9a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_2.json new file mode 100644 index 00000000..5a5e5692 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_3.json new file mode 100644 index 00000000..bec94f1b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_4.json new file mode 100644 index 00000000..e0951dad --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_5.json new file mode 100644 index 00000000..d9c99c9e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_6.json new file mode 100644 index 00000000..47959abb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_7.json new file mode 100644 index 00000000..76d698cb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flaming_axe_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/flaming_axe_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe.json index 198a09db..57a68827 100644 --- a/src/main/resources/assets/ebwizardry/models/item/frost_axe.json +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe.json @@ -2,5 +2,62 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/frost_axe" - } + }, + "overrides": [ + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/frost_axe_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/frost_axe_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/frost_axe_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/frost_axe_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/frost_axe_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/frost_axe_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/frost_axe_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/frost_axe_conjuring_7" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_0.json new file mode 100644 index 00000000..d72f22b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_1.json new file mode 100644 index 00000000..39af7f39 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_2.json new file mode 100644 index 00000000..bed93962 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_3.json new file mode 100644 index 00000000..1d1f7838 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_4.json new file mode 100644 index 00000000..fb7d4fd3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_5.json new file mode 100644 index 00000000..6f34b837 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_6.json new file mode 100644 index 00000000..6eeac463 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_7.json new file mode 100644 index 00000000..33bc3775 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/frost_axe_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/frost_axe_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/grand_crystal.json b/src/main/resources/assets/ebwizardry/models/item/grand_crystal.json new file mode 100644 index 00000000..c58097d9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/grand_crystal.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/crystal_grand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/healing_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/healing_crystal_block.json new file mode 100644 index 00000000..2932f2ad --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/healing_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/healing_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/healing_runestone.json b/src/main/resources/assets/ebwizardry/models/item/healing_runestone.json new file mode 100644 index 00000000..c583f383 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/healing_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_healing_0", + "rune": "ebwizardry:blocks/runestone_healing_1", + "overlay": "ebwizardry:blocks/runestone_healing_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/healing_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/healing_runestone_pedestal.json new file mode 100644 index 00000000..7089d574 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/healing_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_healing", + "top": "ebwizardry:blocks/runestone_healing_0", + "bottom": "ebwizardry:blocks/runestone_healing_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_healing_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ice_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/ice_crystal_block.json new file mode 100644 index 00000000..f2bf82fd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ice_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/ice_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/ice_runestone.json b/src/main/resources/assets/ebwizardry/models/item/ice_runestone.json new file mode 100644 index 00000000..f3313d51 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ice_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_ice_0", + "rune": "ebwizardry:blocks/runestone_ice_1", + "overlay": "ebwizardry:blocks/runestone_ice_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/ice_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/ice_runestone_pedestal.json new file mode 100644 index 00000000..e90ed3eb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ice_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_ice", + "top": "ebwizardry:blocks/runestone_ice_0", + "bottom": "ebwizardry:blocks/runestone_ice_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_ice_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/large_mana_flask.json b/src/main/resources/assets/ebwizardry/models/item/large_mana_flask.json new file mode 100644 index 00000000..be926384 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/large_mana_flask.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/mana_flask_large" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/lightning_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/lightning_crystal_block.json new file mode 100644 index 00000000..5b306ff5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/lightning_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/lightning_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/lightning_hammer.json b/src/main/resources/assets/ebwizardry/models/item/lightning_hammer.json new file mode 100644 index 00000000..6019ecf1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/lightning_hammer.json @@ -0,0 +1,122 @@ +{ + "credit": "Made with Blockbench", + "textures": { + "0": "ebwizardry:entity/lightning_hammer", + "particle": "ebwizardry:entity/lightning_hammer" + }, + "elements": [ + { + "name": "hammer_head", + "from": [-2, 1, 2], + "to": [18, 13, 14], + "faces": { + "north": {"uv": [3, 3, 8, 6], "texture": "#0"}, + "east": {"uv": [0, 3, 3, 6], "texture": "#0"}, + "south": {"uv": [11, 3, 16, 6], "texture": "#0"}, + "west": {"uv": [8, 3, 11, 6], "texture": "#0"}, + "up": {"uv": [3, 0, 8, 3], "texture": "#0"}, + "down": {"uv": [8, 0, 13, 3], "texture": "#0"} + } + }, + { + "name": "handle", + "from": [6, 13, 6], + "to": [10, 27, 10], + "faces": { + "north": {"uv": [1, 7, 2, 10.5], "texture": "#0"}, + "east": {"uv": [0, 7, 1, 10.5], "texture": "#0"}, + "south": {"uv": [3, 7, 4, 10.5], "texture": "#0"}, + "west": {"uv": [2, 7, 3, 10.5], "texture": "#0"}, + "up": {"uv": [1, 6, 2, 7], "texture": "#0"}, + "down": {"uv": [2, 6, 3, 7], "texture": "#0"} + } + }, + { + "name": "handle_end", + "from": [5.5, 27, 5.5], + "to": [10.5, 32, 10.5], + "faces": { + "north": {"uv": [1.25, 13.5, 2.5, 14.75], "texture": "#0"}, + "east": {"uv": [0, 13.5, 1.25, 14.75], "texture": "#0"}, + "south": {"uv": [3.75, 13.5, 5, 14.75], "texture": "#0"}, + "west": {"uv": [2.5, 13.5, 3.75, 14.75], "texture": "#0"}, + "up": {"uv": [1.25, 12.25, 2.5, 13.5], "texture": "#0"}, + "down": {"uv": [2.5, 12.25, 3.75, 13.5], "texture": "#0"} + } + }, + { + "name": "handle_base", + "from": [5.5, 13, 5.5], + "to": [10.5, 15, 10.5], + "faces": { + "north": {"uv": [1.25, 11.75, 2.5, 12.25], "texture": "#0"}, + "east": {"uv": [0, 11.75, 1.25, 12.25], "texture": "#0"}, + "south": {"uv": [3.75, 11.75, 5, 12.25], "texture": "#0"}, + "west": {"uv": [2.5, 11.75, 3.75, 12.25], "texture": "#0"}, + "up": {"uv": [1.25, 10.5, 2.5, 11.75], "texture": "#0"}, + "down": {"uv": [2.5, 10.5, 3.75, 11.75], "texture": "#0"} + } + }, + { + "name": "ring1", + "from": [0, 0, 1], + "to": [2, 14, 15], + "faces": { + "north": {"uv": [8.5, 9.5, 9, 13], "texture": "#0"}, + "east": {"uv": [5, 9.5, 8.5, 13], "texture": "#0"}, + "south": {"uv": [12.5, 9.5, 13, 13], "texture": "#0"}, + "west": {"uv": [9, 9.5, 12.5, 13], "texture": "#0"}, + "up": {"uv": [8.5, 6, 9, 9.5], "texture": "#0"}, + "down": {"uv": [9, 6, 9.5, 9.5], "texture": "#0"} + } + }, + { + "name": "ring2", + "from": [14, 0, 1], + "to": [16, 14, 15], + "faces": { + "north": {"uv": [8.5, 9.5, 9, 13], "texture": "#0"}, + "east": {"uv": [5, 9.5, 8.5, 13], "texture": "#0"}, + "south": {"uv": [12.5, 9.5, 13, 13], "texture": "#0"}, + "west": {"uv": [9, 9.5, 12.5, 13], "texture": "#0"}, + "up": {"uv": [8.5, 6, 9, 9.5], "texture": "#0"}, + "down": {"uv": [9, 6, 9.5, 9.5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [90, 90, 90], + "translation": [0, 10, 2], + "scale": [0.75, 0.75, 0.75] + }, + "thirdperson_lefthand": { + "rotation": [90, 90, 90], + "translation": [0.1, 10, 2], + "scale": [0.75, 0.75, 0.75] + }, + "firstperson_righthand": { + "rotation": [90, 90, 90], + "translation": [0.1, 10, 2], + "scale": [0.75, 0.75, 0.75] + }, + "firstperson_lefthand": { + "rotation": [90, 90, 90], + "translation": [0, 10, 2], + "scale": [0.75, 0.75, 0.75] + }, + "ground": { + "scale": [0.75, 0.75, 0.75] + }, + "gui": { + "rotation": [0, -26, 135], + "translation": [1, 1.5, 0], + "scale": [0.45, 0.45, 0.45] + }, + "fixed": { + "rotation": [0, 0, -135], + "translation": [-2, 2, 0], + "scale": [0.6, 0.6, 0.6] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/lightning_runestone.json b/src/main/resources/assets/ebwizardry/models/item/lightning_runestone.json new file mode 100644 index 00000000..abdb1136 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/lightning_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_lightning_0", + "rune": "ebwizardry:blocks/runestone_lightning_1", + "overlay": "ebwizardry:blocks/runestone_lightning_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/lightning_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/lightning_runestone_pedestal.json new file mode 100644 index 00000000..bd76dcda --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/lightning_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_lightning", + "top": "ebwizardry:blocks/runestone_lightning_0", + "bottom": "ebwizardry:blocks/runestone_lightning_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_lightning_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/magic_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/magic_crystal_block.json new file mode 100644 index 00000000..d01cb6d3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/magic_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/magic_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/magic_wand.json b/src/main/resources/assets/ebwizardry/models/item/magic_wand.json index 661d9e14..59387cf7 100644 --- a/src/main/resources/assets/ebwizardry/models/item/magic_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/magic_wand.json @@ -1,6 +1,6 @@ { "parent": "item/handheld", "textures": { - "layer0": "ebwizardry:items/wand_basic" + "layer0": "ebwizardry:items/wand_novice" } } diff --git a/src/main/resources/assets/ebwizardry/models/item/medium_mana_flask.json b/src/main/resources/assets/ebwizardry/models/item/medium_mana_flask.json new file mode 100644 index 00000000..6bda3fba --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/medium_mana_flask.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/mana_flask_medium" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/melee_upgrade.json b/src/main/resources/assets/ebwizardry/models/item/melee_upgrade.json new file mode 100644 index 00000000..1aa6fd77 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/melee_upgrade.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/upgrade_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/necromancy_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/necromancy_crystal_block.json new file mode 100644 index 00000000..b0c03762 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/necromancy_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/necromancy_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone.json b/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone.json new file mode 100644 index 00000000..48e70eb4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_necromancy_0", + "rune": "ebwizardry:blocks/runestone_necromancy_1", + "overlay": "ebwizardry:blocks/runestone_necromancy_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone_pedestal.json new file mode 100644 index 00000000..9c980c83 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/necromancy_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_necromancy", + "top": "ebwizardry:blocks/runestone_necromancy_0", + "bottom": "ebwizardry:blocks/runestone_necromancy_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_necromancy_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_fire_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json similarity index 51% rename from src/main/resources/assets/ebwizardry/models/item/basic_fire_wand.json rename to src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json index 3a01a02e..7431443b 100644 --- a/src/main/resources/assets/ebwizardry/models/item/basic_fire_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json @@ -1,6 +1,6 @@ { "parent": "item/handheld", "textures": { - "layer0": "ebwizardry:items/wand_basic_fire" + "layer0": "ebwizardry:items/wand_novice_earth" } } diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_earth_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json similarity index 52% rename from src/main/resources/assets/ebwizardry/models/item/basic_earth_wand.json rename to src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json index abf377f1..27055741 100644 --- a/src/main/resources/assets/ebwizardry/models/item/basic_earth_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json @@ -1,6 +1,6 @@ { "parent": "item/handheld", "textures": { - "layer0": "ebwizardry:items/wand_basic_earth" + "layer0": "ebwizardry:items/wand_novice_fire" } } diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_healing_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json similarity index 50% rename from src/main/resources/assets/ebwizardry/models/item/basic_healing_wand.json rename to src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json index 2dcb118d..e9095bf3 100644 --- a/src/main/resources/assets/ebwizardry/models/item/basic_healing_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json @@ -1,6 +1,6 @@ { "parent": "item/handheld", "textures": { - "layer0": "ebwizardry:items/wand_basic_healing" + "layer0": "ebwizardry:items/wand_novice_healing" } } diff --git a/src/main/resources/assets/ebwizardry/models/item/basic_ice_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json similarity index 52% rename from src/main/resources/assets/ebwizardry/models/item/basic_ice_wand.json rename to src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json index 44181bcd..feb24c4c 100644 --- a/src/main/resources/assets/ebwizardry/models/item/basic_ice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json @@ -1,6 +1,6 @@ { "parent": "item/handheld", "textures": { - "layer0": "ebwizardry:items/wand_basic_ice" + "layer0": "ebwizardry:items/wand_novice_ice" } } diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_lightning_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_lightning_wand.json new file mode 100644 index 00000000..ce044666 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/novice_lightning_wand.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/wand_novice_lightning" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_necromancy_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_necromancy_wand.json new file mode 100644 index 00000000..ab64fb68 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/novice_necromancy_wand.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/wand_novice_necromancy" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_sorcery_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_sorcery_wand.json new file mode 100644 index 00000000..968ff38e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/novice_sorcery_wand.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/wand_novice_sorcery" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/purifying_elixir.json b/src/main/resources/assets/ebwizardry/models/item/purifying_elixir.json new file mode 100644 index 00000000..a213668e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/purifying_elixir.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/purifying_elixir" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_arcane_frost.json b/src/main/resources/assets/ebwizardry/models/item/ring_arcane_frost.json new file mode 100644 index 00000000..799e7483 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_arcane_frost.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_arcane_frost" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_battlemage.json b/src/main/resources/assets/ebwizardry/models/item/ring_battlemage.json new file mode 100644 index 00000000..af3c2e35 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_battlemage.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_battlemage" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_blockwrangler.json b/src/main/resources/assets/ebwizardry/models/item/ring_blockwrangler.json new file mode 100644 index 00000000..654f22b7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_blockwrangler.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_blockwrangler" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_combustion.json b/src/main/resources/assets/ebwizardry/models/item/ring_combustion.json new file mode 100644 index 00000000..55f914e3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_combustion.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_combustion" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_condensing.json b/src/main/resources/assets/ebwizardry/models/item/ring_condensing.json new file mode 100644 index 00000000..442f589d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_condensing.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_condensing" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_conjurer.json b/src/main/resources/assets/ebwizardry/models/item/ring_conjurer.json new file mode 100644 index 00000000..8c8a26f5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_conjurer.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_conjurer" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_defender.json b/src/main/resources/assets/ebwizardry/models/item/ring_defender.json new file mode 100644 index 00000000..3b5ad34d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_defender.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_defender" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_disintegration.json b/src/main/resources/assets/ebwizardry/models/item/ring_disintegration.json new file mode 100644 index 00000000..e8e3a131 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_disintegration.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_disintegration" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_earth_biome.json b/src/main/resources/assets/ebwizardry/models/item/ring_earth_biome.json new file mode 100644 index 00000000..7f5c46c9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_earth_biome.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_earth_biome" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_earth_melee.json b/src/main/resources/assets/ebwizardry/models/item/ring_earth_melee.json new file mode 100644 index 00000000..19213d66 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_earth_melee.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_earth_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_extraction.json b/src/main/resources/assets/ebwizardry/models/item/ring_extraction.json new file mode 100644 index 00000000..49c344f2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_extraction.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_extraction" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_fire_biome.json b/src/main/resources/assets/ebwizardry/models/item/ring_fire_biome.json new file mode 100644 index 00000000..fa30a48d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_fire_biome.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_fire_biome" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_fire_melee.json b/src/main/resources/assets/ebwizardry/models/item/ring_fire_melee.json new file mode 100644 index 00000000..4e89c680 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_fire_melee.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_fire_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_full_moon.json b/src/main/resources/assets/ebwizardry/models/item/ring_full_moon.json new file mode 100644 index 00000000..565a90c0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_full_moon.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_full_moon" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_hammer.json b/src/main/resources/assets/ebwizardry/models/item/ring_hammer.json new file mode 100644 index 00000000..ca3a1586 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_hammer.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_hammer" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_ice_biome.json b/src/main/resources/assets/ebwizardry/models/item/ring_ice_biome.json new file mode 100644 index 00000000..821fa0b7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_ice_biome.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_ice_biome" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_ice_melee.json b/src/main/resources/assets/ebwizardry/models/item/ring_ice_melee.json new file mode 100644 index 00000000..00951549 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_ice_melee.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_ice_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_interdiction.json b/src/main/resources/assets/ebwizardry/models/item/ring_interdiction.json new file mode 100644 index 00000000..d956a49a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_interdiction.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_interdiction" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_leeching.json b/src/main/resources/assets/ebwizardry/models/item/ring_leeching.json new file mode 100644 index 00000000..add07d6e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_leeching.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_leeching" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_lightning_melee.json b/src/main/resources/assets/ebwizardry/models/item/ring_lightning_melee.json new file mode 100644 index 00000000..44b4ba59 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_lightning_melee.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_lightning_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_mana_return.json b/src/main/resources/assets/ebwizardry/models/item/ring_mana_return.json new file mode 100644 index 00000000..07e9de7b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_mana_return.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_mana_return" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_mind_control.json b/src/main/resources/assets/ebwizardry/models/item/ring_mind_control.json new file mode 100644 index 00000000..d9657f06 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_mind_control.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_mind_control" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_necromancy_melee.json b/src/main/resources/assets/ebwizardry/models/item/ring_necromancy_melee.json new file mode 100644 index 00000000..14c32043 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_necromancy_melee.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_necromancy_melee" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_paladin.json b/src/main/resources/assets/ebwizardry/models/item/ring_paladin.json new file mode 100644 index 00000000..b41dc94a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_paladin.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_paladin" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_poison.json b/src/main/resources/assets/ebwizardry/models/item/ring_poison.json new file mode 100644 index 00000000..0273aad5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_poison.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_poison" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_seeking.json b/src/main/resources/assets/ebwizardry/models/item/ring_seeking.json new file mode 100644 index 00000000..bb5759dc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_seeking.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_seeking" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_shattering.json b/src/main/resources/assets/ebwizardry/models/item/ring_shattering.json new file mode 100644 index 00000000..509900bc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_shattering.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_shattering" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_siphoning.json b/src/main/resources/assets/ebwizardry/models/item/ring_siphoning.json new file mode 100644 index 00000000..d94758d9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_siphoning.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_siphoning" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_soulbinding.json b/src/main/resources/assets/ebwizardry/models/item/ring_soulbinding.json new file mode 100644 index 00000000..23e710f4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_soulbinding.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_soulbinding" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_spirit_animal.json b/src/main/resources/assets/ebwizardry/models/item/ring_spirit_animal.json new file mode 100644 index 00000000..838291be --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_spirit_animal.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_spirit_animal" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_storm.json b/src/main/resources/assets/ebwizardry/models/item/ring_storm.json new file mode 100644 index 00000000..1227579e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_storm.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_storm" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/runestone_item.json b/src/main/resources/assets/ebwizardry/models/item/runestone_item.json new file mode 100644 index 00000000..77b8d431 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/runestone_item.json @@ -0,0 +1,28 @@ +{ + "parent": "block/block", + "textures": { + "particle": "#rune" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "up" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#rune", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "shade": false, + "faces": { + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "north" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/item/runestone_pedestal_item.json b/src/main/resources/assets/ebwizardry/models/item/runestone_pedestal_item.json new file mode 100644 index 00000000..66455ce8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/runestone_pedestal_item.json @@ -0,0 +1,31 @@ +{ + "parent": "block/block", + "textures": { + "particle": "#side" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top", "cullface": "up" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 16 ], + "shade": false, + "faces": { + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "north" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "west" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#overlay", "cullface": "east" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/item/small_mana_flask.json b/src/main/resources/assets/ebwizardry/models/item/small_mana_flask.json new file mode 100644 index 00000000..8bbca59b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/small_mana_flask.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/mana_flask_small" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/sorcery_crystal_block.json b/src/main/resources/assets/ebwizardry/models/item/sorcery_crystal_block.json new file mode 100644 index 00000000..ecc2f3df --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/sorcery_crystal_block.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/sorcery_crystal_block" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone.json b/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone.json new file mode 100644 index 00000000..fb9805f3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone.json @@ -0,0 +1,8 @@ +{ + "parent": "ebwizardry:item/runestone_item", + "textures": { + "side": "ebwizardry:blocks/runestone_sorcery_0", + "rune": "ebwizardry:blocks/runestone_sorcery_1", + "overlay": "ebwizardry:blocks/runestone_sorcery_1_overlay" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone_pedestal.json b/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone_pedestal.json new file mode 100644 index 00000000..7b87f885 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/sorcery_runestone_pedestal.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:item/runestone_pedestal_item", + "textures": { + "side": "ebwizardry:blocks/runestone_pedestal_sorcery", + "top": "ebwizardry:blocks/runestone_sorcery_0", + "bottom": "ebwizardry:blocks/runestone_sorcery_0", + "overlay": "ebwizardry:blocks/runestone_pedestal_sorcery_overlay" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/mana_flask.json b/src/main/resources/assets/ebwizardry/models/item/spark_bomb.json similarity index 55% rename from src/main/resources/assets/ebwizardry/models/item/mana_flask.json rename to src/main/resources/assets/ebwizardry/models/item/spark_bomb.json index 278c497e..5e7423fa 100644 --- a/src/main/resources/assets/ebwizardry/models/item/mana_flask.json +++ b/src/main/resources/assets/ebwizardry/models/item/spark_bomb.json @@ -1,6 +1,6 @@ { "parent": "item/generated", "textures": { - "layer0": "ebwizardry:items/mana_flask" + "layer0": "ebwizardry:items/spark_bomb" } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow.json index e6e97ae8..befb8c49 100644 --- a/src/main/resources/assets/ebwizardry/models/item/spectral_bow.json +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow.json @@ -45,6 +45,61 @@ "pull": 0.9 }, "model": "ebwizardry:item/spectral_bow_pulling_2" + }, + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/spectral_bow_conjuring_7" } ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_0.json new file mode 100644 index 00000000..96be3a05 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_1.json new file mode 100644 index 00000000..1d515028 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_2.json new file mode 100644 index 00000000..811e8bcb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_3.json new file mode 100644 index 00000000..82a1134f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_4.json new file mode 100644 index 00000000..53925e92 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_5.json new file mode 100644 index 00000000..119c0e08 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_6.json new file mode 100644 index 00000000..5db23b8c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_7.json new file mode 100644 index 00000000..d4424975 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_bow_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/spectral_bow_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe.json index 3f4b3eb2..84f23972 100644 --- a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe.json +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe.json @@ -2,5 +2,62 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/spectral_pickaxe" - } + }, + "overrides": [ + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/spectral_pickaxe_conjuring_7" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_0.json new file mode 100644 index 00000000..08b5ec38 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_1.json new file mode 100644 index 00000000..e619f89f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_2.json new file mode 100644 index 00000000..664b43c8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_3.json new file mode 100644 index 00000000..5f35d953 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_4.json new file mode 100644 index 00000000..c9466a44 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_5.json new file mode 100644 index 00000000..e848aef6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_6.json new file mode 100644 index 00000000..4b100b8a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_7.json new file mode 100644 index 00000000..18b6c06c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_pickaxe_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_pickaxe_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword.json index c217987b..e812a899 100644 --- a/src/main/resources/assets/ebwizardry/models/item/spectral_sword.json +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword.json @@ -2,5 +2,62 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/spectral_sword" - } + }, + "overrides": [ + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/spectral_sword_conjuring_7" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_0.json new file mode 100644 index 00000000..89cae15a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_1.json new file mode 100644 index 00000000..5cf533e0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_2.json new file mode 100644 index 00000000..47d254b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_3.json new file mode 100644 index 00000000..e4e22c97 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_4.json new file mode 100644 index 00000000..c4a0a651 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_5.json new file mode 100644 index 00000000..5d84cb34 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_6.json new file mode 100644 index 00000000..78b6502e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_7.json new file mode 100644 index 00000000..9ec91330 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_sword_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/handheld", + "textures": { + "layer0": "ebwizardry:items/spectral_sword_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/thorns.json b/src/main/resources/assets/ebwizardry/models/item/thorns.json new file mode 100644 index 00000000..4255e8bb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/thorns.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:blocks/thorns_upper_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/recipes/arcane_workbench.json b/src/main/resources/assets/ebwizardry/recipes/arcane_workbench.json index 6727cb0c..735cacc0 100644 --- a/src/main/resources/assets/ebwizardry/recipes/arcane_workbench.json +++ b/src/main/resources/assets/ebwizardry/recipes/arcane_workbench.json @@ -14,9 +14,40 @@ "item": "minecraft:carpet", "data": 10 }, - "x": { - "item": "ebwizardry:magic_crystal" - }, + "x": [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ], "y": { "type": "forge:ore_dict", "ore": "blockLapis" diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block.json index e70bb53c..2bf0ef30 100644 --- a/src/main/resources/assets/ebwizardry/recipes/crystal_block.json +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block.json @@ -7,10 +7,12 @@ ], "key": { "z": { - "item": "ebwizardry:magic_crystal" + "item": "ebwizardry:magic_crystal", + "data": 0 } }, "result": { - "item": "ebwizardry:crystal_block" + "item": "ebwizardry:crystal_block", + "data": 0 } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_earth.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_earth.json new file mode 100644 index 00000000..c24d9441 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_earth.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 5 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_fire.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_fire.json new file mode 100644 index 00000000..fbb0b15a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_fire.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 1 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_healing.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_healing.json new file mode 100644 index 00000000..2cf4e092 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_healing.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 7 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_ice.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_ice.json new file mode 100644 index 00000000..3e172395 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_ice.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 2 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_lightning.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_lightning.json new file mode 100644 index 00000000..8f7417fc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_lightning.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 3 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_necromancy.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_necromancy.json new file mode 100644 index 00000000..4d76abdc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_necromancy.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 4 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_sorcery.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_sorcery.json new file mode 100644 index 00000000..079793ad --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_sorcery.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zzz", + "zzz" + ], + "key": { + "z": { + "item": "ebwizardry:magic_crystal", + "data": 6 + } + }, + "result": { + "item": "ebwizardry:crystal_block", + "data": 6 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals.json index 488efbac..d2cb4435 100644 --- a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals.json +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals.json @@ -3,11 +3,13 @@ "group": "magic_crystal", "ingredients": [ { - "item": "ebwizardry:crystal_block" + "item": "ebwizardry:crystal_block", + "data": 0 } ], "result": { "item": "ebwizardry:magic_crystal", + "data": 0, "count": 9 } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_earth.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_earth.json new file mode 100644 index 00000000..81d77ff9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_earth.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 5 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 5, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_fire.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_fire.json new file mode 100644 index 00000000..2760c161 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_fire.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 1 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 1, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_healing.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_healing.json new file mode 100644 index 00000000..46f180de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_healing.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 7 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 7, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_ice.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_ice.json new file mode 100644 index 00000000..bc34ba01 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_ice.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 2 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 2, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_lightning.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_lightning.json new file mode 100644 index 00000000..3bf151c0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_lightning.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 3 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 3, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_necromancy.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_necromancy.json new file mode 100644 index 00000000..0de3b757 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_necromancy.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 4 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 4, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_sorcery.json b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_sorcery.json new file mode 100644 index 00000000..cde5784c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_block_to_crystals_sorcery.json @@ -0,0 +1,15 @@ +{ + "type": "minecraft:crafting_shapeless", + "group": "magic_crystal", + "ingredients": [ + { + "item": "ebwizardry:crystal_block", + "data": 6 + } + ], + "result": { + "item": "ebwizardry:magic_crystal", + "data": 6, + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_flower_to_crystals.json b/src/main/resources/assets/ebwizardry/recipes/crystal_flower_to_crystals.json index 2ba98699..4a86c790 100644 --- a/src/main/resources/assets/ebwizardry/recipes/crystal_flower_to_crystals.json +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_flower_to_crystals.json @@ -8,6 +8,7 @@ ], "result": { "item": "ebwizardry:magic_crystal", + "data": 0, "count": 2 } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/crystal_shard.json b/src/main/resources/assets/ebwizardry/recipes/crystal_shard.json new file mode 100644 index 00000000..8055c3cf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/crystal_shard.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + } + ], + "result": { + "item": "ebwizardry:crystal_shard", + "count": 9 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/large_mana_flask.json b/src/main/resources/assets/ebwizardry/recipes/large_mana_flask.json new file mode 100644 index 00000000..ddd76bc0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/large_mana_flask.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + " y ", + "yxy", + " y " + ], + "key": { + "x": { + "item": "minecraft:glass_bottle" + }, + "y": { + "item": "ebwizardry:grand_crystal" + } + }, + "result": { + "item": "ebwizardry:large_mana_flask" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/magic_missile_spell_book.json b/src/main/resources/assets/ebwizardry/recipes/magic_missile_spell_book.json index b366af36..f182d17b 100644 --- a/src/main/resources/assets/ebwizardry/recipes/magic_missile_spell_book.json +++ b/src/main/resources/assets/ebwizardry/recipes/magic_missile_spell_book.json @@ -7,7 +7,8 @@ ], "key": { "x": { - "item": "ebwizardry:magic_crystal" + "item": "ebwizardry:magic_crystal", + "data": 0 }, "y": { "item": "minecraft:book" diff --git a/src/main/resources/assets/ebwizardry/recipes/magic_silk.json b/src/main/resources/assets/ebwizardry/recipes/magic_silk.json index 7f2cad0c..033a85e4 100644 --- a/src/main/resources/assets/ebwizardry/recipes/magic_silk.json +++ b/src/main/resources/assets/ebwizardry/recipes/magic_silk.json @@ -9,9 +9,40 @@ "x": { "item": "minecraft:string" }, - "y": { - "item": "ebwizardry:magic_crystal" - } + "y": [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ] }, "result": { "item": "ebwizardry:magic_silk", diff --git a/src/main/resources/assets/ebwizardry/recipes/magic_wand.json b/src/main/resources/assets/ebwizardry/recipes/magic_wand.json index ab8988a1..0326f1d3 100644 --- a/src/main/resources/assets/ebwizardry/recipes/magic_wand.json +++ b/src/main/resources/assets/ebwizardry/recipes/magic_wand.json @@ -15,7 +15,8 @@ "ore": "stickWood" }, "z": { - "item": "ebwizardry:magic_crystal" + "item": "ebwizardry:magic_crystal", + "data": 0 } }, "result": { diff --git a/src/main/resources/assets/ebwizardry/recipes/medium_mana_flask.json b/src/main/resources/assets/ebwizardry/recipes/medium_mana_flask.json new file mode 100644 index 00000000..05d32520 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/medium_mana_flask.json @@ -0,0 +1,50 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "yyy", + "yxy", + "yyy" + ], + "key": { + "x": { + "item": "minecraft:glass_bottle" + }, + "y": [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ] + }, + "result": { + "item": "ebwizardry:medium_mana_flask" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_earth.json b/src/main/resources/assets/ebwizardry/recipes/runestone_earth.json new file mode 100644 index 00000000..e9c6525d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_earth.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 5, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_fire.json b/src/main/resources/assets/ebwizardry/recipes/runestone_fire.json new file mode 100644 index 00000000..bbad7ccb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_fire.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 1, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_healing.json b/src/main/resources/assets/ebwizardry/recipes/runestone_healing.json new file mode 100644 index 00000000..91a51a69 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_healing.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 7 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 7, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_ice.json b/src/main/resources/assets/ebwizardry/recipes/runestone_ice.json new file mode 100644 index 00000000..cce3b6de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_ice.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 2, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_lightning.json b/src/main/resources/assets/ebwizardry/recipes/runestone_lightning.json new file mode 100644 index 00000000..d18ed59c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_lightning.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 3, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_necromancy.json b/src/main/resources/assets/ebwizardry/recipes/runestone_necromancy.json new file mode 100644 index 00000000..2c90d6e1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_necromancy.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 4, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_earth.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_earth.json new file mode 100644 index 00000000..740650c9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_earth.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 5, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_fire.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_fire.json new file mode 100644 index 00000000..966acb1f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_fire.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 1, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_healing.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_healing.json new file mode 100644 index 00000000..d61a20a3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_healing.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 7 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 7, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_ice.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_ice.json new file mode 100644 index 00000000..c9bb2a29 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_ice.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 2, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_lightning.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_lightning.json new file mode 100644 index 00000000..f04d08ae --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_lightning.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 3, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_necromancy.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_necromancy.json new file mode 100644 index 00000000..5b55f0cb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_necromancy.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 4, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_sorcery.json b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_sorcery.json new file mode 100644 index 00000000..3de61998 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_pedestal_sorcery.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "yyy", + "zyz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone_pedestal", + "data": 6, + "count": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/runestone_sorcery.json b/src/main/resources/assets/ebwizardry/recipes/runestone_sorcery.json new file mode 100644 index 00000000..b32ec980 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/runestone_sorcery.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "zzz", + "zyz", + "zzz" + ], + "key": { + "y": { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + "z": { + "type": "forge:ore_dict", + "ore": "stone" + } + }, + "result": { + "item": "ebwizardry:runestone", + "data": 6, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/mana_flask.json b/src/main/resources/assets/ebwizardry/recipes/small_mana_flask.json similarity index 71% rename from src/main/resources/assets/ebwizardry/recipes/mana_flask.json rename to src/main/resources/assets/ebwizardry/recipes/small_mana_flask.json index af183c45..50bd84d3 100644 --- a/src/main/resources/assets/ebwizardry/recipes/mana_flask.json +++ b/src/main/resources/assets/ebwizardry/recipes/small_mana_flask.json @@ -10,10 +10,10 @@ "item": "minecraft:glass_bottle" }, "y": { - "item": "ebwizardry:magic_crystal" - } + "item": "ebwizardry:crystal_shard" + } }, "result": { - "item": "ebwizardry:mana_flask" + "item": "ebwizardry:small_mana_flask" } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/spark_bomb.json b/src/main/resources/assets/ebwizardry/recipes/spark_bomb.json new file mode 100644 index 00000000..84ce4135 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/spark_bomb.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "minecraft:glass_bottle" + }, + { + "item": "minecraft:gunpowder" + } + ], + "result": { + "item": "ebwizardry:spark_bomb", + "count": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/transportation_stone.json b/src/main/resources/assets/ebwizardry/recipes/transportation_stone.json index f28c629b..f8f8bb27 100644 --- a/src/main/resources/assets/ebwizardry/recipes/transportation_stone.json +++ b/src/main/resources/assets/ebwizardry/recipes/transportation_stone.json @@ -10,9 +10,40 @@ "type": "forge:ore_dict", "ore": "stone" }, - "y": { - "item": "ebwizardry:magic_crystal" - } + "y": [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ] }, "result": { "item": "ebwizardry:transportation_stone", diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_earth.json b/src/main/resources/assets/ebwizardry/recipes/wand_earth.json new file mode 100644 index 00000000..efd14864 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_earth.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 5 + } + }, + "result": { + "item": "ebwizardry:novice_earth_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_fire.json b/src/main/resources/assets/ebwizardry/recipes/wand_fire.json new file mode 100644 index 00000000..fab82814 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_fire.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 1 + } + }, + "result": { + "item": "ebwizardry:novice_fire_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_healing.json b/src/main/resources/assets/ebwizardry/recipes/wand_healing.json new file mode 100644 index 00000000..4e5ec1e3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_healing.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + }, + "result": { + "item": "ebwizardry:novice_healing_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_ice.json b/src/main/resources/assets/ebwizardry/recipes/wand_ice.json new file mode 100644 index 00000000..c7a8db84 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_ice.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 2 + } + }, + "result": { + "item": "ebwizardry:novice_ice_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_lightning.json b/src/main/resources/assets/ebwizardry/recipes/wand_lightning.json new file mode 100644 index 00000000..fd88205e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_lightning.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 3 + } + }, + "result": { + "item": "ebwizardry:novice_lightning_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_necromancy.json b/src/main/resources/assets/ebwizardry/recipes/wand_necromancy.json new file mode 100644 index 00000000..8173afa3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_necromancy.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 4 + } + }, + "result": { + "item": "ebwizardry:novice_necromancy_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wand_sorcery.json b/src/main/resources/assets/ebwizardry/recipes/wand_sorcery.json new file mode 100644 index 00000000..d8cc12a3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/wand_sorcery.json @@ -0,0 +1,25 @@ +{ + "type": "forge:ore_shaped", + "pattern": [ + " z", + " y ", + "x " + ], + "key": { + "x": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "y": { + "type": "forge:ore_dict", + "ore": "stickWood" + }, + "z": { + "item": "ebwizardry:magic_crystal", + "data": 6 + } + }, + "result": { + "item": "ebwizardry:novice_sorcery_wand" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/wizard_handbook.json b/src/main/resources/assets/ebwizardry/recipes/wizard_handbook.json index f9bee5da..fa51a004 100644 --- a/src/main/resources/assets/ebwizardry/recipes/wizard_handbook.json +++ b/src/main/resources/assets/ebwizardry/recipes/wizard_handbook.json @@ -4,9 +4,40 @@ { "item": "minecraft:book" }, - { - "item": "ebwizardry:magic_crystal" - } + [ + { + "item": "ebwizardry:magic_crystal", + "data": 0 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 1 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 2 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 3 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 4 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 5 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 6 + }, + { + "item": "ebwizardry:magic_crystal", + "data": 7 + } + ] ], "result": { "item": "ebwizardry:wizard_handbook" diff --git a/src/main/resources/assets/ebwizardry/shaders/post/possession.json b/src/main/resources/assets/ebwizardry/shaders/post/possession.json new file mode 100644 index 00000000..04c52685 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/shaders/post/possession.json @@ -0,0 +1,73 @@ +{ + "targets": [ + "swap", + "previous", + "a", + "b", + "c" + ], + "passes": [ + { + "name": "color_convolve", + "intarget": "minecraft:main", + "outtarget": "a", + "uniforms": [ + { + "name": "Saturation", + "values": [ 0.2 ] + } + ] + }, + { + "name": "color_convolve", + "intarget": "a", + "outtarget": "b", + "uniforms": [ + { + "name": "RedMatrix", + "values": [ 0.2, 0.2, 0.1 ] + }, + { + "name": "GreenMatrix", + "values": [ 0.1, 0.1, 0.1 ] + }, + { + "name": "BlueMatrix", + "values": [ 0.2, 0.2, 0.3 ] + } + ] + }, + { + "name": "deconverge", + "intarget": "a", + "outtarget": "c" + }, + { + "name": "phosphor", + "intarget": "c", + "outtarget": "swap", + "auxtargets": [ + { + "name": "PrevSampler", + "id": "previous" + } + ], + "uniforms": [ + { + "name": "Phosphor", + "values": [ 0.8, 0.8, 0.8 ] + } + ] + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "previous" + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "minecraft:main" + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/shaders/post/sixth_sense.json b/src/main/resources/assets/ebwizardry/shaders/post/sixth_sense.json new file mode 100644 index 00000000..0c66b6ae --- /dev/null +++ b/src/main/resources/assets/ebwizardry/shaders/post/sixth_sense.json @@ -0,0 +1,67 @@ +{ + "targets": [ + "swap", + "previous", + "a", + "b" + ], + "passes": [ + { + "name": "color_convolve", + "intarget": "minecraft:main", + "outtarget": "a", + "uniforms": [ + { + "name": "Saturation", + "values": [ 0.3 ] + } + ] + }, + { + "name": "color_convolve", + "intarget": "a", + "outtarget": "b", + "uniforms": [ + { + "name": "RedMatrix", + "values": [ 0.3, 0, 0 ] + }, + { + "name": "GreenMatrix", + "values": [ 0.3, 0.6, 0.3 ] + }, + { + "name": "BlueMatrix", + "values": [ 0.2, 0.2, 0.5 ] + } + ] + }, + { + "name": "phosphor", + "intarget": "b", + "outtarget": "swap", + "auxtargets": [ + { + "name": "PrevSampler", + "id": "previous" + } + ], + "uniforms": [ + { + "name": "Phosphor", + "values": [ 0.8, 0.8, 0.8 ] + } + ] + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "previous" + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "minecraft:main" + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/shaders/post/slow_time.json b/src/main/resources/assets/ebwizardry/shaders/post/slow_time.json new file mode 100644 index 00000000..09f32ab1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/shaders/post/slow_time.json @@ -0,0 +1,37 @@ +{ + "targets": [ + "a" + ], + "passes": [ + { + "name": "color_convolve", + "intarget": "minecraft:main", + "outtarget": "a", + "uniforms": [ + { + "name": "Saturation", + "values": [ 0.2 ] + } + ] + }, + { + "name": "color_convolve", + "intarget": "a", + "outtarget": "minecraft:main", + "uniforms": [ + { + "name": "RedMatrix", + "values": [ 0.6, 0, 0 ] + }, + { + "name": "GreenMatrix", + "values": [ 0, 0.7, 0 ] + }, + { + "name": "BlueMatrix", + "values": [ 0.1, 0.1, 1 ] + } + ] + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/shaders/post/transience.json b/src/main/resources/assets/ebwizardry/shaders/post/transience.json new file mode 100644 index 00000000..9b30fe4f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/shaders/post/transience.json @@ -0,0 +1,67 @@ +{ + "targets": [ + "swap", + "previous", + "a", + "b" + ], + "passes": [ + { + "name": "color_convolve", + "intarget": "minecraft:main", + "outtarget": "a", + "uniforms": [ + { + "name": "Saturation", + "values": [ 0.8 ] + } + ] + }, + { + "name": "color_convolve", + "intarget": "a", + "outtarget": "b", + "uniforms": [ + { + "name": "RedMatrix", + "values": [ 0.9, 0.9, 0.7 ] + }, + { + "name": "GreenMatrix", + "values": [ 0.9, 0.9, 0.7 ] + }, + { + "name": "BlueMatrix", + "values": [ 0.8, 0.8, 0.7 ] + } + ] + }, + { + "name": "phosphor", + "intarget": "b", + "outtarget": "swap", + "auxtargets": [ + { + "name": "PrevSampler", + "id": "previous" + } + ], + "uniforms": [ + { + "name": "Phosphor", + "values": [ 0.8, 0.8, 0.8 ] + } + ] + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "previous" + }, + { + "name": "blit", + "intarget": "swap", + "outtarget": "minecraft:main" + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/sounds.json b/src/main/resources/assets/ebwizardry/sounds.json index 649770ba..9ed093b0 100644 --- a/src/main/resources/assets/ebwizardry/sounds.json +++ b/src/main/resources/assets/ebwizardry/sounds.json @@ -1,39 +1,361 @@ { - "arc": {"category": "player","sounds": [ - {"name": "ebwizardry:arc1","stream": false}, - {"name": "ebwizardry:arc1","stream": false}, - {"name": "ebwizardry:arc3","stream": false} - ]}, - - "aura": {"category": "player","sounds": [{"name": "ebwizardry:aura","stream": false}]}, - "boom": {"category": "player","sounds": [{"name": "ebwizardry:boom","stream": false}]}, - "crackle": {"category": "player","sounds": [{"name": "ebwizardry:crackle","stream": false}]}, + "block.arcane_workbench.bind_spell": {"category": "blocks", "sounds": ["ebwizardry:spellbind"]}, + "block.pedestal.activate": {"category": "blocks", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "block.pedestal.conquer": {"category": "blocks", "sounds": ["ui/toast/challenge_complete"]}, + + "item.wand.switch_spell": {"category": "player", "sounds": ["ebwizardry:select"]}, + "item.wand.levelup": {"category": "player", "sounds": ["random/levelup"]}, + "item.wand.melee": {"category": "player", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "item.armour.equip_silk": {"category": "player", "sounds": ["item/armor/equip_leather1", "item/armor/equip_leather2", "item/armor/equip_leather3", "item/armor/equip_leather4", "item/armor/equip_leather5", "item/armor/equip_leather6"]}, + "item.purifying_elixir.drink": {"category": "player", "sounds": ["ebwizardry:spellbind"]}, - "darkaura": {"category": "player","sounds": [ - {"name": "ebwizardry:darkaura1","stream": false}, - {"name": "ebwizardry:darkaura2","stream": false} - ]}, - - "effect": {"category": "player","sounds": [ - {"name": "ebwizardry:effect1","stream": false}, - {"name": "ebwizardry:effect2","stream": false} - ]}, - - "electricitya": {"category": "player","sounds": [{"name": "ebwizardry:electricitya","stream": false}]}, - "electricityb": {"category": "player","sounds": [{"name": "ebwizardry:electricityb","stream": false}]}, + "entity.black_hole.ambient": {"category": "spells", "sounds": ["portal/portal"]}, + "entity.black_hole.vanish": {"category": "spells", "sounds": ["portal/trigger"]}, + "entity.bubble.pop": {"category": "spells", "sounds": ["random/pop"]}, + "entity.blizzard.ambient": {"category": "spells", "sounds": ["ebwizardry:wind"]}, + "entity.decay.ambient": {"category": "spells", "sounds": ["liquid/lava"]}, + "entity.entrapment.ambient": {"category": "spells", "sounds": ["portal/portal"]}, + "entity.entrapment.vanish": {"category": "spells", "sounds": ["portal/trigger"]}, + "entity.fire_ring.ambient": {"category": "spells", "sounds": ["fire/fire"]}, + "entity.fire_sigil.trigger": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "entity.forcefield.deflect": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "entity.frost_sigil.trigger": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, + "entity.hammer.attack": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.hammer.explode": {"category": "spells", "sounds": ["random/explode1", "random/explode2", "random/explode3", "random/explode4"]}, + "entity.hammer.throw": {"category": "spells", "sounds": ["random/bow"]}, + "entity.hammer.land": {"category": "spells", "sounds": ["random/anvil_land"]}, + "entity.heal_aura.ambient": {"category": "spells", "sounds": ["ebwizardry:sparkle"]}, + "entity.ice_spike.extend": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "entity.lightning_sigil.trigger": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.meteor.falling": {"category": "spells", "sounds": ["ebwizardry:flames_loop"]}, + "entity.shield.deflect": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "entity.tornado.ambient": {"category": "spells", "sounds": ["ebwizardry:wind"]}, - "flameray": {"category": "player","sounds": [{"name": "ebwizardry:flameray2","stream": false}]}, - "force": {"category": "player","sounds": [{"name": "ebwizardry:force","stream": false}]}, - - "freeze": {"category": "player","sounds": [{"name": "ebwizardry:freeze","stream": false}]}, - "frostray": {"category": "player","sounds": [{"name": "ebwizardry:frostray","stream": false}]}, - "heal": {"category": "player","sounds": [{"name": "ebwizardry:heal","stream": false}]}, - "ice": {"category": "player","sounds": [{"name": "ebwizardry:ice","stream": false}]}, - "largeaura": {"category": "player","sounds": [{"name": "ebwizardry:largeaura","stream": false}]}, + "entity.evil_wizard.ambient": {"category": "hostile", "sounds": ["mob/evocation_illager/idle1", "mob/evocation_illager/idle2", "mob/evocation_illager/idle3", "mob/evocation_illager/idle4"]}, + "entity.evil_wizard.hurt": {"category": "hostile", "sounds": ["mob/evocation_illager/hurt1", "mob/evocation_illager/hurt2"]}, + "entity.evil_wizard.death": {"category": "hostile", "sounds": ["mob/evocation_illager/death1", "mob/evocation_illager/death2"]}, + "entity.ice_giant.attack": {"category": "hostile", "sounds": ["mob/irongolem/throw"]}, + "entity.ice_giant.despawn": {"category": "hostile", "sounds": ["ebwizardry:freeze"]}, + "entity.ice_wraith.ambient": {"category": "hostile", "sounds": ["ebwizardry:wind"]}, + "entity.magic_slime.attack": {"category": "hostile", "sounds": ["mob/slime/attack1", "mob/slime/attack2"]}, + "entity.magic_slime.explode": {"category": "hostile", "sounds": ["fireworks/blast_far1"]}, + "entity.magic_slime.splat": {"category": "hostile", "sounds": ["mob/slime/attack1", "mob/slime/attack2"]}, + "entity.phoenix.ambient": {"category": "hostile", "sounds": ["mob/blaze/breathe1", "mob/blaze/breathe2", "mob/blaze/breathe3", "mob/blaze/breathe4"]}, + "entity.phoenix.burn": {"category": "hostile", "sounds": ["fire/fire"]}, + "entity.phoenix.flap": {"category": "hostile", "sounds": ["mob/enderdragon/wings1", "mob/enderdragon/wings2", "mob/enderdragon/wings3", "mob/enderdragon/wings4", "mob/enderdragon/wings5", "mob/enderdragon/wings6"]}, + "entity.phoenix.hurt": {"category": "hostile", "sounds": ["mob/blaze/hit1", "mob/blaze/hit2", "mob/blaze/hit3", "mob/blaze/hit4"]}, + "entity.phoenix.death": {"category": "hostile", "sounds": ["mob/blaze/death"]}, + "entity.shadow_wraith.ambient": {"category": "hostile", "sounds": ["mob/blaze/breathe1", "mob/blaze/breathe2", "mob/blaze/breathe3", "mob/blaze/breathe4"]}, + "entity.shadow_wraith.noise": {"category": "hostile", "sounds": ["portal/portal"]}, + "entity.shadow_wraith.hurt": {"category": "hostile", "sounds": ["mob/blaze/hit1", "mob/blaze/hit2", "mob/blaze/hit3", "mob/blaze/hit4"]}, + "entity.shadow_wraith.death": {"category": "hostile", "sounds": ["mob/blaze/death"]}, + "entity.spirit_horse.vanish": {"category": "neutral", "sounds": ["ebwizardry:conjure_large"]}, + "entity.spirit_wolf.vanish": {"category": "neutral", "sounds": ["ebwizardry:conjure_large"]}, + "entity.storm_elemental.ambient": {"category": "hostile", "sounds": ["mob/blaze/breathe1", "mob/blaze/breathe2", "mob/blaze/breathe3", "mob/blaze/breathe4"]}, + "entity.storm_elemental.burn": {"category": "hostile", "sounds": ["fire/fire"]}, + "entity.storm_elemental.wind": {"category": "hostile", "sounds": ["ebwizardry:wind"]}, + "entity.storm_elemental.hurt": {"category": "hostile", "sounds": ["mob/blaze/hit1", "mob/blaze/hit2", "mob/blaze/hit3", "mob/blaze/hit4"]}, + "entity.storm_elemental.death": {"category": "hostile", "sounds": ["mob/blaze/death"]}, + "entity.wizard.ambient": {"category": "neutral", "sounds": ["mob/villager/idle1", "mob/villager/idle2", "mob/villager/idle3"]}, + "entity.wizard.trading": {"category": "neutral", "sounds": ["mob/villager/haggle1", "mob/villager/haggle2", "mob/villager/haggle3"]}, + "entity.wizard.yes": {"category": "neutral", "sounds": ["mob/villager/yes1", "mob/villager/yes2", "mob/villager/yes3"]}, + "entity.wizard.no": {"category": "neutral", "sounds": ["mob/villager/no1", "mob/villager/no2", "mob/villager/no3"]}, + "entity.wizard.hurt": {"category": "neutral", "sounds": ["mob/villager/hit1", "mob/villager/hit2", "mob/villager/hit3", "mob/villager/hit4"]}, + "entity.wizard.death": {"category": "neutral", "sounds": ["mob/villager/death"]}, - "magic": {"category": "player","sounds": [{"name": "ebwizardry:magic1","stream": false}]}, - - "sparkle": {"category": "player","sounds": [{"name": "ebwizardry:sparkle","stream": false}]}, - "wind": {"category": "player","sounds": [{"name": "ebwizardry:wind","stream": false}]}, - "rumble": {"category": "player","sounds": [{"name": "ebwizardry:rumble","stream": false}]} + "entity.darkness_orb.hit": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]}, + "entity.dart.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.dart.hit_block": {"category": "spells", "sounds": ["random/bowhit1", "random/bowhit2", "random/bowhit3"]}, + "entity.firebolt.hit": {"category": "spells", "sounds": ["liquid/lavapop"]}, + "entity.firebomb.throw": {"category": "spells", "sounds": ["random/bow"]}, + "entity.firebomb.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.firebomb.fire": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "entity.force_arrow.hit": {"category": "spells", "sounds": ["fireworks/blast1"]}, + "entity.force_orb.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.force_orb.hit_block": {"category": "spells", "sounds": ["fireworks/blast1"]}, + "entity.iceball.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.ice_charge.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.ice_charge.ice": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "entity.ice_lance.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.ice_lance.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.ice_shard.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.ice_shard.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.lightning_arrow.hit": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.lightning_disc.hit": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.magic_missile.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.poison_bomb.throw": {"category": "spells", "sounds": ["random/bow"]}, + "entity.poison_bomb.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.poison_bomb.poison": {"category": "spells", "sounds": ["random/fizz"]}, + "entity.smoke_bomb.throw": {"category": "spells", "sounds": ["random/bow"]}, + "entity.smoke_bomb.smash": {"category": "spells", "sounds": ["random/glass1", "random/glass2", "random/glass3"]}, + "entity.smoke_bomb.smoke": {"category": "spells", "sounds": ["random/fizz"]}, + "entity.homing_spark.hit": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.spark_bomb.throw": {"category": "spells", "sounds": ["random/bow"]}, + "entity.spark_bomb.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.spark_bomb.hit_block": {"category": "spells", "sounds": ["fireworks/blast_far1"]}, + "entity.spark_bomb.chain": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "entity.thunderbolt.hit": {"category": "spells", "sounds": ["fireworks/largeblast1"]}, + + "misc.discover_spell": {"category": "player", "sounds": ["random/levelup"]}, + "misc.page_turn": {"category": "player", "sounds": ["ebwizardry:page1", "ebwizardry:page2", "ebwizardry:page3"]}, + "misc.book_open": {"category": "player", "sounds": ["ebwizardry:book"]}, + + "misc.freeze": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, + + "spell.agility": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.arc": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "spell.arcane_jammer": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "spell.arcane_lock": {"category": "spells", "sounds": ["entity/endereye/dead1", "entity/endereye/dead2"]}, + "spell.arrow_rain": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "spell.banish": {"category": "spells", "sounds": ["mob/endermen/portal", "mob/endermen/portal2"]}, + "spell.black_hole": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, + "spell.blink": {"category": "spells", "sounds": ["mob/endermen/portal", "mob/endermen/portal2"]}, + "spell.blizzard": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.bubble.shoot": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.bubble.splash": {"category": "spells", "sounds": ["liquid/swim1", "liquid/swim2", "liquid/swim3", "liquid/swim4"], "volume": 2}, + "spell.chain_lightning": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "spell.charge": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "spell.clairvoyance": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.cobwebs": {"category": "spells", "sounds": ["random/fizz"]}, + "spell.combustion_rune": {"category": "spells", "sounds": ["fire/ignite"]}, + "spell.containment": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "spell.conjure_armour": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.conjure_block": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.conjure_bow": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.conjure_pickaxe": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.conjure_sword": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.cure_effects": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.curse_of_enfeeblement": {"category": "spells", "sounds": ["mob/wither/spawn"]}, + "spell.curse_of_soulbinding": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "spell.curse_of_soulbinding.retaliate": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]}, + "spell.curse_of_undeath": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "spell.darkness_orb": {"category": "spells", "sounds": ["mob/wither/shoot"]}, + "spell.darkvision": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.dart": {"category": "spells", "sounds": ["random/bow"]}, + "spell.decay": {"category": "spells", "sounds": ["mob/wither/shoot"]}, + "spell.decoy": {"category": "spells", "sounds": ["mob/illusion_illager/mirror_move1", "mob/illusion_illager/mirror_move2"]}, + "spell.detonate": {"category": "spells", "sounds": ["random/explode1", "random/explode2", "random/explode3", "random/explode4"]}, + "spell.diamondflesh": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.disintegration": {"category": "spells", "sounds": ["ebwizardry:firebolt"]}, + "spell.divination": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.dragon_fireball": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "spell.earthquake": {"category": "spells", "sounds": ["ebwizardry:rumble"]}, + "spell.empowering_presence": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.entrapment": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, + "spell.evade": {"category": "spells", "sounds": ["random/bow"]}, + "spell.fireball": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "spell.firebolt": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "spell.firebomb": {"category": "spells", "sounds": ["random/bow"]}, + "spell.fire_resistance": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.fire_sigil": {"category": "spells", "sounds": ["fire/ignite"]}, + "spell.fireskin": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.fire_breath.start": {"category": "spells", "sounds": ["ebwizardry:flames_start"]}, + "spell.fire_breath.loop": {"category": "spells", "sounds": ["ebwizardry:flames_loop"]}, + "spell.fire_breath.end": {"category": "spells", "sounds": ["ebwizardry:flames_end"]}, + "spell.flame_ray.start": {"category": "spells", "sounds": ["ebwizardry:flames_start"]}, + "spell.flame_ray.loop": {"category": "spells", "sounds": ["ebwizardry:flames_loop"]}, + "spell.flame_ray.end": {"category": "spells", "sounds": ["ebwizardry:flames_end"]}, + "spell.flaming_axe": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.flaming_weapon": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.flight": {"category": "spells", "sounds": ["mob/enderdragon/wings1", "mob/enderdragon/wings2", "mob/enderdragon/wings3", "mob/enderdragon/wings4", "mob/enderdragon/wings5", "mob/enderdragon/wings6"]}, + "spell.font_of_mana": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.font_of_vitality": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.force_arrow": {"category": "spells", "sounds": ["ebwizardry:force"]}, + "spell.forcefield": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.force_orb": {"category": "spells", "sounds": ["random/bow"]}, + "spell.forests_curse": {"category": "spells", "sounds": ["mob/wither/spawn"]}, + "spell.forest_of_thorns": {"category": "spells", "sounds": ["ebwizardry:grow"]}, + "spell.freeze": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.freezing_weapon": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.frost_axe": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.frost_ray.start": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.frost_ray.loop": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.frost_ray.end": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.frost_sigil": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.frost_step": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.glide": {"category": "spells", "sounds": [{"name": "item/elytra/elytra_loop", "volume": 0.6}]}, + "spell.grapple.shoot": {"category": "spells", "sounds": ["random/bow"]}, + "spell.grapple.attach": {"category": "spells", "sounds": ["dig/grass1", "dig/grass2", "dig/grass3", "dig/grass4"]}, + "spell.grapple.pull": {"category": "spells", "sounds": ["ebwizardry:pull1", "ebwizardry:pull2"]}, + "spell.grapple.release": {"category": "spells", "sounds": ["random/bowhit1", "random/bowhit2", "random/bowhit3"]}, + "spell.greater_fireball": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "spell.greater_heal": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.greater_telekinesis.start": {"category": "spells", "sounds": ["ebwizardry:aura_start"]}, + "spell.greater_telekinesis.loop": {"category": "spells", "sounds": ["ebwizardry:aura_loop"]}, + "spell.greater_telekinesis.end": {"category": "spells", "sounds": ["ebwizardry:aura_end"]}, + "spell.greater_ward": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.group_heal": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.growth_aura": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.hailstorm": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "spell.heal": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.heal_ally": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.healing_aura": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.homing_spark": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.iceball": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_age": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_charge": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_lance": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_shard": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_shroud": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.ice_spikes": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_statue.shoot": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.ice_statue.freeze": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, + "spell.ignite": {"category": "spells", "sounds": ["fire/ignite"]}, + "spell.imbue_weapon": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.intimidate": {"category": "spells", "sounds": ["mob/enderdragon/growl1", "mob/enderdragon/growl2", "mob/enderdragon/growl3", "mob/enderdragon/growl4"]}, + "spell.invigorating_presence": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.invisibility": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.invoke_weather": {"category": "spells", "sounds": ["ambient/weather/thunder1", "ambient/weather/thunder2", "ambient/weather/thunder3"]}, + "spell.ironflesh": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.leap": {"category": "spells", "sounds": ["mob/enderdragon/wings1", "mob/enderdragon/wings2", "mob/enderdragon/wings3", "mob/enderdragon/wings4", "mob/enderdragon/wings5", "mob/enderdragon/wings6"]}, + "spell.levitation.start": {"category": "spells", "sounds": ["ebwizardry:aura_start"]}, + "spell.levitation.loop": {"category": "spells", "sounds": ["ebwizardry:aura_loop"]}, + "spell.levitation.end": {"category": "spells", "sounds": ["ebwizardry:aura_end"]}, + "spell.life_drain.start": {"category": "spells", "sounds": ["ebwizardry:dark_aura_start"]}, + "spell.life_drain.loop": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]}, + "spell.life_drain.end": {"category": "spells", "sounds": ["ebwizardry:dark_aura_end"]}, + "spell.light": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.lightning_arrow": {"category": "spells", "sounds": ["mob/evocation_illager/cast1", "mob/evocation_illager/cast2"]}, + "spell.lightning_bolt": {"category": "spells", "sounds": []}, + "spell.lightning_disc": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_start"]}, + "spell.lightning_hammer": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "spell.lightning_pulse.spark": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_start"]}, + "spell.lightning_pulse.explosion": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "spell.lightning_ray.start": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_start"]}, + "spell.lightning_ray.loop": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_loop"]}, + "spell.lightning_ray.end": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_end"]}, + "spell.lightning_sigil": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.lightning_web.start": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_start"]}, + "spell.lightning_web.loop": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_loop"]}, + "spell.lightning_web.end": {"category": "spells", "sounds": ["ebwizardry:lightning_ray_end"]}, + "spell.magic_missile": {"category": "spells", "sounds": ["ebwizardry:magic1"]}, + "spell.metamorphosis": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_mirror"]}, + "spell.meteor": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "spell.mind_control": {"category": "spells", "sounds": ["mob/guardian/elder_death"]}, + "spell.mind_trick": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "spell.mine": {"category": "spells", "sounds": []}, + "spell.muffle": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.none": {"category": "spells", "sounds": []}, + "spell.oakflesh": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.paralysis": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "spell.petrify": {"category": "spells", "sounds": ["mob/wither/spawn"]}, + "spell.phase_step": {"category": "spells", "sounds": ["mob/endermen/portal", "mob/endermen/portal2"]}, + "spell.plague_of_darkness": {"category": "spells", "sounds": ["mob/wither/death"]}, + "spell.pocket_furnace": {"category": "spells", "sounds": ["block/furnace/fire_crackle1", "block/furnace/fire_crackle2", "block/furnace/fire_crackle3", "block/furnace/fire_crackle4", "block/furnace/fire_crackle5"]}, + "spell.pocket_workbench": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.poison": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.poison_bomb": {"category": "spells", "sounds": ["random/bow"]}, + "spell.possession.possess": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, + "spell.possession.end": {"category": "spells", "sounds": ["mob/guardian/elder_idle1", "mob/guardian/elder_idle2", "mob/guardian/elder_idle3", "mob/guardian/elder_idle4"]}, + "spell.ray_of_purification.start": {"category": "spells", "sounds": ["ebwizardry:aura_start"]}, + "spell.ray_of_purification.loop": {"category": "spells", "sounds": ["ebwizardry:aura_loop"]}, + "spell.ray_of_purification.end": {"category": "spells", "sounds": ["ebwizardry:aura_end"]}, + "spell.remove_curse": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.replenish_hunger": {"category": "spells", "sounds": ["ebwizardry:heal"]}, + "spell.resurrection": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.reversal": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]}, + "spell.ring_of_fire": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "spell.satiety": {"category": "spells", "sounds": ["ebwizardry:spellbind"]}, + "spell.shadow_ward.start": {"category": "spells", "sounds": ["ebwizardry:dark_aura_start"]}, + "spell.shadow_ward.loop": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]}, + "spell.shadow_ward.end": {"category": "spells", "sounds": ["ebwizardry:dark_aura_end"]}, + "spell.shield.start": {"category": "spells", "sounds": ["ebwizardry:small_aura_start"]}, + "spell.shield.loop": {"category": "spells", "sounds": ["ebwizardry:small_aura_loop"]}, + "spell.shield.end": {"category": "spells", "sounds": ["ebwizardry:small_aura_end"]}, + "spell.shulker_bullet": {"category": "spells", "sounds": ["entity/shulker/shoot1", "entity/shulker/shoot2", "entity/shulker/shoot3", "entity/shulker/shoot4"]}, + "spell.shockwave": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "spell.silverfish_swarm": {"category": "spells", "sounds": ["random/fizz"]}, + "spell.sixth_sense": {"category": "spells", "sounds": ["mob/wither/shoot"]}, + "spell.slime": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.slime.squelch": {"category": "spells", "sounds": ["mob/slime/attack1", "mob/slime/attack2"], "pitch": 0.5}, + "spell.slow_time": {"category": "spells", "sounds": ["portal/travel"]}, + "spell.smoke_bomb": {"category": "spells", "sounds": ["random/bow"]}, + "spell.snare": {"category": "spells", "sounds": ["dig/grass1", "dig/grass2", "dig/grass3", "dig/grass4"]}, + "spell.snowball": {"category": "spells", "sounds": ["random/bow"]}, + "spell.spark_bomb": {"category": "spells", "sounds": ["random/bow"]}, + "spell.spectral_pathway": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.speed_time.start": {"category": "spells", "sounds": ["ebwizardry:aura_start"]}, + "spell.speed_time.loop": {"category": "spells", "sounds": ["ebwizardry:aura_loop"]}, + "spell.speed_time.end": {"category": "spells", "sounds": ["ebwizardry:aura_end"]}, + "spell.spider_swarm": {"category": "spells", "sounds": ["random/fizz"]}, + "spell.static_aura": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.static_aura.retaliate": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "spell.summon_blaze": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_ice_giant": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "spell.summon_ice_wraith": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_iron_golem": {"category": "spells", "sounds": ["mob/wither/spawn"]}, + "spell.summon_lightning_wraith": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_phoenix": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_shadow_wraith": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_skeleton": {"category": "spells", "sounds": ["ebwizardry:summon1", "ebwizardry:summon2"]}, + "spell.summon_skeleton_legion": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "spell.summon_snow_golem": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.summon_spirit_horse": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.summon_spirit_wolf": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.summon_storm_elemental": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "spell.summon_wither_skeleton": {"category": "spells", "sounds": ["ebwizardry:summon1", "ebwizardry:summon2"]}, + "spell.summon_zombie": {"category": "spells", "sounds": ["ebwizardry:summon1", "ebwizardry:summon2"]}, + "spell.telekinesis": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "spell.thunderbolt": {"category": "spells", "sounds": ["mob/evocation_illager/cast1", "mob/evocation_illager/cast2"]}, + "spell.thunderstorm": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, + "spell.tornado": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.transience": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.transportation": {"category": "spells", "sounds": ["portal/trigger"]}, + "spell.transportation.travel": {"category": "spells", "sounds": ["portal/travel"]}, + "spell.vanishing_box": {"category": "spells", "sounds": ["block/enderchest/open"]}, + "spell.vex_swarm": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "spell.wall_of_frost.start": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.wall_of_frost.loop": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.wall_of_frost.end": {"category": "spells", "sounds": ["ebwizardry:frostray"]}, + "spell.ward": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.water_breathing": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.whirlwind": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.wither": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]}, + "spell.wither_skull": {"category": "spells", "sounds": ["mob/wither/shoot"]}, + + "forfeit.burn_self": {"category": "spells", "sounds": ["fire/ignite"]}, + "forfeit.fireball": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "forfeit.firebomb": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, + "forfeit.explode": {"category": "spells", "sounds": ["random/explode1", "random/explode2", "random/explode3", "random/explode4"]}, + "forfeit.blazes": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "forfeit.burn_surroundings": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, + "forfeit.meteors": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "forfeit.freeze_self": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, + "forfeit.freeze_self_2": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, + "forfeit.ice_spikes": {"category": "spells", "sounds": []}, + "forfeit.blizzard": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "forfeit.ice_wraiths": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "forfeit.hailstorm": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "forfeit.ice_giant": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "forfeit.thunder": {"category": "spells", "sounds": ["fireworks/largeblast1"]}, + "forfeit.storm": {"category": "spells", "sounds": ["ambient/weather/thunder1", "ambient/weather/thunder2", "ambient/weather/thunder3"]}, + "forfeit.lightning_sigils": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, + "forfeit.lightning": {"category": "spells", "sounds": []}, + "forfeit.paralyse_self": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "forfeit.lightning_wraiths": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "forfeit.storm_elementals": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "forfeit.nausea": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "forfeit.zombie_horde": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "forfeit.wither_self": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]}, + "forfeit.cripple_self": {"category": "spells", "sounds": ["mob/wither/death"]}, + "forfeit.shadow_wraiths": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, + "forfeit.snares": {"category": "spells", "sounds": ["dig/grass1", "dig/grass2", "dig/grass3", "dig/grass4"]}, + "forfeit.squid": {"category": "spells", "sounds": ["liquid/swim1", "liquid/swim2", "liquid/swim3", "liquid/swim4"], "volume": 2}, + "forfeit.uproot_plants": {"category": "spells", "sounds": []}, + "forfeit.poison_self": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "forfeit.flood": {"category": "spells", "sounds": ["item/bucket/empty1", "item/bucket/empty2", "item/bucket/empty3"]}, + "forfeit.bury_self": {"category": "spells", "sounds": ["ebwizardry:rumble"]}, + "forfeit.spill_inventory": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "forfeit.teleport_self": {"category": "spells", "sounds": ["mob/endermen/portal", "mob/endermen/portal2"]}, + "forfeit.levitate_self": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "forfeit.vex_horde": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_summon"]}, + "forfeit.black_hole": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, + "forfeit.arrow_rain": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, + "forfeit.damage_self": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "forfeit.spill_armour": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "forfeit.hunger": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "forfeit.blind_self": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "forfeit.weaken_self": {"category": "spells", "sounds": ["mob/guardian/curse"]}, + "forfeit.jam_self": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "forfeit.curse_self": {"category": "spells", "sounds": ["mob/guardian/curse"]} } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/sounds/aura_end.ogg b/src/main/resources/assets/ebwizardry/sounds/aura_end.ogg new file mode 100644 index 00000000..89170073 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/aura_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/aura_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/aura_loop.ogg new file mode 100644 index 00000000..616ed77c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/aura_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/aura_start.ogg b/src/main/resources/assets/ebwizardry/sounds/aura_start.ogg new file mode 100644 index 00000000..1736c3bb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/aura_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/book.ogg b/src/main/resources/assets/ebwizardry/sounds/book.ogg new file mode 100644 index 00000000..54ee5020 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/book.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/boom.ogg b/src/main/resources/assets/ebwizardry/sounds/boom.ogg deleted file mode 100644 index 26b3261d..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/boom.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/buff.ogg b/src/main/resources/assets/ebwizardry/sounds/buff.ogg new file mode 100644 index 00000000..4ac23bd7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/buff.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/aura.ogg b/src/main/resources/assets/ebwizardry/sounds/conjure.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/aura.ogg rename to src/main/resources/assets/ebwizardry/sounds/conjure.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/largeaura.ogg b/src/main/resources/assets/ebwizardry/sounds/conjure_large.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/largeaura.ogg rename to src/main/resources/assets/ebwizardry/sounds/conjure_large.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/dark_aura_end.ogg b/src/main/resources/assets/ebwizardry/sounds/dark_aura_end.ogg new file mode 100644 index 00000000..3e3093c1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/dark_aura_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/dark_aura_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/dark_aura_loop.ogg new file mode 100644 index 00000000..2268fe1d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/dark_aura_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/dark_aura_start.ogg b/src/main/resources/assets/ebwizardry/sounds/dark_aura_start.ogg new file mode 100644 index 00000000..04fd8be5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/dark_aura_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/firebolt.ogg b/src/main/resources/assets/ebwizardry/sounds/firebolt.ogg new file mode 100644 index 00000000..84babd4e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/firebolt.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flameray1.ogg b/src/main/resources/assets/ebwizardry/sounds/flameray1.ogg deleted file mode 100644 index 587f2ac9..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/flameray1.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flameray2.ogg b/src/main/resources/assets/ebwizardry/sounds/flameray2.ogg deleted file mode 100644 index 1f556a3b..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/flameray2.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flames_end.ogg b/src/main/resources/assets/ebwizardry/sounds/flames_end.ogg new file mode 100644 index 00000000..b3488b59 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/flames_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flames_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/flames_loop.ogg new file mode 100644 index 00000000..634727f6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/flames_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flames_start.ogg b/src/main/resources/assets/ebwizardry/sounds/flames_start.ogg new file mode 100644 index 00000000..40108d2c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/flames_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/grow.ogg b/src/main/resources/assets/ebwizardry/sounds/grow.ogg new file mode 100644 index 00000000..bf48758b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/grow.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/electricityb.ogg b/src/main/resources/assets/ebwizardry/sounds/lightning_ray_end.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/electricityb.ogg rename to src/main/resources/assets/ebwizardry/sounds/lightning_ray_end.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/lightning_ray_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/lightning_ray_loop.ogg new file mode 100644 index 00000000..041658ec Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/lightning_ray_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/electricitya.ogg b/src/main/resources/assets/ebwizardry/sounds/lightning_ray_start.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/electricitya.ogg rename to src/main/resources/assets/ebwizardry/sounds/lightning_ray_start.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/page1.ogg b/src/main/resources/assets/ebwizardry/sounds/page1.ogg new file mode 100644 index 00000000..3e8f81b9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/page1.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/page2.ogg b/src/main/resources/assets/ebwizardry/sounds/page2.ogg new file mode 100644 index 00000000..da1529fa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/page2.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/page3.ogg b/src/main/resources/assets/ebwizardry/sounds/page3.ogg new file mode 100644 index 00000000..5f3a4e82 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/page3.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/pull1.ogg b/src/main/resources/assets/ebwizardry/sounds/pull1.ogg new file mode 100644 index 00000000..4ce3730c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/pull1.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/pull2.ogg b/src/main/resources/assets/ebwizardry/sounds/pull2.ogg new file mode 100644 index 00000000..737809f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/pull2.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/select.ogg b/src/main/resources/assets/ebwizardry/sounds/select.ogg new file mode 100644 index 00000000..cf9450ce Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/select.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/shockwave.ogg b/src/main/resources/assets/ebwizardry/sounds/shockwave.ogg new file mode 100644 index 00000000..3af866c0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/shockwave.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/small_aura_end.ogg b/src/main/resources/assets/ebwizardry/sounds/small_aura_end.ogg new file mode 100644 index 00000000..9010c3d5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/small_aura_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/small_aura_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/small_aura_loop.ogg new file mode 100644 index 00000000..77c76ac2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/small_aura_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/small_aura_start.ogg b/src/main/resources/assets/ebwizardry/sounds/small_aura_start.ogg new file mode 100644 index 00000000..9004f213 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/small_aura_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/spellbind.ogg b/src/main/resources/assets/ebwizardry/sounds/spellbind.ogg new file mode 100644 index 00000000..d14bdcde Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/spellbind.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/darkaura1.ogg b/src/main/resources/assets/ebwizardry/sounds/summon1.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/darkaura1.ogg rename to src/main/resources/assets/ebwizardry/sounds/summon1.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/darkaura2.ogg b/src/main/resources/assets/ebwizardry/sounds/summon2.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/darkaura2.ogg rename to src/main/resources/assets/ebwizardry/sounds/summon2.ogg diff --git a/src/main/resources/assets/ebwizardry/spells/agility.json b/src/main/resources/assets/ebwizardry/spells/agility.json new file mode 100644 index 00000000..938d4f76 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/agility.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "buff", + "cost": 20, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "speed_duration": 600, + "speed_strength": 1, + "jump_boost_duration": 600, + "jump_boost_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/arc.json b/src/main/resources/assets/ebwizardry/spells/arc.json new file mode 100644 index 00000000..fbb70d87 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/arc.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "lightning", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "damage": 3, + "range": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json b/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json new file mode 100644 index 00000000..bdd61d41 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "attack", + "cost": 30, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "range": 10, + "effect_duration": 300 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/arcane_lock.json b/src/main/resources/assets/ebwizardry/spells/arcane_lock.json new file mode 100644 index 00000000..2c4f6c19 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/arcane_lock.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 50, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "range": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/arrow_rain.json b/src/main/resources/assets/ebwizardry/spells/arrow_rain.json new file mode 100644 index 00000000..e0c5602b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/arrow_rain.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "attack", + "cost": 75, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "range": 20, + "duration": 120 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/banish.json b/src/main/resources/assets/ebwizardry/spells/banish.json new file mode 100644 index 00000000..e0a42600 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/banish.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "attack", + "cost": 15, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 10, + "minimum_teleport_distance": 8, + "maximum_teleport_distance": 16 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/black_hole.json b/src/main/resources/assets/ebwizardry/spells/black_hole.json new file mode 100644 index 00000000..290ad352 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/black_hole.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "construct", + "cost": 150, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "duration": 400, + "range": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/blink.json b/src/main/resources/assets/ebwizardry/spells/blink.json new file mode 100644 index 00000000..db82fcd1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/blink.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 15, + "chargeup": 0, + "cooldown": 25, + "base_properties": { + "range": 25 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/blizzard.json b/src/main/resources/assets/ebwizardry/spells/blizzard.json new file mode 100644 index 00000000..cda68e4c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/blizzard.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "construct", + "cost": 40, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "duration": 600, + "range": 20, + "effect_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/bubble.json b/src/main/resources/assets/ebwizardry/spells/bubble.json new file mode 100644 index 00000000..11304d34 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/bubble.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "attack", + "cost": 15, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "duration": 200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/chain_lightning.json b/src/main/resources/assets/ebwizardry/spells/chain_lightning.json new file mode 100644 index 00000000..0f7fcc3c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/chain_lightning.json @@ -0,0 +1,29 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "attack", + "cost": 25, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "primary_damage": 10, + "secondary_damage": 8, + "tertiary_damage": 6, + "range": 10, + "secondary_range": 5, + "tertiary_range": 5, + "secondary_max_targets": 5, + "tertiary_max_targets": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/charge.json b/src/main/resources/assets/ebwizardry/spells/charge.json new file mode 100644 index 00000000..79045d51 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/charge.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "attack", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "charge_speed": 2.0, + "duration": 10, + "damage": 8, + "knockback_strength": 1.0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/clairvoyance.json b/src/main/resources/assets/ebwizardry/spells/clairvoyance.json new file mode 100644 index 00000000..e8b43887 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/clairvoyance.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 20, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "range": 256, + "duration": 1800 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/cobwebs.json b/src/main/resources/assets/ebwizardry/spells/cobwebs.json new file mode 100644 index 00000000..7735ecb6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/cobwebs.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "attack", + "cost": 30, + "chargeup": 0, + "cooldown": 70, + "base_properties": { + "range": 12, + "effect_radius": 1.23, + "duration": 400 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/combustion_rune.json b/src/main/resources/assets/ebwizardry/spells/combustion_rune.json new file mode 100644 index 00000000..c5d588b7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/combustion_rune.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "construct", + "cost": 30, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "range": 10, + "blast_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_armour.json b/src/main/resources/assets/ebwizardry/spells/conjure_armour.json new file mode 100644 index 00000000..2cdb95f9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/conjure_armour.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 45, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1800 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_block.json b/src/main/resources/assets/ebwizardry/spells/conjure_block.json new file mode 100644 index 00000000..a7b2a8a5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/conjure_block.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "sorcery", + "type": "utility", + "cost": 10, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 10, + "block_lifetime": 900 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_bow.json b/src/main/resources/assets/ebwizardry/spells/conjure_bow.json new file mode 100644 index 00000000..1083f635 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/conjure_bow.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 40, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_pickaxe.json b/src/main/resources/assets/ebwizardry/spells/conjure_pickaxe.json new file mode 100644 index 00000000..a80a00de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/conjure_pickaxe.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 25, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_sword.json b/src/main/resources/assets/ebwizardry/spells/conjure_sword.json new file mode 100644 index 00000000..a80a00de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/conjure_sword.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 25, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/containment.json b/src/main/resources/assets/ebwizardry/spells/containment.json new file mode 100644 index 00000000..4c5d86c5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/containment.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "alteration", + "cost": 40, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "range": 10, + "effect_duration": 400, + "effect_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/cure_effects.json b/src/main/resources/assets/ebwizardry/spells/cure_effects.json new file mode 100644 index 00000000..5d9c7b3e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/cure_effects.json @@ -0,0 +1,20 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "defence", + "cost": 25, + "chargeup": 0, + "cooldown": 40, + "base_properties": {} +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/curse_of_enfeeblement.json b/src/main/resources/assets/ebwizardry/spells/curse_of_enfeeblement.json new file mode 100644 index 00000000..8fbb4684 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_enfeeblement.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "alteration", + "cost": 60, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "range": 10, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json b/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json new file mode 100644 index 00000000..12adc02a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "alteration", + "cost": 35, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "range": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json b/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json new file mode 100644 index 00000000..f77ecc30 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "alteration", + "cost": 40, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "range": 10, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/darkness_orb.json b/src/main/resources/assets/ebwizardry/spells/darkness_orb.json new file mode 100644 index 00000000..a3e12642 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/darkness_orb.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "projectile", + "cost": 20, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 30, + "damage": 8, + "effect_duration": 150, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/darkvision.json b/src/main/resources/assets/ebwizardry/spells/darkvision.json new file mode 100644 index 00000000..c6fca8f8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/darkvision.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "buff", + "cost": 20, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "night_vision_duration": 900, + "night_vision_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/dart.json b/src/main/resources/assets/ebwizardry/spells/dart.json new file mode 100644 index 00000000..1054f77a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/dart.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "earth", + "type": "projectile", + "cost": 5, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 15, + "damage": 4, + "effect_duration": 200, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/decay.json b/src/main/resources/assets/ebwizardry/spells/decay.json new file mode 100644 index 00000000..1c3673ef --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/decay.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "attack", + "cost": 50, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "range": 12, + "duration": 400, + "effect_duration": 400, + "decay_patches_spawned": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/decoy.json b/src/main/resources/assets/ebwizardry/spells/decoy.json new file mode 100644 index 00000000..6d684c52 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/decoy.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 40, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "decoy_lifetime": 600, + "mob_trick_chance": 0.5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/detonate.json b/src/main/resources/assets/ebwizardry/spells/detonate.json new file mode 100644 index 00000000..a6c39af5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/detonate.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "attack", + "cost": 45, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "range": 16, + "max_damage": 12, + "blast_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/diamondflesh.json b/src/main/resources/assets/ebwizardry/spells/diamondflesh.json new file mode 100644 index 00000000..8872e569 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/diamondflesh.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "healing", + "type": "defence", + "cost": 100, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "resistance_duration": 600, + "resistance_strength": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/disintegration.json b/src/main/resources/assets/ebwizardry/spells/disintegration.json new file mode 100644 index 00000000..174f4a3f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/disintegration.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "attack", + "cost": 35, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 10, + "damage": 8, + "burn_duration": 10, + "ember_lifetime": 300, + "ember_count": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/divination.json b/src/main/resources/assets/ebwizardry/spells/divination.json new file mode 100644 index 00000000..7455a39a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/divination.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 35, + "chargeup": 0, + "cooldown": 80, + "base_properties": { + "range": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/dragon_fireball.json b/src/main/resources/assets/ebwizardry/spells/dragon_fireball.json new file mode 100644 index 00000000..46c4b129 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/dragon_fireball.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "attack", + "cost": 30, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "acceleration": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/earthquake.json b/src/main/resources/assets/ebwizardry/spells/earthquake.json new file mode 100644 index 00000000..18401a07 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/earthquake.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "earth", + "type": "attack", + "cost": 75, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "effect_radius": 8, + "spread_speed": 0.4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/empowering_presence.json b/src/main/resources/assets/ebwizardry/spells/empowering_presence.json new file mode 100644 index 00000000..aac89354 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/empowering_presence.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "buff", + "cost": 30, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "effect_radius": 5, + "effect_duration": 900, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/entrapment.json b/src/main/resources/assets/ebwizardry/spells/entrapment.json new file mode 100644 index 00000000..c6dc3d24 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/entrapment.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "attack", + "cost": 35, + "chargeup": 0, + "cooldown": 75, + "base_properties": { + "range": 10, + "effect_duration": 200, + "damage_interval": 30 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/evade.json b/src/main/resources/assets/ebwizardry/spells/evade.json new file mode 100644 index 00000000..46035dec --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/evade.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "healing", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 5, + "base_properties": { + "evade_velocity": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/fire_breath.json b/src/main/resources/assets/ebwizardry/spells/fire_breath.json new file mode 100644 index 00000000..aaf9401b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fire_breath.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "fire", + "type": "attack", + "cost": 15, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 6, + "burn_duration": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/fire_resistance.json b/src/main/resources/assets/ebwizardry/spells/fire_resistance.json new file mode 100644 index 00000000..e143b964 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fire_resistance.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "defence", + "cost": 20, + "chargeup": 0, + "cooldown": 80, + "base_properties": { + "fire_resistance_duration": 600, + "fire_resistance_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/fire_sigil.json b/src/main/resources/assets/ebwizardry/spells/fire_sigil.json new file mode 100644 index 00000000..62816eca --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fire_sigil.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "construct", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "damage": 6, + "burn_duration": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/fireball.json b/src/main/resources/assets/ebwizardry/spells/fireball.json new file mode 100644 index 00000000..e0274ed7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fireball.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "range": 20, + "damage": 5, + "burn_duration": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/firebolt.json b/src/main/resources/assets/ebwizardry/spells/firebolt.json new file mode 100644 index 00000000..4fb17f97 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/firebolt.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 15, + "damage": 5, + "burn_duration": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/firebomb.json b/src/main/resources/assets/ebwizardry/spells/firebomb.json new file mode 100644 index 00000000..a1aed46b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/firebomb.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "projectile", + "cost": 15, + "chargeup": 0, + "cooldown": 25, + "base_properties": { + "range": 10, + "direct_damage": 5, + "splash_damage": 3, + "blast_radius": 3, + "burn_duration": 7 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/fireskin.json b/src/main/resources/assets/ebwizardry/spells/fireskin.json new file mode 100644 index 00000000..6322054b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fireskin.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "defence", + "cost": 40, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "fireskin_duration": 600, + "fireskin_strength": 0, + "burn_duration": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/flame_ray.json b/src/main/resources/assets/ebwizardry/spells/flame_ray.json new file mode 100644 index 00000000..cf2deda4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/flame_ray.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 3, + "burn_duration": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/flaming_axe.json b/src/main/resources/assets/ebwizardry/spells/flaming_axe.json new file mode 100644 index 00000000..cbfab3ec --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/flaming_axe.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "utility", + "cost": 45, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1200, + "damage": 8, + "burn_duration": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/flaming_weapon.json b/src/main/resources/assets/ebwizardry/spells/flaming_weapon.json new file mode 100644 index 00000000..2f9564f3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/flaming_weapon.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "utility", + "cost": 35, + "chargeup": 0, + "cooldown": 70, + "base_properties": { + "effect_duration": 900 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/flight.json b/src/main/resources/assets/ebwizardry/spells/flight.json new file mode 100644 index 00000000..2059cf73 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/flight.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "earth", + "type": "utility", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "speed": 0.5, + "acceleration": 0.05 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/font_of_mana.json b/src/main/resources/assets/ebwizardry/spells/font_of_mana.json new file mode 100644 index 00000000..c4bac0d8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/font_of_mana.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "healing", + "type": "utility", + "cost": 100, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "effect_radius": 5, + "effect_duration": 600, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json b/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json new file mode 100644 index 00000000..f858b6ed --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "healing", + "type": "defence", + "cost": 75, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "absorption_duration": 1200, + "absorption_strength": 1, + "regeneration_duration": 300, + "regeneration_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/force_arrow.json b/src/main/resources/assets/ebwizardry/spells/force_arrow.json new file mode 100644 index 00000000..193e3275 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/force_arrow.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "projectile", + "cost": 15, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 20, + "damage": 7 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/force_orb.json b/src/main/resources/assets/ebwizardry/spells/force_orb.json new file mode 100644 index 00000000..47f6cc44 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/force_orb.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "projectile", + "cost": 20, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "damage": 4, + "blast_radius": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/forcefield.json b/src/main/resources/assets/ebwizardry/spells/forcefield.json new file mode 100644 index 00000000..bb6cbf3a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/forcefield.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 45, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "duration": 600, + "effect_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json b/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json new file mode 100644 index 00000000..4d5e2414 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "earth", + "type": "construct", + "cost": 100, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "effect_radius": 3, + "duration": 600, + "damage": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/forests_curse.json b/src/main/resources/assets/ebwizardry/spells/forests_curse.json new file mode 100644 index 00000000..1e12f461 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/forests_curse.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "earth", + "type": "attack", + "cost": 75, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "effect_radius": 5, + "damage": 4, + "effect_duration": 140, + "effect_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/freeze.json b/src/main/resources/assets/ebwizardry/spells/freeze.json new file mode 100644 index 00000000..9ea7eb27 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/freeze.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "ice", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 10, + "damage": 3, + "effect_duration": 200, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/freezing_weapon.json b/src/main/resources/assets/ebwizardry/spells/freezing_weapon.json new file mode 100644 index 00000000..b369b6d7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/freezing_weapon.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "utility", + "cost": 35, + "chargeup": 0, + "cooldown": 70, + "base_properties": { + "effect_duration": 900 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/frost_axe.json b/src/main/resources/assets/ebwizardry/spells/frost_axe.json new file mode 100644 index 00000000..b76cbfdf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_axe.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "utility", + "cost": 45, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "item_lifetime": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/frost_ray.json b/src/main/resources/assets/ebwizardry/spells/frost_ray.json new file mode 100644 index 00000000..2bbc677d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_ray.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 3, + "effect_duration": 200, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/frost_sigil.json b/src/main/resources/assets/ebwizardry/spells/frost_sigil.json new file mode 100644 index 00000000..02770806 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_sigil.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "construct", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "damage": 8, + "effect_duration": 200, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/frost_step.json b/src/main/resources/assets/ebwizardry/spells/frost_step.json new file mode 100644 index 00000000..9a8ce814 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_step.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "utility", + "cost": 50, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "frost_step_duration": 600, + "frost_step_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/glide.json b/src/main/resources/assets/ebwizardry/spells/glide.json new file mode 100644 index 00000000..7e6d6f91 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/glide.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "speed": 0.4, + "fall_speed": 0.1, + "acceleration": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/grapple.json b/src/main/resources/assets/ebwizardry/spells/grapple.json new file mode 100644 index 00000000..29a33e07 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/grapple.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 20, + "extension_speed": 3.5, + "reel_speed": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/greater_fireball.json b/src/main/resources/assets/ebwizardry/spells/greater_fireball.json new file mode 100644 index 00000000..d94fc93c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/greater_fireball.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "projectile", + "cost": 20, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "range": 20, + "damage": 6, + "explosion_power": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/greater_heal.json b/src/main/resources/assets/ebwizardry/spells/greater_heal.json new file mode 100644 index 00000000..5797115c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/greater_heal.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 15, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "health": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/greater_telekinesis.json b/src/main/resources/assets/ebwizardry/spells/greater_telekinesis.json new file mode 100644 index 00000000..623db44e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/greater_telekinesis.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 8, + "hold_range": 5, + "throw_velocity": 0.15, + "damage": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/greater_ward.json b/src/main/resources/assets/ebwizardry/spells/greater_ward.json new file mode 100644 index 00000000..9fde216e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/greater_ward.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "buff", + "cost": 20, + "chargeup": 0, + "cooldown": 65, + "base_properties": { + "ward_duration": 600, + "ward_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/group_heal.json b/src/main/resources/assets/ebwizardry/spells/group_heal.json new file mode 100644 index 00000000..c1449e69 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/group_heal.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 35, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "effect_radius": 5, + "health": 6 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/growth_aura.json b/src/main/resources/assets/ebwizardry/spells/growth_aura.json new file mode 100644 index 00000000..f1c5954a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/growth_aura.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "utility", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "effect_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/hailstorm.json b/src/main/resources/assets/ebwizardry/spells/hailstorm.json new file mode 100644 index 00000000..b7dd6e29 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/hailstorm.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "ice", + "type": "attack", + "cost": 75, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "range": 20, + "duration": 120 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/heal.json b/src/main/resources/assets/ebwizardry/spells/heal.json new file mode 100644 index 00000000..5f786836 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/heal.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "healing", + "type": "defence", + "cost": 5, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "health": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/heal_ally.json b/src/main/resources/assets/ebwizardry/spells/heal_ally.json new file mode 100644 index 00000000..178755ef --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/heal_ally.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "defence", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "health": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/healing_aura.json b/src/main/resources/assets/ebwizardry/spells/healing_aura.json new file mode 100644 index 00000000..18d81c27 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/healing_aura.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "construct", + "cost": 35, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "duration": 600, + "damage": 1, + "health": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/homing_spark.json b/src/main/resources/assets/ebwizardry/spells/homing_spark.json new file mode 100644 index 00000000..ecf10c02 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/homing_spark.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 25, + "damage": 6, + "seeking_strength": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_age.json b/src/main/resources/assets/ebwizardry/spells/ice_age.json new file mode 100644 index 00000000..f7befce5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_age.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "ice", + "type": "attack", + "cost": 70, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "effect_radius": 7, + "effect_duration": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_charge.json b/src/main/resources/assets/ebwizardry/spells/ice_charge.json new file mode 100644 index 00000000..dc7ae619 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_charge.json @@ -0,0 +1,29 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "projectile", + "cost": 20, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "range": 15, + "damage": 4, + "effect_radius": 3, + "direct_effect_duration": 120, + "direct_effect_strength": 1, + "splash_effect_duration": 100, + "splash_effect_strength": 0, + "ice_shards": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_lance.json b/src/main/resources/assets/ebwizardry/spells/ice_lance.json new file mode 100644 index 00000000..a259d9b1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_lance.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "projectile", + "cost": 20, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 15, + "damage": 10, + "effect_duration": 300, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_shard.json b/src/main/resources/assets/ebwizardry/spells/ice_shard.json new file mode 100644 index 00000000..1a427760 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_shard.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 15, + "damage": 6, + "effect_duration": 200, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_shroud.json b/src/main/resources/assets/ebwizardry/spells/ice_shroud.json new file mode 100644 index 00000000..2d4418c2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_shroud.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "defence", + "cost": 40, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "ice_shroud_duration": 600, + "ice_shroud_strength": 0, + "effect_duration": 100, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_spikes.json b/src/main/resources/assets/ebwizardry/spells/ice_spikes.json new file mode 100644 index 00000000..e49e72fd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_spikes.json @@ -0,0 +1,27 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "attack", + "cost": 30, + "chargeup": 0, + "cooldown": 75, + "base_properties": { + "range": 20, + "effect_radius": 2.5, + "ice_spike_count": 18, + "damage": 5, + "effect_duration": 100, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ice_statue.json b/src/main/resources/assets/ebwizardry/spells/ice_statue.json new file mode 100644 index 00000000..2c6bd7c0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ice_statue.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "attack", + "cost": 15, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 10, + "effect_duration": 400 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/iceball.json b/src/main/resources/assets/ebwizardry/spells/iceball.json new file mode 100644 index 00000000..b4badec9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/iceball.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "range": 20, + "damage": 5, + "effect_duration": 100, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ignite.json b/src/main/resources/assets/ebwizardry/spells/ignite.json new file mode 100644 index 00000000..fb2d8d5b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ignite.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "fire", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 10, + "burn_duration": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/imbue_weapon.json b/src/main/resources/assets/ebwizardry/spells/imbue_weapon.json new file mode 100644 index 00000000..ebf5e8ab --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/imbue_weapon.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "effect_duration": 900 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/intimidate.json b/src/main/resources/assets/ebwizardry/spells/intimidate.json new file mode 100644 index 00000000..b503c3ec --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/intimidate.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "attack", + "cost": 20, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "effect_radius": 8, + "effect_duration": 600, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/invigorating_presence.json b/src/main/resources/assets/ebwizardry/spells/invigorating_presence.json new file mode 100644 index 00000000..aac89354 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/invigorating_presence.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "buff", + "cost": 30, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "effect_radius": 5, + "effect_duration": 900, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/invisibility.json b/src/main/resources/assets/ebwizardry/spells/invisibility.json new file mode 100644 index 00000000..04fe24b1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/invisibility.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "buff", + "cost": 35, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "invisibility_duration": 600, + "invisibility_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/invoke_weather.json b/src/main/resources/assets/ebwizardry/spells/invoke_weather.json new file mode 100644 index 00000000..9c4ba918 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/invoke_weather.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "utility", + "cost": 30, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "thunderstorm_chance": 0.2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ironflesh.json b/src/main/resources/assets/ebwizardry/spells/ironflesh.json new file mode 100644 index 00000000..ae4bc234 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ironflesh.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 30, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "resistance_duration": 600, + "resistance_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/leap.json b/src/main/resources/assets/ebwizardry/spells/leap.json new file mode 100644 index 00000000..8e3ce48d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/leap.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "earth", + "type": "utility", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "horizontal_speed": 0.3, + "vertical_speed": 0.65 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/levitation.json b/src/main/resources/assets/ebwizardry/spells/levitation.json new file mode 100644 index 00000000..9d2e1ab2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/levitation.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "speed": 0.5, + "acceleration": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/life_drain.json b/src/main/resources/assets/ebwizardry/spells/life_drain.json new file mode 100644 index 00000000..318db019 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/life_drain.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 2, + "heal_factor": 0.35 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/light.json b/src/main/resources/assets/ebwizardry/spells/light.json new file mode 100644 index 00000000..bdf048c7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/light.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "sorcery", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "range": 4, + "duration": 600 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_arrow.json b/src/main/resources/assets/ebwizardry/spells/lightning_arrow.json new file mode 100644 index 00000000..5fbbfcdd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_arrow.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "projectile", + "cost": 15, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 25, + "damage": 7 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json b/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json new file mode 100644 index 00000000..9be2d4a5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "attack", + "cost": 40, + "chargeup": 0, + "cooldown": 80, + "base_properties": { + "range": 80 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_disc.json b/src/main/resources/assets/ebwizardry/spells/lightning_disc.json new file mode 100644 index 00000000..fd2f9ff9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_disc.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "projectile", + "cost": 25, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "range": 30, + "damage": 12, + "seeking_strength": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json b/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json new file mode 100644 index 00000000..3d93cbb4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json @@ -0,0 +1,28 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "lightning", + "type": "attack", + "cost": 100, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "range": 40, + "duration": 600, + "effect_radius": 10, + "secondary_max_targets": 8, + "attack_interval": 40, + "direct_damage": 10, + "splash_damage": 6 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_pulse.json b/src/main/resources/assets/ebwizardry/spells/lightning_pulse.json new file mode 100644 index 00000000..22841d4b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_pulse.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "attack", + "cost": 25, + "chargeup": 0, + "cooldown": 75, + "base_properties": { + "effect_radius": 3, + "damage": 8, + "repulsion_velocity": 0.8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_ray.json b/src/main/resources/assets/ebwizardry/spells/lightning_ray.json new file mode 100644 index 00000000..b18b94e5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_ray.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "attack", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json b/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json new file mode 100644 index 00000000..77ce013f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "construct", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "direct_damage": 6, + "effect_radius": 5, + "secondary_max_targets": 3, + "splash_damage": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_web.json b/src/main/resources/assets/ebwizardry/spells/lightning_web.json new file mode 100644 index 00000000..cc16ee60 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/lightning_web.json @@ -0,0 +1,29 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "lightning", + "type": "attack", + "cost": 15, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "primary_damage": 5, + "secondary_damage": 4, + "tertiary_damage": 3, + "range": 10, + "secondary_range": 5, + "tertiary_range": 5, + "secondary_max_targets": 5, + "tertiary_max_targets": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/magic_missile.json b/src/main/resources/assets/ebwizardry/spells/magic_missile.json new file mode 100644 index 00000000..f9a4f2fe --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/magic_missile.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "magic", + "type": "projectile", + "cost": 5, + "chargeup": 0, + "cooldown": 5, + "base_properties": { + "damage": 3, + "range": 18 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/metamorphosis.json b/src/main/resources/assets/ebwizardry/spells/metamorphosis.json new file mode 100644 index 00000000..cffc7f47 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/metamorphosis.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "utility", + "cost": 15, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "range": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/meteor.json b/src/main/resources/assets/ebwizardry/spells/meteor.json new file mode 100644 index 00000000..d56985aa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/meteor.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "fire", + "type": "attack", + "cost": 100, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "range": 40, + "blast_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/mind_control.json b/src/main/resources/assets/ebwizardry/spells/mind_control.json new file mode 100644 index 00000000..e019a77f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/mind_control.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "attack", + "cost": 40, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "range": 8, + "effect_duration": 600 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/mind_trick.json b/src/main/resources/assets/ebwizardry/spells/mind_trick.json new file mode 100644 index 00000000..123fa0ab --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/mind_trick.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "necromancy", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 8, + "effect_duration": 300 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/mine.json b/src/main/resources/assets/ebwizardry/spells/mine.json new file mode 100644 index 00000000..48101055 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/mine.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "earth", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 5, + "base_properties": { + "range": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/muffle.json b/src/main/resources/assets/ebwizardry/spells/muffle.json new file mode 100644 index 00000000..4e3d6803 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/muffle.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "sorcery", + "type": "buff", + "cost": 5, + "chargeup": 0, + "cooldown": 25, + "base_properties": { + "muffle_duration": 600, + "muffle_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/none.json b/src/main/resources/assets/ebwizardry/spells/none.json new file mode 100644 index 00000000..73f0994f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/none.json @@ -0,0 +1,20 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "magic", + "type": "utility", + "cost": 0, + "chargeup": 0, + "cooldown": 0, + "base_properties": {} +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/oakflesh.json b/src/main/resources/assets/ebwizardry/spells/oakflesh.json new file mode 100644 index 00000000..165a4eca --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/oakflesh.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "defence", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "resistance_duration": 600, + "resistance_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/paralysis.json b/src/main/resources/assets/ebwizardry/spells/paralysis.json new file mode 100644 index 00000000..8691b64b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/paralysis.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "alteration", + "cost": 20, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "range": 10, + "damage": 4, + "effect_duration": 100, + "critical_health": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/petrify.json b/src/main/resources/assets/ebwizardry/spells/petrify.json new file mode 100644 index 00000000..9add942a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/petrify.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "attack", + "cost": 40, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "range": 10, + "minimum_effect_duration": 900 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/phase_step.json b/src/main/resources/assets/ebwizardry/spells/phase_step.json new file mode 100644 index 00000000..99d0e097 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/phase_step.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 35, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 8, + "wall_thickness": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json b/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json new file mode 100644 index 00000000..3da3cc81 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "attack", + "cost": 75, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "effect_radius": 5, + "damage": 8, + "effect_duration": 140, + "effect_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/pocket_furnace.json b/src/main/resources/assets/ebwizardry/spells/pocket_furnace.json new file mode 100644 index 00000000..6bc74cce --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/pocket_furnace.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "fire", + "type": "utility", + "cost": 30, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "items_smelted": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/pocket_workbench.json b/src/main/resources/assets/ebwizardry/spells/pocket_workbench.json new file mode 100644 index 00000000..9abe4c74 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/pocket_workbench.json @@ -0,0 +1,20 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "sorcery", + "type": "utility", + "cost": 30, + "chargeup": 0, + "cooldown": 40, + "base_properties": {} +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/poison.json b/src/main/resources/assets/ebwizardry/spells/poison.json new file mode 100644 index 00000000..a1a95a3f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/poison.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "damage": 1, + "effect_duration": 200, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/poison_bomb.json b/src/main/resources/assets/ebwizardry/spells/poison_bomb.json new file mode 100644 index 00000000..bfd83d3c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/poison_bomb.json @@ -0,0 +1,29 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "projectile", + "cost": 15, + "chargeup": 0, + "cooldown": 25, + "base_properties": { + "range": 10, + "direct_damage": 5, + "splash_damage": 3, + "effect_radius": 3, + "direct_effect_duration": 120, + "direct_effect_strength": 1, + "splash_effect_duration": 100, + "splash_effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/possession.json b/src/main/resources/assets/ebwizardry/spells/possession.json new file mode 100644 index 00000000..ade443bb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/possession.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "alteration", + "cost": 100, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "range": 8, + "effect_duration": 600, + "critical_health": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ray_of_purification.json b/src/main/resources/assets/ebwizardry/spells/ray_of_purification.json new file mode 100644 index 00000000..940eb3e5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ray_of_purification.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "range": 10, + "damage": 2, + "effect_duration": 60, + "burn_duration": 5, + "undead_damage_multiplier": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/remove_curse.json b/src/main/resources/assets/ebwizardry/spells/remove_curse.json new file mode 100644 index 00000000..ca69c0bf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/remove_curse.json @@ -0,0 +1,20 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "defence", + "cost": 50, + "chargeup": 0, + "cooldown": 80, + "base_properties": {} +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/replenish_hunger.json b/src/main/resources/assets/ebwizardry/spells/replenish_hunger.json new file mode 100644 index 00000000..7f5d82c4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/replenish_hunger.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "buff", + "cost": 10, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "hunger_points": 3, + "saturation_modifier": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/resurrection.json b/src/main/resources/assets/ebwizardry/spells/resurrection.json new file mode 100644 index 00000000..402e2f2d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/resurrection.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "healing", + "type": "alteration", + "cost": 150, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "effect_radius": 8, + "wait_time": 300 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/reversal.json b/src/main/resources/assets/ebwizardry/spells/reversal.json new file mode 100644 index 00000000..aa189cfa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/reversal.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "alteration", + "cost": 40, + "chargeup": 0, + "cooldown": 80, + "base_properties": { + "range": 8, + "reversed_effects": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json b/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json new file mode 100644 index 00000000..79f7510e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "construct", + "cost": 30, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "duration": 600, + "damage": 1, + "burn_duration": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/satiety.json b/src/main/resources/assets/ebwizardry/spells/satiety.json new file mode 100644 index 00000000..c8c9da91 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/satiety.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "buff", + "cost": 40, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "hunger_points": 8, + "saturation_modifier": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/shadow_ward.json b/src/main/resources/assets/ebwizardry/spells/shadow_ward.json new file mode 100644 index 00000000..d019f094 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/shadow_ward.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "defence", + "cost": 10, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "reflected_fraction": 0.5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/shield.json b/src/main/resources/assets/ebwizardry/spells/shield.json new file mode 100644 index 00000000..0bcd6195 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/shield.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "healing", + "type": "defence", + "cost": 5, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/shockwave.json b/src/main/resources/assets/ebwizardry/spells/shockwave.json new file mode 100644 index 00000000..c2fbf6dd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/shockwave.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "attack", + "cost": 65, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "blast_radius": 5, + "damage": 8, + "max_repulsion_velocity": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/shulker_bullet.json b/src/main/resources/assets/ebwizardry/spells/shulker_bullet.json new file mode 100644 index 00000000..42169f25 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/shulker_bullet.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "projectile", + "cost": 25, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "range": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json b/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json new file mode 100644 index 00000000..9390042c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "earth", + "type": "minion", + "cost": 80, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 20, + "summon_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/sixth_sense.json b/src/main/resources/assets/ebwizardry/spells/sixth_sense.json new file mode 100644 index 00000000..7b69daff --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/sixth_sense.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "buff", + "cost": 20, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "effect_duration": 400, + "effect_radius": 20 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/slime.json b/src/main/resources/assets/ebwizardry/spells/slime.json new file mode 100644 index 00000000..d8d839e3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/slime.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "attack", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "range": 8, + "duration": 200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/slow_time.json b/src/main/resources/assets/ebwizardry/spells/slow_time.json new file mode 100644 index 00000000..ec7eee50 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/slow_time.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "alteration", + "cost": 100, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "slow_time_duration": 300, + "slow_time_strength": 0, + "effect_radius": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/smoke_bomb.json b/src/main/resources/assets/ebwizardry/spells/smoke_bomb.json new file mode 100644 index 00000000..cd6b5122 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/smoke_bomb.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "fire", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "blast_radius": 3, + "effect_duration": 120 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/snare.json b/src/main/resources/assets/ebwizardry/spells/snare.json new file mode 100644 index 00000000..45351d88 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/snare.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "earth", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 10, + "base_properties": { + "range": 10, + "damage": 6, + "effect_duration": 100, + "effect_strength": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/snowball.json b/src/main/resources/assets/ebwizardry/spells/snowball.json new file mode 100644 index 00000000..2191aa3a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/snowball.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "ice", + "type": "attack", + "cost": 1, + "chargeup": 0, + "cooldown": 1, + "base_properties": { + "range": 15 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/spark_bomb.json b/src/main/resources/assets/ebwizardry/spells/spark_bomb.json new file mode 100644 index 00000000..1b330503 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/spark_bomb.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "lightning", + "type": "projectile", + "cost": 15, + "chargeup": 0, + "cooldown": 25, + "base_properties": { + "range": 10, + "direct_damage": 6, + "effect_radius": 5, + "secondary_max_targets": 4, + "splash_damage": 5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json b/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json new file mode 100644 index 00000000..0e775f40 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 40, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "length": 25, + "duration": 1200 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/speed_time.json b/src/main/resources/assets/ebwizardry/spells/speed_time.json new file mode 100644 index 00000000..cd364039 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/speed_time.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "utility", + "cost": 15, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "effect_radius": 8, + "time_increment": 30, + "extra_ticks": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/spider_swarm.json b/src/main/resources/assets/ebwizardry/spells/spider_swarm.json new file mode 100644 index 00000000..ffdc4197 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/spider_swarm.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "minion", + "cost": 45, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 5, + "summon_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/static_aura.json b/src/main/resources/assets/ebwizardry/spells/static_aura.json new file mode 100644 index 00000000..f703c5eb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/static_aura.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "defence", + "cost": 40, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "static_aura_duration": 600, + "static_aura_strength": 0, + "damage": 4 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_blaze.json b/src/main/resources/assets/ebwizardry/spells/summon_blaze.json new file mode 100644 index 00000000..7621260f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_blaze.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "fire", + "type": "minion", + "cost": 40, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json b/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json new file mode 100644 index 00000000..810f0c67 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "ice", + "type": "minion", + "cost": 100, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json new file mode 100644 index 00000000..934b94b6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "ice", + "type": "minion", + "cost": 40, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json b/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json new file mode 100644 index 00000000..01991e04 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "sorcery", + "type": "minion", + "cost": 175, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json new file mode 100644 index 00000000..e04aa12b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "lightning", + "type": "minion", + "cost": 40, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json b/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json new file mode 100644 index 00000000..13664fcf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "fire", + "type": "minion", + "cost": 150, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json new file mode 100644 index 00000000..e699bf4b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "minion", + "cost": 100, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_skeleton.json b/src/main/resources/assets/ebwizardry/spells/summon_skeleton.json new file mode 100644 index 00000000..47d241b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_skeleton.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "minion", + "cost": 15, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json b/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json new file mode 100644 index 00000000..3bc7f2a8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "minion", + "cost": 100, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "minion_lifetime": 1200, + "minion_count": 6, + "summon_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_snow_golem.json b/src/main/resources/assets/ebwizardry/spells/summon_snow_golem.json new file mode 100644 index 00000000..56da6c92 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_snow_golem.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "minion", + "cost": 15, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json b/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json new file mode 100644 index 00000000..73b29590 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "minion", + "cost": 50, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_spirit_wolf.json b/src/main/resources/assets/ebwizardry/spells/summon_spirit_wolf.json new file mode 100644 index 00000000..c78650e4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_spirit_wolf.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "minion", + "cost": 25, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json b/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json new file mode 100644 index 00000000..02c1f50d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "lightning", + "type": "minion", + "cost": 100, + "chargeup": 0, + "cooldown": 400, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json b/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json new file mode 100644 index 00000000..f16431f2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "minion", + "cost": 35, + "chargeup": 0, + "cooldown": 150, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/summon_zombie.json b/src/main/resources/assets/ebwizardry/spells/summon_zombie.json new file mode 100644 index 00000000..c40d941c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/summon_zombie.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "necromancy", + "type": "minion", + "cost": 10, + "chargeup": 0, + "cooldown": 40, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 1, + "summon_radius": 2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/telekinesis.json b/src/main/resources/assets/ebwizardry/spells/telekinesis.json new file mode 100644 index 00000000..1b549be5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/telekinesis.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "sorcery", + "type": "utility", + "cost": 5, + "chargeup": 0, + "cooldown": 5, + "base_properties": { + "range": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/thunderbolt.json b/src/main/resources/assets/ebwizardry/spells/thunderbolt.json new file mode 100644 index 00000000..0fb8b374 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/thunderbolt.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "lightning", + "type": "projectile", + "cost": 10, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "range": 12, + "damage": 3, + "knockback_strength": 0.2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/thunderstorm.json b/src/main/resources/assets/ebwizardry/spells/thunderstorm.json new file mode 100644 index 00000000..c414078e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/thunderstorm.json @@ -0,0 +1,29 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "lightning", + "type": "attack", + "cost": 100, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "effect_radius": 10, + "lightning_bolts": 10, + "secondary_range": 10, + "secondary_max_targets": 12, + "secondary_damage": 10, + "tertiary_range": 10, + "tertiary_max_targets": 3, + "tertiary_damage": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/tornado.json b/src/main/resources/assets/ebwizardry/spells/tornado.json new file mode 100644 index 00000000..87d8cfbd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/tornado.json @@ -0,0 +1,26 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "attack", + "cost": 35, + "chargeup": 0, + "cooldown": 80, + "base_properties": { + "duration": 200, + "speed": 0.33, + "effect_radius": 4, + "damage": 1, + "upward_acceleration": 0.2 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/transience.json b/src/main/resources/assets/ebwizardry/spells/transience.json new file mode 100644 index 00000000..ff5886c2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/transience.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "healing", + "type": "buff", + "cost": 50, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "effect_duration": 400 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/transportation.json b/src/main/resources/assets/ebwizardry/spells/transportation.json new file mode 100644 index 00000000..0dbfcbfb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/transportation.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 100, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "teleport_countdown": 75 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/vanishing_box.json b/src/main/resources/assets/ebwizardry/spells/vanishing_box.json new file mode 100644 index 00000000..c6e1eab5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/vanishing_box.json @@ -0,0 +1,20 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "utility", + "cost": 45, + "chargeup": 0, + "cooldown": 70, + "base_properties": {} +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/vex_swarm.json b/src/main/resources/assets/ebwizardry/spells/vex_swarm.json new file mode 100644 index 00000000..e54c7cc3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/vex_swarm.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "sorcery", + "type": "minion", + "cost": 50, + "chargeup": 0, + "cooldown": 200, + "base_properties": { + "minion_lifetime": 600, + "minion_count": 5, + "summon_radius": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json b/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json new file mode 100644 index 00000000..87e3daf6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "ice", + "type": "utility", + "cost": 15, + "chargeup": 0, + "cooldown": 0, + "base_properties": { + "duration": 600, + "range": 10 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/ward.json b/src/main/resources/assets/ebwizardry/spells/ward.json new file mode 100644 index 00000000..e8f258f2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/ward.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "novice", + "element": "healing", + "type": "buff", + "cost": 5, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "ward_duration": 600, + "ward_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/water_breathing.json b/src/main/resources/assets/ebwizardry/spells/water_breathing.json new file mode 100644 index 00000000..9cee0e43 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/water_breathing.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "earth", + "type": "buff", + "cost": 30, + "chargeup": 0, + "cooldown": 250, + "base_properties": { + "water_breathing_duration": 1200, + "water_breathing_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/whirlwind.json b/src/main/resources/assets/ebwizardry/spells/whirlwind.json new file mode 100644 index 00000000..7f0c9e26 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/whirlwind.json @@ -0,0 +1,23 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "earth", + "type": "defence", + "cost": 10, + "chargeup": 0, + "cooldown": 15, + "base_properties": { + "range": 10, + "repulsion_velocity": 1.5 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/wither.json b/src/main/resources/assets/ebwizardry/spells/wither.json new file mode 100644 index 00000000..0b690672 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/wither.json @@ -0,0 +1,25 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "necromancy", + "type": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 20, + "base_properties": { + "range": 10, + "damage": 1, + "effect_duration": 200, + "effect_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/wither_skull.json b/src/main/resources/assets/ebwizardry/spells/wither_skull.json new file mode 100644 index 00000000..e1bc8083 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/wither_skull.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "advanced", + "element": "necromancy", + "type": "attack", + "cost": 20, + "chargeup": 0, + "cooldown": 30, + "base_properties": { + "acceleration": 0.1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/structures/obelisk_0.nbt b/src/main/resources/assets/ebwizardry/structures/obelisk_0.nbt new file mode 100644 index 00000000..97ba3e49 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/obelisk_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/obelisk_1.nbt b/src/main/resources/assets/ebwizardry/structures/obelisk_1.nbt new file mode 100644 index 00000000..6fd0cde2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/obelisk_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/obelisk_2.nbt b/src/main/resources/assets/ebwizardry/structures/obelisk_2.nbt new file mode 100644 index 00000000..0b2ec08f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/obelisk_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/obelisk_3.nbt b/src/main/resources/assets/ebwizardry/structures/obelisk_3.nbt new file mode 100644 index 00000000..8da47408 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/obelisk_3.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/obelisk_4.nbt b/src/main/resources/assets/ebwizardry/structures/obelisk_4.nbt new file mode 100644 index 00000000..83533027 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/obelisk_4.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_0.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_0.nbt new file mode 100644 index 00000000..19427425 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_1.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_1.nbt new file mode 100644 index 00000000..312c1cd6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_2.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_2.nbt new file mode 100644 index 00000000..c961ddf2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_3.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_3.nbt new file mode 100644 index 00000000..29f41f45 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_3.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_4.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_4.nbt new file mode 100644 index 00000000..21fa5f76 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_4.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_5.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_5.nbt new file mode 100644 index 00000000..7a61e0bc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_5.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_6.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_6.nbt new file mode 100644 index 00000000..d699d08c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_6.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/shrine_7.nbt b/src/main/resources/assets/ebwizardry/structures/shrine_7.nbt new file mode 100644 index 00000000..59cd6518 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/shrine_7.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_0.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_0.nbt new file mode 100644 index 00000000..90322560 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_1.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_1.nbt new file mode 100644 index 00000000..eb8d78e4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_2.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_2.nbt new file mode 100644 index 00000000..b19b6de4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_3.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_3.nbt new file mode 100644 index 00000000..1022822a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_3.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_0.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_0.nbt new file mode 100644 index 00000000..d5591b4d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_1.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_1.nbt new file mode 100644 index 00000000..d3437a36 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_2.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_2.nbt new file mode 100644 index 00000000..7291b424 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_3.nbt b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_3.nbt new file mode 100644 index 00000000..2fbe5952 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/wizard_tower_chest_3.nbt differ diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json new file mode 100644 index 00000000..3b1dd848 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json @@ -0,0 +1,750 @@ +{ + "bookmark_start_section": "introduction", + + "colours": { + "text": "#000000", + "caption": "#666666", + "hyperlink": "#601ba0", + "highlight": "#dd4c1d", + "new_section": "#ee88f7" + }, + + "images": { + "workbench": { + "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "caption": "The Arcane Workbench", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/ore_picture.png", + "caption": "Crystal Ore", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "A Magic Crystal", + "u": 6, + "v": 6, + "width": 36, + "height": 33, + "texture_width": 48, + "texture_height": 48, + "border": false + }, + "magic_wand": { + "location": "ebwizardry:textures/items/wand_novice.png", + "caption": "A Magic Wand", + "u": 0, + "v": 0, + "width": 64, + "height": 64, + "border": false + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/flower_picture.png", + "caption": "A Crystal Flower", + "u": 0, + "v": 0, + "width": 48, + "height": 48 + }, + "wizard_tower": { + "location": "ebwizardry:textures/gui/tower_picture.png", + "caption": "A Wizard's Tower", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "wizard_armour": { + "location": "ebwizardry:textures/gui/armour_picture.png", + "caption": "A Set Of Wizard Armour", + "u": 0, + "v": 0, + "width": 90, + "height": 90 + }, + "obelisk": { + "location": "ebwizardry:textures/gui/obelisk_picture.png", + "caption": "An Obelisk", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "shrine": { + "location": "ebwizardry:textures/gui/shrine_picture.png", + "caption": "An Earth Shrine", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "tiers": { + "location": "ebwizardry:textures/gui/tiers_picture.png", + "caption": "The 4 Tiers Of Wand", + "u": 0, + "v": 0, + "width": 96, + "height": 24, + "border": false + }, + "elements": { + "location": "ebwizardry:textures/gui/elements_picture.png", + "caption": "The 7 Arcane Elements", + "u": 0, + "v": 0, + "width": 106, + "height": 20, + "border": false + } + }, + + "recipes": { + "arcane_workbench": { + "locations": [ "ebwizardry:arcane_workbench" ] + }, + "magic_wand": { + "locations": [ "ebwizardry:magic_wand" ] + }, + "novice_wands": { + "locations": [ + "ebwizardry:magic_wand", + "ebwizardry:wand_fire", + "ebwizardry:wand_ice", + "ebwizardry:wand_lightning", + "ebwizardry:wand_necromancy", + "ebwizardry:wand_earth", + "ebwizardry:wand_sorcery", + "ebwizardry:wand_healing" + ] + }, + "magic_missile_spell_book": { + "locations": [ "ebwizardry:magic_missile_spell_book" ] + }, + "wizard_handbook": { + "locations": [ "ebwizardry:wizard_handbook" ] + }, + "crystal_flower_to_crystals": { + "locations": [ "ebwizardry:crystal_flower_to_crystals" ] + }, + "crystal_blocks": { + "locations": [ + "ebwizardry:crystal_block", + "ebwizardry:crystal_block_fire", + "ebwizardry:crystal_block_ice", + "ebwizardry:crystal_block_lightning", + "ebwizardry:crystal_block_necromancy", + "ebwizardry:crystal_block_earth", + "ebwizardry:crystal_block_sorcery", + "ebwizardry:crystal_block_healing" + ] + }, + "crystal_blocks_to_crystals": { + "locations": [ + "ebwizardry:crystal_block_to_crystals", + "ebwizardry:crystal_block_to_crystals_fire", + "ebwizardry:crystal_block_to_crystals_ice", + "ebwizardry:crystal_block_to_crystals_lightning", + "ebwizardry:crystal_block_to_crystals_necromancy", + "ebwizardry:crystal_block_to_crystals_earth", + "ebwizardry:crystal_block_to_crystals_sorcery", + "ebwizardry:crystal_block_to_crystals_healing" + ] + }, + "crystal_shard": { + "locations": [ "ebwizardry:crystal_shard" ] + }, + "small_mana_flask": { + "locations": [ "ebwizardry:small_mana_flask" ] + }, + "medium_mana_flask": { + "locations": [ "ebwizardry:medium_mana_flask" ] + }, + "large_mana_flask": { + "locations": [ "ebwizardry:large_mana_flask" ] + }, + "runestone": { + "locations": [ + "ebwizardry:runestone_fire", + "ebwizardry:runestone_ice", + "ebwizardry:runestone_lightning", + "ebwizardry:runestone_necromancy", + "ebwizardry:runestone_earth", + "ebwizardry:runestone_sorcery", + "ebwizardry:runestone_healing" + ] + }, + "runestone_pedestal": { + "locations": [ + "ebwizardry:runestone_pedestal_fire", + "ebwizardry:runestone_pedestal_ice", + "ebwizardry:runestone_pedestal_lightning", + "ebwizardry:runestone_pedestal_necromancy", + "ebwizardry:runestone_pedestal_earth", + "ebwizardry:runestone_pedestal_sorcery", + "ebwizardry:runestone_pedestal_healing" + ] + }, + "transportation_stone": { + "locations": [ "ebwizardry:transportation_stone" ] + }, + "magic_silk": { + "locations": [ "ebwizardry:magic_silk" ] + }, + "wizard_hat": { + "locations": [ "ebwizardry:wizard_hat" ] + }, + "wizard_robe": { + "locations": [ "ebwizardry:wizard_robe" ] + }, + "wizard_leggings": { + "locations": [ "ebwizardry:wizard_leggings" ] + }, + "wizard_boots": { + "locations": [ "ebwizardry:wizard_boots" ] + }, + "blank_scroll": { + "locations": [ "ebwizardry:blank_scroll" ] + }, + "firebomb": { + "locations": [ "ebwizardry:firebomb" ] + }, + "poison_bomb": { + "locations": [ "ebwizardry:poison_bomb" ] + }, + "smoke_bomb": { + "locations": [ "ebwizardry:smoke_bomb" ] + }, + "spark_bomb": { + "locations": [ "ebwizardry:spark_bomb" ] + } + }, + + "sections": { + + "inside_cover": { + "centre": { + "x": true, + "y": true + }, + "text": [ + + "The Wizard's Handbook", + + "By Electroblob" + ] + }, + + "introduction": { + "title": "Introduction", + "text": [ + + "Greetings, wizard! This book explains the many ways of the arcane and how to use them. This is no ordinary book, though - its pages will materialise before you as you discover more about the magical world.", + + "Use the arrow buttons to turn between pages, and the double-arrow buttons to quickly flip between sections. Use the central menu button to return to the main contents page.", + + "Click on any purple link text to jump straight to the relevant page. Right-click the bookmark to move it to the current page, and left-click to return to the bookmarked page." + ] + }, + + "main_contents": { + "title": "Contents", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "Setting Up", + "include_in_contents": "main_contents", + "text": [ + + "To get started with wizardry, you will need:", + + "- A magic wand, crafted as shown:", + + "#recipe magic_wand", + + "- An @arcane_workbench arcane workbench@, crafted like so:", + + "#recipe arcane_workbench", + + "- A @spells spell@ book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, as shown:", + + "#recipe magic_missile_spell_book", + + "You will also need a bunch more magic crystals to supply your wand with @mana@." + ] + }, + + "mana": { + "title": "Mana", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "Mana is the arcane energy that gives wizards their powers. It is not a physical substance in its own right; rather, it is an everpresent aura which permeates everything in the world. In particular, it manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. @wands Wands@ can store mana within them, and this mana is channelled into the @spells@ that you cast. Different spells require different amounts of mana, depending on how powerful they are and how long they last.", + + "#image crystal_ore", + + "#image magic_crystal", + + "It is widely accepted that mana cannot be created or destroyed, and that when a spell is cast, the mana it channels is simply dissipated into the surroundings. Indeed, this is how the vast majority of mana exists - spread thinly throughout the world. However, to be of any use, it must be concentrated, either naturally over thousands of years, as is the case with crystals underground, or artificially - an advanced subject not covered in this book. There also exist various ways of making spells more mana-efficient and recovering some of the mana dissipated during spellcasting." + ] + }, + + "wands": { + "title": "Wands", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "The wand is the implement of choice for a wizard. With it, you can cast any @spells spell@ provided the wand can contain its power (see @tiers Tiers@). Wands come in many different varieties but you will almost certainly start with a basic magic wand.", + + "#image magic_wand", + + "Most wands begin their life as a simple arrangement of a magic crystal of some sort attached to a wooden stick, with a gold nugget affixed to the other end. The crystal is, of course, the source of the wand's power, as it provides the focus necessary to channel @spells@. The rest of the wand is important too though, as it provides not only a means with which to hold the wand, but also a path through which @mana@ can be channelled and directed.", + + "As more spells are cast with a wand, it grows more effective at channelling @spells@, and can therefore cast spells of greater power. This effect can be discerned from subtle changes that occur in the shape and appearance of the wand itself: as a wand grows more powerful, its crystal will become more vibrant, its colour may change depending on its @elements element@, and after a while, the wood from which it is made will start to grow and twist into more complex shapes.", + + "When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. It also shows the names of the next and previous spells bound to the wand. To switch between spells, use the #next_spell_key and #previous_spell_key keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking.", + + "When viewing an inventory, hovering over a wand will display how much @mana@ is stored in it, along with its currently selected @spells spell@ and any specific abilities it may have. More in-depth information about a wand can be viewed by placing it in an @arcane_workbench arcane workbench@." + ] + }, + + "spells": { + "title": "Spells", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "A @wands wand@ is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary.", + + "Spell books are of little use on their own; instead they are used to bind the spell they contain to @wands@. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more about it.", + + "Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself (see @enchanting_scrolls Enchanting Scrolls@).", + + "Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your @wands wand@. It should be noted, however, that they are an inefficient method of casting spells compared to using a wand.", + + "To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. Futhermore, there is always a chance of misreading an unknown spell when casting, with potentially dangerous consequences depending on how powerful the spell is. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right-clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification in the process." + ] + }, + + "arcane_workbench": { + "title": "Arcane Workbench", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind @spells@ to your @wands wand@. It may be crafted as follows:", + + "#recipe arcane_workbench", + + "The following pages explain the various actions that may be performed in the arcane workbench.", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "Binding Spells", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "To bind a @spells spell@ to your @wands wand@, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press confirm. You will notice that the spell book is left untouched during this process - this is because the act of spell binding does not 'take' the spell from the spell book; rather, it attunes the wand to the spell. As such, you may change the spells bound to your wand as much as you wish by simply repeating the spell binding process. You can bind up to five spells to a wand at one time, though this number may be increased with attunement upgrades." + ] + }, + "charging_wands": { + "title": "Charging Wands", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "To charge your @wands wand@, place the wand in the central slot of the arcane workbench and place some magic crystals in the bottom-left slot, and then press confirm. Each crystal is worth #mana_per_crystal @mana@, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals, so if the wand needs, for example, 30 more mana, #example_charging_loss mana will be lost when charging it." + ] + }, + "upgrading_wands": { + "title": "Upgrading Wands", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text": [ + + "To upgrade your @wands wand@, you will need a tome of arcana of the appropriate @tiers tier@ or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the top-right slot, then press confirm.", + + "Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed." + ] + }, + "enchanting_scrolls": { + "title": "Enchanting Scrolls", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/scrolls" + ], + "text": [ + + "The arcane workbench can also be used to enchant @spells spell@ scrolls. To do so, you will require a blank scroll, crafted from a piece of paper and some string, a spell book for your chosen spell, and enough magic crystals to provide @mana@ to cast it.", + + "#recipe blank_scroll", + + "Place the crystals in the bottom-left slot, the blank scroll in the central slot, and the spell book in the single slot above it, then press confirm to enchant the scroll." + ] + } + } + }, + + "wizard_armour": { + "title": "Wizard Armour", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "As a wizard, you will need something to protect you from the creatures you fight. No ordinary armour will do though. To make the most of your spells, you will need wizard armour: hat, robes, leggings and boots. Unlike ordinary armour, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much @mana@. Charge them as you would a wand in the @arcane_workbench arcane workbench@ or with a @mana_flasks mana flask@, but be careful - if they run out of mana, you will find yourself defenceless.", + + "#image wizard_armour", + + "You can obtain wizard armour by crafting it from magical silk:", + + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + + "One may enchant wizard armour using a normal enchantment table, and due to the inherent properties of magical silk, it tends to be quite effective at holding enchantments. Of particular utility to a wizard are the Magic, Frost and Shock Protection enchantments, which may prove useful defending against other wielders of magic.", + + "Those wizards who devote themselves to the practice of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find.", + + "Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armour." + ] + }, + + "magical_world": { + "title": "Magic in the World", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/on_subsection_unlock" + ], + "text": [ + + "One cannot master the arcane just by staying at home - there is a world full of magic out there to explore! Ancient ruins, relics and mysterious beings good and bad await discovery - if you know where to look. The greatest wizards are also the most curious, and gain much of their knowledge and power through exploration of the environment around them, and experimenting with what they find.", + + "Whilst much of a wizard's time is inevitably spent alone, the sharing of knowledge is also vital to learning the arcane arts. Seek out fellow practicioners of magic, and learn as much as you can from them: communication is arguably the greatest power of all." + ], + "sections": { + "crystal_flowers": { + "title": "Crystal Flowers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "During your travels, you will probably come across curious glowing flowers growing in the wild from time to time. These distinctive blooms are known as crystal flowers, and they are strangely effective at concentrating @mana@ - to this day, nobody is quite sure why. We do know, however, that they can be harvested and crafted to extract the mana as crystals, making them a rather useful above-ground source of mana. The amount of mana obtainable this way is limited, though, by the small size of the flowers and due to them only growing in small patches.", + + "#image crystal_flower", + "#recipe crystal_flower_to_crystals" + ] + }, + "wizard_towers": { + "title": "Wizard Towers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:wizard_tower" + ], + "text": [ + + "Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter a tall tower or two with a distinctive pointed roof. This is the residence of a fellow wizard. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder.", + + "If, however, it is a @tiers master@ spell you seek, you will need to speak to a specialist.", + + "#image wizard_tower", + + "Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking.", + + "It should also be noted that no wizard will take kindly to being attacked or stolen from - and they will readily take the law into their own hands." + ] + }, + "obelisks": { + "title": "Obelisks", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/obelisks" + ], + "text": [ + + "Vestiges of ancient magic are scattered throughout the world, the most notable of which are carved stone structures which bear a great many symbols and runes. These ruins are all that remain of what appears to have been some kind of ancient civilisation. Whoever, or whatever, built them, they clearly had a considerable knowledge of magic, and used it to place protective enchantments over such locations that still persist to this day.", + + "These stuctures fit broadly into two types. The first, and more common, of these are obelisks: tall spikes of carved stone, known as runestone, with an open structure at the bottom containing minor arcane relics from the forgotten past.", + + "#image obelisk", + + "These structures are typically protected by an enchantment that summons hostile @creatures magical creatures@ should any human stray too close. Fend off these creatures and destroy their source, and the relics are yours." + ] + }, + "shrines": { + "title": "Shrines", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:visit_shrine" + ], + "text": [ + + "The second, and rarer, kind of structure is known as a shrine. These structures consist of a circle of runestone pillars surrounding a central pedestal, upon which sits a chest filled with ancient @artefacts@. This chest is typically protected by an arcane lock enchantment, preventing anyone except the owner from opening it. The entire structure is also protected by a containment field, which prevents anything that strays too close from escaping.", + + "#image shrine", + + "Due to their great arcane power and significance, not to mention the riches within, these structures are particularly attractive to any aspiring wizard - but be cautious. There are numerous reports of wizards becoming trapped within containment fields and slowly being driven insane, perhaps by claustrophobia. A growing number, however, believe such occurences to be a deliberate part of the shrine's protective magic, and that the wizards trapped within are in fact being controlled to protect the structure. A chilling prospect, surely, for anyone who dares to venture near..." + ] + }, + "creatures": { + "title": "Magical Creatures", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/magical_creatures" + ], + "text": [ + + "Besides wizards, a multitude of other creatures also use magic. Many of these creatures are arcane in origin, and most can be summoned using certain magical @spells spells@. One may encounter these creatures guarding an @obelisks obelisk@, or perhaps a powerful summoner. Lesser arcane beings are even found occasionally in the wilderness." + ] + }, + "artefacts": { + "title": "Artefacts", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:artefact" + ], + "text": [ + + "When exploring a @shrines shrine@, you may be lucky enough to uncover some kind of ancient magical artefact - an object capable of granting its wearer unique and powerful buffs and special powers. Three types are known to have been found: rings, which grant bonuses and effects to spells, amulets, which improve defensive abilities, and charms, which give utility effects. These artefacts appear to function only when worn appropriately; simply having them on one's person is insufficient." + ] + } + } + }, + + "tiers": { + "title": "Tiers", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text":[ + + "@wands Wands@ and @spells@ come in four tiers: #colour_novicenovice#colour_reset, #colour_apprenticeapprentice#colour_reset, #colour_advancedadvanced #colour_resetand #colour_mastermaster#colour_reset. Each tier is more powerful than the last.", + + "#image tiers", + + "#colour_noviceNovice #colour_resetwands are the ones that can be crafted. They hold up to #novice_max_charge mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells.", + + "#colour_apprenticeApprentice #colour_resetwands are the next tier up from novice wands. They hold up to #apprentice_max_charge mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more.", + + "#colour_advancedAdvanced #colour_resetwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold #advanced_max_charge mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on foes.", + + "#colour_masterMaster #colour_resetwands are the most powerful wands in existence. They hold up to #master_max_charge mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution.", + + "To be upgraded to a higher tier, a wand must first become powerful enough to channel spells of that tier. Wands gain power as spells are cast with them. Spell variety, spell power, elemental effects and other external factors may all have an effect on how quickly a wand matures.", + + "Once a wand is sufficiently powerful, a tome of arcana provides the final catalyst needed to elevate the wand to the next tier (see @upgrading_wands Upgrading Wands@), and once this has happened, the maturing process can begin again for the next tier." + ] + }, + + "elements": { + "title": "Elements", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/elements" + ], + "text": [ + + "@spells Spells@ belong to different elements, which determine the nature of the spell and also grant perks when used with certain @wands@.", + + "#image elements", + + "#colour_fireFire#colour_reset \nPerhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs.", + + "#colour_iceIce#colour_reset \nThe element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river.", + + "#colour_lightningLightning#colour_reset \nThis element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers.", + + "#colour_necromancyNecromancy#colour_reset \nNecromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon @creatures@ to fight for you or even bend the will of your enemies.", + + "#colour_earthEarth#colour_reset \nThe element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defence and utility.", + + "#colour_sorcerySorcery#colour_reset \nSorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will.", + + "#colour_healingHealing#colour_reset \nThe element of healing is concerned with defence and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat.", + + "The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own.", + + "If you are lucky, you may happen upon an elemental crystal. Elemental crystals are magic crystals that have absorbed elemental properties as they have grown - though the exact conditions required and mechanism by which this happens remain a mystery. We do know, however, that such crystals may be used to create elemental wands, which allow spells of the right element to be cast with more potency than usual." + ] + }, + + "miscellaneous": { + "title": "Miscellaneous", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:on_subsection_unlock" + ], + "sections": { + "mana_flasks": { + "title": "Mana Flasks", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/mana_flasks" + ], + "text": [ + + "Arkendur's Arcane Supplies Co. - magical items for your every need!", + + "Need to recharge your @wands wand@ on the go? No problem! @mana Mana@ can now be bottled. Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge. *", + + "#recipe medium_mana_flask", + + "NEW! Introducing the brand-new small and large mana flasks - now you can choose a size of mana flask to meet your needs!", + + "#recipe small_mana_flask", + "#recipe large_mana_flask", + + "* This process will destroy the bottle. Some mana is lost during bottling." + ] + }, + "throwable_items": { + "title": "Throwable Items", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/throwables" + ], + "text": [ + + "Various @spells@ conjure physical items, some of which can also be crafted directly. Firebombs, poison bombs, spark bombs and smoke bombs can all be crafted:", + + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + "automated_casting": { + "title": "Automated Casting", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:enchant_scroll" + ], + "text": [ + + "Recent experimentation has revealed that it is possible to automate @spells spell@ casting, to a degree, using nothing more than a simple dispenser. Nobody is quite sure why, but it would appear that the strange properties of redstone even extend to triggering the activation of spell scrolls. Placing a few into a dispenser and powering it ought to do the trick..." + ] + } + } + }, + + "crafting_recipes": { + "title": "Crafting Recipes", + "include_in_contents": "main_contents", + "text": [ + + "#recipe arcane_workbench", + "#recipe novice_wands", + "#recipe magic_missile_spell_book", + "#recipe wizard_handbook", + "#recipe crystal_flower_to_crystals", + "#recipe crystal_blocks", + "#recipe crystal_blocks_to_crystals", + "#recipe crystal_shard", + "#recipe small_mana_flask", + "#recipe medium_mana_flask", + "#recipe large_mana_flask", + "#recipe runestone", + "#recipe runestone_pedestal", + "#recipe transportation_stone", + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + "#recipe blank_scroll", + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + + "credits": { + "title": "Credits", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion", + + "Designed, coded and textured by Electroblob", + + "Thanks to Minecraft Forge and MCP, without which this mod would not have been possible.", + + "Thanks also to the Minecraft modding community, which always has an answer to my modding problems!", + + "In addition, I'd like to thank the following individuals for their contributions to the mod:", + + "Code:", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- Tora-B \n- Avatair \n- Aeronica", + + "Translations:", + + "- Spanish and Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese: ZHENGLOC, dragon-evol \n-Korean: shejery, rewi_wire", + + "Lightning ray sound effect from OhhWowProductions", + + "For more information, check out the @https://github.com/Electroblob77/Wizardry/wiki wiki@.", + + "Can't get enough wizardry? Join the @https://discord.gg/MTmMzMv Discord server@ for the latest news, discussions and extras!" + ] + } + + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.txt b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.txt deleted file mode 100644 index b96617ab..00000000 --- a/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.txt +++ /dev/null @@ -1,225 +0,0 @@ -PAGEBREAK -LINEBREAK - - - - -The Wizard's Handbook - - - -By Electroblob -PAGEBREAK -Introduction --------------------- - -Greetings, wizard! This book explains the many ways of the arcane and how to use them. - -Should you ever lose this book, you can get a new one by crafting a book with a magic crystal. -PAGEBREAK -Contents --------------------- -PAGEBREAK -SECTION Setting up -Setting up --------------------- -To get started with wizardry, you will need a few things: - -- A magic wand, crafted with a gold nugget, a stick, and a magic crystal. - -- An arcane workbench, crafted with 3 stone, 1 lapis lazuli block, 2 magic crystals, 2 gold nuggets and 1 purple carpet. - -- A spell book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, with a book and 4 magic crystals. - -You will also need a bunch more magic crystals to supply your wand with mana. -PAGEBREAK -SECTION Mana -Mana --------------------- -Mana is the arcane energy that gives wizards their powers. It manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. Wands can store mana within them, and this mana is channeled into the spells that you cast. Different spells cost different amounts of mana, depending on how powerful they are and how long they last. - -Mouse over a wand to see how much mana is stored in it. -IMAGE CRYSTAL -LINEBREAK - - - - - - - Magic - Crystal Ore Crystal -PAGEBREAK -SECTION Wands -Wands --------------------- -The wand is the implement of choice for a wizard. With it, you can cast any spell provided the wand can contain its power (see Tiers for a more in-depth explanation). Wands come in -various different varieties but you will almost certainly start with a basic magic wand. - -When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. -PAGEBREAK -SECTION Spells -Spells --------------------- -A wand is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary. - -Spell books are of little use on their own; instead they are used to bind the spell they contain to wands. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more detailed information about the spell. - -Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself by crafting a blank scroll from a piece of paper and some string. You can then bind a spell to the scroll using the arcane workbench, but only if you have knowledge of that spell. Doing so will consume a number of magic crystals equal to the mana cost of the spell (rounded up to the nearest MANA_PER_CRYSTAL). - -To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right- -clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification. - -Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your wand. It should be noted, however, that scrolls are at best an inefficient method of casting spells. -PAGEBREAK -SECTION Arcane Workbench -The Arcane Workbench --------------------- -The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind spells to your wand. Simply place it anywhere and right click, and you will see a gui appear like the one to the right. -PAGEBREAK -IMAGE WORKBENCH -LINEBREAK - - - - - - - - - - - - - - The Arcane Workbench -PAGEBREAK -SECTION Binding Spells -Binding Spells --------------------- -To bind a spell to your wand, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press apply. You can bind up to five spells to a wand at one time, though this may be increased with attunement upgrades. When holding a wand, you can switch between spells with the NEXT_SPELL_KEY and PREVIOUS_SPELL_KEY keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking. - -SECTION Charging Wands -Charging your wand --------------------- -To charge your wand, place the wand in the central slot of the arcane workbench and place some magic crystals in the upper of the two slots on the left, and then press apply. Each crystal is worth MANA_PER_CRYSTAL mana, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals so if the wand needs, for example, 30 more mana, MANA_PER_CRYSTAL_MINUS_30 mana will be lost when charging it. All wands will take an exact number of crystals to charge if they are empty, unless they have a storage upgrade applied. - -SECTION Upgrading Wands -Upgrading your wand --------------------- -To upgrade your wand, you will need a tome of arcana of the appropriate tier (see Tiers) or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the lower of the two slots on the left, then press apply. - -Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed. -PAGEBREAK -SECTION Flowers and Flasks -Flowers and Flasks --------------------- -You will probably come across curious glowing flowers growing in the wild from time to time. These are crystal flowers, a naturally occurring source of mana. They can be harvested and crafted to extract the mana as crystals. - -Need to recharge your wand on the go? No problem! Mana can now be bottled. Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge. This process will consume the bottle. Some mana is lost during bottling. -PAGEBREAK -SECTION Tiers -Tiers --------------------- -Wands and spells come in four tiers: BASIC_COLOURnoviceRESET_COLOUR, APPRENTICE_COLOURapprenticeRESET_COLOUR, ADVANCED_COLOURadvanced RESET_COLOURand MASTER_COLOURmasterRESET_COLOUR. Each tier is more powerful than the last. - -BASIC_COLOURNovice RESET_COLOURwands are the ones that can be crafted. They hold up to BASIC_MAX_CHARGE mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells. - -APPRENTICE_COLOURApprentice RESET_COLOURwands are the next tier up from novice wands. They hold up to APPRENTICE_MAX_CHARGE mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more. - -ADVANCED_COLOURAdvanced RESET_COLOURwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold ADVANCED_MAX_CHARGE mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on enemies. - -MASTER_COLOURMaster RESET_COLOURwands are the most powerful wands in existence. They hold up to MASTER_MAX_CHARGE mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution. -PAGEBREAK -SECTION Elements -Elements --------------------- -Spells belong to different elements, which describe the nature of the spell and also have perks when used with certain wands. - -FIRE_COLOURFire -Perhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs. - -ICE_COLOURIce -The element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river. - -LIGHTNING_COLOURLightning -This element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers. - -NECROMANCY_COLOURNecromancy -Necromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon creatures to fight for you or even bend the will of your enemies. - -EARTH_COLOUREarth -The element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defence and utility. - -SORCERY_COLOURSorcery -Sorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will. - -HEALING_COLOURHealing -The element of healing is concerned with defence and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat. - -The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own. - -If you are lucky, you may happen upon an elemental wand. These wands will allow you to cast spells of the right element for more potency than usual. -PAGEBREAK -SECTION Wizard Armour -Wizard Armour --------------------- -As a wizard, you will need something to protect you from the creatures you fight. No ordinary armour will do though. To make the most of your spells, you will need wizard armour: hat, robes, leggings and boots. Unlike ordinary armour, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much mana. Charge them as you would a wand in the arcane workbench or with a mana flask, but be careful - if they run out of mana, you will find yourself defenceless. - -You can obtain wizard armour by crafting it from magical silk, obtained by crafting string with a magic crystal. - -Those wizards who devote themselves to the practice of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find. - -Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armour. -PAGEBREAK -SECTION Wizards and Towers -Wizards and Towers --------------------- -Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter tall towers with distinctive pointed roofs. These are the residences of fellow wizards. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder. If, however, it is a master spell you seek, you will need to speak to a specialist. - -Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking. -PAGEBREAK -SECTION Crafting Recipes -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Credits --------------------- -Electroblob's Wizardry -Version VERSION -For Minecraft MCVERSION - -Designed, coded and textured by Electroblob - -Thanks to Minecraft Forge and MCP, without which this mod would not have been possible. - -Thanks also to the Minecraft modding community, which always has an answer to my modding problems! - -In addition, I'd like to thank the following individuals for their contributions to the mod: - -Code: - -- Corail31 -- 12foo -- Shadows-of-Fire -- HellFirePvP - -Translations: - -- Russian: VilagVil -- Spanish and Mexican Spanish: MadWrist -- Chinese: ZHENGLOC and dragon-evol - -Lightning ray sound effect from OhhWowProductions diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json new file mode 100644 index 00000000..311a4c20 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json @@ -0,0 +1,750 @@ +{ + "bookmark_start_section": "introduction", + + "colours": { + "text": "#000000", + "caption": "#666666", + "hyperlink": "#601ba0", + "highlight": "#dd4c1d", + "new_section": "#ee88f7" + }, + + "images": { + "workbench": { + "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "caption": "The Arcane Workbench", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/ore_picture.png", + "caption": "Crystal Ore", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "A Magic Crystal", + "u": 6, + "v": 6, + "width": 36, + "height": 33, + "texture_width": 48, + "texture_height": 48, + "border": false + }, + "magic_wand": { + "location": "ebwizardry:textures/items/wand_novice.png", + "caption": "A Magic Wand", + "u": 0, + "v": 0, + "width": 64, + "height": 64, + "border": false + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/flower_picture.png", + "caption": "A Crystal Flower", + "u": 0, + "v": 0, + "width": 48, + "height": 48 + }, + "wizard_tower": { + "location": "ebwizardry:textures/gui/tower_picture.png", + "caption": "A Wizard's Tower", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "wizard_armour": { + "location": "ebwizardry:textures/gui/armour_picture.png", + "caption": "A Set Of Wizard Armour", + "u": 0, + "v": 0, + "width": 90, + "height": 90 + }, + "obelisk": { + "location": "ebwizardry:textures/gui/obelisk_picture.png", + "caption": "An Obelisk", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "shrine": { + "location": "ebwizardry:textures/gui/shrine_picture.png", + "caption": "An Earth Shrine", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "tiers": { + "location": "ebwizardry:textures/gui/tiers_picture.png", + "caption": "The 4 Tiers Of Wand", + "u": 0, + "v": 0, + "width": 96, + "height": 24, + "border": false + }, + "elements": { + "location": "ebwizardry:textures/gui/elements_picture.png", + "caption": "The 7 Arcane Elements", + "u": 0, + "v": 0, + "width": 106, + "height": 20, + "border": false + } + }, + + "recipes": { + "arcane_workbench": { + "locations": [ "ebwizardry:arcane_workbench" ] + }, + "magic_wand": { + "locations": [ "ebwizardry:magic_wand" ] + }, + "novice_wands": { + "locations": [ + "ebwizardry:magic_wand", + "ebwizardry:wand_fire", + "ebwizardry:wand_ice", + "ebwizardry:wand_lightning", + "ebwizardry:wand_necromancy", + "ebwizardry:wand_earth", + "ebwizardry:wand_sorcery", + "ebwizardry:wand_healing" + ] + }, + "magic_missile_spell_book": { + "locations": [ "ebwizardry:magic_missile_spell_book" ] + }, + "wizard_handbook": { + "locations": [ "ebwizardry:wizard_handbook" ] + }, + "crystal_flower_to_crystals": { + "locations": [ "ebwizardry:crystal_flower_to_crystals" ] + }, + "crystal_blocks": { + "locations": [ + "ebwizardry:crystal_block", + "ebwizardry:crystal_block_fire", + "ebwizardry:crystal_block_ice", + "ebwizardry:crystal_block_lightning", + "ebwizardry:crystal_block_necromancy", + "ebwizardry:crystal_block_earth", + "ebwizardry:crystal_block_sorcery", + "ebwizardry:crystal_block_healing" + ] + }, + "crystal_blocks_to_crystals": { + "locations": [ + "ebwizardry:crystal_block_to_crystals", + "ebwizardry:crystal_block_to_crystals_fire", + "ebwizardry:crystal_block_to_crystals_ice", + "ebwizardry:crystal_block_to_crystals_lightning", + "ebwizardry:crystal_block_to_crystals_necromancy", + "ebwizardry:crystal_block_to_crystals_earth", + "ebwizardry:crystal_block_to_crystals_sorcery", + "ebwizardry:crystal_block_to_crystals_healing" + ] + }, + "crystal_shard": { + "locations": [ "ebwizardry:crystal_shard" ] + }, + "small_mana_flask": { + "locations": [ "ebwizardry:small_mana_flask" ] + }, + "medium_mana_flask": { + "locations": [ "ebwizardry:medium_mana_flask" ] + }, + "large_mana_flask": { + "locations": [ "ebwizardry:large_mana_flask" ] + }, + "runestone": { + "locations": [ + "ebwizardry:runestone_fire", + "ebwizardry:runestone_ice", + "ebwizardry:runestone_lightning", + "ebwizardry:runestone_necromancy", + "ebwizardry:runestone_earth", + "ebwizardry:runestone_sorcery", + "ebwizardry:runestone_healing" + ] + }, + "runestone_pedestal": { + "locations": [ + "ebwizardry:runestone_pedestal_fire", + "ebwizardry:runestone_pedestal_ice", + "ebwizardry:runestone_pedestal_lightning", + "ebwizardry:runestone_pedestal_necromancy", + "ebwizardry:runestone_pedestal_earth", + "ebwizardry:runestone_pedestal_sorcery", + "ebwizardry:runestone_pedestal_healing" + ] + }, + "transportation_stone": { + "locations": [ "ebwizardry:transportation_stone" ] + }, + "magic_silk": { + "locations": [ "ebwizardry:magic_silk" ] + }, + "wizard_hat": { + "locations": [ "ebwizardry:wizard_hat" ] + }, + "wizard_robe": { + "locations": [ "ebwizardry:wizard_robe" ] + }, + "wizard_leggings": { + "locations": [ "ebwizardry:wizard_leggings" ] + }, + "wizard_boots": { + "locations": [ "ebwizardry:wizard_boots" ] + }, + "blank_scroll": { + "locations": [ "ebwizardry:blank_scroll" ] + }, + "firebomb": { + "locations": [ "ebwizardry:firebomb" ] + }, + "poison_bomb": { + "locations": [ "ebwizardry:poison_bomb" ] + }, + "smoke_bomb": { + "locations": [ "ebwizardry:smoke_bomb" ] + }, + "spark_bomb": { + "locations": [ "ebwizardry:spark_bomb" ] + } + }, + + "sections": { + + "inside_cover": { + "centre": { + "x": true, + "y": true + }, + "text": [ + + "The Wizard's Handbook", + + "By Electroblob" + ] + }, + + "introduction": { + "title": "Introduction", + "text": [ + + "Greetings, wizard! This book explains the many ways of the arcane and how to use them. This is no ordinary book, though - its pages will materialize before you as you discover more about the magical world.", + + "Use the arrow buttons to turn between pages, and the double-arrow buttons to quickly flip between sections. Use the central menu button to return to the main contents page.", + + "Click on any purple link text to jump straight to the relevant page. Right-click the bookmark to move it to the current page, and left-click to return to the bookmarked page." + ] + }, + + "main_contents": { + "title": "Contents", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "Setting Up", + "include_in_contents": "main_contents", + "text": [ + + "To get started with wizardry, you will need:", + + "- A magic wand, crafted as shown:", + + "#recipe magic_wand", + + "- An @arcane_workbench arcane workbench@, crafted like so:", + + "#recipe arcane_workbench", + + "- A @spells spell@ book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, as shown:", + + "#recipe magic_missile_spell_book", + + "You will also need a bunch more magic crystals to supply your wand with @mana@." + ] + }, + + "mana": { + "title": "Mana", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "Mana is the arcane energy that gives wizards their powers. It is not a physical substance in its own right; rather, it is an everpresent aura which permeates everything in the world. In particular, it manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. @wands Wands@ can store mana within them, and this mana is channeled into the @spells@ that you cast. Different spells require different amounts of mana, depending on how powerful they are and how long they last.", + + "#image crystal_ore", + + "#image magic_crystal", + + "It is widely accepted that mana cannot be created or destroyed, and that when a spell is cast, the mana it channels is simply dissipated into the surroundings. Indeed, this is how the vast majority of mana exists - spread thinly throughout the world. However, to be of any use, it must be concentrated, either naturally over thousands of years, as is the case with crystals underground, or artificially - an advanced subject not covered in this book. There also exist various ways of making spells more mana-efficient and recovering some of the mana dissipated during spellcasting." + ] + }, + + "wands": { + "title": "Wands", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "The wand is the implement of choice for a wizard. With it, you can cast any @spells spell@ provided the wand can contain its power (see @tiers Tiers@). Wands come in many different varieties but you will almost certainly start with a basic magic wand.", + + "#image magic_wand", + + "Most wands begin their life as a simple arrangement of a magic crystal of some sort attached to a wooden stick, with a gold nugget affixed to the other end. The crystal is, of course, the source of the wand's power, as it provides the focus necessary to channel @spells@. The rest of the wand is important too though, as it provides not only a means with which to hold the wand, but also a path through which @mana@ can be channeled and directed.", + + "As more spells are cast with a wand, it grows more effective at channeling @spells@, and can therefore cast spells of greater power. This effect can be discerned from subtle changes that occur in the shape and appearance of the wand itself: as a wand grows more powerful, its crystal will become more vibrant, its colour may change depending on its @elements element@, and after a while, the wood from which it is made will start to grow and twist into more complex shapes.", + + "When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. It also shows the names of the next and previous spells bound to the wand. To switch between spells, use the #next_spell_key and #previous_spell_key keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking.", + + "When viewing an inventory, hovering over a wand will display how much @mana@ is stored in it, along with its currently selected @spells spell@ and any specific abilities it may have. More in-depth information about a wand can be viewed by placing it in an @arcane_workbench arcane workbench@." + ] + }, + + "spells": { + "title": "Spells", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "A @wands wand@ is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary.", + + "Spell books are of little use on their own; instead they are used to bind the spell they contain to @wands@. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more about it.", + + "Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself (see @enchanting_scrolls Enchanting Scrolls@).", + + "Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your @wands wand@. It should be noted, however, that they are an inefficient method of casting spells compared to using a wand.", + + "To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. Futhermore, there is always a chance of misreading an unknown spell when casting, with potentially dangerous consequences depending on how powerful the spell is. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right-clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification in the process." + ] + }, + + "arcane_workbench": { + "title": "Arcane Workbench", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind @spells@ to your @wands wand@. It may be crafted as follows:", + + "#recipe arcane_workbench", + + "The following pages explain the various actions that may be performed in the arcane workbench.", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "Binding Spells", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "To bind a @spells spell@ to your @wands wand@, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press apply. You will notice that the spell book is left untouched during this process - this is because the act of spell binding does not 'take' the spell from the spell book; rather, it attunes the wand to the spell. As such, you may change the spells bound to your wand as much as you wish by simply repeating the spell binding process. You can bind up to five spells to a wand at one time, though this number may be increased with attunement upgrades." + ] + }, + "charging_wands": { + "title": "Charging Wands", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "To charge your @wands wand@, place the wand in the central slot of the arcane workbench and place some magic crystals in the bottom-left slot, and then press apply. Each crystal is worth #mana_per_crystal @mana@, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals, so if the wand needs, for example, 30 more mana, #example_charging_loss mana will be lost when charging it." + ] + }, + "upgrading_wands": { + "title": "Upgrading Wands", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text": [ + + "To upgrade your @wands wand@, you will need a tome of arcana of the appropriate @tiers tier@ or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the top-right slot, then press apply.", + + "Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed." + ] + }, + "enchanting_scrolls": { + "title": "Enchanting Scrolls", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/scrolls" + ], + "text": [ + + "The arcane workbench can also be used to enchant @spells spell@ scrolls. To do so, you will require a blank scroll, crafted from a piece of paper and some string, a spell book for your chosen spell, and enough magic crystals to provide @mana@ to cast it.", + + "#recipe blank_scroll", + + "Place the crystals in the bottom-left slot, the blank scroll in the central slot, and the spell book in the single slot above it, then press confirm to enchant the scroll." + ] + } + } + }, + + "wizard_armour": { + "title": "Wizard Armor", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "As a wizard, you will need something to protect you from the creatures you fight. No ordinary armor will do though. To make the most of your spells, you will need wizard armor: hat, robes, leggings and boots. Unlike ordinary armor, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much @mana@. Charge them as you would a wand in the @arcane_workbench arcane workbench@ or with a @mana_flasks mana flask@, but be careful - if they run out of mana, you will find yourself defenseless.", + + "#image wizard_armour", + + "You can obtain wizard armor by crafting it from magical silk:", + + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + + "One may enchant wizard armour using a normal enchantment table, and due to the inherent properties of magical silk, it tends to be quite effective at holding enchantments. Of particular utility to a wizard are the Magic, Frost and Shock Protection enchantments, which may prove useful defending against other wielders of magic.", + + "Those wizards who devote themselves to the practise of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find.", + + "Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armor." + ] + }, + + "magical_world": { + "title": "Magic in the World", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/on_subsection_unlock" + ], + "text": [ + + "One cannot master the arcane just by staying at home - there is a world full of magic out there to explore! Ancient ruins, relics and mysterious beings good and bad await discovery - if you know where to look. The greatest wizards are also the most curious, and gain much of their knowledge and power through exploration of the environment around them, and experimenting with what they find.", + + "Whilst much of a wizard's time is inevitably spent alone, the sharing of knowledge is also vital to learning the arcane arts. Seek out fellow practicioners of magic, and learn as much as you can from them: communication is arguably the greatest power of all." + ], + "sections": { + "crystal_flowers": { + "title": "Crystal Flowers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "During your travels, you will probably come across curious glowing flowers growing in the wild from time to time. These distinctive blooms are known as crystal flowers, and they are strangely effective at concentrating @mana@ - to this day, nobody is quite sure why. We do know, however, that they can be harvested and crafted to extract the mana as crystals, making them a rather useful above-ground source of mana. The amount of mana obtainable this way is limited, though, by the small size of the flowers and due to them only growing in small patches.", + + "#image crystal_flower", + "#recipe crystal_flower_to_crystals" + ] + }, + "wizard_towers": { + "title": "Wizard Towers", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:wizard_tower" + ], + "text": [ + + "Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter a tall tower or two with a distinctive pointed roof. This is the residence of a fellow wizard. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder.", + + "If, however, it is a @tiers master@ spell you seek, you will need to speak to a specialist.", + + "#image wizard_tower", + + "Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking.", + + "It should also be noted that no wizard will take kindly to being attacked or stolen from - and they will readily take the law into their own hands." + ] + }, + "obelisks": { + "title": "Obelisks", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/obelisks" + ], + "text": [ + + "Vestiges of ancient magic are scattered throughout the world, the most notable of which are carved stone structures which bear a great many symbols and runes. These ruins are all that remain of what appears to have been some kind of ancient civilisation. Whoever, or whatever, built them, they clearly had a considerable knowledge of magic, and used it to place protective enchantments over such locations that still persist to this day.", + + "These stuctures fit broadly into two types. The first, and more common, of these are obelisks: tall spikes of carved stone, known as runestone, with an open structure at the bottom containing minor arcane relics from the forgotten past.", + + "#image obelisk", + + "These structures are typically protected by an enchantment that summons hostile @creatures magical creatures@ should any human stray too close. Fend off these creatures and destroy their source, and the relics are yours." + ] + }, + "shrines": { + "title": "Shrines", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:visit_shrine" + ], + "text": [ + + "The second, and rarer, kind of structure is known as a shrine. These structures consist of a circle of runestone pillars surrounding a central pedestal, upon which sits a chest filled with ancient @artefacts artifacts@. This chest is typically protected by an arcane lock enchantment, preventing anyone except the owner from opening it. The entire structure is also protected by a containment field, which prevents anything that strays too close from escaping.", + + "#image shrine", + + "Due to their great arcane power and significance, not to mention the riches within, these structures are particularly attractive to any aspiring wizard - but be cautious. There are numerous reports of wizards becoming trapped within containment fields and slowly being driven insane, perhaps by claustrophobia. A growing number, however, believe such occurences to be a deliberate part of the shrine's protective magic, and that the wizards trapped within are in fact being controlled to protect the structure. A chilling prospect, surely, for anyone who dares to venture near..." + ] + }, + "creatures": { + "title": "Magical Creatures", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/magical_creatures" + ], + "text": [ + + "Besides wizards, a multitude of other creatures also use magic. Many of these creatures are arcane in origin, and most can be summoned using certain magical @spells spells@. One may encounter these creatures guarding an @obelisks obelisk@, or perhaps a powerful summoner. Lesser arcane beings are even found occasionally in the wilderness." + ] + }, + "artefacts": { + "title": "Artifacts", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:artefact" + ], + "text": [ + + "When exploring a @shrines shrine@, you may be lucky enough to uncover some kind of ancient magical artifact - an object capable of granting its wearer unique and powerful buffs and special powers. Three types are known to have been found: rings, which grant bonuses and effects to spells, amulets, which improve defensive abilities, and charms, which give utility effects. These artifacts appear to function only when worn appropriately; simply having them on one's person is insufficient." + ] + } + } + }, + + "tiers": { + "title": "Tiers", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text":[ + + "@wands Wands@ and @spells@ come in four tiers: #colour_novicenovice#colour_reset, #colour_apprenticeapprentice#colour_reset, #colour_advancedadvanced #colour_resetand #colour_mastermaster#colour_reset. Each tier is more powerful than the last.", + + "#image tiers", + + "#colour_noviceNovice #colour_resetwands are the ones that can be crafted. They hold up to #novice_max_charge mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells.", + + "#colour_apprenticeApprentice #colour_resetwands are the next tier up from novice wands. They hold up to #apprentice_max_charge mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more.", + + "#colour_advancedAdvanced #colour_resetwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold #advanced_max_charge mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on foes.", + + "#colour_masterMaster #colour_resetwands are the most powerful wands in existence. They hold up to #master_max_charge mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution.", + + "To be upgraded to a higher tier, a wand must first become powerful enough to channel spells of that tier. Wands gain power as spells are cast with them. Spell variety, spell power, elemental effects and other external factors may all have an effect on how quickly a wand matures.", + + "Once a wand is sufficiently powerful, a tome of arcana provides the final catalyst needed to elevate the wand to the next tier (see @upgrading_wands Upgrading Wands@), and once this has happened, the maturing process can begin again for the next tier." + ] + }, + + "elements": { + "title": "Elements", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/elements" + ], + "text": [ + + "@spells Spells@ belong to different elements, which determine the nature of the spell and also grant perks when used with certain @wands@.", + + "#image elements", + + "#colour_fireFire#colour_reset \nPerhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs.", + + "#colour_iceIce#colour_reset \nThe element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river.", + + "#colour_lightningLightning#colour_reset \nThis element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers.", + + "#colour_necromancyNecromancy#colour_reset \nNecromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon @creatures@ to fight for you or even bend the will of your enemies.", + + "#colour_earthEarth#colour_reset \nThe element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defense and utility.", + + "#colour_sorcerySorcery#colour_reset \nSorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will.", + + "#colour_healingHealing#colour_reset \nThe element of healing is concerned with defense and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat.", + + "The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own.", + + "If you are lucky, you may happen upon an elemental crystal. Elemental crystals are magic crystals that have absorbed elemental properties as they have grown - though the exact conditions required and mechanism by which this happens remain a mystery. We do know, however, that such crystals may be used to create elemental wands, which allow spells of the right element to be cast with more potency than usual." + ] + }, + + "miscellaneous": { + "title": "Miscellaneous", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:on_subsection_unlock" + ], + "sections": { + "mana_flasks": { + "title": "Mana Flasks", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/mana_flasks" + ], + "text": [ + + "Arkendur's Arcane Supplies Co. - magical items for your every need!", + + "Need to recharge your @wands wand@ on the go? No problem! @mana Mana@ can now be bottled. Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge. *", + + "#recipe medium_mana_flask", + + "NEW! Introducing the brand-new small and large mana flasks - now you can choose a size of mana flask to meet your needs!", + + "#recipe small_mana_flask", + "#recipe large_mana_flask", + + "* This process will destroy the bottle. Some mana is lost during bottling." + ] + }, + "throwable_items": { + "title": "Throwable Items", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/throwables" + ], + "text": [ + + "Various @spells@ conjure physical items, some of which can also be crafted directly. Firebombs, poison bombs, spark bombs and smoke bombs can all be crafted:", + + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + "automated_casting": { + "title": "Automated Casting", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:enchant_scroll" + ], + "text": [ + + "Recent experimentation has revealed that it is possible to automate @spells spell@ casting, to a degree, using nothing more than a simple dispenser. Nobody is quite sure why, but it would appear that the strange properties of redstone even extend to triggering the activation of spell scrolls. Placing a few into a dispenser and powering it ought to do the trick..." + ] + } + } + }, + + "crafting_recipes": { + "title": "Crafting Recipes", + "include_in_contents": "main_contents", + "text": [ + + "#recipe arcane_workbench", + "#recipe novice_wands", + "#recipe magic_missile_spell_book", + "#recipe wizard_handbook", + "#recipe crystal_flower_to_crystals", + "#recipe crystal_blocks", + "#recipe crystal_blocks_to_crystals", + "#recipe crystal_shard", + "#recipe small_mana_flask", + "#recipe medium_mana_flask", + "#recipe large_mana_flask", + "#recipe runestone", + "#recipe runestone_pedestal", + "#recipe transportation_stone", + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + "#recipe blank_scroll", + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + + "credits": { + "title": "Credits", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion", + + "Designed, coded and textured by Electroblob", + + "Thanks to Minecraft Forge and MCP, without which this mod would not have been possible.", + + "Thanks also to the Minecraft modding community, which always has an answer to my modding problems!", + + "In addition, I'd like to thank the following individuals for their contributions to the mod:", + + "Code:", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- Tora-B \n- Avatair \n- Aeronica", + + "Translations:", + + "- Spanish and Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese: ZHENGLOC, dragon-evol \n-Korean: shejery, rewi_wire", + + "Lightning ray sound effect from OhhWowProductions", + + "For more information, check out the @https://github.com/Electroblob77/Wizardry/wiki wiki@.", + + "Can't get enough wizardry? Join the @https://discord.gg/MTmMzMv Discord server@ for the latest news, discussions and extras!" + ] + } + + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.txt b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.txt deleted file mode 100644 index 5cae001e..00000000 --- a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.txt +++ /dev/null @@ -1,225 +0,0 @@ -PAGEBREAK -LINEBREAK - - - - -The Wizard's Handbook - - - -By Electroblob -PAGEBREAK -Introduction --------------------- - -Greetings, wizard! This book explains the many ways of the arcane and how to use them. - -Should you ever lose this book, you can get a new one by crafting a book with a magic crystal. -PAGEBREAK -Contents --------------------- -PAGEBREAK -SECTION Setting up -Setting up --------------------- -To get started with wizardry, you will need a few things: - -- A magic wand, crafted with a gold nugget, a stick, and a magic crystal. - -- An arcane workbench, crafted with 3 stone, 1 lapis lazuli block, 2 magic crystals, 2 gold nuggets and 1 purple carpet. - -- A spell book. These can be found in chests, dropped as loot, purchased from wizards, or you can make a beginner's spell, magic missile, with a book and 4 magic crystals. - -You will also need a bunch more magic crystals to supply your wand with mana. -PAGEBREAK -SECTION Mana -Mana --------------------- -Mana is the arcane energy that gives wizards their powers. It manifests itself in naturally occurring crystals, which can be found embedded in rocks underground. Wands can store mana within them, and this mana is channeled into the spells that you cast. Different spells cost different amounts of mana, depending on how powerful they are and how long they last. - -Mouse over a wand to see how much mana is stored in it. -IMAGE CRYSTAL -LINEBREAK - - - - - - - Magic - Crystal Ore Crystal -PAGEBREAK -SECTION Wands -Wands --------------------- -The wand is the implement of choice for a wizard. With it, you can cast any spell provided the wand can contain its power (see Tiers for a more in-depth explanation). Wands come in -various different varieties but you will almost certainly start with a basic magic wand. - -When holding a wand, a small heads-up display will show in the corner of the screen. This indicates the spell that is currently selected along with a pictorial representation of it, and, if the spell has been cast, a cooldown bar indicating the time until that spell can be cast again. -PAGEBREAK -SECTION Spells -Spells --------------------- -A wand is useless without spells to cast with it. Spells are recorded in two forms: in books, which are permanent, and in scrolls, which are temporary. - -Spell books are of little use on their own; instead they are used to bind the spell they contain to wands. Spell books can be found throughout the world and it will not take long for you to come across one. When you do find one, you can mouse over the spell book to see basic information about the spell at a glance. You can also right-click whilst holding a spell book to read more detailed information about the spell. - -Scrolls, on the other hand, serve a different purpose: right-clicking whilst holding one will cast the spell that is bound to it, destroying the scroll in the process. Like spell books, scrolls can be found throughout the world, but you can also make them yourself by crafting a blank scroll from a piece of paper and some string. You can then bind a spell to the scroll using the arcane workbench, but only if you have knowledge of that spell. Doing so will consume a number of magic crystals equal to the mana cost of the spell (rounded up to the nearest MANA_PER_CRYSTAL). - -To the untrained eye, the magical runes with which spells are written are unreadable. In order to identify a spell, you could, of course, cast it and see what happens - though doing so may cause unwanted side-effects, and many spells need certain conditions in order to work. A safer and more reliable option is to use a scroll of identification, which can be found in chests or purchased from a wizard. Right- -clicking whilst holding one will identify the first unknown spell book or scroll on your hotbar, consuming the scroll of identification. - -Scrolls are quite useful when you need to use a spell once or twice on a particular quest but don't want it to take up a slot on your wand. It should be noted, however, that scrolls are at best an inefficient method of casting spells. -PAGEBREAK -SECTION Arcane Workbench -The Arcane Workbench --------------------- -The arcane workbench is a block similar in appearance to the enchantment table where you can charge, upgrade, and bind spells to your wand. Simply place it anywhere and right click, and you will see a gui appear like the one to the right. -PAGEBREAK -IMAGE WORKBENCH -LINEBREAK - - - - - - - - - - - - - - The Arcane Workbench -PAGEBREAK -SECTION Binding Spells -Binding Spells --------------------- -To bind a spell to your wand, simply place your wand in the central slot of the workbench, then place the spell book in one of the surrounding slots and press apply. You can bind up to five spells to a wand at one time, though this may be increased with attunement upgrades. When holding a wand, you can switch between spells with the NEXT_SPELL_KEY and PREVIOUS_SPELL_KEY keys (these can be changed in options -> controls). You can also switch spells by scrolling the mouse wheel while sneaking. - -SECTION Charging Wands -Charging your wand --------------------- -To charge your wand, place the wand in the central slot of the arcane workbench and place some magic crystals in the upper of the two slots on the left, and then press apply. Each crystal is worth MANA_PER_CRYSTAL mana, and the wand will only take what it needs, so you can keep a supply of crystals in the workbench for when you need them. However, bear in mind that the wand can only take a whole number of crystals so if the wand needs, for example, 30 more mana, MANA_PER_CRYSTAL_MINUS_30 mana will be lost when charging it. All wands will take an exact number of crystals to charge if they are empty, unless they have a storage upgrade applied. - -SECTION Upgrading Wands -Upgrading your wand --------------------- -To upgrade your wand, you will need a tome of arcana of the appropriate tier (see Tiers) or a special wand upgrade. Both of these can be found as loot or purchased from a wizard. To upgrade your wand, place it in the central slot and the upgrade in the lower of the two slots on the left, then press apply. - -Special wand upgrades improve a particular aspect of a wand. Each type of upgrade can stack up to three times, and the total number of upgrades that a wand can take depends on its tier. Upgrades cannot be removed. -PAGEBREAK -SECTION Flowers and Flasks -Flowers and Flasks --------------------- -You will probably come across curious glowing flowers growing in the wild from time to time. These are crystal flowers, a naturally occurring source of mana. They can be harvested and crafted to extract the mana as crystals. - -Need to recharge your wand on the go? No problem! Mana can now be bottled. Simply craft a mana flask with a glass bottle and eight magic crystals and take it with you. When you need to use it, simply craft it with your wand and it will restore some of the charge. This process will consume the bottle. Some mana is lost during bottling. -PAGEBREAK -SECTION Tiers -Tiers --------------------- -Wands and spells come in four tiers: BASIC_COLOURnoviceRESET_COLOUR, APPRENTICE_COLOURapprenticeRESET_COLOUR, ADVANCED_COLOURadvanced RESET_COLOURand MASTER_COLOURmasterRESET_COLOUR. Each tier is more powerful than the last. - -BASIC_COLOURNovice RESET_COLOURwands are the ones that can be crafted. They hold up to BASIC_MAX_CHARGE mana, and can only cast novice spells. Novice spells are simple enough to be cast by anyone and they do not usually cost much mana. This does not necessarily mean that they are useless at higher tiers however; their low cost makes some novice spells useful even when you have access to master spells. - -APPRENTICE_COLOURApprentice RESET_COLOURwands are the next tier up from novice wands. They hold up to APPRENTICE_MAX_CHARGE mana and can cast novice and apprentice spells. Apprentice spells are a little more impressive than novice spells but usually cost more. - -ADVANCED_COLOURAdvanced RESET_COLOURwands are quite rare and are much more powerful. They can cast all spells except master spells, and hold ADVANCED_MAX_CHARGE mana. Advanced spells can grant superhuman powers such as invisibility and wreak havoc on enemies. - -MASTER_COLOURMaster RESET_COLOURwands are the most powerful wands in existence. They hold up to MASTER_MAX_CHARGE mana, and can cast any spell. Master spell books are very rare and the spells can cause complete devastation not only to enemies but even the world itself. Use with caution. -PAGEBREAK -SECTION Elements -Elements --------------------- -Spells belong to different elements, which describe the nature of the spell and also have perks when used with certain wands. - -FIRE_COLOURFire -Perhaps the most destructive element, fire concerns burning, lava, and explosions. A powerful pyromancer will unleash hell upon their enemies and probably set the world alight in the process. Most fire attack spells set fire to their target, causing ongoing damage over time. However, be wary that fire spells may have no effect on nether mobs. - -ICE_COLOURIce -The element of ice is made of all that is cold. Frost spells often slow enemies down or freeze them completely, and are especially effective on creatures of fire. Frost magic can prove equally useful outside of combat, such as freezing water to cross a river. - -LIGHTNING_COLOURLightning -This element concerns lightning, storms, and the weather. A powerful storm mage is a force to be reckoned with, and some posess the ability to call down lightning at will. Lightning spells often damage multiple enemies at once and usually seek their target, making them an effective attack against any mob... but beware of creepers. - -NECROMANCY_COLOURNecromancy -Necromancy is the element of darkness, chaos and the undead. Necromancers are mysterious and often regarded as evil, though this is usually not the case. Necromancy spells are commonly used to summon creatures to fight for you or even bend the will of your enemies. - -EARTH_COLOUREarth -The element of earth is focused on the natural world: animals, plants, the wind, and such like. Earth magic is diverse and takes a wide variety of forms, from poisoning enemies to unleashing the fury of the weather. Earth spells are a mixture of attack, defense and utility. - -SORCERY_COLOURSorcery -Sorcery is the element of force and change. Sorcerers manipulate light, gravity and even reality itself to fit their needs. Sorcery spells can be used, among other things, to grant their caster magical powers or move objects to their will. - -HEALING_COLOURHealing -The element of healing is concerned with defense and regeneration. Healers seek to protect themselves and their allies as much as possible and as such can be very difficult opponents. Though generally not used as an attack, the purifying light of some healing spells will do considerable damage to the undead. Healing spells are indispensable when in combat. - -The boundaries between the different elements are not always distinct, and some spells bear traits of elements other than their own. - -If you are lucky, you may happen upon an elemental wand. These wands will allow you to cast spells of the right element for more potency than usual. -PAGEBREAK -SECTION Wizard Armor -Wizard Armor --------------------- -As a wizard, you will need something to protect you from the creatures you fight. No ordinary armor will do though. To make the most of your spells, you will need wizard armor: hat, robes, leggings and boots. Unlike ordinary armor, these will not break. Instead, they use arcane energy to shield the wearer. This of course means that they must be charged with magic crystals, just like wands. Fortunately, these garments do not consume much mana. Charge them as you would a wand in the arcane workbench or with a mana flask, but be careful - if they run out of mana, you will find yourself defenseless. - -You can obtain wizard armor by crafting it from magical silk, obtained by crafting string with a magic crystal. - -Those wizards who devote themselves to the practice of one element sometimes wear special garments which grant bonuses for that element. Full sets of such garments are very sought after among wizards and are not easy to find. - -Legend speaks of an arcane seal used by the most powerful wizards to greatly improve their armor. -PAGEBREAK -SECTION Wizards and Towers -Wizards and Towers --------------------- -Fear not - you are not the only practicioner of magic in this world. On your travels, you may well encounter tall towers with distinctive pointed roofs. These are the residences of fellow wizards. A life of solitude can make wizards a little grumpy at times, but they are usually friendly folk who are willing to share their knowledge with you - albeit for a price. Expect to pay precious metals and gems, and in return you will recieve many a great arcane wonder. If, however, it is a master spell you seek, you will need to speak to a specialist. - -Unfortunately, there are a few wizards out there who do not welcome visitors. These wizards are typically outcasts, and are hostile to anyone crossing their path. Approach these individuals with caution, and be prepared to defend yourself against powerful magic. Defeat them however, and their knowledge is yours for the taking. -PAGEBREAK -SECTION Crafting Recipes -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Crafting Recipes --------------------- -PAGEBREAK -PAGEBREAK -Credits --------------------- -Electroblob's Wizardry -Version VERSION -For Minecraft MCVERSION - -Designed, coded and textured by Electroblob - -Thanks to Minecraft Forge and MCP, without which this mod would not have been possible. - -Thanks also to the Minecraft modding community, which always has an answer to my modding problems! - -In addition, I'd like to thank the following individuals for their contributions to the mod: - -Code: - -- Corail31 -- 12foo -- Shadows-of-Fire -- HellFirePvP - -Translations: - -- Russian: VilagVil -- Spanish and Mexican Spanish: MadWrist -- Chinese: ZHENGLOC and dragon-evol - -Lightning ray sound effect from OhhWowProductions diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_es_es.txt b/src/main/resources/assets/ebwizardry/texts/handbook_es_es.txt index 90cea3cd..237a026e 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_es_es.txt +++ b/src/main/resources/assets/ebwizardry/texts/handbook_es_es.txt @@ -173,7 +173,7 @@ Niveles Las varitas y los hechizos vienen en cuatro niveles: BASIC_COLOURBasicoRESET_COLOUR, APPRENTICE_COLOURAprendizRESET_COLOUR, ADVANCED_COLOURAvanzadoRESET_COLOUR y MASTER_COLOURMaestroRESET_COLOUR. Cada nivel es mejor que el anterior. Las varitas BASIC_COLOURBasico RESET_COLOURson las que pueden ser crafteadas. Almacenan -BASIC_MAX_CHARGE mana, y solo pueden lanzar hechizos basicos. Hechizos basicos son suficientemente simples para ser lanzados por cualquiera y no usan mucho mana. Esto +BASIC_MAX_CHARGE mana, y solo pueden lanzar hechizos basicos. Hechizos basicos son suficientemente simples para ser lanzados por cualquiera y no usan mucho mana. Esto no significa que son inservibles en niveles mas altos, sin embargo; su costo pequeno hace que algunos hechizos basicos sean utiles cuando tienes acceso a hechizos maestros. diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_es_mx.txt b/src/main/resources/assets/ebwizardry/texts/handbook_es_mx.txt index 90cea3cd..237a026e 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_es_mx.txt +++ b/src/main/resources/assets/ebwizardry/texts/handbook_es_mx.txt @@ -173,7 +173,7 @@ Niveles Las varitas y los hechizos vienen en cuatro niveles: BASIC_COLOURBasicoRESET_COLOUR, APPRENTICE_COLOURAprendizRESET_COLOUR, ADVANCED_COLOURAvanzadoRESET_COLOUR y MASTER_COLOURMaestroRESET_COLOUR. Cada nivel es mejor que el anterior. Las varitas BASIC_COLOURBasico RESET_COLOURson las que pueden ser crafteadas. Almacenan -BASIC_MAX_CHARGE mana, y solo pueden lanzar hechizos basicos. Hechizos basicos son suficientemente simples para ser lanzados por cualquiera y no usan mucho mana. Esto +BASIC_MAX_CHARGE mana, y solo pueden lanzar hechizos basicos. Hechizos basicos son suficientemente simples para ser lanzados por cualquiera y no usan mucho mana. Esto no significa que son inservibles en niveles mas altos, sin embargo; su costo pequeno hace que algunos hechizos basicos sean utiles cuando tienes acceso a hechizos maestros. diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_ko_kr.txt b/src/main/resources/assets/ebwizardry/texts/handbook_ko_kr.txt new file mode 100644 index 00000000..6071ff5a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_ko_kr.txt @@ -0,0 +1,239 @@ +PAGEBREAK +LINEBREAK + + + + +마법사의 안내서 + + + +By Electroblob +PAGEBREAK +소개 +-------------------- + +안녕하신가 마법사 친구! 이 책에서는 마법과 그 사용법을 다룬다네. + +만약 이 책을 잃어버려도 슬퍼하지 말게. 책과 마법 수정으로 다시 만들 수 있으니 말이야. +PAGEBREAK +목차 +-------------------- +PAGEBREAK +SECTION 시작하는 법 +Setting up +-------------------- +마도의 길을 걷기 위해선 몇가지 도구가 필요하다네. + +- 마법 지팡이, 금 조각과 막대기, 마법 수정 각 1개를 작업대에 대각선으로 놓아 시작의 지팡이를 만들 수 있네. + +- 아케인 작업대, 작업대에 돌 3개를 아래에 두고 그 위로 청금석 블록과 보라색 카펫을 쌓아 올리게, 마지막으로 양 끝을 마법 수정 - 금 조각 순으로 쌓으면 되네. + +- 주문서. 이건 상자에서 찾거나 마법사와 거래하는 수 밖에 없네, 그래도 가장 기본적인 주문은 책 주위를 마법 수정 4개로 둘러싸 만들 수 있지. + +그리고 마법 수정은 마나를 충전하는데 쓰이니 많이 필요할 걸세. +PAGEBREAK +SECTION 마나 +마나 +-------------------- +마나는 우리에게 힘을 주는 신비한 에너지일세. 보통은 지하에서 결정체로 나타나지. 마법 수정 한개에는 100의 마나가 담겨 있네. + +우리는 마나를 지팡이에 저장해서 주문을 사용하고, 주문마다 그 위력이나 지속력에 따라 사용하는 마력의 양이 다르다네. + +지팡이에 마우스를 올리면 남은 마력의 양을 알 수 있네. +PAGEBREAK +IMAGE CRYSTAL +LINEBREAK + + + + + 수정 광석 마법 수정 +PAGEBREAK +SECTION 지팡이 +지팡이 +-------------------- +지팡이는 마법사의 무기일세. 지팡이가 감당하는 한에서는 어떠한 주문이라도 쓸 수 있지. + +지팡이에는 많은 종류가 있지만 대부분이 처음에는 시작의 지팡이를 사용할 걸세. + +지팡이를 들고 있다면 화면 구석에 작은 창이 나타날 게야. 거기 있는 그림이 지금 대기중인 주문이고 그 옆의 막대기가 다 차야먄 그 주문을 다시 쓸 수 있네. +PAGEBREAK +SECTION 주문 +주문 +-------------------- +주문 없는 지팡이는 물 없는 호수와도 같지. 주문은 두가지 형태로 기록되는데, 첫 번째는 영구적인 주문서이고 두 번째는 일시적인 두루마리라네. + +주문서는 아케인 작업대를 이용해 지팡이에 주문을 저장하는데 쓰이네. 이건 잠시 뒤에 설명하지. 주문서는 전 세계 어디서든 찾을 수 있네. 주문서를 찾으면 간단한 정보를 훑어볼 수 있고, 책을 펴 보면 자세한 정보를 얻을 수 있네. + +두루마리는 좀 다르다네. 단 한번만 사용할 수 있지. 두루마리도 세계 어디서든 찾을 수 있지만 종이 한 장과 실을 사용해 직접 만들 수도 있네. 그리고 아케인 작업대에서 알고 있는 주문을 각인하면 되네. 이때 마법 수정으로 마력을 주입해야 하니 주의하게. + +대부분의 마법의 문자들은 배우지 않은 사람은 읽을 수 없네. 주문을 써봐서 알아내는 방법도 있지만 위험하지. 이럴때는 지식의 두루마리를 사용하는게 좋네. 이 두루마리는 마법사와 거래하거나 여러 유적을 찾아보면 있을 걸세. + +두루마리는 한두번 사용해야 하지만 지팡이의 공간이 부족할때 쓸만한 수단이네. 하지만 두루마리는 몹시 비 효율적이라는 걸 기억하게. +PAGEBREAK +SECTION 아케인 작업대 +아케인 작업대 +-------------------- +아케인 작업대는 겉보기에는 인챈트 테이블과 비슷하게 생겼네. 마법사는 여기서 지팡이를 강화하고 주문을 저장하지. 사용하려 하면 다음과 같은 화면이 나올 걸세. +PAGEBREAK +IMAGE WORKBENCH +LINEBREAK + + + + + + + + + + + + + + 아케인 작업대 +PAGEBREAK +SECTION 주문 저장 +주문 저장 +-------------------- +지팡이에 주문을 넣으려면 아케인 작업대에 지팡이를 올리고 주변에 주문서를 두면 된다네. 한번에 최대 5개의 주문을 저장할 수 있지만 지팡이를 개조하면 최대 8개의 주문을 저장할 수 있네. + +지팡이를 든 채로 주문을 바꾸려면 쉬프트-휠을 쓰거나 지정된 키를 쓰면 되네. + +SECTION 지팡이 충전 +지팡이 충전 +-------------------- +마법을 쓰다 보면 지팡이의 마력이 부족하기 마련이지. 지팡이를 충전하려면 아케인 작업대에 지팡이를 올리고 왼쪽 칸에 마법 수정을 올리면 되네. 그러면 100의 마력을 충전하지. + +SECTION 지팡이 강화 +지팡이 강화 +-------------------- +마법사가 지팡이를 강화하는 방법에는 두가지 종류가 있지. + +첫 번째는 등급에 맞는 마도서를 찾아 지팡이 자체의 격을 높이는 걸세. +두 번째는 특수한 도구를 사용하여 지팡이의 효율을 높일 수 있네. 마도서와 도구들 모두 유적이나 마법사에게서 얻을 수 있네. + +특수한 개조는 지팡이의 어느 한쪽 능력을 높혀주지. 같은 개조는 3번까지 중첩할 수 있고 최대한 개조할 수 있는 횟수는 얼마나 중첩했느냐에 따라 다르네. 또한 한번 개조된 지팡이는 되돌릴 수 없네. +PAGEBREAK +SECTION 꽃과 플라스크 +꽃과 플라스크 +-------------------- +전에 마법 수정은 지하 암석에서 얻을 수 있다는 말 기억하나? 사실 마법 수정을 얻는 방법에는 한가지가 더 있네. 지상의 꽃들은 가끔씩 마력을 띄고있는 경우가 있어. 이러한 꽃들에게서 마력을 추출하면 마법 수정을 만들 수 있네. + +혹여 이동중에 마력을 충전해야 하나? 문제 없네. 마력은 병에 담을 수도 있다네. 유리병 한개에 8개의 마력 수정을 두르면 마나 플라스크가 완성되네. 언제든 지팡이와 이를 조합하여 700의 마력을 회복할 수 있네. +PAGEBREAK +SECTION 등급 +등급 +-------------------- +지팡이와 주문들은 4개의 등급으로 나뉜다네 : BASIC_COLOUR초보자RESET_COLOUR, APPRENTICE_COLOUR견습생RESET_COLOUR, ADVANCED_COLOUR숙련자 RESET_COLOUR그리고 MASTER_COLOUR대가RESET_COLOUR. + +BASIC_COLOUR초보자RESET_COLOUR가 사용하는 시작의 지팡이는 BASIC_MAX_CHARGE 마력을 지니고 있네. 초보자 주문들은 금방 익힐 수 있을 정도로 간단하고 사용하는 마력이 적어. 하지만 그렇다고 해서 높은 수준에서 쓸모없지는 않다네. 특유의 저마력으로 대가들 조차 유용하게 사용하지. + +APPRENTICE_COLOUR견습생RESET_COLOUR들의 지팡이는 초보자들의 다음 지팡이지. APPRENTICE_MAX_CHARGE 마력을 지녔고 초보와 견습 주문을 외울 수 있네. 여기엔 유용하고 적당한 주문들이 많이 있지. + +ADVANCED_COLOUR숙련자RESET_COLOUR가 된 마법사는 몹시 강력하다네. 그들의 지팡이는 ADVANCED_MAX_CHARGE 마력을 지니고 대가의 주문을 제외한 모든 주문을 외울 수 있어. 이 등급의 주문들은 적을 직접 파괴하고 초인적인 힘을 줄 걸세. + +MASTER_COLOUR대가RESET_COLOUR의 지팡이는 현존하는 최강의 지팡이라네. MASTER_MAX_CHARGE 마력을 지니고 모든 주문을 사용할 수 있지. 이 단계의 주문은 몹시 드묾어서 찾아보기도 힘들지만 그 여파는 엄청날 걸세. 주의하게나. +PAGEBREAK +SECTION 원소 +원소 +-------------------- +거의 모든 주문들에는 각자의 속성이 존재하네. 이에 알맞는 지팡이를 사용하면 높은 효율을 보일 수 있지. + +FIRE_COLOUR화염 +아마도 가장 파괴적인 속성이 아닐까 하는군. 강력한 화염술사는 적을 불태우고 자연을 파괴하네. 화염 주문들은 불꽃을 내뿜고 지속되는 고통을 야기하네. + +ICE_COLOUR빙결 +서리와 얼음, 그리고 모든 차가운 것을 포함하는 속성일세. 적들을 얼려 둔하게 만들 수 있지. 아니면 아예 얼음에 가두는건 어떤가? + +LIGHTNING_COLOUR전격 +위험하기로는 이만한 속성도 없을 걸세. 번개와 폭풍을 다루는 속성이네. 강력한 폭풍술사는 무시무시한 번개를 마음대로 다루지. 이 주문들은 동시에 여러 적을 상대하기에 알맞네. + +NECROMANCY_COLOUR사령 +사령술은 어둠과 혼돈, 언데드를 부리는 마법일세. 보통은 사악한 이미지가 많다마는 꼭 그런것도 아닐세. 이들은 자신을 도울 친구들을 부르고 적들의 의지를 꺽어버리는데 능숙해. + +EARTH_COLOUR자연 +자연은 매우 다양한 요소의 집합체이지. 동물과 식물, 하늘과 대지. 자연의 마법은 그 유형이 몹시 다양하다네. 아무렴 모든 것은 자연의 일부니까 말이야. + +SORCERY_COLOUR신비 +이 신비한 힘은 자세히 밝혀진게 적네. 빛부터 중력, 공간까지 뭐 하나 쉽게 설명할 수 없는 요소를 두루 다루지. 그래도 확실히 편리하기는 하다는군. + +HEALING_COLOUR치유 +치유의 힘은 자네를 보호하고 다시 일으켜 세울 게야. 대부분의 주문이 생명의 보호와 유지에 몰려있고 공격 능력은 매우 낮아. 하지만 일부 언데드에게는 치명적일 수 있네. + +물론 이 모든 속성들이 엄격히 구분되는 것은 아니네. 어떤 주문들은 다른 속성의 힘을 쓰기도 하지. + +운이 좋다면 원소가 깃든 지팡이를 얻을지도 모르지. 이 지팡이들은 맞는 힘에게 강력한 조력이 될게야. +PAGEBREAK +SECTION 마법사의 방어구 +마법사의 방어구 +-------------------- +마법사로서 싸워 나가려면 그에 걸맞는 갑옷이 필요하지. 오래전부터 전해지던 마법사의 방어구는 평범한 갑옷과는 다르다네. 이 방어구들은 마력이 존재하는 한 찢어지거나 파괴되지 않을 걸세. + +마법 수정에 4개의 실을 둘러 마법 천을 짤 수 있네. 이 천들을 이용하면 마법사의 방어구가 완성되지. + +간혹 특정한 분야에 전문화된 마법사는 그 분야에 도움을 주는 더욱 특별한 옷을 찾아다닌다네. 물론 이런 방어구는 몹시 드묾지. + +전설에 의하면 특수한 마법으로 이 방어구들을 더욱 강화할 수 있다더군. 믿거나 말거나 말이야. +PAGEBREAK +SECTION 마법사와 탑 +마법사와 탑 +-------------------- +두려워 말게 어린 마법사여. 이 넓은 세상에 어디 마법사가 자네 하나겠나? + +세상을 떠돌다 보면 뾰족한 탑에 사는 고독한 마법사들을 보게 될 걸세. 이들은 홀로 지식을 탐구하는 이들이지. + +자네가 원한다면 대가를 내고 그들의 지식을 배울 수 있네. 물론, 그 반대도 가능하지. 단지 이들은 좀 너무 혼자 있어서 그런지 꽤나 괘팍하다네. 절대 이들의 집에선 아무것도 부수지 말게. + +그리고 불행하게도 너무 오래 혼자인 마법사들은 세상을 배척하게 되기도 하지. 이들은 옛 향수에 빠져서는 모든 이들에게 적대적이지. + +이들은 위험한 존재일세. 하지만 이들을 무찌른다면, 그의 지식은 자네 것일세. +PAGEBREAK +PAGEBREAK +SECTION 조합법 +조합법 +-------------------- +PAGEBREAK +PAGEBREAK +조합법 +-------------------- +PAGEBREAK +PAGEBREAK +조합법 +-------------------- +PAGEBREAK +PAGEBREAK +조합법 +-------------------- +PAGEBREAK +PAGEBREAK +마치며 +-------------------- +Electroblob's Wizardry +Version VERSION +For Minecraft MCVERSION + +디자인, 코드 및 텍스쳐 by Electroblob + +마인크래프트 포지와 MCP에게 감사합니다. 이들이 없었다면 이 모드는 완성되지 못했을 겁니다. + +또한 마인크래프트 모딩 커뮤니티에게도 감사합니다. 문제가 생기면 늘 대답을 해줬어요! + +마지막으로, 모드에 도움을 준 아래의 각 개인들에게 감사합니다. + +코드: + +- Corail31 +- 12foo +- Shadows-of-Fire +- HellFirePvP + +번역: + +- 러시아어: VilagVil +- 스페인어와 멕시코 스페인어: MadWrist +- 중국어: ZHENGLOC and dragon-evol +- 한국어: rewi_wire + +번개 광선의 음향 효과: OhhWowProductions diff --git a/src/main/resources/assets/ebwizardry/textures/armour/invisible_armour.png b/src/main/resources/assets/ebwizardry/textures/armour/invisible_armour.png deleted file mode 100644 index 892b9537..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/invisible_armour.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour.png new file mode 100644 index 00000000..71917bbb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth.png new file mode 100644 index 00000000..81ed1947 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth_legs.png new file mode 100644 index 00000000..c7c4fb3e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_earth_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire.png new file mode 100644 index 00000000..972abbc6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire_legs.png new file mode 100644 index 00000000..a6669579 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_fire_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing.png new file mode 100644 index 00000000..5d4c2fae Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing_legs.png new file mode 100644 index 00000000..da815502 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_healing_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice.png new file mode 100644 index 00000000..2ee99037 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice_legs.png new file mode 100644 index 00000000..bc446e7a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_ice_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_legs.png new file mode 100644 index 00000000..c63bea71 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning.png new file mode 100644 index 00000000..abd9333f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning_legs.png new file mode 100644 index 00000000..2ce2af0c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_lightning_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy.png new file mode 100644 index 00000000..f90e785c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy_legs.png new file mode 100644 index 00000000..ebb496be Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_necromancy_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery.png new file mode 100644 index 00000000..1d9a9f7c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery_legs.png new file mode 100644 index 00000000..2b4ae4a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/armour/legendary_wizard_armour_sorcery_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour.png b/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour.png index 748577d3..739309e1 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour.png and b/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour_legs.png index 584f1e9c..f667c7ac 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour_legs.png and b/src/main/resources/assets/ebwizardry/textures/armour/spectral_armour_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour.png index 4f391e29..7a5d974e 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth.png index 46ec5132..3baa6184 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth_legs.png index d6ef5a39..e1e6fad0 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth_legs.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_earth_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_fire.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_fire.png index 5040c866..47b1556d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_fire.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_healing.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_healing.png index 866fd486..5a1c4b51 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_healing.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice.png index 3e25a69c..ffda7c92 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice_legs.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice_legs.png index a62eef4e..f18ace75 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice_legs.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_ice_legs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_lightning.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_lightning.png index 29cf5755..f8c5ae62 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_lightning.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_necromancy.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_necromancy.png index 1efbdf7b..682f432f 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_necromancy.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_sorcery.png b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_sorcery.png index d5e31f09..b8b1c695 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_sorcery.png and b/src/main/resources/assets/ebwizardry/textures/armour/wizard_armour_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_bottom.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_bottom.png index 0271489e..5591555f 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_bottom.png and b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_bottom.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_side.png index 49ddaf0a..9050fa29 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_side.png and b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_top.png index 2b772b89..69e7e0b7 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_top.png and b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_workbench_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block.png index 1a8ece1c..9e6d71d4 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block.png and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_earth.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_earth.png new file mode 100644 index 00000000..cf587dc1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_fire.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_fire.png new file mode 100644 index 00000000..10940157 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_healing.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_healing.png new file mode 100644 index 00000000..d20fb9f0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_ice.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_ice.png new file mode 100644 index 00000000..230ffdc2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_lightning.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_lightning.png new file mode 100644 index 00000000..b5f6e8ae Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_necromancy.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_necromancy.png new file mode 100644 index 00000000..2db8bb8f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_sorcery.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_sorcery.png new file mode 100644 index 00000000..e416e570 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_block_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_flower.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_flower.png index 65b5ec0e..f08694c4 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_flower.png and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_flower.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_ore.png b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_ore.png index f9e633b2..abf35bb2 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/blocks/crystal_ore.png and b/src/main/resources/assets/ebwizardry/textures/blocks/crystal_ore.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_0.png new file mode 100644 index 00000000..0e33184a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_1.png new file mode 100644 index 00000000..7c1d50ff Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_2.png new file mode 100644 index 00000000..638c98b8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_3.png new file mode 100644 index 00000000..794e478c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/obsidian_crust_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_0.png new file mode 100644 index 00000000..9db7eda6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1.png new file mode 100644 index 00000000..707aa8e9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1_overlay.png new file mode 100644 index 00000000..a8899b02 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2.png new file mode 100644 index 00000000..685fb307 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2_overlay.png new file mode 100644 index 00000000..07c2e82e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3.png new file mode 100644 index 00000000..5a23e576 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3_overlay.png new file mode 100644 index 00000000..c4087bc9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4.png new file mode 100644 index 00000000..4c8a18d3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4_overlay.png new file mode 100644 index 00000000..000189ca Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_earth_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_0.png new file mode 100644 index 00000000..d1828720 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1.png new file mode 100644 index 00000000..71eda33e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1_overlay.png new file mode 100644 index 00000000..5a373e23 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2.png new file mode 100644 index 00000000..5e338e21 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2_overlay.png new file mode 100644 index 00000000..f32c33c3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3.png new file mode 100644 index 00000000..9e841dbd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3_overlay.png new file mode 100644 index 00000000..c5ecfe21 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4.png new file mode 100644 index 00000000..ec07cdc1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4_overlay.png new file mode 100644 index 00000000..3f98c9fb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_fire_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_0.png new file mode 100644 index 00000000..5779cfc4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1.png new file mode 100644 index 00000000..40d6daaa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1_overlay.png new file mode 100644 index 00000000..8f4700e7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2.png new file mode 100644 index 00000000..37f3818d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2_overlay.png new file mode 100644 index 00000000..fe258a3c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3.png new file mode 100644 index 00000000..d7784e67 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3_overlay.png new file mode 100644 index 00000000..53767af6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4.png new file mode 100644 index 00000000..4bd76397 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4_overlay.png new file mode 100644 index 00000000..73287db9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_healing_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_0.png new file mode 100644 index 00000000..7ce93728 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1.png new file mode 100644 index 00000000..9c1455fe Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1_overlay.png new file mode 100644 index 00000000..db2558a7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2.png new file mode 100644 index 00000000..1822ef74 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2_overlay.png new file mode 100644 index 00000000..d29cf159 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3.png new file mode 100644 index 00000000..68c28800 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3_overlay.png new file mode 100644 index 00000000..503b6a63 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4.png new file mode 100644 index 00000000..765defc9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4_overlay.png new file mode 100644 index 00000000..ee63b247 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_ice_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_0.png new file mode 100644 index 00000000..e6cec378 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1.png new file mode 100644 index 00000000..b6e4f889 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1_overlay.png new file mode 100644 index 00000000..a6e02d84 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2.png new file mode 100644 index 00000000..cf4e9029 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2_overlay.png new file mode 100644 index 00000000..f862c84c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3.png new file mode 100644 index 00000000..d4efba15 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3_overlay.png new file mode 100644 index 00000000..2fd6dfaa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4.png new file mode 100644 index 00000000..7c13720c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4_overlay.png new file mode 100644 index 00000000..d739791c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_lightning_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_0.png new file mode 100644 index 00000000..d5a69c0e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1.png new file mode 100644 index 00000000..f9172656 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1_overlay.png new file mode 100644 index 00000000..67291b32 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2.png new file mode 100644 index 00000000..62ec09ce Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2_overlay.png new file mode 100644 index 00000000..c0f945a1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3.png new file mode 100644 index 00000000..6de4425d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3_overlay.png new file mode 100644 index 00000000..fe129162 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4.png new file mode 100644 index 00000000..406c0b17 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4_overlay.png new file mode 100644 index 00000000..7428d44d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_necromancy_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth.png new file mode 100644 index 00000000..cb5c70b3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth_overlay.png new file mode 100644 index 00000000..8974ca8c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_earth_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire.png new file mode 100644 index 00000000..f0676906 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire_overlay.png new file mode 100644 index 00000000..593ef1cb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_fire_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing.png new file mode 100644 index 00000000..96799d5f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing_overlay.png new file mode 100644 index 00000000..8d7b3ed0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_healing_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice.png new file mode 100644 index 00000000..911daa0e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice_overlay.png new file mode 100644 index 00000000..b2c4faad Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_ice_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning.png new file mode 100644 index 00000000..0b84d374 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning_overlay.png new file mode 100644 index 00000000..02c27b7b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_lightning_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy.png new file mode 100644 index 00000000..90b51c88 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy_overlay.png new file mode 100644 index 00000000..da450fb8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_necromancy_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery.png new file mode 100644 index 00000000..4747ba9e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery_overlay.png new file mode 100644 index 00000000..3900046d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_pedestal_sorcery_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_0.png new file mode 100644 index 00000000..973f908d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1.png new file mode 100644 index 00000000..d9e317f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1_overlay.png new file mode 100644 index 00000000..c8b8d7a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_1_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2.png new file mode 100644 index 00000000..6975500c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2_overlay.png new file mode 100644 index 00000000..f469c0b4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_2_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3.png new file mode 100644 index 00000000..31f45408 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3_overlay.png new file mode 100644 index 00000000..30453513 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_3_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4.png new file mode 100644 index 00000000..29a30021 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4_overlay.png new file mode 100644 index 00000000..8f4bc948 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/runestone_sorcery_4_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_0.png new file mode 100644 index 00000000..d64a4336 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_1.png new file mode 100644 index 00000000..a646ebc6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_2.png new file mode 100644 index 00000000..cae47064 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_3.png new file mode 100644 index 00000000..29855ae1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_4.png new file mode 100644 index 00000000..cb640aad Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_5.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_5.png new file mode 100644 index 00000000..fafffc97 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_6.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_6.png new file mode 100644 index 00000000..f4b8765c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_7.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_7.png new file mode 100644 index 00000000..9c4a5ef0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_lower_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_0.png new file mode 100644 index 00000000..9a25c6a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_1.png new file mode 100644 index 00000000..9a25c6a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_2.png new file mode 100644 index 00000000..9a25c6a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_3.png new file mode 100644 index 00000000..f5dd330c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_4.png new file mode 100644 index 00000000..7b494a8d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_5.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_5.png new file mode 100644 index 00000000..a07c1912 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_6.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_6.png new file mode 100644 index 00000000..737c2a66 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_7.png b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_7.png new file mode 100644 index 00000000..06172a83 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/thorns_upper_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_0.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_0.png deleted file mode 100644 index 9d0c4a46..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_0.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_1.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_1.png deleted file mode 100644 index 35470422..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_1.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_10.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_10.png deleted file mode 100644 index 82ec1d74..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_10.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_11.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_11.png deleted file mode 100644 index f57e4bad..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_11.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_12.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_12.png deleted file mode 100644 index 43c78d93..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_12.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_13.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_13.png deleted file mode 100644 index b8372d7d..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_13.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_14.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_14.png deleted file mode 100644 index 9e17a144..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_14.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_15.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_15.png deleted file mode 100644 index d3fd0a96..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_15.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_2.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_2.png deleted file mode 100644 index a281efed..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_2.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_3.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_3.png deleted file mode 100644 index cdca5f2d..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_3.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_4.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_4.png deleted file mode 100644 index 1085eb86..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_4.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_5.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_5.png deleted file mode 100644 index 486881b1..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_5.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_6.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_6.png deleted file mode 100644 index 4038e71e..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_6.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_7.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_7.png deleted file mode 100644 index 383ef639..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_7.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_8.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_8.png deleted file mode 100644 index 0dc3a0e3..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_8.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arc_9.png b/src/main/resources/assets/ebwizardry/textures/entity/arc_9.png deleted file mode 100644 index 4ffae58f..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/arc_9.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_0.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_0.png new file mode 100644 index 00000000..489e93bc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_1.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_1.png new file mode 100644 index 00000000..de7dc96a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_2.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_2.png new file mode 100644 index 00000000..2303f343 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_3.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_3.png new file mode 100644 index 00000000..5b2701ae Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_4.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_4.png new file mode 100644 index 00000000..9a40b183 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_5.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_5.png new file mode 100644 index 00000000..2a0cd2ac Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_6.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_6.png new file mode 100644 index 00000000..d6133cb3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_7.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_7.png new file mode 100644 index 00000000..a2a2234f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/combustion_rune.png b/src/main/resources/assets/ebwizardry/textures/entity/combustion_rune.png new file mode 100644 index 00000000..01ff7e71 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/combustion_rune.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_0.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_0.png new file mode 100644 index 00000000..efc6c6e1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_1.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_1.png new file mode 100644 index 00000000..fef3644c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_2.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_2.png new file mode 100644 index 00000000..03536e9b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_3.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_3.png new file mode 100644 index 00000000..9245b9a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_4.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_4.png new file mode 100644 index 00000000..30bfd1ce Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_5.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_5.png new file mode 100644 index 00000000..a5c53956 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_6.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_6.png new file mode 100644 index 00000000..e6bc543d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_7.png b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_7.png new file mode 100644 index 00000000..7eb0e5c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/containment_field_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/ember.png b/src/main/resources/assets/ebwizardry/textures/entity/ember.png new file mode 100644 index 00000000..27e6e197 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/ember.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/fireball.png b/src/main/resources/assets/ebwizardry/textures/entity/fireball.png new file mode 100644 index 00000000..138e68c9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/fireball.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/firebolt.png b/src/main/resources/assets/ebwizardry/textures/entity/firebolt.png index 31316107..a01db1e0 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/firebolt.png and b/src/main/resources/assets/ebwizardry/textures/entity/firebolt.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/frost_overlay.png b/src/main/resources/assets/ebwizardry/textures/entity/frost_overlay.png new file mode 100644 index 00000000..788f43c6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/frost_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/ice_charge.png b/src/main/resources/assets/ebwizardry/textures/entity/ice_charge.png index 0c44b047..a181149a 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/ice_charge.png and b/src/main/resources/assets/ebwizardry/textures/entity/ice_charge.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/iceball.png b/src/main/resources/assets/ebwizardry/textures/entity/iceball.png new file mode 100644 index 00000000..ae5adc82 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/iceball.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/spark_bomb.png b/src/main/resources/assets/ebwizardry/textures/entity/spark_bomb.png deleted file mode 100644 index 7d7adbe3..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/spark_bomb.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_32.png b/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_32.png deleted file mode 100644 index c624f268..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_32.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_64.png b/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_64.png deleted file mode 100644 index 2a7a2ad9..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/stone_statue_64.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wing.png b/src/main/resources/assets/ebwizardry/textures/entity/wing.png index da24e164..f7081580 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/wing.png and b/src/main/resources/assets/ebwizardry/textures/entity/wing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_7.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard_7.png new file mode 100644 index 00000000..69ccda2b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/wizard_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_8.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard_8.png new file mode 100644 index 00000000..38e371a1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/wizard_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_0.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_0.png new file mode 100644 index 00000000..d3b87137 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_1.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_1.png new file mode 100644 index 00000000..4b980a75 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_2.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_2.png new file mode 100644 index 00000000..1eceba28 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/advancement_background.png b/src/main/resources/assets/ebwizardry/textures/gui/advancement_background.png index aaa8b52c..c5de2e88 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/advancement_background.png and b/src/main/resources/assets/ebwizardry/textures/gui/advancement_background.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench.png b/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench.png index 02a43a70..29914ec9 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench.png and b/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png new file mode 100644 index 00000000..7587aba0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/armour_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/armour_picture.png new file mode 100644 index 00000000..5fd04918 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/armour_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/blink_overlay.png b/src/main/resources/assets/ebwizardry/textures/gui/blink_overlay.png new file mode 100644 index 00000000..d71610b6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/blink_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/curse_background.png b/src/main/resources/assets/ebwizardry/textures/gui/curse_background.png new file mode 100644 index 00000000..f81ad1b6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/curse_background.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png b/src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png index 37cb27c6..ba5dff08 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png and b/src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_simple.png b/src/main/resources/assets/ebwizardry/textures/gui/element_icon_magic.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_simple.png rename to src/main/resources/assets/ebwizardry/textures/gui/element_icon_magic.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/elements_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/elements_picture.png new file mode 100644 index 00000000..263b4ca3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/elements_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png new file mode 100644 index 00000000..d00b9a49 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook.png index 9e8aa947..e45749c6 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/handbook.png and b/src/main/resources/assets/ebwizardry/textures/gui/handbook.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png deleted file mode 100644 index 6aab80dc..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/handbook_recipes.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/logo.png b/src/main/resources/assets/ebwizardry/textures/gui/logo.png index 1df56bc8..024b8556 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/logo.png and b/src/main/resources/assets/ebwizardry/textures/gui/logo.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/obelisk_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/obelisk_picture.png new file mode 100644 index 00000000..9b027819 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/obelisk_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_arcane_jammer.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_arcane_jammer.png new file mode 100644 index 00000000..2022b4c7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_arcane_jammer.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_containment.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_containment.png new file mode 100644 index 00000000..9d278140 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_containment.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_enfeeblement.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_enfeeblement.png new file mode 100644 index 00000000..69a67492 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_enfeeblement.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_soulbinding.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_soulbinding.png new file mode 100644 index 00000000..a45e94f3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_soulbinding.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_undeath.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_undeath.png new file mode 100644 index 00000000..fba554f8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_undeath.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/decay_icon.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_decay.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/decay_icon.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icon_decay.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_empowerment.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_empowerment.png new file mode 100644 index 00000000..165092d4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_empowerment.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fear.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fear.png new file mode 100644 index 00000000..de0039ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fear.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fireskin.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fireskin.png new file mode 100644 index 00000000..d557e4cb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fireskin.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_font_of_mana.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_font_of_mana.png new file mode 100644 index 00000000..f0ba647f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_font_of_mana.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/frost_icon.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/frost_icon.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost_step.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost_step.png new file mode 100644 index 00000000..d0c4f081 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost_step.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ice_shroud.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ice_shroud.png new file mode 100644 index 00000000..56ecdd6a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ice_shroud.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_control.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_control.png new file mode 100644 index 00000000..fcfe1558 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_control.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_trick.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_trick.png new file mode 100644 index 00000000..99d59d3a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_trick.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_muffle.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_muffle.png new file mode 100644 index 00000000..40ae2462 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_muffle.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_paralysis.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_paralysis.png new file mode 100644 index 00000000..8225301a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_paralysis.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_sixth_sense.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_sixth_sense.png new file mode 100644 index 00000000..c8bb5810 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_sixth_sense.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_slow_time.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_slow_time.png new file mode 100644 index 00000000..a91a7fd5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_slow_time.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_static_aura.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_static_aura.png new file mode 100644 index 00000000..8549cb13 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_static_aura.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_transience.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_transience.png new file mode 100644 index 00000000..cfa42fe4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_transience.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ward.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ward.png new file mode 100644 index 00000000..ebcf2844 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ward.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icons.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons.png deleted file mode 100644 index d31d92c6..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/potion_icons.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/shrine_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/shrine_picture.png new file mode 100644 index 00000000..a0e92631 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/shrine_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_advanced.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_advanced.png new file mode 100644 index 00000000..72b51efd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_advanced.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_apprentice.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_apprentice.png new file mode 100644 index 00000000..7c1cab34 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_apprentice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_master.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_master.png new file mode 100644 index 00000000..6a6396e1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_master.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_novice.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_novice.png new file mode 100644 index 00000000..19a78f8f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_book_novice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud.png deleted file mode 100644 index 8fb74e4e..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/_index.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/_index.json new file mode 100644 index 00000000..a97d369c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/_index.json @@ -0,0 +1,78 @@ +{ + "default": { + "texture": "ebwizardry:gui/spell_hud/default", + "metadata": "ebwizardry:gui/spell_hud/default" + }, + "classic": { + "texture": "ebwizardry:gui/spell_hud/classic", + "metadata": "ebwizardry:gui/spell_hud/classic" + }, + "vanilla_style": { + "texture": "ebwizardry:gui/spell_hud/vanilla_style", + "metadata": "ebwizardry:gui/spell_hud/vanilla_style" + }, + "redwood": { + "texture": "ebwizardry:gui/spell_hud/redwood", + "metadata": "ebwizardry:gui/spell_hud/redwood" + }, + "silverwood": { + "texture": "ebwizardry:gui/spell_hud/silverwood", + "metadata": "ebwizardry:gui/spell_hud/silverwood" + }, + "stone": { + "texture": "ebwizardry:gui/spell_hud/stone", + "metadata": "ebwizardry:gui/spell_hud/stone" + }, + "sandstone": { + "texture": "ebwizardry:gui/spell_hud/sandstone", + "metadata": "ebwizardry:gui/spell_hud/sandstone" + }, + "jungle": { + "texture": "ebwizardry:gui/spell_hud/jungle", + "metadata": "ebwizardry:gui/spell_hud/jungle" + }, + "spell_book": { + "texture": "ebwizardry:gui/spell_hud/spell_book", + "metadata": "ebwizardry:gui/spell_hud/spell_book" + }, + "minimal": { + "texture": "ebwizardry:gui/spell_hud/minimal", + "metadata": "ebwizardry:gui/spell_hud/minimal" + }, + "no_icon": { + "texture": "ebwizardry:gui/spell_hud/no_icon", + "metadata": "ebwizardry:gui/spell_hud/no_icon" + }, + "skyrim_style": { + "texture": "ebwizardry:gui/spell_hud/skyrim_style", + "metadata": "ebwizardry:gui/spell_hud/skyrim_style" + }, + "futuristic": { + "texture": "ebwizardry:gui/spell_hud/futuristic", + "metadata": "ebwizardry:gui/spell_hud/futuristic" + }, + "steampunk": { + "texture": "ebwizardry:gui/spell_hud/steampunk", + "metadata": "ebwizardry:gui/spell_hud/steampunk" + }, + "dragon": { + "texture": "ebwizardry:gui/spell_hud/dragon", + "metadata": "ebwizardry:gui/spell_hud/dragon" + }, + "compass": { + "texture": "ebwizardry:gui/spell_hud/compass", + "metadata": "ebwizardry:gui/spell_hud/compass" + }, + "mahogany": { + "texture": "ebwizardry:gui/spell_hud/mahogany", + "metadata": "ebwizardry:gui/spell_hud/mahogany" + }, + "oceanic": { + "texture": "ebwizardry:gui/spell_hud/oceanic", + "metadata": "ebwizardry:gui/spell_hud/oceanic" + }, + "planks": { + "texture": "ebwizardry:gui/spell_hud/planks", + "metadata": "ebwizardry:gui/spell_hud/planks" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.json new file mode 100644 index 00000000..4e198adc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.json @@ -0,0 +1,33 @@ +{ + "name": "Classic", + "description": "The original spell HUD, for that nostalgic feel!", + "width": 128, + "height": 36, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 17 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 42, + "y": 0, + "length": 82, + "height": 6, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.png new file mode 100644 index 00000000..dd656158 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/classic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.json new file mode 100644 index 00000000..11d17569 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.json @@ -0,0 +1,33 @@ +{ + "name": "Compass", + "description": "A curious artefact that once belonged to a powerful sorcerer. Definitely doesn't point north.", + "width": 128, + "height": 46, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 5, + "y": 5 + }, + "text_inset": { + "x": 49, + "y": 20 + }, + "spell_cascade_offset": { + "x": 1, + "y": 8 + }, + "cooldown_bar": { + "x": 44, + "y": 2, + "length": 77, + "height": 4, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.png new file mode 100644 index 00000000..18546a77 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compass.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.json new file mode 100644 index 00000000..b4173a15 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.json @@ -0,0 +1,33 @@ +{ + "name": "Default", + "description": "The default, oak wood look of the spell HUD.", + "width": 128, + "height": 50, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 42, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.png new file mode 100644 index 00000000..000d3ee1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/default.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.json new file mode 100644 index 00000000..d014a1de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.json @@ -0,0 +1,33 @@ +{ + "name": "Dragonstone", + "description": "A replica of the insignia of Nefazar, the only wizard to have single-handedly slain a fully-grown dragon.", + "width": 128, + "height": 42, + "mirror": { + "x": true, + "y": false + }, + "spell_icon_inset": { + "x": 3, + "y": 4 + }, + "text_inset": { + "x": 42, + "y": 20 + }, + "spell_cascade_offset": { + "x": 1, + "y": 8 + }, + "cooldown_bar": { + "x": 33, + "y": 2, + "length": 93, + "height": 5, + "mirror": { + "x": true, + "y": false + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.png new file mode 100644 index 00000000..751dd605 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/dragon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.json new file mode 100644 index 00000000..17be6ee0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.json @@ -0,0 +1,33 @@ +{ + "name": "Futuristic", + "description": "Any sufficiently advanced technology...", + "width": 128, + "height": 39, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 44, + "y": 2, + "length": 80, + "height": 3, + "mirror": { + "x": true, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.png new file mode 100644 index 00000000..776bd527 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/futuristic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.json new file mode 100644 index 00000000..e5273e4b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.json @@ -0,0 +1,33 @@ +{ + "name": "Jungle", + "description": "For the most intrepid explorers!", + "width": 128, + "height": 37, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 41, + "y": 2, + "length": 82, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.png new file mode 100644 index 00000000..0015cb10 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/jungle.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.json new file mode 100644 index 00000000..c01b3f91 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.json @@ -0,0 +1,33 @@ +{ + "name": "Mahogany", + "description": "A touch of sophistication.", + "width": 126, + "height": 39, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 43, + "y": 20 + }, + "spell_cascade_offset": { + "x": 1, + "y": 8 + }, + "cooldown_bar": { + "x": 43, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.png new file mode 100644 index 00000000..fd68c7d3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/mahogany.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.json new file mode 100644 index 00000000..f63b60f3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.json @@ -0,0 +1,33 @@ +{ + "name": "Minimal", + "description": "A modern, sleek, minimalist look.", + "width": 128, + "height": 36, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 39, + "y": 16 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 36, + "y": 1, + "length": 86, + "height": 2, + "mirror": { + "x": true, + "y": true + }, + "show_when_full": false + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.png new file mode 100644 index 00000000..ba38076d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/minimal.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.json new file mode 100644 index 00000000..cbee2a6f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.json @@ -0,0 +1,33 @@ +{ + "name": "No Icon", + "description": "A variation on the default skin that doesn't show the spell icon.", + "width": 96, + "height": 37, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": -999, + "y": -999 + }, + "text_inset": { + "x": 10, + "y": 18 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 10, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.png new file mode 100644 index 00000000..e8e8615b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.json new file mode 100644 index 00000000..c8b126b7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.json @@ -0,0 +1,33 @@ +{ + "name": "Oceanic", + "description": "An ornate relic from a sunken civilisation.", + "width": 126, + "height": 39, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 43, + "y": 20 + }, + "spell_cascade_offset": { + "x": 1, + "y": 8 + }, + "cooldown_bar": { + "x": 43, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.png new file mode 100644 index 00000000..90205e04 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/oceanic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.json new file mode 100644 index 00000000..f5bf90f0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.json @@ -0,0 +1,33 @@ +{ + "name": "Planks", + "description": "'Look, it doesn't have to look pretty, it just has to work, okay?'", + "width": 128, + "height": 43, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 20 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 43, + "y": 2, + "length": 75, + "height": 4, + "mirror": { + "x": true, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.png new file mode 100644 index 00000000..f29e78a0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/planks.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.json new file mode 100644 index 00000000..634b5f72 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.json @@ -0,0 +1,33 @@ +{ + "name": "Redwood", + "description": "A variation on the default skin with red-coloured wood and a purple-pink cooldown bar.", + "width": 128, + "height": 50, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 42, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.png new file mode 100644 index 00000000..ae931624 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/redwood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.json new file mode 100644 index 00000000..513d7068 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.json @@ -0,0 +1,33 @@ +{ + "name": "Sandstone", + "description": "Explore ancient ruins with this desert-themed skin!", + "width": 128, + "height": 37, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 41, + "y": 2, + "length": 82, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.png new file mode 100644 index 00000000..e4b23c41 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/sandstone.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.json new file mode 100644 index 00000000..83c64dbf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.json @@ -0,0 +1,33 @@ +{ + "name": "Silverwood", + "description": "A variation on the default skin with silver-coloured wood and a blue cooldown bar.", + "width": 128, + "height": 50, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 42, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.png new file mode 100644 index 00000000..e097a57e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/silverwood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.json new file mode 100644 index 00000000..2ffc29c6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.json @@ -0,0 +1,33 @@ +{ + "name": "Skyrim-style", + "description": "A HUD skin in the styling of The Elder Scrolls V, for that Skyrim feel!", + "width": 128, + "height": 38, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 3, + "y": 3 + }, + "text_inset": { + "x": 42, + "y": 19 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 44, + "y": 2, + "length": 79, + "height": 3, + "mirror": { + "x": true, + "y": false + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.png new file mode 100644 index 00000000..074a9575 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/skyrim_style.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.json new file mode 100644 index 00000000..eef32610 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.json @@ -0,0 +1,33 @@ +{ + "name": "Spell book", + "description": "Knowledge is power, or so they say.", + "width": 128, + "height": 38, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 41, + "y": 20 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 41, + "y": 3, + "length": 79, + "height": 3, + "mirror": { + "x": true, + "y": true + }, + "show_when_full": false + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.png new file mode 100644 index 00000000..50feb347 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/spell_book.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.json new file mode 100644 index 00000000..3ac97376 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.json @@ -0,0 +1,33 @@ +{ + "name": "Steampunk", + "description": "Magic, powered by steam!", + "width": 128, + "height": 45, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 43, + "y": 19 + }, + "spell_cascade_offset": { + "x": 3, + "y": 8 + }, + "cooldown_bar": { + "x": 57, + "y": 2, + "length": 62, + "height": 2, + "mirror": { + "x": true, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.png new file mode 100644 index 00000000..9c433337 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/steampunk.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.json new file mode 100644 index 00000000..e5662623 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.json @@ -0,0 +1,33 @@ +{ + "name": "Stone", + "description": "A HUD skin hewn from solid rock, with a red cooldown bar.", + "width": 128, + "height": 37, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 18 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 41, + "y": 2, + "length": 82, + "height": 3, + "mirror": { + "x": false, + "y": true + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.png new file mode 100644 index 00000000..e09ba13e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/stone.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.json new file mode 100644 index 00000000..6c541a38 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.json @@ -0,0 +1,33 @@ +{ + "name": "Vanilla-style", + "description": "A vanilla Minecraft-styled skin, for the purists out there.", + "width": 128, + "height": 36, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 40, + "y": 18 + }, + "spell_cascade_offset": { + "x": 0, + "y": 8 + }, + "cooldown_bar": { + "x": 40, + "y": 0, + "length": 81, + "height": 5, + "mirror": { + "x": false, + "y": false + }, + "show_when_full": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.png new file mode 100644 index 00000000..3e6e037b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/vanilla_style.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spellbook.png b/src/main/resources/assets/ebwizardry/textures/gui/spellbook.png deleted file mode 100644 index ccb7aef4..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spellbook.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/tiers_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/tiers_picture.png new file mode 100644 index 00000000..a21c24a4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/tiers_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/tower_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/tower_picture.png new file mode 100644 index 00000000..82dfba28 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/tower_picture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/transportation_marker.png b/src/main/resources/assets/ebwizardry/textures/gui/transportation_marker.png new file mode 100644 index 00000000..9ffa80c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/transportation_marker.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/obelisk.png b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/obelisk.png new file mode 100644 index 00000000..76182507 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/obelisk.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/shrine.png b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/shrine.png new file mode 100644 index 00000000..951b2741 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/shrine.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/wizard_tower.png b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/wizard_tower.png new file mode 100644 index 00000000..6bbd8449 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/wizard_tower.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_anchoring.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_anchoring.png new file mode 100644 index 00000000..31490816 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_anchoring.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_arcane_defence.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_arcane_defence.png new file mode 100644 index 00000000..d1aa14b4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_arcane_defence.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_auto_shield.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_auto_shield.png new file mode 100644 index 00000000..960407ee Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_auto_shield.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_banishing.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_banishing.png new file mode 100644 index 00000000..cc1b0e1b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_banishing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_channeling.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_channeling.png new file mode 100644 index 00000000..1c4c1e07 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_channeling.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_cloaking.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_cloaking.png new file mode 100644 index 00000000..080f16ac Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_cloaking.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_protection.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_protection.png new file mode 100644 index 00000000..daecc008 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_fire_protection.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_glide.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_glide.png new file mode 100644 index 00000000..7ca29d2d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_glide.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_immunity.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_immunity.png new file mode 100644 index 00000000..5fb31676 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_immunity.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_protection.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_protection.png new file mode 100644 index 00000000..dc49826a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_ice_protection.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_lich.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_lich.png new file mode 100644 index 00000000..15f706b9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_lich.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_potential.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_potential.png new file mode 100644 index 00000000..6dfd63d6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_potential.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_recovery.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_recovery.png new file mode 100644 index 00000000..41312cef Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_recovery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_resurrection.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_resurrection.png new file mode 100644 index 00000000..704eee33 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_resurrection.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_transience.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_transience.png new file mode 100644 index 00000000..db9cc5db Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_transience.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_warding.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_warding.png new file mode 100644 index 00000000..1c7a6381 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_warding.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_wisdom.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_wisdom.png new file mode 100644 index 00000000..9ad1d0c0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_wisdom.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_wither_immunity.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_wither_immunity.png new file mode 100644 index 00000000..289a6b97 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_wither_immunity.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/astral_diamond.png b/src/main/resources/assets/ebwizardry/textures/items/astral_diamond.png new file mode 100644 index 00000000..679f6954 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/astral_diamond.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_abseiling.png b/src/main/resources/assets/ebwizardry/textures/items/charm_abseiling.png new file mode 100644 index 00000000..1833411c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_abseiling.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_auto_smelt.png b/src/main/resources/assets/ebwizardry/textures/items/charm_auto_smelt.png new file mode 100644 index 00000000..3acec715 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_auto_smelt.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_experience_tome.png b/src/main/resources/assets/ebwizardry/textures/items/charm_experience_tome.png new file mode 100644 index 00000000..cbedcc6e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_experience_tome.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_feeding.png b/src/main/resources/assets/ebwizardry/textures/items/charm_feeding.png new file mode 100644 index 00000000..a0b1ce07 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_feeding.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_flight.png b/src/main/resources/assets/ebwizardry/textures/items/charm_flight.png new file mode 100644 index 00000000..7332c485 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_flight.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_growth.png b/src/main/resources/assets/ebwizardry/textures/items/charm_growth.png new file mode 100644 index 00000000..408867e5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_growth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_haggler.png b/src/main/resources/assets/ebwizardry/textures/items/charm_haggler.png new file mode 100644 index 00000000..613b7058 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_haggler.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_lava_walking.png b/src/main/resources/assets/ebwizardry/textures/items/charm_lava_walking.png new file mode 100644 index 00000000..439dd649 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_lava_walking.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_light.png b/src/main/resources/assets/ebwizardry/textures/items/charm_light.png new file mode 100644 index 00000000..e7aa5ae6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_light.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_minion_health.png b/src/main/resources/assets/ebwizardry/textures/items/charm_minion_health.png new file mode 100644 index 00000000..51360eb5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_minion_health.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_minion_variants.png b/src/main/resources/assets/ebwizardry/textures/items/charm_minion_variants.png new file mode 100644 index 00000000..23085a5d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_minion_variants.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_silk_touch.png b/src/main/resources/assets/ebwizardry/textures/items/charm_silk_touch.png new file mode 100644 index 00000000..7741346e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_silk_touch.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_stop_time.png b/src/main/resources/assets/ebwizardry/textures/items/charm_stop_time.png new file mode 100644 index 00000000..f1a166fd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_stop_time.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png b/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png new file mode 100644 index 00000000..32bb5b8c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_transportation.png b/src/main/resources/assets/ebwizardry/textures/items/charm_transportation.png new file mode 100644 index 00000000..75f8e7ef Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_transportation.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_earth.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_earth.png new file mode 100644 index 00000000..8b38d0cc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_fire.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_fire.png new file mode 100644 index 00000000..a044d552 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_grand.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_grand.png new file mode 100644 index 00000000..ccb7a620 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_grand.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_healing.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_healing.png new file mode 100644 index 00000000..9caa21f1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_ice.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_ice.png new file mode 100644 index 00000000..aee7e55a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_lightning.png new file mode 100644 index 00000000..fc02db23 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_magic.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_magic.png new file mode 100644 index 00000000..e74c1d92 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_magic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_necromancy.png new file mode 100644 index 00000000..b7a1a759 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_shard.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_shard.png new file mode 100644 index 00000000..ee3945c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_shard.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/crystal_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/crystal_sorcery.png new file mode 100644 index 00000000..9b76a977 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/crystal_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/firebomb.png b/src/main/resources/assets/ebwizardry/textures/items/firebomb.png index 60f6b11a..53eb999b 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/firebomb.png and b/src/main/resources/assets/ebwizardry/textures/items/firebomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_0.png new file mode 100644 index 00000000..401c1591 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png new file mode 100644 index 00000000..9a0d327e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_1.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png new file mode 100644 index 00000000..4317eeb8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_2.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png new file mode 100644 index 00000000..66363f76 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_3.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png new file mode 100644 index 00000000..d8b8c53b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_4.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png new file mode 100644 index 00000000..a5aea570 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_5.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png new file mode 100644 index 00000000..56bf06f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_6.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png new file mode 100644 index 00000000..5004e883 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flaming_axe_conjuring_7.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_0.png new file mode 100644 index 00000000..401c1591 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_1.png new file mode 100644 index 00000000..411b4bb2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_2.png new file mode 100644 index 00000000..f6e962f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_3.png new file mode 100644 index 00000000..754012d9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_4.png new file mode 100644 index 00000000..8f100fe0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_5.png new file mode 100644 index 00000000..f43d6b40 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_6.png new file mode 100644 index 00000000..af4cd7b5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_7.png new file mode 100644 index 00000000..0824b1eb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/frost_axe_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/magic_crystal.png b/src/main/resources/assets/ebwizardry/textures/items/magic_crystal.png deleted file mode 100644 index 9035e562..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/magic_crystal.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/mana_flask.png b/src/main/resources/assets/ebwizardry/textures/items/mana_flask.png deleted file mode 100644 index d8194312..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/mana_flask.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/mana_flask_large.png b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_large.png new file mode 100644 index 00000000..9c1641ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_large.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/mana_flask_medium.png b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_medium.png new file mode 100644 index 00000000..e256f6c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_medium.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/mana_flask_small.png b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_small.png new file mode 100644 index 00000000..4d85ab29 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/mana_flask_small.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/poison_bomb.png b/src/main/resources/assets/ebwizardry/textures/items/poison_bomb.png index c80e8d86..3ac6a8da 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/poison_bomb.png and b/src/main/resources/assets/ebwizardry/textures/items/poison_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/purifying_elixir.png b/src/main/resources/assets/ebwizardry/textures/items/purifying_elixir.png new file mode 100644 index 00000000..67d28d41 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/purifying_elixir.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_arcane_frost.png b/src/main/resources/assets/ebwizardry/textures/items/ring_arcane_frost.png new file mode 100644 index 00000000..2ee2a69f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_arcane_frost.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_battlemage.png b/src/main/resources/assets/ebwizardry/textures/items/ring_battlemage.png new file mode 100644 index 00000000..a1c8d676 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_battlemage.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_blockwrangler.png b/src/main/resources/assets/ebwizardry/textures/items/ring_blockwrangler.png new file mode 100644 index 00000000..309cb388 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_blockwrangler.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_bronze_generic.png b/src/main/resources/assets/ebwizardry/textures/items/ring_bronze_generic.png new file mode 100644 index 00000000..73e2daa1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_bronze_generic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png b/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png new file mode 100644 index 00000000..1200c271 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/ring_combustion.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 2, + + "frames": [ +0, + 1, 2, 3, 4, 5, 6, 7] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_condensing.png b/src/main/resources/assets/ebwizardry/textures/items/ring_condensing.png new file mode 100644 index 00000000..69fb7a25 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_condensing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_conjurer.png b/src/main/resources/assets/ebwizardry/textures/items/ring_conjurer.png new file mode 100644 index 00000000..209d95db Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_conjurer.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png b/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png new file mode 100644 index 00000000..69d8fed4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png.mcmeta new file mode 100644 index 00000000..eb1a25f7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/ring_defender.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 40, + + "interpolate": true, + + "frames": [0, 1] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_disintegration.png b/src/main/resources/assets/ebwizardry/textures/items/ring_disintegration.png new file mode 100644 index 00000000..9ac2518d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_disintegration.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_earth_biome.png b/src/main/resources/assets/ebwizardry/textures/items/ring_earth_biome.png new file mode 100644 index 00000000..a1a9e59d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_earth_biome.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_earth_melee.png b/src/main/resources/assets/ebwizardry/textures/items/ring_earth_melee.png new file mode 100644 index 00000000..3973e433 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_earth_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_extraction.png b/src/main/resources/assets/ebwizardry/textures/items/ring_extraction.png new file mode 100644 index 00000000..97f55eb0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_extraction.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_fire_biome.png b/src/main/resources/assets/ebwizardry/textures/items/ring_fire_biome.png new file mode 100644 index 00000000..508e40fb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_fire_biome.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_fire_melee.png b/src/main/resources/assets/ebwizardry/textures/items/ring_fire_melee.png new file mode 100644 index 00000000..0186e60a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_fire_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_full_moon.png b/src/main/resources/assets/ebwizardry/textures/items/ring_full_moon.png new file mode 100644 index 00000000..273d1f04 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_full_moon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_gold_generic.png b/src/main/resources/assets/ebwizardry/textures/items/ring_gold_generic.png new file mode 100644 index 00000000..c1c80af6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_gold_generic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_hammer.png b/src/main/resources/assets/ebwizardry/textures/items/ring_hammer.png new file mode 100644 index 00000000..105676e2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_hammer.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_ice_biome.png b/src/main/resources/assets/ebwizardry/textures/items/ring_ice_biome.png new file mode 100644 index 00000000..c132dabb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_ice_biome.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_ice_melee.png b/src/main/resources/assets/ebwizardry/textures/items/ring_ice_melee.png new file mode 100644 index 00000000..319ad2b2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_ice_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_interdiction.png b/src/main/resources/assets/ebwizardry/textures/items/ring_interdiction.png new file mode 100644 index 00000000..c0070997 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_interdiction.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_leeching.png b/src/main/resources/assets/ebwizardry/textures/items/ring_leeching.png new file mode 100644 index 00000000..ff76f78d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_leeching.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_lightning_melee.png b/src/main/resources/assets/ebwizardry/textures/items/ring_lightning_melee.png new file mode 100644 index 00000000..36d36b71 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_lightning_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_mana_return.png b/src/main/resources/assets/ebwizardry/textures/items/ring_mana_return.png new file mode 100644 index 00000000..688e63d0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_mana_return.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_mind_control.png b/src/main/resources/assets/ebwizardry/textures/items/ring_mind_control.png new file mode 100644 index 00000000..eca9c552 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_mind_control.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_necromancy_melee.png b/src/main/resources/assets/ebwizardry/textures/items/ring_necromancy_melee.png new file mode 100644 index 00000000..2d59a223 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_necromancy_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_paladin.png b/src/main/resources/assets/ebwizardry/textures/items/ring_paladin.png new file mode 100644 index 00000000..a4a7c90e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_paladin.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_poison.png b/src/main/resources/assets/ebwizardry/textures/items/ring_poison.png new file mode 100644 index 00000000..39782113 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_poison.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_seeking.png b/src/main/resources/assets/ebwizardry/textures/items/ring_seeking.png new file mode 100644 index 00000000..4348bfe9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_seeking.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_shattering.png b/src/main/resources/assets/ebwizardry/textures/items/ring_shattering.png new file mode 100644 index 00000000..a9af158d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_shattering.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_silver_generic.png b/src/main/resources/assets/ebwizardry/textures/items/ring_silver_generic.png new file mode 100644 index 00000000..81fac930 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_silver_generic.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_siphoning.png b/src/main/resources/assets/ebwizardry/textures/items/ring_siphoning.png new file mode 100644 index 00000000..26f43037 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_siphoning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_soulbinding.png b/src/main/resources/assets/ebwizardry/textures/items/ring_soulbinding.png new file mode 100644 index 00000000..5fed6da5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_soulbinding.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png b/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png new file mode 100644 index 00000000..663f998a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png.mcmeta new file mode 100644 index 00000000..4c786d66 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/ring_storm.png.mcmeta @@ -0,0 +1,42 @@ +{ + "animation": { + + "frametime": 1, + + "frames": [ + { + "index": 0, + "time": 20 + }, + 1, + 2, + 3, + 4, + { + "index": 0, + "time": 10 + }, + 5, + 6, + 7, + 8, + { + "index": 0, + "time": 12 + }, + 9, + 10, + 11, + 12, + { + "index": 0, + "time": 8 + }, + 13, + 14, + 15 + ] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/smoke_bomb.png b/src/main/resources/assets/ebwizardry/textures/items/smoke_bomb.png index d98dba0e..78c43daf 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/smoke_bomb.png and b/src/main/resources/assets/ebwizardry/textures/items/smoke_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spark_bomb.png b/src/main/resources/assets/ebwizardry/textures/items/spark_bomb.png new file mode 100644 index 00000000..6b7aa36e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spark_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_boots.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_boots.png index 4ca991da..84d3ae0f 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_boots.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_boots.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_0.png new file mode 100644 index 00000000..2d5e513c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_1.png new file mode 100644 index 00000000..ddaf2e8e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_2.png new file mode 100644 index 00000000..68898fde Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_3.png new file mode 100644 index 00000000..9d276179 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_4.png new file mode 100644 index 00000000..0548ae60 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_5.png new file mode 100644 index 00000000..47e5608a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_6.png new file mode 100644 index 00000000..47731927 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_7.png new file mode 100644 index 00000000..e7ee0d92 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_0.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_0.png index f089e38d..47525aee 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_0.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_1.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_1.png index 8f89801a..7497c598 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_1.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_2.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_2.png index ff4df979..37e3305d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_2.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_pulling_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_standby.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_standby.png index 9be035b3..a19b8b5c 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_standby.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_bow_standby.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_chestplate.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_chestplate.png index 0b4917de..2f123932 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_chestplate.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_chestplate.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_helmet.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_helmet.png index cd8b2e5d..dd773a5d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_helmet.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_helmet.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_leggings.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_leggings.png index 20a2d845..e650dc33 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_leggings.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_leggings.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe.png index 1f49fdce..7e7dfcf8 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_0.png new file mode 100644 index 00000000..43689fe4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_1.png new file mode 100644 index 00000000..9f3141db Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_2.png new file mode 100644 index 00000000..bce26267 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_3.png new file mode 100644 index 00000000..1fda3e0a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_4.png new file mode 100644 index 00000000..5278653e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_5.png new file mode 100644 index 00000000..164c0c45 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_6.png new file mode 100644 index 00000000..6d7731bf Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_7.png new file mode 100644 index 00000000..845a98e3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_pickaxe_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword.png index 95b2dfae..104d3efd 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword.png and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_0.png new file mode 100644 index 00000000..45edef5b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_1.png new file mode 100644 index 00000000..8b471166 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_2.png new file mode 100644 index 00000000..c49f2bf0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_3.png new file mode 100644 index 00000000..e5a4eb38 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_4.png new file mode 100644 index 00000000..d040f8e8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_5.png new file mode 100644 index 00000000..1e4c435b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_6.png new file mode 100644 index 00000000..aa0b9480 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_7.png new file mode 100644 index 00000000..35273d26 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_sword_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_attunement.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_attunement.png index bfaad401..01e46ac0 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_attunement.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_attunement.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_blast.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_blast.png index d3e98d96..3c3254f8 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_blast.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_blast.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_condenser.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_condenser.png index cf456b02..fbe44f7b 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_condenser.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_condenser.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_cooldown.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_cooldown.png index 4a4bcb24..41aa9956 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_cooldown.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_cooldown.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_duration.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_duration.png index c79d0923..74991dd4 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_duration.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_duration.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_melee.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_melee.png new file mode 100644 index 00000000..671c09fa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_melee.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_range.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_range.png index b7a21cec..1f9f08d9 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_range.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_range.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_siphon.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_siphon.png index ff7bbd37..f41cc2fb 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_siphon.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_siphon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/upgrade_storage.png b/src/main/resources/assets/ebwizardry/textures/items/upgrade_storage.png index 170f6e26..e349f695 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/upgrade_storage.png and b/src/main/resources/assets/ebwizardry/textures/items/upgrade_storage.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced.png index 02a3044e..578a9128 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_earth.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_earth.png index 940da730..1c165aaf 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_earth.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_fire.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_fire.png index 2acb5a57..08af0787 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_fire.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_healing.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_healing.png index 4bd169d8..e706a076 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_healing.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_ice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_ice.png index 653e67ed..851321d3 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_ice.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_lightning.png index e3932007..3130ba90 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_lightning.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_necromancy.png index 89ba55c2..05031dfa 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_necromancy.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_sorcery.png index 135cf840..d4b862c4 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_sorcery.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_advanced_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice.png index 3b39afe1..4b2fd9ef 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_earth.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_earth.png index 4b25eb50..a9d518fa 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_earth.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_fire.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_fire.png index ab83f220..85ee1118 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_fire.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_healing.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_healing.png index 5633f153..7de1b623 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_healing.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_ice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_ice.png index e596af7f..9daebb79 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_ice.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_lightning.png index 1626715b..4fda22fd 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_lightning.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_necromancy.png index 83f0ded1..36106069 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_necromancy.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_sorcery.png index 9e5f37e3..9a85fa38 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_sorcery.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_apprentice_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic.png deleted file mode 100644 index 3b21bdff..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_earth.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_earth.png deleted file mode 100644 index 83c3e104..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_earth.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_fire.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_fire.png deleted file mode 100644 index 3e07a039..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_fire.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_healing.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_healing.png deleted file mode 100644 index e88fc7d0..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_healing.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_ice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_ice.png deleted file mode 100644 index 9ed786eb..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_ice.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_lightning.png deleted file mode 100644 index 8d6a6118..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_lightning.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_necromancy.png deleted file mode 100644 index 7ba5ad30..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_necromancy.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/wand_basic_sorcery.png deleted file mode 100644 index 042487ab..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_basic_sorcery.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master.png index 7bd7f7d7..f0246a37 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_earth.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_earth.png index 6b90ba21..a68c8ae3 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_earth.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_fire.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_fire.png index 923d6d44..fba2e06f 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_fire.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_healing.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_healing.png index a67c6dca..a98e8e5a 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_healing.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_ice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_ice.png index 529cf932..6960135e 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_ice.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_lightning.png index e3f1dad0..be99025d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_lightning.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_necromancy.png index dd729046..b7f06fd6 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_necromancy.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_master_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/wand_master_sorcery.png index 0c19262e..c53377e0 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wand_master_sorcery.png and b/src/main/resources/assets/ebwizardry/textures/items/wand_master_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice.png new file mode 100644 index 00000000..f4df48d6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_earth.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_earth.png new file mode 100644 index 00000000..02caa3b4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_fire.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_fire.png new file mode 100644 index 00000000..6c9b0e40 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_healing.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_healing.png new file mode 100644 index 00000000..7bb70cf7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_ice.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_ice.png new file mode 100644 index 00000000..3ed19add Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_lightning.png new file mode 100644 index 00000000..3da8db48 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_necromancy.png new file mode 100644 index 00000000..f13ac75f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wand_novice_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_sorcery.png new file mode 100644 index 00000000..5be62495 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/wand_novice_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/wizard_handbook.png b/src/main/resources/assets/ebwizardry/textures/items/wizard_handbook.png index aa0dffee..32fa1337 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/wizard_handbook.png and b/src/main/resources/assets/ebwizardry/textures/items/wizard_handbook.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/buff.png b/src/main/resources/assets/ebwizardry/textures/particle/buff.png new file mode 100644 index 00000000..bdc069b0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/buff.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_0.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_0.png new file mode 100644 index 00000000..78375f2e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_1.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_1.png new file mode 100644 index 00000000..6b3bdae2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_2.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_2.png new file mode 100644 index 00000000..7bb1ee53 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_3.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_3.png new file mode 100644 index 00000000..97cabc9b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_4.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_4.png new file mode 100644 index 00000000..c24c08d4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_5.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_5.png new file mode 100644 index 00000000..47c0b845 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_6.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_6.png new file mode 100644 index 00000000..40e9368f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_0_7.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_7.png new file mode 100644 index 00000000..53631e91 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_0_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_0.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_0.png new file mode 100644 index 00000000..07e3f4bd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_1.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_1.png new file mode 100644 index 00000000..d7c637d1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_2.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_2.png new file mode 100644 index 00000000..55be8e7a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_3.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_3.png new file mode 100644 index 00000000..767f8d38 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_4.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_4.png new file mode 100644 index 00000000..45f97bed Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_5.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_5.png new file mode 100644 index 00000000..00758eea Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_6.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_6.png new file mode 100644 index 00000000..86f65667 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_1_7.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_7.png new file mode 100644 index 00000000..83d962c6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_1_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_0.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_0.png new file mode 100644 index 00000000..e680b0fb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_1.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_1.png new file mode 100644 index 00000000..ca3e4079 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_2.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_2.png new file mode 100644 index 00000000..d6163a6e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_3.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_3.png new file mode 100644 index 00000000..1d8e5c96 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_4.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_4.png new file mode 100644 index 00000000..2aaa59bd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_5.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_5.png new file mode 100644 index 00000000..25185d96 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_6.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_6.png new file mode 100644 index 00000000..509a8dc1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_2_7.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_7.png new file mode 100644 index 00000000..1807427b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_2_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_0.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_0.png new file mode 100644 index 00000000..7be866f0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_1.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_1.png new file mode 100644 index 00000000..ae440af7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_2.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_2.png new file mode 100644 index 00000000..c9cc8e80 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_3.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_3.png new file mode 100644 index 00000000..ce76fcc2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_4.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_4.png new file mode 100644 index 00000000..86f9199b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_5.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_5.png new file mode 100644 index 00000000..7f1e42fc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_6.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_6.png new file mode 100644 index 00000000..011d9ec1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/flame_3_7.png b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_7.png new file mode 100644 index 00000000..b8e465a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/flame_3_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_0.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_0.png new file mode 100644 index 00000000..d8e719ba Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_1.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_1.png new file mode 100644 index 00000000..6368f04f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_2.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_2.png new file mode 100644 index 00000000..7bf5ec91 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_3.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_3.png new file mode 100644 index 00000000..85a339fd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_4.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_4.png new file mode 100644 index 00000000..0bf02ea5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_5.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_5.png new file mode 100644 index 00000000..8749f94d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_6.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_6.png new file mode 100644 index 00000000..881ed9a8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_7.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_7.png new file mode 100644 index 00000000..e47f4f30 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/ice_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/ice_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/ice_particles.png deleted file mode 100644 index 96c99ead..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/ice_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_0.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_0.png new file mode 100644 index 00000000..0260459d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_1.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_1.png new file mode 100644 index 00000000..52f737ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_10.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_10.png new file mode 100644 index 00000000..4fede7a4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_10.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_11.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_11.png new file mode 100644 index 00000000..5bd76b42 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_11.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_12.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_12.png new file mode 100644 index 00000000..7e198d34 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_12.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_13.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_13.png new file mode 100644 index 00000000..ea5d76f2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_13.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_14.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_14.png new file mode 100644 index 00000000..c9812cb6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_14.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_15.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_15.png new file mode 100644 index 00000000..d7852008 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_15.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_2.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_2.png new file mode 100644 index 00000000..94dca3bc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_3.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_3.png new file mode 100644 index 00000000..be1517ef Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_4.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_4.png new file mode 100644 index 00000000..7670bd22 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_5.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_5.png new file mode 100644 index 00000000..d8de1b84 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_6.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_6.png new file mode 100644 index 00000000..063a569e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_7.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_7.png new file mode 100644 index 00000000..2a444fe1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_8.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_8.png new file mode 100644 index 00000000..ced7e467 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_9.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_9.png new file mode 100644 index 00000000..ff4824f1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/leaf_9.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/leaf_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/leaf_particles.png deleted file mode 100644 index d24657c9..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/leaf_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_0.png new file mode 100644 index 00000000..e8961570 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_1.png new file mode 100644 index 00000000..d27a4235 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_2.png new file mode 100644 index 00000000..ac92d34b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_3.png new file mode 100644 index 00000000..b78a86ff Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_0_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_0.png new file mode 100644 index 00000000..60d2e3d2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_1.png new file mode 100644 index 00000000..0d98efd6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_2.png new file mode 100644 index 00000000..19d23ff3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_3.png new file mode 100644 index 00000000..ab8dbf03 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_1_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_0.png new file mode 100644 index 00000000..4b189159 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_1.png new file mode 100644 index 00000000..224c39a8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_2.png new file mode 100644 index 00000000..bc914ecc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_3.png new file mode 100644 index 00000000..b2d2c942 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_2_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_0.png new file mode 100644 index 00000000..3b33edd7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_1.png new file mode 100644 index 00000000..bd62e1ca Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_2.png new file mode 100644 index 00000000..e5c306fa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_3.png new file mode 100644 index 00000000..ee15f872 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_3_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_0.png new file mode 100644 index 00000000..b5d0830b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_1.png new file mode 100644 index 00000000..96304d15 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_2.png new file mode 100644 index 00000000..36a82906 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_3.png new file mode 100644 index 00000000..a185c530 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_4_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_0.png new file mode 100644 index 00000000..3b9dd28b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_1.png new file mode 100644 index 00000000..6607b21f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_2.png new file mode 100644 index 00000000..706d10a1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_3.png new file mode 100644 index 00000000..4a67952b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_5_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_0.png new file mode 100644 index 00000000..07ac3f77 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_1.png new file mode 100644 index 00000000..c7a7d874 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_2.png new file mode 100644 index 00000000..3dddd174 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_3.png new file mode 100644 index 00000000..e84fbc4d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_6_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_0.png new file mode 100644 index 00000000..7bc98d4e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_1.png new file mode 100644 index 00000000..5ec85410 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_2.png new file mode 100644 index 00000000..249ea1fc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_3.png new file mode 100644 index 00000000..a7e8abb6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/lightning_7_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/lightning_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_particles.png deleted file mode 100644 index 94dc49c5..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/lightning_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_0.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_0.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_1.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_1.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_2.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_2.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_3.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_3.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_4.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_4.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_5.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_5.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_6.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_6.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_6.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_6.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_7.png b/src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/lightning_pulse_7.png rename to src/main/resources/assets/ebwizardry/textures/particle/lightning_pulse_7.png diff --git a/src/main/resources/assets/ebwizardry/textures/particle/path.png b/src/main/resources/assets/ebwizardry/textures/particle/path.png new file mode 100644 index 00000000..1e6c9d72 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/path.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/path_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/path_particles.png deleted file mode 100644 index ab02959d..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/path_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_0.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_0.png new file mode 100644 index 00000000..b2d2a22d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_1.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_1.png new file mode 100644 index 00000000..7eba17d2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_2.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_2.png new file mode 100644 index 00000000..8a908c43 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_3.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_3.png new file mode 100644 index 00000000..7f09067a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_4.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_4.png new file mode 100644 index 00000000..65765f7b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_5.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_5.png new file mode 100644 index 00000000..904c7093 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_6.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_6.png new file mode 100644 index 00000000..454b064c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/scorch_7.png b/src/main/resources/assets/ebwizardry/textures/particle/scorch_7.png new file mode 100644 index 00000000..547fc6ef Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/scorch_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/snow_0.png b/src/main/resources/assets/ebwizardry/textures/particle/snow_0.png new file mode 100644 index 00000000..9ed0fc92 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/snow_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/snow_1.png b/src/main/resources/assets/ebwizardry/textures/particle/snow_1.png new file mode 100644 index 00000000..6fd9ea9c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/snow_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/snow_2.png b/src/main/resources/assets/ebwizardry/textures/particle/snow_2.png new file mode 100644 index 00000000..3572b91f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/snow_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/snow_3.png b/src/main/resources/assets/ebwizardry/textures/particle/snow_3.png new file mode 100644 index 00000000..dd08a3cb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/snow_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/snow_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/snow_particles.png deleted file mode 100644 index 9d636de1..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/snow_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_0.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_0.png new file mode 100644 index 00000000..71c13453 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_1.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_1.png new file mode 100644 index 00000000..43f7abb9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_10.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_10.png new file mode 100644 index 00000000..0aa5da02 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_10.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_2.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_2.png new file mode 100644 index 00000000..0b9243f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_3.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_3.png new file mode 100644 index 00000000..ae54b361 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_4.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_4.png new file mode 100644 index 00000000..c5e11ecd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_5.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_5.png new file mode 100644 index 00000000..2677d350 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_6.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_6.png new file mode 100644 index 00000000..357138ba Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_7.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_7.png new file mode 100644 index 00000000..f126d045 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_8.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_8.png new file mode 100644 index 00000000..e81ac0db Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_9.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_9.png new file mode 100644 index 00000000..b707b3e4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_9.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_particles.png b/src/main/resources/assets/ebwizardry/textures/particle/sparkle_particles.png deleted file mode 100644 index d767eac3..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/sparkle_particles.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/summon.png b/src/main/resources/assets/ebwizardry/textures/particle/summon.png new file mode 100644 index 00000000..58c7ceca Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/summon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine.png b/src/main/resources/assets/ebwizardry/textures/particle/vine.png new file mode 100644 index 00000000..ca1195bf Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_0.png b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_0.png new file mode 100644 index 00000000..2eb6fd3b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_1.png b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_1.png new file mode 100644 index 00000000..e68d7c80 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_2.png b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_2.png new file mode 100644 index 00000000..799d2620 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_3.png b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_3.png new file mode 100644 index 00000000..2304504f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_4.png b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_4.png new file mode 100644 index 00000000..1454713a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/vine_leaf_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/agility.png b/src/main/resources/assets/ebwizardry/textures/spells/agility.png index a037f78a..ff971333 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/agility.png and b/src/main/resources/assets/ebwizardry/textures/spells/agility.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/arcane_lock.png b/src/main/resources/assets/ebwizardry/textures/spells/arcane_lock.png new file mode 100644 index 00000000..6df7d7d8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/arcane_lock.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/arrow_rain.png b/src/main/resources/assets/ebwizardry/textures/spells/arrow_rain.png index e1df005c..9d1a0808 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/arrow_rain.png and b/src/main/resources/assets/ebwizardry/textures/spells/arrow_rain.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/chain_lightning.png b/src/main/resources/assets/ebwizardry/textures/spells/chain_lightning.png index 6fbc732a..b29679db 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/chain_lightning.png and b/src/main/resources/assets/ebwizardry/textures/spells/chain_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/charge.png b/src/main/resources/assets/ebwizardry/textures/spells/charge.png new file mode 100644 index 00000000..af62451e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/charge.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/clairvoyance.png b/src/main/resources/assets/ebwizardry/textures/spells/clairvoyance.png index 81ac7356..a4c57a57 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/clairvoyance.png and b/src/main/resources/assets/ebwizardry/textures/spells/clairvoyance.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/combustion_rune.png b/src/main/resources/assets/ebwizardry/textures/spells/combustion_rune.png new file mode 100644 index 00000000..5d883ec8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/combustion_rune.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_armour.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_armour.png index 712476c5..e15ece3c 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/conjure_armour.png and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_armour.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_axe.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_axe.png new file mode 100644 index 00000000..1a50b0e4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_axe.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_block.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_block.png new file mode 100644 index 00000000..dca80333 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_block.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_bow.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_bow.png index 64197180..e172b8a8 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/conjure_bow.png and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_bow.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_pickaxe.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_pickaxe.png index e7a7febc..9b1ae64b 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/conjure_pickaxe.png and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_pickaxe.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_shovel.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_shovel.png new file mode 100644 index 00000000..4d331493 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_shovel.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/conjure_sword.png b/src/main/resources/assets/ebwizardry/textures/spells/conjure_sword.png index cc78cf8e..104d5e74 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/conjure_sword.png and b/src/main/resources/assets/ebwizardry/textures/spells/conjure_sword.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/containment.png b/src/main/resources/assets/ebwizardry/textures/spells/containment.png new file mode 100644 index 00000000..029eb68e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/containment.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/curse_of_enfeeblement.png b/src/main/resources/assets/ebwizardry/textures/spells/curse_of_enfeeblement.png new file mode 100644 index 00000000..2515ec50 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/curse_of_enfeeblement.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/curse_of_undeath.png b/src/main/resources/assets/ebwizardry/textures/spells/curse_of_undeath.png new file mode 100644 index 00000000..d36d96f3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/curse_of_undeath.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/decay.png b/src/main/resources/assets/ebwizardry/textures/spells/decay.png index 4eb345dd..8ccbbd47 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/decay.png and b/src/main/resources/assets/ebwizardry/textures/spells/decay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/disintegration.png b/src/main/resources/assets/ebwizardry/textures/spells/disintegration.png new file mode 100644 index 00000000..0039d680 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/disintegration.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/divination.png b/src/main/resources/assets/ebwizardry/textures/spells/divination.png new file mode 100644 index 00000000..bf5999c6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/divination.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/dragon_fireball.png b/src/main/resources/assets/ebwizardry/textures/spells/dragon_fireball.png new file mode 100644 index 00000000..3baa68e8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/dragon_fireball.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/earthquake.png b/src/main/resources/assets/ebwizardry/textures/spells/earthquake.png index 5cddf1e7..fe034c51 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/earthquake.png and b/src/main/resources/assets/ebwizardry/textures/spells/earthquake.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/empowering_presence.png b/src/main/resources/assets/ebwizardry/textures/spells/empowering_presence.png new file mode 100644 index 00000000..b32c872d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/empowering_presence.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/entrapment.png b/src/main/resources/assets/ebwizardry/textures/spells/entrapment.png index 5f949852..ad1bc032 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/entrapment.png and b/src/main/resources/assets/ebwizardry/textures/spells/entrapment.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/evade.png b/src/main/resources/assets/ebwizardry/textures/spells/evade.png new file mode 100644 index 00000000..aab4dec0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/evade.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/fire_breath.png b/src/main/resources/assets/ebwizardry/textures/spells/fire_breath.png new file mode 100644 index 00000000..0e0ee447 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/fire_breath.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/fire_resistance.png b/src/main/resources/assets/ebwizardry/textures/spells/fire_resistance.png index 5fd495a6..3824d4e3 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/fire_resistance.png and b/src/main/resources/assets/ebwizardry/textures/spells/fire_resistance.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/fire_sigil.png b/src/main/resources/assets/ebwizardry/textures/spells/fire_sigil.png index 63b0993d..dc892b01 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/fire_sigil.png and b/src/main/resources/assets/ebwizardry/textures/spells/fire_sigil.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/firebomb.png b/src/main/resources/assets/ebwizardry/textures/spells/firebomb.png index bad4d728..2d7e475d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/firebomb.png and b/src/main/resources/assets/ebwizardry/textures/spells/firebomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/firestorm.png b/src/main/resources/assets/ebwizardry/textures/spells/firestorm.png index 161cf144..faffc5c9 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/firestorm.png and b/src/main/resources/assets/ebwizardry/textures/spells/firestorm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/flight.png b/src/main/resources/assets/ebwizardry/textures/spells/flight.png index 16aec1c6..62bc8d67 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/flight.png and b/src/main/resources/assets/ebwizardry/textures/spells/flight.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/forest_of_thorns.png b/src/main/resources/assets/ebwizardry/textures/spells/forest_of_thorns.png new file mode 100644 index 00000000..16fbe282 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/forest_of_thorns.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/frost_sigil.png b/src/main/resources/assets/ebwizardry/textures/spells/frost_sigil.png index 5972bd58..da065599 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/frost_sigil.png and b/src/main/resources/assets/ebwizardry/textures/spells/frost_sigil.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/frost_step.png b/src/main/resources/assets/ebwizardry/textures/spells/frost_step.png new file mode 100644 index 00000000..60ed882f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/frost_step.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/glide.png b/src/main/resources/assets/ebwizardry/textures/spells/glide.png index d4890ccb..57f6b994 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/glide.png and b/src/main/resources/assets/ebwizardry/textures/spells/glide.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/grapple.png b/src/main/resources/assets/ebwizardry/textures/spells/grapple.png new file mode 100644 index 00000000..da78b69b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/grapple.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/greater_telekinesis.png b/src/main/resources/assets/ebwizardry/textures/spells/greater_telekinesis.png new file mode 100644 index 00000000..190a4cc1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/greater_telekinesis.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/greater_ward.png b/src/main/resources/assets/ebwizardry/textures/spells/greater_ward.png new file mode 100644 index 00000000..b174311c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/greater_ward.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/group_heal.png b/src/main/resources/assets/ebwizardry/textures/spells/group_heal.png index 3c99c033..0ce1f0ee 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/group_heal.png and b/src/main/resources/assets/ebwizardry/textures/spells/group_heal.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/guardian_angel.png b/src/main/resources/assets/ebwizardry/textures/spells/guardian_angel.png new file mode 100644 index 00000000..6aeb76aa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/guardian_angel.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/guardian_beam.png b/src/main/resources/assets/ebwizardry/textures/spells/guardian_beam.png new file mode 100644 index 00000000..30eef9ce Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/guardian_beam.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/healing_totem.png b/src/main/resources/assets/ebwizardry/textures/spells/healing_totem.png new file mode 100644 index 00000000..47f92055 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/healing_totem.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/ice_charge.png b/src/main/resources/assets/ebwizardry/textures/spells/ice_charge.png index 128342d4..26572495 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/ice_charge.png and b/src/main/resources/assets/ebwizardry/textures/spells/ice_charge.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/ice_spikes.png b/src/main/resources/assets/ebwizardry/textures/spells/ice_spikes.png index 87f5e23e..378eb5d5 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/ice_spikes.png and b/src/main/resources/assets/ebwizardry/textures/spells/ice_spikes.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/iceball.png b/src/main/resources/assets/ebwizardry/textures/spells/iceball.png new file mode 100644 index 00000000..7522bce3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/iceball.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/invigorating_presence.png b/src/main/resources/assets/ebwizardry/textures/spells/invigorating_presence.png index 950c64cc..fe7735dc 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/invigorating_presence.png and b/src/main/resources/assets/ebwizardry/textures/spells/invigorating_presence.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/levitation.png b/src/main/resources/assets/ebwizardry/textures/spells/levitation.png index f49b2809..3d2817b9 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/levitation.png and b/src/main/resources/assets/ebwizardry/textures/spells/levitation.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_bolt.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_bolt.png index 7ad6746e..18d4f750 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_bolt.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_bolt.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_disc.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_disc.png index 1052aaa1..a71d399a 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_disc.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_disc.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_hammer.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_hammer.png index 91332331..9c61bfea 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_hammer.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_hammer.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_pulse.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_pulse.png index 967ba43a..99af9a94 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_pulse.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_pulse.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_ray.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_ray.png index 5b7f8dd7..fee797bc 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_ray.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_ray.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_sigil.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_sigil.png index 961bc7ae..33f3a5e3 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_sigil.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_sigil.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/lightning_web.png b/src/main/resources/assets/ebwizardry/textures/spells/lightning_web.png index 9c89c25f..0c89ca12 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/lightning_web.png and b/src/main/resources/assets/ebwizardry/textures/spells/lightning_web.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/metamorphosis.png b/src/main/resources/assets/ebwizardry/textures/spells/metamorphosis.png index f0a2113b..d25f298c 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/metamorphosis.png and b/src/main/resources/assets/ebwizardry/textures/spells/metamorphosis.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/mine.png b/src/main/resources/assets/ebwizardry/textures/spells/mine.png new file mode 100644 index 00000000..ff5ae76e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/mine.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/muffle.png b/src/main/resources/assets/ebwizardry/textures/spells/muffle.png new file mode 100644 index 00000000..8ad845f9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/muffle.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/paralysis.png b/src/main/resources/assets/ebwizardry/textures/spells/paralysis.png new file mode 100644 index 00000000..d79ef576 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/paralysis.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/permafrost.png b/src/main/resources/assets/ebwizardry/textures/spells/permafrost.png new file mode 100644 index 00000000..e15d4a79 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/permafrost.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/phase_step.png b/src/main/resources/assets/ebwizardry/textures/spells/phase_step.png index e58a26f5..51ca73d5 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/phase_step.png and b/src/main/resources/assets/ebwizardry/textures/spells/phase_step.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/plague_of_darkness.png b/src/main/resources/assets/ebwizardry/textures/spells/plague_of_darkness.png index eeb386b6..e9f7d1cf 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/plague_of_darkness.png and b/src/main/resources/assets/ebwizardry/textures/spells/plague_of_darkness.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/poison_bomb.png b/src/main/resources/assets/ebwizardry/textures/spells/poison_bomb.png index 116b079e..51de80fd 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/poison_bomb.png and b/src/main/resources/assets/ebwizardry/textures/spells/poison_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/possession.png b/src/main/resources/assets/ebwizardry/textures/spells/possession.png new file mode 100644 index 00000000..7aaf8fd2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/possession.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/ray_of_purification.png b/src/main/resources/assets/ebwizardry/textures/spells/ray_of_purification.png new file mode 100644 index 00000000..1eed533f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/ray_of_purification.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/remove_curse.png b/src/main/resources/assets/ebwizardry/textures/spells/remove_curse.png new file mode 100644 index 00000000..c5b3f15d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/remove_curse.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/resurrection.png b/src/main/resources/assets/ebwizardry/textures/spells/resurrection.png new file mode 100644 index 00000000..8d8a9cdb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/resurrection.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/reversal.png b/src/main/resources/assets/ebwizardry/textures/spells/reversal.png new file mode 100644 index 00000000..71f0cb95 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/reversal.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/rupture.png b/src/main/resources/assets/ebwizardry/textures/spells/rupture.png new file mode 100644 index 00000000..a976e1e9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/rupture.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/satiety.png b/src/main/resources/assets/ebwizardry/textures/spells/satiety.png new file mode 100644 index 00000000..2e794f38 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/satiety.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png b/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png new file mode 100644 index 00000000..8d151467 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shield.png b/src/main/resources/assets/ebwizardry/textures/spells/shield.png index 0b1588b4..f760a416 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/shield.png and b/src/main/resources/assets/ebwizardry/textures/spells/shield.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shocking_weapon.png b/src/main/resources/assets/ebwizardry/textures/spells/shocking_weapon.png new file mode 100644 index 00000000..8eef6c87 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/shocking_weapon.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shockwave.png b/src/main/resources/assets/ebwizardry/textures/spells/shockwave.png index 49f64999..554a6e25 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/shockwave.png and b/src/main/resources/assets/ebwizardry/textures/spells/shockwave.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shulker_bullet.png b/src/main/resources/assets/ebwizardry/textures/spells/shulker_bullet.png new file mode 100644 index 00000000..9800214a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/shulker_bullet.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/slow_time.png b/src/main/resources/assets/ebwizardry/textures/spells/slow_time.png new file mode 100644 index 00000000..3534adcd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/slow_time.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/smoke_bomb.png b/src/main/resources/assets/ebwizardry/textures/spells/smoke_bomb.png index 74ed1234..21ed914a 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/smoke_bomb.png and b/src/main/resources/assets/ebwizardry/textures/spells/smoke_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/snowball.png b/src/main/resources/assets/ebwizardry/textures/spells/snowball.png index a38ca350..9ecb3c41 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/snowball.png and b/src/main/resources/assets/ebwizardry/textures/spells/snowball.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/spark_bomb.png b/src/main/resources/assets/ebwizardry/textures/spells/spark_bomb.png index 1ea4084b..80538999 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/spark_bomb.png and b/src/main/resources/assets/ebwizardry/textures/spells/spark_bomb.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/speed_time.png b/src/main/resources/assets/ebwizardry/textures/spells/speed_time.png new file mode 100644 index 00000000..bb95bfad Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/speed_time.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/static_aura.png b/src/main/resources/assets/ebwizardry/textures/spells/static_aura.png index eea54a25..693a711e 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/static_aura.png and b/src/main/resources/assets/ebwizardry/textures/spells/static_aura.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_creeper.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_creeper.png new file mode 100644 index 00000000..2a678b75 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/summon_creeper.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_iron_golem.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_iron_golem.png index 8c30b082..bcebc77d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/summon_iron_golem.png and b/src/main/resources/assets/ebwizardry/textures/spells/summon_iron_golem.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_lightning_wraith.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_lightning_wraith.png index 1464dba6..8ffe9cf6 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/summon_lightning_wraith.png and b/src/main/resources/assets/ebwizardry/textures/spells/summon_lightning_wraith.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_skeleton_legion.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_skeleton_legion.png index 7e10c45f..03750155 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/summon_skeleton_legion.png and b/src/main/resources/assets/ebwizardry/textures/spells/summon_skeleton_legion.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_horse.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_horse.png index 45171297..e295eb8d 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_horse.png and b/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_horse.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_wolf.png b/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_wolf.png index 1c5a2901..2db3e7e1 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_wolf.png and b/src/main/resources/assets/ebwizardry/textures/spells/summon_spirit_wolf.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/thunderstorm.png b/src/main/resources/assets/ebwizardry/textures/spells/thunderstorm.png index 28920c9a..371daf99 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/thunderstorm.png and b/src/main/resources/assets/ebwizardry/textures/spells/thunderstorm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/tornado.png b/src/main/resources/assets/ebwizardry/textures/spells/tornado.png index dffc70b2..737a6c2b 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/tornado.png and b/src/main/resources/assets/ebwizardry/textures/spells/tornado.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/vex_swarm.png b/src/main/resources/assets/ebwizardry/textures/spells/vex_swarm.png new file mode 100644 index 00000000..7bdb9189 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/vex_swarm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/wall_of_frost.png b/src/main/resources/assets/ebwizardry/textures/spells/wall_of_frost.png index 308cda1f..313247ce 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/wall_of_frost.png and b/src/main/resources/assets/ebwizardry/textures/spells/wall_of_frost.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/ward.png b/src/main/resources/assets/ebwizardry/textures/spells/ward.png new file mode 100644 index 00000000..1b8284f6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/ward.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 959ca048..4aa7d470 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -2,17 +2,14 @@ { "modid" : "ebwizardry", "name" : "Electroblob's Wizardry", - "version" : "4.1.4", + "version" : "4.2.0", + "mcversion" : "1.12.2", "url" : "https://minecraft.curseforge.com/projects/electroblobs-wizardry", - "credits" : "Designed, coded and textured by Electroblob. Code contributed by: Corail31, 12foo, Shadows-of-Fire, HellFirePvP. Translators: MadWrist (Spanish, Mexican Spanish), VilagVil (Russian), ZHENGLOC/dragon-evol (Chinese).", - "authors" : [ + "credits" : "\nDesigned, coded and textured by Electroblob.\nCode contributed by: Corail31, 12foo, Shadows-of-Fire, Tora-B, Avatair, Aeronica.\nTranslators: MadWrist (Spanish, Mexican Spanish), VilagVil & kellixon (Russian), Hahdrim (French), lorrampi (Brazilian Portuguese), ZHENGLOC & dragon-evol (Chinese), shejery & rewi_wire (Korean).", + "authorList" : [ "Electroblob" ], "description" : "Electroblob's Wizardry adds an rpg-style system of magic 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!", - "logoFile" : "assets/ebwizardry/textures/gui/logo.png", - "updateUrl" : "", - "parent" : "", - "screenshots": [ - ] + "logoFile" : "assets/ebwizardry/textures/gui/logo.png" } ]